web-dev-qa-db-fra.com

Logeurs à l'intérieur des setters et getters des propriétés de classe

Essayer de construire un dossier contre cela car nous avons un développeur qui aime utiliser les propriétés de classe à la place des méthodes. Je pense que c'est une mauvaise idée. Dans certains cas, il accède à la couche de données à l'intérieur du setter et du getter.

voici un extrait de code, qu'en pensez-vous?

private Dictionary<string, List<string>> _activeFilters;
    public Dictionary<string, List<string>> ActiveFilters
    {
        get
        {
            if (_activeFilters == null)
            {
                _activeFilters = new Dictionary<string, List<string>>();
                var qs = HttpContext.Current.Request.QueryString;
                foreach (var widget in SrpDto.Widgets)
                {
                    _activeFilters[widget.SearchParameter] = qs[widget.SearchParameter] != null ? qs[widget.SearchParameter].Split(',').Skip(1).ToList() : new List<string>();
                }
            }

            return _activeFilters;
        }
    }

    private Constants.SearchBarLayout? _searchBarLayout;
    public Constants.SearchBarLayout SearchBarLayout
    {
        get
        {
            if (_searchBarLayout == null)
            {
                var searchBarLayoutType = typeof(Constants.SearchBarLayout);

                // Use the first enum value as the default if no module setting is present
                _searchBarLayout = (Constants.SearchBarLayout)Enum.Parse(searchBarLayoutType,
                    this.ModuleContext.Settings[Constants.SRPSearchBarLayoutKey] as string ?? Enum.GetName(searchBarLayoutType, 0));
            }

            return _searchBarLayout.Value;
        }
    }

    public string SearchBarTemplate
    {
        get
        {
            switch (this.SearchBarLayout)
            {
                case Constants.SearchBarLayout.SimpleSearchBar:
                    return Constants.SRPSimpleSearchTemplateName;
                    break;
                case Constants.SearchBarLayout.FacetedSearchBar:
                    return Constants.SRPFacetSearchTemplateName;
                    break;
                default:
                    return string.Empty;
            }
        }
    }
9
JBeckton

Je vais dire que dans certains cas, c'est exactement ce que vous voulez. Avoir de la logique dans la propriété get/set n'est pas inconnu, et en fait, les directives Microsoft pour les propriétés montrent l'appel d'une méthode pour rendre un contrôle actif lorsqu'une valeur change .

Je peux penser à de nombreux scénarios où vous voudriez récupérer les valeurs de la base de données lors de l'appel à get, et dans les cas ci-dessus, les propriétés sont en lecture seule car elles n'ont pas de méthode set définie, donc vous ne le feriez pas par inadvertance mettez à jour quelque chose dans votre code et brisez le principe de la moindre surprise.

D'un autre côté, si chaque action qui appelle la base de données ou contient une logique pour déterminer une valeur doit être une méthode, comment appelleriez-vous la méthode? GetActiveFilters()? Cela semble être une propriété.

Par tous les moyens, vous pouvez définir une norme de codage différente pour votre propre équipe si ce n'est pas ce que vous préférez, mais étant donné que Microsoft lui-même adopte et montre des moyens de regrouper la logique dans les accesseurs et les paramétreurs de propriétés, je dirais: et dites-lui que vous préférez ne pas le faire de cette façon.

8
Maurice Reeves

S'il n'y avait pas de logique à l'intérieur des getters et setters, alors quelles seraient les propriétés? SearchBarLayout est un conditionnel simple, et je pense que ça va. Les autres pourraient être plus discutables, mais c'est un schéma courant:

private T _value;
public T Value {
    get {
        if (_value == null) {
            _value = [initialization logic for the field]
        }
        return _value;
     }
}

Cela signifie que si quelqu'un instancie cette classe mais n'accède jamais à Value, du temps a été économisé en ignorant la logique d'initialisation. Et s'il est accédé plus d'une fois, il n'est toujours traité qu'une seule fois (sauf dans les cas où l'initialisation pourrait retourner null). Il conserve également le code plus séquentiellement organisé qu'il ne le serait si les champs/propriétés étaient répertoriés et documentés en XML dans une section et initialisés dans d'autres méthodes.

Vous devez décider si ces avantages s'appliquent réellement. Peut-être qu'ils font des appels séparés à la couche de données qui pourraient être plus efficaces en un seul appel, vous devriez donc initialiser les deux champs à la fois? Je ne connais pas les détails, mais personnellement, je n'aurais aucun problème à écrire du code comme vous l'avez publié.

6
nmclean

Je voudrais avoir une vue plus large sur le modèle de classe si je faisais un examen complet du code, mais tout cela ressemble à des cas très évidents où l'on devrait certainement utiliser une propriété plutôt qu'une méthode.

Prenons d'abord ActiveFilters. Si je vois une propriété en lecture seule sur un objet appelé ActiveFilters, alors je m'attends à ce que "Obtient les filtres actifs pour l'objet" soit ce que je verrais si je regardais dans la documentation; Le principe de la moindre surprise dit donc que si c'est ce que fait cette propriété, alors tout va bien, sinon ça ne l'est peut-être pas.

Et c'est.

Il se trouve que c'est une propriété mémorisée; c'est-à-dire qu'il calcule la valeur à renvoyer au premier appel et met en cache le résultat pour les appels suivants. Il s'agit d'une optimisation dans le cas où une propriété est susceptible d'être appelée plus d'une fois (sinon, elle ne sauvegarde rien). Il est plus utile dans les cas où la propriété peut parfois ne pas être accessible (car dans de tels cas, le code ne s'exécute jamais), même si le getter de la propriété est appelé à chaque fois, il est toujours agréable de garder la logique pour obtenir la valeur près de l'endroit où on y accède; aider ceux qui sont capables de comprendre le code.

Bien sûr, le code d'appel extérieur ne sait pas s'il est mémorisé, une lecture dans un champ sans autre logique ou s'il calcule le résultat à chaque appel.

Le code appelant ne sait pas non plus si cette mémorisation est permanente, ou s'il existe une méthode ou un setter ailleurs dans la classe qui pourrait soit remplir à nouveau _activeFilters, Soit le mettre à null pour forcer le recalcul.

C'est l'une des principales raisons d'utiliser des propriétés plutôt que de simplement exposer des champs - s'ils avaient exposé un champ (et s'était assuré qu'il était correctement rempli lors de la construction), ce serait un changement de rupture pour changer plus tard en quelque chose comme ça.

Il semble raisonnablement robuste (je voudrais examiner les détails de SrpDto.Widgets Etc. et savoir pourquoi un élément est ignoré avant de dire plus fermement que "raisonnablement").

Dans l'ensemble, un cas d'école d'un getter propriété memoised.

Si je devais le critiquer, ce serait peut-être plutôt que de renvoyer un Dictionary contenant List qui pourrait ensuite être modifié, il serait peut-être préférable de renvoyer un IDictionary ou ReadOnlyCollection ou des copies des listes. Une autre conception alternative consisterait à en faire un getter indexé qui renvoie des versions en lecture seule de la liste en fonction d'une clé de chaîne. Toutefois:

  1. Cela peut être une économie raisonnable pour le développeur qui l'écrit, les développeurs qui l'utilisent et l'ordinateur de traitement, surtout s'il s'agit d'une classe interne.
  2. Ce pourrait être absolument la chose parfaite à faire; Je ne peux pas savoir que ce n'est pas sans en voir plus.
  3. De toute façon, l'implémentation ressemblerait beaucoup à ce qui précède, mais avec un travail supplémentaire ajouté pour gérer la création d'un dictionnaire en lecture seule ou la gestion de l'indexation.

(Je voudrais également voir le nom Widgets justifié; s'il se rapporte à un élément d'interface utilisateur fonctionnel, alors c'est peut-être correct, mais je le vois parfois utilisé sans autre justification que "oh, je voulais juste utiliser un morceau idiot d'argot des années 30 pour le rendre moins lisible ").

SearchBarLayout est un autre getter de propriétés mémorisées de manuels. Je ne suis pas sûr de la convention de dénomination qui nous donne Constants.SearchBarLayout (qui devrait vraiment être une classe interne ou une énumération), mais en ce qui concerne le getter de propriété; bien sûr, cela devrait être quoi d'autre?

SearchBarTemplate est encore plus évidemment quelque chose qui devrait être une propriété; il associe un ensemble de valeurs d'un type à un ensemble de valeurs d'un autre. Peut-être que cette logique devrait être ailleurs, mais peut-être pas (encore une fois, je ne peux pas dire juste d'ici; l'essentiel est de savoir si cette logique est dupliquée partout). Pourtant, ce n'est pas pertinent pour la question ici; en termes de getter vs méthode, celui-ci est un getter évident.

Il y a une mauvaise odeur dans l'utilisation d'un commutateur effectuant un mappage d'apparence arbitraire, car c'est souvent quelque chose qui peut être mieux fait d'une autre manière. Toutefois;

  1. Parfois, c'est juste la meilleure façon de le faire.
  2. S'il peut être amélioré, il peut être amélioré en tant que changement permanent, à cet endroit.

En tout, bien qu'il y ait un certain jugement sur la question de savoir si quelque chose devrait être une propriété ou une méthode, il existe quelques principes généraux:

  1. Si nous considérons les classes et les structures comme des noms, les objets comme des instances de ces noms et les méthodes comme des verbes (et les classes statiques comme des noms abstraits), alors les propriétés devraient être des choses que nous pouvons raisonnablement considérer comme des adjectifs ou des prépositions (décrivant une relation entre le objet que nous avons appelé et objet que nous avons obtenu ou défini).
  2. Les getters ne devraient généralement pas lancer car l'appelant fait un choix incorrect (lancer en raison, par exemple, d'une connexion à une base de données qui tombe en panne est une autre affaire). S'ils le font, ils doivent le faire d'une manière qui est évidente à partir de la connaissance de la classe/structure et peut tester (par exemple la façon dont default(int?).Value jette InvalidOperationException) ou parce que la propriété est indexée et l'erreur concerne la clé.
  3. Getter ne devrait pas changer d'état vu de l'extérieur, mais seulement rendre compte de l'état. (La mémoisation change l'état interne, mais vue de l'extérieur, ce n'est pas différent que de calculer le résultat à chaque fois ou de l'avoir déjà calculé).
  4. Un setter doit lancer des valeurs invalides en cours de définition. C'est leur principale raison d'exister (plutôt que d'exposer simplement des champs), bien qu'une autre raison importante soit qu'ils puissent mettre à jour plus d'un état à la fois.
  5. Idéalement, les getters et setters devraient être assez rapides (comme tous ceux illustrés ci-dessus), surtout s'ils sont amortis sur plusieurs appels (encore une fois, comme les deux premiers de ceux ci-dessus). Il n'y a pas de raison sémantique à cela, mais avoir ce qui ressemble à une propriété devrait en fait être une méthode peut être un avertissement subtil que quelque chose coûte cher (par exemple DateTime.UtcNow Appelle toutes les méthodes du système d'exploitation sous-jacent pour obtenir le courant) comme "GetSystemTime followed by SystemTimeToFileTime" sous Windows, il n'y a donc aucune raison qui ne serait pas évidente pour créer une propriété, mais une méthode pour contacter plusieurs horloges atomiques à travers le monde et déduire ensuite les retards réseau à donner une bonne estimation de l'heure actuelle, il vaut peut-être mieux être une méthode).

Une autre façon de voir la question est de considérer les propriétés comme des "champs intelligents", comme tous les exemples que vous donnez.

Je ne vois pas comment quelqu'un s'opposerait à ces getters. Je soupçonnerais que quiconque l'a fait aurait tendance à utiliser des méthodes alors qu'il devrait vraiment utiliser des propriétés.

6
Jon Hanna