Est-ce une erreur d'utiliser un paramètre booléen pour déterminer le comportement?

194

J'ai vu de temps en temps une pratique qui "se sent" mal, mais je ne peux pas tout à fait dire ce qui ne va pas. Ou peut-être que c'est juste mes préjugés. Voici:

Un développeur définit une méthode avec un booléen comme l'un de ses paramètres, et cette méthode en appelle une autre, et ainsi de suite, et éventuellement ce booléen est utilisé uniquement pour déterminer si une action doit être prise ou non. Cela peut être utilisé, par exemple, pour autoriser l'action uniquement si l'utilisateur dispose de certains droits, ou si nous sommes (ou ne sommes pas) en mode test, en mode batch ou en mode réel, ou peut-être uniquement lorsque le système est en mode de test. certain état.

Eh bien, il y a toujours une autre façon de le faire, que ce soit en interrogeant le moment opportun (plutôt que de transmettre le paramètre), en ayant plusieurs versions de la méthode, ou plusieurs implémentations de la classe, etc. Ma question est: Pas tellement comment améliorer cela, mais plutôt si c'est vraiment faux (comme je le soupçonne), et si c'est le cas, qu'est-ce qui ne va pas à ce sujet?

Rayon
la source
1
C'est une question de savoir à qui appartiennent les décisions. Déplacez les décisions dans un endroit central au lieu de les jeter partout. Cela gardera la complexité inférieure à celle d'avoir un facteur deux de chemins de code à chaque fois que vous avez un if.
28
Martin Fowler a publié
Christoffer Hammarström
@ ChristofferHammarström Lien de Nice. J'inclurai cela dans ma réponse car elle explique de manière très détaillée la même idée de mon explication.
Alex
1
Je ne suis pas toujours d'accord avec ce que Nick a à dire, mais dans ce cas, je suis d'accord à 100%: n'utilisez pas de paramètres booléens .
Marjan Venema

Réponses:

107

Oui, il s'agit probablement d'une odeur de code, ce qui conduirait à un code difficile à comprendre, incompréhensible et qui a moins de chance d'être facilement réutilisé.

Comme d'autres affiches l'ont noté , tout est dans le contexte (ne vous précipitez pas s'il s'agit d'un événement isolé ou si la pratique a été reconnue comme une dette technique contractée délibérément pour être redéfinie ultérieurement), mais d'une manière générale si un paramètre est passé dans une fonction qui sélectionne un comportement spécifique à exécuter, un raffinement par étapes supplémentaire est nécessaire; Briser cette fonction en fonctions plus petites produira des fonctions plus cohérentes.

Alors, quelle est une fonction hautement cohésive?

C'est une fonction qui fait une chose et une seule chose.

Le problème avec un paramètre transmis, comme vous l'avez décrit, est que la fonction fait plus de deux choses. En fonction de l'état du paramètre booléen, il peut vérifier ou non les droits d'accès des utilisateurs. En fonction de cet arbre de décision, il exécute une fonctionnalité.

Il serait préférable de séparer les préoccupations du contrôle d'accès de celles de la tâche, de l'action ou du commandement.

Comme vous l'avez déjà noté, ces préoccupations semblent inextricablement liées.

Ainsi, la notion de cohésion nous aide à identifier que la fonction en question n’est pas très cohérente et que nous pourrions refactoriser le code pour produire un ensemble de fonctions plus cohérentes.

Donc, la question pourrait être reformulée; Étant donné que nous sommes tous d’accord pour dire qu’il est préférable d’éviter de passer des paramètres de sélection comportementale, comment améliorer la situation?

Je me débarrasserais complètement du paramètre. Le fait de pouvoir désactiver le contrôle d'accès même à des fins de test constitue un risque potentiel pour la sécurité. À des fins de test, ou le contrôle d'accès pour tester les scénarios d'accès autorisé et d'accès refusé.

Réf: Cohésion (informatique)

Rob
la source
Rob, expliqueriez-vous ce qu'est la cohésion et son application?
Ray
Ray, plus j'y pense, plus vous devriez vous opposer au code basé sur la faille de sécurité que le booléen met en place pour le contrôle d'accès introduit dans l'application. L'amélioration de la qualité de la base de code sera un bel effet secondaire;)
Rob
1
Très belle explication de la cohésion et de son application. Cela devrait vraiment obtenir plus de votes. Et je suis également d'accord avec le problème de la sécurité ... mais si toutes les méthodes sont privées, c'est une vulnérabilité potentielle plus petite
Ray
1
Merci Ray. On dirait qu'il sera assez facile de re-factoriser lorsque le temps le permettra. Peut-être vaut-il la peine de laisser tomber un commentaire de TODO pour souligner le problème, en établissant un équilibre entre le pouvoir technique et la sensibilité à la pression que nous subissons parfois pour faire avancer les choses.
Rob
"immuable"
aaa90210
149

J'ai arrêté d'utiliser ce modèle il y a longtemps, pour une raison très simple. coût de maintenance. Plusieurs fois, j’ai trouvé que j’avais une fonction frobnicate(something, forwards_flag)qui dit que celle-ci était appelée plusieurs fois dans mon code et devait localiser tous les emplacements du code où la valeur falseétait passée forwards_flag. Vous ne pouvez pas facilement les rechercher, alors cela devient un casse-tête de maintenance. Et si vous devez créer un correctif sur chacun de ces sites, vous risquez d’avoir un problème si vous en oubliez un.

Mais ce problème spécifique est facilement résolu sans changer fondamentalement l'approche:

enum FrobnicationDirection {
  FrobnicateForwards,
  FrobnicateBackwards;
};

void frobnicate(Object what, FrobnicationDirection direction);

Avec ce code, il suffirait de rechercher des instances de FrobnicateBackwards. Bien qu'il soit possible qu'un code attribue cette variable à une variable, vous devez donc suivre quelques threads de contrôle, mais en pratique, je trouve que c'est assez rare pour que cette alternative fonctionne correctement.

Cependant, le fait de passer le drapeau de cette manière pose un autre problème, du moins en principe. Ceci est dû au fait que certains (seulement certains) systèmes ayant cette conception exposent peut-être trop d'informations sur les détails d'implémentation des parties profondément imbriquées du code (qui utilise l'indicateur) aux couches extérieures (qui ont besoin de savoir quelle valeur passer). dans ce drapeau). Pour utiliser la terminologie de Larry Constantine, cette conception peut avoir un couplage trop fort entre le passeur et l'utilisateur du drapeau booléen. Franky est difficile à dire avec certitude sur cette question sans en savoir plus sur la base de code.

Pour aborder les exemples spécifiques que vous donnez, je voudrais avoir un certain degré de préoccupation dans chacun d’eux, mais principalement pour des raisons de risque / exactitude. Autrement dit, si votre système doit transmettre des indicateurs indiquant l'état du système, il se peut que vous disposiez d'un code qui aurait dû en tenir compte, mais ne vérifie pas le paramètre (car il n'a pas été transmis à cette fonction). Donc vous avez un bug parce que quelqu'un a omis de passer le paramètre.

Il convient également d'admettre qu'un indicateur d'état du système qui doit être transmis à presque toutes les fonctions est en fait essentiellement une variable globale. La plupart des inconvénients d'une variable globale s'appliqueront. Je pense que dans de nombreux cas, il est préférable d'appliquer la connaissance de l'état du système (ou des informations d'identification de l'utilisateur, ou de l'identité du système) au sein d'un objet chargé d'agir correctement sur la base de ces données. Ensuite, vous transmettez une référence à cet objet par opposition aux données brutes. Le concept clé ici est l' encapsulation .

James Youngman
la source
9
Vraiment d'excellents exemples concrets, ainsi que des informations sur la nature de ce à quoi nous sommes confrontés et sur la manière dont cela nous affecte.
Ray
33
+1 J'utilise des énumérations pour cela autant que possible. J'ai vu des fonctions pour lesquelles des boolparamètres supplémentaires ont été ajoutés plus tard et les appels commencent à ressembler DoSomething( myObject, false, false, true, false ). Il est impossible de comprendre ce que signifient les arguments extra booléens, alors qu'avec des valeurs enum ayant un nom significatif, rien de plus simple.
Graeme Perrow
17
Oh mec, enfin un bon exemple de la façon de FrobnicateBackwards. Je cherchais ça depuis toujours.
Alex Pritchard
38

Ce n'est pas nécessairement faux mais cela peut représenter une odeur de code .

Le scénario de base à éviter en ce qui concerne les paramètres booléens est le suivant:

public void foo(boolean flag) {
    doThis();
    if (flag)
        doThat();
}

Ensuite, lorsque vous appelez, vous appelez généralement foo(false)et en foo(true)fonction du comportement exact que vous souhaitez.

C'est vraiment un problème parce que c'est un cas de mauvaise cohésion. Vous créez une dépendance entre les méthodes qui n'est pas vraiment nécessaire.

Ce que vous devriez faire dans ce cas, c'est de partir doThiset en doThattant que méthode séparée et publique:

doThis();
doThat();

ou

doThis();

De cette façon, vous laissez la décision correcte à l'appelant (exactement comme si vous passiez un paramètre booléen) sans créer de couplage.

Bien sûr, tous les paramètres booléens ne sont pas utilisés de manière aussi mauvaise, mais il s'agit certainement d'une odeur de code et vous avez raison de vous méfier si vous voyez cela beaucoup dans le code source.

Ceci est juste un exemple de la façon de résoudre ce problème basé sur les exemples que j'ai écrits. Il existe d'autres cas où une approche différente sera nécessaire.

Martin Fowler a publié un bon article dans lequel il explique en détail la même idée.

PS: si la méthode, fooau lieu d’appeler deux méthodes simples, implémentait une implémentation plus complexe, il vous suffisait d’appliquer une petite méthode d’extraction de refactoring pour que le code résultant ressemble à l’implémentation de foocelle que j’ai écrite.

Alex
la source
1
Merci d'avoir appelé le terme "odeur de code" - je savais que ça sentait mauvais, mais je ne pouvais pas vraiment comprendre ce que l'odeur était. Votre exemple correspond assez bien à ce que je regarde.
Ray
24
Il y a beaucoup de situations où l' if (flag) doThat()intérieur foo()est légitime. Pousser la décision d'invoquer doThat()à chaque appelant oblige la répétition qui devra être supprimée si vous découvrez plus tard pour certaines méthodes, le flagcomportement doit également appeler doTheOther(). Je préférerais de beaucoup mettre les dépendances entre les méthodes dans la même classe que de devoir parcourir plus tard tous les appelants.
Blrfl
1
@Blrfl: Oui, je pense que les refactorisations les plus simples consisteraient soit à créer une méthode doOneand doBoth(respectivement pour les cas faux et vrai), soit à l'aide d'un type d'énumération distinct, comme suggéré par James Youngman
hugomg
@missingno: Vous auriez toujours le même problème en transmettant du code redondant aux appelants pour qu'ils prennent la décision doOne()ou doBoth(). Les sous-programmes / fonctions / méthodes ont des arguments pour que leur comportement puisse être modifié. Utiliser une énumération pour une condition vraiment booléenne ressemble beaucoup à une répétition si le nom de l'argument explique déjà ce qu'il fait.
Blrfl
3
Si appeler deux méthodes l'une après l'autre ou une seule méthode peut être considéré comme redondant, il est également redondant de savoir comment écrire un bloc try-catch ou éventuellement un if. Cela signifie-t-il que vous allez écrire une fonction pour les résumer toutes? Non! Attention, créer une méthode qui appelle uniquement deux autres méthodes ne représente pas nécessairement une bonne abstraction.
Alex
29

Tout d'abord: la programmation n'est pas une science, mais un art. Il existe donc très rarement une "mauvaise" et une "bonne" façon de programmer. La plupart des normes de codage ne sont que des "préférences" que certains programmeurs jugent utiles; mais finalement ils sont plutôt arbitraires. Donc, je ne qualifierais jamais un choix de paramètre comme étant "faux" en soi - et certainement pas quelque chose d'aussi générique et utile qu'un paramètre booléen. L'utilisation d'un boolean(ou d' intailleurs) pour encapsuler un état est parfaitement justifiable dans de nombreux cas.

Dans l’ensemble, les décisions de codage devraient reposer principalement sur les performances et la maintenabilité. Si la performance n'est pas en jeu (et je ne peux pas imaginer comment cela pourrait être dans vos exemples), alors votre considération suivante devrait être: à quel point cela sera-t-il facile à maintenir pour moi (ou un futur rédacteur)? Est-ce intuitif et compréhensible? Est-ce isolé? Votre exemple d'appels de fonction chaînés semble en fait potentiellement fragile à cet égard: si vous décidez de changer votre bIsUpen bIsDown, combien d'autres endroits dans le code devront également être changés? De plus, votre liste de paramètres est-elle en train de gonfler? Si votre fonction comporte 17 paramètres, la lisibilité pose problème et vous devez vous demander si vous appréciez les avantages de l'architecture orientée objet.

kmote
la source
4
J'apprécie la mise en garde dans le premier paragraphe. J'étais intentionnellement provocateur en disant "faux" et je reconnais certainement que nous agissons dans le domaine des "meilleures pratiques" et des principes de conception, et que ce genre de choses est souvent situationnelle et qu'il faut peser de nombreux facteurs
Ray
13
Votre réponse me rappelle une citation dont je ne me souviens plus la source - "si votre fonction a 17 paramètres, il vous en manque probablement un".
Joris Timmermans
Je suis tout à fait d'accord avec celui-ci et j'applique à la question de dire que oui, c'est souvent une mauvaise idée de passer dans un drapeau booléen, mais ce n'est jamais aussi simple que bon / mauvais ...
JohnB
15

Je pense que l' article de code propre de Robert C Martins stipule qu'il est préférable d'éliminer les arguments booléens dans la mesure du possible, car ils montrent qu'une méthode fait plus d'une chose. Une méthode devrait faire une chose et une seule chose, à mon avis, est l’une de ses devises.

Dreza
la source
@ dreza vous parlez de la loi Curlys .
MattDavey
Bien sûr, avec l'expérience, vous devez savoir quand ignorer de tels arguments.
gnasher729
8

Je pense que la chose la plus importante ici est d'être pratique.

Lorsque le booléen détermine l'intégralité du comportement, créez une deuxième méthode.

Lorsque le booléen détermine seulement un comportement au milieu, vous pouvez le garder dans un pour réduire la duplication de code. Dans la mesure du possible, vous pourriez même être en mesure de scinder la méthode en trois: deux méthodes d'appel pour l'une ou l'autre des options booléennes et une qui effectue la majeure partie du travail.

Par exemple:

private void FooInternal(bool flag)
{
  //do work
}

public void Foo1()
{
  FooInternal(true);
}

public void Foo2()
{
  FooInternal(false);
}

Bien entendu, en pratique, vous aurez toujours un point entre ces deux extrêmes. Habituellement, je me contente de ce qui me semble juste, mais je préfère pécher par excès de duplication de code.

Jorn
la source
J'utilise uniquement des arguments booléens pour contrôler le comportement dans les méthodes privées (comme illustré ici). Mais le problème: si un dufus décide d’augmenter la visibilité de FooInternall’avenir, alors quoi?
ADTC
En fait, je prendrais un autre chemin. Le code à l'intérieur de FooInternal devrait être divisé en 4 parties: 2 fonctions pour gérer les cas booléens true / false, une pour le travail qui précède et une autre pour le travail postérieur. Ensuite, votre Foo1devient: { doWork(); HandleTrueCase(); doMoreWork() }. Idéalement, les fonctions doWorket doMoreWorksont chacune divisées en (un ou plusieurs) morceaux significatifs d'actions discrètes (c.-à-d. En tant que fonctions séparées), et non pas en deux fonctions uniquement pour se séparer.
Jpaugh
7

J'aime l'approche consistant à personnaliser le comportement à l'aide de méthodes de générateur qui renvoient des instances immuables. Voici comment Guava l'Splitter utilise:

private static final Splitter MY_SPLITTER = Splitter.on(',')
       .trimResults()
       .omitEmptyStrings();

MY_SPLITTER.split("one,,,,  two,three");

Les avantages de ceci sont:

  • Lisibilité supérieure
  • Séparation claire des méthodes de configuration et d'action
  • Favorise la cohésion en vous obligeant à réfléchir sur ce qu'est l'objet, ce qu'il devrait et ne devrait pas faire. Dans ce cas c'est un Splitter. Vous ne mettriez jamais someVaguelyStringRelatedOperation(List<Entity> myEntities)dans une classe appelée Splitter, mais vous envisageriez de la définir comme une méthode statique dans une StringUtilsclasse.
  • Les instances sont pré-configurées, donc facilement injectables en dépendance. Le client n'a pas besoin de se demander s'il doit passer trueou falseà une méthode pour obtenir le comportement correct.
oksayt
la source
1
Je suis partisan de votre solution en tant que grand amateur de goyave et évangéliste ... mais je ne peux pas vous donner un +1, car vous sautez la partie que je cherche vraiment, qui est ce qui ne va pas (ou malodorant) à propos de l'autre sens. Je pense que cela est en fait implicite dans certaines de vos explications, alors peut-être que si vous pouviez expliquer cela explicitement, cela répondrait mieux à la question.
Ray
J'aime l'idée de séparer les méthodes de configuration et d'acton!
Sher10ck
Liens vers la bibliothèque goyave sont brisées
Josh Noe
4

Certainement une odeur de code . S'il n'enfreint pas le principe de responsabilité unique, il enfreint probablement Tell, Don't Ask . Considérer:

S'il s'avère ne pas enfreindre l'un de ces deux principes, vous devriez toujours utiliser une énumération. Les drapeaux booléens sont l'équivalent booléen des nombres magiques . foo(false)fait autant de sens que bar(42). Les énumérations peuvent être utiles pour le modèle de stratégie et vous permettent d’ajouter une autre stratégie. (N'oubliez pas de les nommer correctement .)

Votre exemple particulier me dérange particulièrement. Pourquoi ce drapeau est-il passé par tant de méthodes? Cela ressemble à la nécessité de scinder votre paramètre en sous-classes .

Eva
la source
4

TL; DR: n'utilisez pas d'arguments booléens.

Voir ci-dessous pourquoi ils sont mauvais et comment les remplacer (en caractères gras).


Les arguments booléens sont très difficiles à lire et donc difficiles à maintenir. Le problème principal est que l'objectif est généralement clair lorsque vous lisez la signature de la méthode où l'argument est nommé. Cependant, nommer un paramètre n'est généralement pas requis dans la plupart des langues. Ainsi, vous aurez des anti-modèles comme RSACryptoServiceProvider#encrypt(Byte[], Boolean)où le paramètre booléen détermine quel type de cryptage doit être utilisé dans la fonction.

Donc, vous recevrez un appel comme:

rsaProvider.encrypt(data, true);

où le lecteur doit rechercher la signature de la méthode simplement pour déterminer ce que l'enfer truepourrait réellement vouloir dire. Passer un entier est bien sûr tout aussi mauvais:

rsaProvider.encrypt(data, 1);

vous en dirait autant - ou plutôt: tout aussi peu. Même si vous définissez des constantes à utiliser pour l'entier, les utilisateurs de la fonction peuvent simplement les ignorer et continuer à utiliser des valeurs littérales.

La meilleure façon de résoudre ce problème consiste à utiliser une énumération . Si vous devez passer une énumération RSAPaddingavec deux valeurs: OAEPou PKCS1_V1_5alors vous pourrez immédiatement lire le code:

rsaProvider.encrypt(data, RSAPadding.OAEP);

Les booléens ne peuvent avoir que deux valeurs. Cela signifie que si vous avez une troisième option, vous devrez alors refactoriser votre signature. Généralement, cela ne peut pas être facilement réalisé si la compatibilité avec les versions antérieures pose un problème. Vous devez donc étendre toute classe publique avec une autre méthode publique. C'est ce que Microsoft a finalement fait quand ils ont introduit RSACryptoServiceProvider#encrypt(Byte[], RSAEncryptionPadding)où ils utilisaient une énumération (ou au moins une classe imitant une énumération) au lieu d'un booléen.

Il peut même être plus facile d’utiliser un objet complet ou une interface en tant que paramètre, au cas où le paramètre lui-même aurait besoin d’être paramétré. Dans l'exemple ci-dessus, le rembourrage OAEP lui-même pourrait être paramétré avec la valeur de hachage à utiliser en interne. Notez qu’il existe maintenant 6 algorithmes de hachage SHA-2 et 4 algorithmes de hachage SHA-3. Par conséquent, le nombre de valeurs d’énumération peut exploser si vous utilisez une seule énumération plutôt que des paramètres (c’est peut-être la prochaine chose que Microsoft va découvrir. ).


Les paramètres booléens peuvent également indiquer que la méthode ou la classe n'est pas bien conçue. Comme dans l'exemple ci-dessus: aucune bibliothèque cryptographique autre que la librairie .NET n'utilise du tout d'indicateur de remplissage dans la signature de la méthode.


Presque tous les gourous du logiciel que j'aime mettent en garde contre les arguments booléens. Par exemple, Joshua Bloch les met en garde dans le livre très apprécié "Effective Java". En général, ils ne devraient tout simplement pas être utilisés. Vous pourriez faire valoir qu'ils pourraient être utilisés s'il y avait un paramètre facile à comprendre. Mais même dans ce cas: il Bit.set(boolean)est probablement préférable de mettre en œuvre deux méthodes : Bit.set()et Bit.unset().


Si vous ne pouvez pas refactoriser directement le code, vous pouvez définir des constantes pour au moins les rendre plus lisibles:

const boolean ENCRYPT = true;
const boolean DECRYPT = false;

...

cipher.init(key, ENCRYPT);

est beaucoup plus lisible que:

cipher.init(key, true);

même si vous préférez avoir:

cipher.initForEncryption(key);
cipher.initForDecryption(key);

au lieu.

Maarten Bodewes
la source
3

Je suis surpris que personne n'ait mentionné les paramètres nommés .

Le problème que je vois avec les drapeaux booléens est qu'ils nuisent à la lisibilité. Par exemple, que fait-on truedans

myObject.UpdateTimestamps(true);

faire? Je n'ai aucune idée. Mais qu'en est-il:

myObject.UpdateTimestamps(saveChanges: true);

Maintenant, il est clair que le paramètre que nous passons est censé faire: nous disons à la fonction de sauvegarder ses modifications. Dans ce cas, si la classe n'est pas publique, le paramètre booléen est correct.


Bien sûr, vous ne pouvez pas forcer les utilisateurs de votre classe à utiliser des paramètres nommés. Pour cette raison, une enumou deux méthodes distinctes sont généralement préférables, selon la fréquence de votre cas par défaut. C'est exactement ce que fait .Net:

//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);

//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key); 
BlueRaja - Danny Pflughoeft
la source
1
La question n'est pas de savoir ce qui est préférable à un indicateur déterminant le comportement, mais bien de savoir si un tel indicateur est une odeur et, dans l'affirmative, pourquoi
Ray
2
@Ray: Je ne vois pas de différence entre ces deux questions. Dans un langage où vous pouvez imposer l'utilisation de paramètres nommés, ou lorsque vous pouvez être sûr que les paramètres nommés seront toujours utilisés (par exemple, les méthodes privées) , les paramètres booléens conviennent. Si les paramètres nommés ne peuvent pas être appliqués par le langage (C #) et que la classe fait partie d'une API publique, ou si le langage ne prend pas en charge les paramètres nommés (C ++), de sorte qu'un code similaire à myFunction(true)peut être écrit, c'est un code - odeur.
BlueRaja - Danny Pflughoeft
L'approche du paramètre nommé est encore plus fausse. Sans nom, on sera obligé de lire la documentation de l'API. Avec un nom, vous pensez que vous n’avez pas besoin: mais le paramètre pourrait être mal interprété. Par exemple, il aurait pu être utilisé pour enregistrer (toutes) les modifications, mais par la suite, l’implémentation a été légèrement modifiée afin de ne sauvegarder que les modifications importantes (pour une valeur de valeur élevée).
Ingo
@ Ingo je ne suis pas d'accord. C'est un problème de programmation générique; vous pouvez facilement définir un autre SaveBignom. N'importe quel code peut être foiré, ce genre de foutu n'est pas particulier aux paramètres nommés.
Maarten Bodewes
1
@Ingo: Si vous êtes entouré d'idiots, vous allez chercher du travail ailleurs. Ce genre de chose est ce que vous avez des critiques de code.
gnasher729
1

Je ne peux pas vraiment expliquer ce qui ne va pas.

Si cela ressemble à une odeur de code, à une odeur de code et - bien - à une odeur de code, il s'agit probablement d'une odeur de code.

Ce que tu veux faire c'est:

1) Évitez les méthodes avec des effets secondaires.

2) Traitez les états nécessaires avec une machine à états formelle centrale (comme celle-ci ).

BaBu
la source
1

Je suis d'accord avec toutes les préoccupations liées à l'utilisation de paramètres booléens pour ne pas déterminer les performances afin de: améliorer, lisibilité, fiabilité, réduction de la complexité, réduction des risques d’encapsulation et de cohésion faibles et réduction du coût total de possession avec la maintenabilité.

J'ai commencé à concevoir du matériel au milieu des années 70, que nous appelons maintenant SCADA (contrôle de supervision et acquisition de données). Il s'agissait de matériel finement réglé avec code machine dans EPROM exécutant des télécommandes macro et collectant des données à haute vitesse.

La logique s'appelle machines Mealey & Moore , que nous appelons maintenant machines à états finis . Celles-ci doivent également être effectuées dans les mêmes règles que ci-dessus, à moins qu'il ne s'agisse d'une machine à temps réel avec un temps d'exécution fini et que des raccourcis doivent être utilisés pour atteindre cet objectif.

Les données sont synchrones, mais les commandes sont asynchrones et la logique de commande suit une logique booléenne sans mémoire, mais avec des commandes séquentielles basées sur la mémoire de l'état précédent, présent et souhaité. Pour que cela fonctionne dans le langage machine le plus efficace (seulement 64 Ko), un soin particulier a été apporté à la définition de chaque processus de manière heuristique IBM HIPO. Cela impliquait parfois de passer des variables booléennes et de créer des branches indexées.

Mais maintenant, avec beaucoup de mémoire et la facilité d’utilisation d’OOK, l’encapsulation est un ingrédient essentiel aujourd’hui, mais constitue une pénalité lorsque le code est compté en octets pour le code machine en temps réel et SCADA.

Tony Stewart Sunnyskyguy EE75
la source
0

Ce n'est pas nécessairement faux, mais dans votre exemple concret de l'action dépendant de certains attributs de "l'utilisateur", je passerais par une référence à l'utilisateur plutôt que par un drapeau.

Cela clarifie et aide de plusieurs manières.

Toute personne lisant l'instruction appelante se rendra compte que le résultat changera en fonction de l'utilisateur.

Dans la fonction appelée en fin de compte, vous pouvez facilement implémenter des règles métier plus complexes car vous pouvez accéder à n’importe quel attribut utilisateur.

Si une fonction / méthode de la "chaîne" fait quelque chose de différent en fonction d'un attribut d'utilisateur, il est très probable qu'une dépendance similaire à l'égard d'attributs d'utilisateur soit introduite pour certaines des autres méthodes de la "chaîne".

James Anderson
la source
0

La plupart du temps, je considérerais ce mauvais codage. Je peux cependant penser à deux cas où cela pourrait être une bonne pratique. Comme il y a beaucoup de réponses qui disent déjà pourquoi c'est mauvais, j'offre deux fois quand ça pourrait être bon:

Le premier est si chaque appel de la séquence a un sens en soi. Il serait logique si le code d' appel peut être modifié de vrai à faux ou faux vrai, ou si la méthode appelée peut être modifié pour utiliser le paramètre booléen directement plutôt que de passer sur. La probabilité de dix appels consécutifs est faible, mais cela pourrait arriver. Si c'était le cas, ce serait une bonne pratique en matière de programmation.

Le deuxième cas est un peu exagéré étant donné que nous avons affaire à un booléen. Mais si le programme a plusieurs threads ou événements, la transmission de paramètres est le moyen le plus simple de suivre les données spécifiques à un thread / événement. Par exemple, un programme peut recevoir les entrées de deux sockets ou plus. Le code en cours d'exécution pour un socket peut avoir besoin de générer des messages d'avertissement, et un autre en cours d'exécution peut ne pas l'être. Il est alors logique (en quelque sorte) qu'un ensemble booléen d'un niveau très élevé soit transmis via de nombreux appels de méthode aux emplacements où des messages d'avertissement pourraient être générés. Les données ne peuvent pas être sauvegardées (sauf très difficilement) de manière globale, car plusieurs threads ou événements entrelacés nécessiteraient chacun leur propre valeur.

Certes, dans ce dernier cas , je serais probablement créer une classe / structure dont le seul contenu était un booléen et passer que dans la place. Je serais presque certain d'avoir besoin d'autres champs avant trop longtemps - par exemple, envoyer les messages d'avertissement.

RalphChapin
la source
Une énumération WARN, SILENT aurait plus de sens, même si vous utilisiez une classe / structure telle que vous l’écriviez (c’est-à-dire un contexte ). Ou bien, configurez simplement la journalisation en externe - inutile de transmettre quoi que ce soit.
Maarten Bodewes
-1

Le contexte est important. De telles méthodes sont assez courantes dans iOS. UINavigationController fournit la méthode -pushViewController:animated:et le animatedparamètre est un BOOL. La méthode remplit essentiellement la même fonction dans les deux cas, mais elle anime la transition d'un contrôleur de vue à un autre si vous passez à OUI, et non à NO. Cela semble tout à fait raisonnable. il serait stupide de devoir utiliser deux méthodes à la place de celle-ci afin de pouvoir déterminer si vous souhaitez utiliser une animation.

Il peut être plus facile de justifier ce genre de chose dans Objective-C, où la syntaxe de nommage de méthode fournit plus de contexte pour chaque paramètre que dans des langages tels que C et Java. Néanmoins, je penserais qu'une méthode qui prend un seul paramètre pourrait facilement prendre un booléen et avoir encore un sens:

file.saveWithEncryption(false);    // is there any doubt about the meaning of 'false' here?
Caleb
la source
21
En fait, je n'ai aucune idée de ce que falsesignifie dans l' file.saveWithEncryptionexemple. Cela signifie-t-il qu'il va enregistrer sans chiffrement? Si tel est le cas, pourquoi la méthode aurait-elle "avec cryptage" directement dans le nom? Je pourrais comprendre une méthode du genre save(boolean withEncryption), mais quand je vois file.save(false), il n’est pas du tout évident que le paramètre indique que ce serait avec ou sans cryptage. Je pense que, en fait, cela fait le premier point de James Youngman, à savoir utiliser une énumération.
Ray
6
Une hypothèse alternative est que cela falsesignifie, ne surchargez aucun fichier existant du même nom. Exemple complexe que je connais, mais pour être sûr que vous auriez besoin de vérifier la documentation (ou le code) de la fonction.
James Youngman
5
Une méthode nommée saveWithEncryptionqui parfois n'enregistre pas avec chiffrement est un bogue. Cela devrait être possible file.encrypt().save(), ou comme Javas new EncryptingOutputStream(new FileOutputStream(...)).write(file).
Christoffer Hammarström
2
En fait, un code qui fait autre chose que ce qu'il dit ne fonctionne pas et est donc un bug. Il ne vole pas pour créer une méthode nommée saveWithEncryption(boolean)qui enregistre parfois sans chiffrement, tout comme elle ne vole pas pour créer une méthode qui enregistre saveWithoutEncryption(boolean)parfois avec chiffrement.
Christoffer Hammarström
2
La méthode est mauvaise, car il y a manifestement "un doute sur la signification de" faux "ici". En tout cas, je n'écrirais jamais une méthode comme celle-là en premier lieu. L'enregistrement et le cryptage sont des actions distinctes, et une méthode doit faire une chose et la faire bien. Voir mes commentaires précédents pour de meilleurs exemples comment le faire.
Christoffer Hammarström