Question

Un de mes collègues a déclaré que les arguments de méthodes booléens ne sont pas acceptables . Ils seront remplacés par des énumérations. Au début, je ne voyais aucun avantage, mais il m'a donné un exemple.

Qu'est-ce qui est plus facile à comprendre?

file.writeData( data, true );

Ou

enum WriteMode {
  Append,
  Overwrite
};

file.writeData( data, Append );

Maintenant, je l'ai! ;-)
C’est certainement un exemple où une énumération en tant que deuxième paramètre rend le code beaucoup plus lisible.

Alors, quelle est votre opinion sur ce sujet?

Était-ce utile?

La solution

Les booléens représentent "oui / non". les choix. Si vous souhaitez représenter un "oui / non", utilisez un booléen, cela se passe d'explications.

Mais si c'est un choix entre deux options, dont aucune n'est clairement oui ou non, une énumération peut parfois être plus lisible.

Autres conseils

Les énumérations autorisent également les modifications futures, où vous souhaitez maintenant un troisième choix (ou plus).

Utilisez celui qui représente le mieux votre problème. Dans l'exemple que vous donnez, l'énum est un meilleur choix. Cependant, il y aurait d'autres moments où un booléen est mieux. Ce qui a plus de sens pour vous:

lock.setIsLocked(True);

ou

enum LockState { Locked, Unlocked };
lock.setLockState(Locked);

Dans ce cas, je pourrais choisir l’option booléenne car je pense que c’est assez clair et sans ambiguïté, et je suis à peu près sûr que mon verrou n’aura pas plus de deux états. Pourtant, le deuxième choix est valide, mais inutilement compliqué, à mon humble avis.

Je pense que vous avez failli y répondre vous-même, je pense que le but final est de rendre le code plus lisible, et dans ce cas, l'énumération l'a fait, IMO est toujours mieux de regarder le but final plutôt que des règles générales, pensez peut-être c’est-à-dire que les énumérations sont souvent plus lisibles dans le code que les noms génériques bool, ints, etc., mais il y aura toujours des exceptions à la règle.

N'oubliez pas la question posée par Adlai Stevenson à l'ambassadeur Zorin à l'ONU au cours du missile cubain crise ?

  

"Vous êtes dans la salle d'audience du monde   opinion en ce moment, et vous pouvez répondre    oui ou non . Vous avez nié que [les missiles]   existe, et je veux savoir si je   vous ai bien compris .... je suis   prêt à attendre ma réponse jusqu'à ce que   l'enfer gèle, si c'est votre   décision. "

Si le drapeau que vous utilisez dans votre méthode est de nature à pouvoir l’attribuer à une décision binaire , cette décision ne se transformera jamais en un décision à mi-chemin ou à sens unique, optez pour booléen. Indications: votre drapeau s'appelle isXXX .

Ne le rendez pas booléen en cas de changement de mode . Il existe toujours un mode de plus auquel vous avez pensé lors de l'écriture de la méthode.

Le dilemme un-plus-mode a par exemple. Unix hanté, où les modes de permission possibles d’un fichier ou d’un répertoire peuvent avoir aujourd’hui des résultats étranges en fonction du type de fichier, de la propriété, etc.

Pour moi, ni l’utilisation de booléens ni l’énumération n’est une bonne approche. Robert C. Martin en parle très clairement dans son astuce sur le code simplifié n ° 12: Éliminer les arguments booléens :

  

Les arguments booléens déclarent haut et fort que la fonction fait plus d’une chose. Ils sont déroutants et doivent être éliminés.

Si une méthode fait plus d'une chose, vous devriez plutôt écrire deux méthodes différentes, par exemple dans votre cas: file.append (data) et file.overwrite (data) .

L'utilisation d'une énumération ne rend pas les choses plus claires. Cela ne change rien, c'est toujours un argument de drapeau.

Il y a deux raisons pour lesquelles je suis tombé sur une mauvaise chose:

  1. Parce que certaines personnes écriront des méthodes telles que:

    ProcessBatch(true, false, false, true, false, false, true);
    

    C’est évidemment mauvais parce qu’il est trop facile de mélanger les paramètres, et vous n’avez aucune idée en regardant ce que vous spécifiez. Un seul coup dur n’est pas si grave.

  2. Parce que le contrôle du flux de programme par une simple branche oui / non peut signifier que vous avez deux fonctions totalement différentes qui sont regroupées dans une seule de manière maladroite. Par exemple:

    public void Write(bool toOptical);
    

    Vraiment, cela devrait être deux méthodes

    public void WriteOptical();
    public void WriteMagnetic();
    

    parce que le code dans ceux-ci pourrait être entièrement différent; ils pourraient avoir à faire toutes sortes de traitements et de validation des erreurs, voire même formater les données sortantes différemment. Vous ne pouvez pas le savoir en utilisant simplement Write () ou même Write (Enum.Optical) (bien que vous puissiez bien entendu utiliser l'une de ces méthodes, il suffit d'appeler des méthodes internes WriteOptical / Mag si vous voulez).

Je suppose que cela dépend. Je ne ferais pas une grosse affaire à ce sujet sauf pour le n ° 1.

Les enums sont meilleurs mais je n’appellerais pas les paramètres booléens comme "inacceptables". Parfois, il est plus facile de lancer un petit booléen et d’aller de l’avant (pensez à des méthodes privées, etc.)

Les booléens peuvent être OK dans les langages qui ont des paramètres nommés, comme Python et Objective-C, car le nom peut expliquer ce que fait le paramètre:

file.writeData(data, overwrite=true)

ou:

[file writeData:data overwrite:YES]

Je ne serais pas d'accord pour dire que c'est une règle . De toute évidence, Enum permet d’obtenir un meilleur code explicite ou verbeux dans certains cas, mais en règle générale, cela semble aller bien au-delà.

Tout d’abord, laissez-moi prendre votre exemple: La responsabilité (et la capacité) des programmeurs d'écrire du bon code n'est pas vraiment compromise par la présence d'un paramètre booléen. Dans votre exemple, le programmeur aurait pu écrire comme un code verbeux en écrivant:

dim append as boolean = true
file.writeData( data, append );

ou je préfère plus général

dim shouldAppend as boolean = true
file.writeData( data, shouldAppend );

Deuxième: L’exemple Enum que vous avez donné n’est que "meilleur". parce que vous passez un CONST. Dans la plupart des applications, il est fort probable qu'au moins quelques-uns, sinon la plupart des paramètres de temps transmis aux fonctions soient VARIABLES. Dans ce cas, mon deuxième exemple (donner des variables portant le même nom) est bien meilleur et Enum vous aurait donné peu d'avantages.

Les enums ont un avantage certain, mais vous ne devriez pas simplement remplacer tous vos booléens par des enums. Il existe de nombreux endroits où vrai / faux est en réalité le meilleur moyen de représenter ce qui se passe.

Cependant, les utiliser comme arguments de méthode est un peu suspect, tout simplement parce que vous ne pouvez pas voir sans fouiller dans les choses qu'ils sont censés faire, car ils vous permettent de voir ce que signifie vrai / faux em>

Les propriétés (en particulier avec les initialiseurs d’objets C # 3) ou les arguments de mots clés (à la fois ruby ??ou python) sont un bien meilleur moyen d’utiliser un argument booléen.

Exemple C #:

var worker = new BackgroundWorker { WorkerReportsProgress = true };

Exemple Ruby

validates_presence_of :name, :allow_nil => true

Exemple Python

connect_to_database( persistent=true )

La seule chose à laquelle je puisse penser lorsqu'un argument de méthode booléenne est la bonne chose à faire est en java, où vous n'avez ni arguments de propriété ni mots clés. C’est l’une des raisons pour lesquelles je déteste java: - (

S'il est vrai que dans de nombreux cas, les énumérations sont plus lisibles et plus extensibles que les booléens, une règle absolue selon laquelle "les booléens ne sont pas acceptables" est stupide. Il est inflexible et contre-productif - il ne laisse pas de place au jugement humain. Ils constituent un type intégré fondamental dans la plupart des langues car ils sont utiles; envisagez de l'appliquer à d'autres types intégrés: par exemple, "n'utilisez jamais un int en tant que paramètre". serait juste fou.

Cette règle est juste une question de style, pas de risque de bugs ou de performances d'exécution. Une meilleure règle serait "préférer les enums aux booléens pour des raisons de lisibilité".

Regardez le framework .Net. Les booléens sont utilisés comme paramètres dans plusieurs méthodes. L’API .Net n’est pas parfaite, mais je ne pense pas que l’utilisation de booléens en tant que paramètres pose un gros problème. L'info-bulle vous donne toujours le nom du paramètre. Vous pouvez également créer ce type de guide. Si vous complétez vos commentaires XML sur les paramètres de la méthode, ils apparaîtront dans l'info-bulle.

Je devrais également ajouter qu'il existe un cas dans lequel vous devez clairement reformuler des booléens en énumération - lorsque vous avez deux booléens ou plus dans votre classe ou dans vos paramètres de méthode et que tous les états ne sont pas valides (par exemple, ce n'est pas valide de les définir tous les deux comme vrais)

Par exemple, si votre classe a des propriétés telles que

public bool IsFoo
public bool IsBar

Et c'est une erreur de les avoir tous les deux vrais en même temps. Vous avez en fait trois états valides, mieux exprimés sous la forme suivante:

enum FooBarType { IsFoo, IsBar, IsNeither };

Certaines règles auxquelles votre collègue pourrait mieux adhérer sont les suivantes:

  • Ne soyez pas dogmatique avec votre conception.
  • Choisissez ce qui convient le mieux aux utilisateurs de votre code.
  • N'essayez pas de planter des chevilles en forme d'étoile dans tous les trous juste parce que vous aimez la forme ce mois-ci!

Un booléen ne serait acceptable que si vous n'avez pas l'intention d'étendre les fonctionnalités du framework. L'énumération est préférable car vous pouvez étendre l'énumération et ne pas interrompre les implémentations précédentes de l'appel de fonction.

L’autre avantage du Enum est qu’il est plus facile à lire.

Si la méthode pose une question telle que:

KeepWritingData (DataAvailable());

bool DataAvailable()
{
    return true; //data is ALWAYS available!
}

void KeepWritingData (bool keepGoing)
{
   if (keepGoing)
   {
       ...
   }
}

Les arguments de la méthode booléenne semblent avoir un sens absolument parfait.

Cela dépend de la méthode. Si la méthode fait quelque chose qui est très évidemment une chose vraie / fausse, alors tout va bien, par exemple. ci-dessous [bien que je ne dis pas que c'est la meilleure conception pour cette méthode, c'est juste un exemple d'utilisation évidente].

CommentService.SetApprovalStatus(commentId, false);

Toutefois, dans la plupart des cas, comme dans l'exemple que vous avez mentionné, il est préférable d'utiliser une énumération. Il existe de nombreux exemples dans le .NET Framework même où cette convention n'est pas suivie, mais c'est parce qu'ils ont introduit cette directive de conception assez tard dans le cycle.

Cela rend les choses un peu plus explicites, mais commence à étendre considérablement la complexité de vos interfaces - dans un choix purement booléen tel que l’ajout / le remplacement, cela semble excessif. Si vous devez ajouter une option supplémentaire (à laquelle je ne peux pas penser dans ce cas), vous pouvez toujours effectuer un refactor (selon la langue)

Les énumérations peuvent certainement rendre le code plus lisible. Il reste encore quelques points à surveiller (au moins en .net)

Étant donné que le stockage sous-jacent d’une énumération est un entier, la valeur par défaut sera zéro. Vous devez donc vous assurer que 0 est une valeur raisonnable par défaut. Par exemple, tous les champs sont définis sur zéro lors de la création des structures. Il est donc impossible de spécifier une valeur par défaut autre que 0. Si vous n’avez pas la valeur 0, vous ne pouvez même pas tester l’énumération sans transtypage en int, ce qui serait: mauvais style.)

Si votre enum est privé de votre code (jamais exposé publiquement), alors vous pouvez arrêter de lire ici.

Si vos enums sont publiés de quelque manière que ce soit en code externe et / ou sont enregistrés en dehors du programme, envisagez de les numéroter explicitement. Le compilateur les numérote automatiquement à partir de 0, mais si vous réorganisez vos enums sans leur donner de valeurs, vous risquez de rencontrer des défauts.

Je peux écrire légalement

WriteMode illegalButWorks = (WriteMode)1000000;
file.Write( data, illegalButWorks );

Pour lutter contre cela, tout code utilisant une énumération dont vous ne pouvez pas être certain (par exemple, une API publique) doit vérifier si l’énumération est valide. Vous faites cela via

if (!Enum.IsDefined(typeof(WriteMode), userValue))
    throw new ArgumentException("userValue");

Le seul inconvénient de Enum.IsDefined est qu'il utilise la réflexion et est plus lent. Il souffre également d'un problème de version. Si vous devez vérifier la valeur enum souvent, il serait préférable de ce qui suit:

public static bool CheckWriteModeEnumValue(WriteMode writeMode)
{
  switch( writeMode )
  {
    case WriteMode.Append:
    case WriteMode.OverWrite:
      break;
    default:
      Debug.Assert(false, "The WriteMode '" + writeMode + "' is not valid.");
      return false;
  }
  return true;
}

Le problème de gestion de version est que l’ancien code peut seulement savoir comment gérer les 2 énums que vous avez. Si vous ajoutez une troisième valeur, Enum.IsDefined sera true, mais l'ancien code ne pourra pas nécessairement le gérer. Oups.

Vous pouvez encore plus vous amuser avec les codes [Flags] , et le code de validation correspondant est légèrement différent.

Je noterai également que pour la portabilité, vous devez utiliser l'appel ToString () sur l'énumération, et utiliser Enum.Parse () lors de leur relecture. Les codes ToString () et Enum.Parse () peuvent également gérer les énumérations [Flags] , il n'y a donc aucune raison de ne pas les utiliser. Remarquez que c’est un autre piège, car vous ne pouvez même plus changer le nom de l’énum sans casser le code.

Alors, vous avez parfois besoin de peser tout ce qui précède lorsque vous vous posez la question Puis-je m'en sortir avec juste une folie?

À mon humble avis, il semble qu’une enum serait le choix évident pour toute situation où plus de deux options sont possibles. Mais il existe certainement des situations où un booléen est tout ce dont vous avez besoin. Dans ce cas, je dirais que l'utilisation d'une énumération où une valeur booléenne fonctionnerait serait un exemple d'utilisation de 7 mots lorsque 4 suffiraient.

Les booléens ont du sens quand vous avez une bascule évidente qui ne peut être qu’une des deux choses (c’est-à-dire l’état d’une ampoule, allumé ou éteint). Autre que cela, il est bon de l'écrire de telle manière que ce que vous passez soit évident - par exemple. les écritures sur disque - non mises en mémoire tampon, en mémoire tampon de ligne ou synchrones - doivent être transmises en tant que telles. Même si vous ne souhaitez pas autoriser les écritures synchrones maintenant (et que vous êtes donc limité à deux options), envisagez de les rendre plus verbeuses afin de savoir ce qu'elles font à première vue.

Cela dit, vous pouvez également utiliser False et True (booléens 0 et 1), puis si vous avez besoin de plus de valeurs ultérieurement, développez la fonction pour prendre en charge les valeurs définies par l'utilisateur (par exemple 2 et 3) et votre ancien 0. Les valeurs / 1 seront bien portées, votre code ne devrait donc pas se briser.

Parfois, il est simplement plus simple de modéliser différents comportements avec des surcharges. Pour continuer à partir de votre exemple, procédez comme suit:

file.appendData( data );  
file.overwriteData( data );

Cette approche se dégrade si vous avez plusieurs paramètres, chacun permettant un ensemble d'options fixe. Par exemple, une méthode qui ouvre un fichier peut avoir plusieurs permutations de mode de fichier (ouvrir / créer), d’accès au fichier (lecture / écriture), de mode de partage (aucune / lecture / écriture). Le nombre total de configurations est égal aux produits cartésiens des options individuelles. Naturellement, dans de tels cas, les surcharges multiples ne sont pas appropriées.

Les énumérations peuvent, dans certains cas, rendre le code plus lisible, bien que la validation exacte de la valeur de énumération dans certaines langues (C # par exemple) puisse s'avérer difficile.

Souvent, un paramètre booléen est ajouté à la liste des paramètres en tant que nouvelle surcharge. Un exemple dans .NET est:

Enum.Parse(str);  
Enum.Parse(str, true); // ignore case

Cette dernière surcharge est devenue disponible dans une version plus récente du framework .NET que la première.

Si vous savez qu'il n'y aura jamais que deux choix, un booléen peut convenir. Les énumérations sont extensibles d’une manière qui ne casse pas l’ancien code, bien que les anciennes bibliothèques ne prennent peut-être pas en charge les nouvelles valeurs d’énum, ??de sorte que la gestion des versions ne puisse être complètement ignorée.

MODIFIER

Dans les versions plus récentes de C #, il est possible d'utiliser des arguments nommés qui, Messieurs, permettent de clarifier le code d'appel de la même manière que l'énumération. En utilisant le même exemple que ci-dessus:

Enum.Parse(str, ignoreCase: true);

Là où je conviens que les énumérations sont un bon moyen d’aller dans les méthodes où vous avez 2 options (et seulement 2 options vous pouvez avoir la lisibilité sans énumération.)

par exemple

public void writeData(Stream data, boolean is_overwrite)

Aimez les Enums, mais le booléen est utile aussi.

Il s’agit d’une entrée tardive dans un ancien message. C’est tellement loin en bas de la page que personne ne le lira jamais, mais puisque personne ne l’a déjà dit ....

Un commentaire en ligne permet de résoudre le problème inattendu de bool . L'exemple original est particulièrement odieux: imaginez que vous essayez de nommer la variable dans la fonction de déclinaison! Ce serait quelque chose comme

void writeData( DataObject data, bool use_append_mode );

Mais, à titre d'exemple, supposons que ce soit la déclaration. Ensuite, pour un argument booléen autrement inexpliqué, je mets le nom de la variable dans un commentaire en ligne. Comparer

file.writeData( data, true );

avec

file.writeData( data, true /* use_append_mode */);

Cela dépend vraiment de la nature exacte de l’argument. Si ce n'est pas un oui / non ou vrai / faux, une énumération le rend plus lisible. Mais avec une énumération, vous devez vérifier l’argument ou adopter un comportement par défaut acceptable, car des valeurs indéfinies du type sous-jacent peuvent être passées.

L'utilisation d'énums au lieu de booléens dans votre exemple aide à rendre l'appel de méthode plus lisible. Cependant, ceci remplace mon élément de souhait favori en C #, les arguments nommés dans les appels de méthode. Cette syntaxe:

var v = CallMethod(pData = data, pFileMode = WriteMode, pIsDirty = true);

serait parfaitement lisible, et vous pourriez alors faire ce qu'un programmeur devrait faire: choisir le type le plus approprié pour chaque paramètre de la méthode, quelle que soit son apparence dans l'EDI.

C # 3.0 autorise les arguments nommés dans les constructeurs. Je ne sais pas pourquoi ils ne peuvent pas faire cela avec des méthodes aussi.

Valeurs booléennes true / false uniquement. Donc, on ne sait pas ce que cela représente. Enum peut avoir un nom significatif, par exemple OVERWRITE , APPEND , etc. Ainsi, les énumérations sont meilleures.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top