Les variables de drapeau sont-elles mauvaises? Les types de variables suivants sont-ils profondément immoraux et est-il méchant de les utiliser?
"des variables booléennes ou entières auxquelles vous attribuez une valeur à certains endroits puis en dessous vous vérifiez puis en plus pour faire quelque chose ou pas, comme, par exemple en utilisant
newItem = true
puis quelques lignes en dessous deif (newItem ) then
"
Je me souviens avoir fait quelques projets où j'ai totalement négligé d'utiliser des drapeaux et j'ai fini avec une meilleure architecture/code; cependant, c'est une pratique courante dans d'autres projets sur lesquels je travaille, et lorsque du code grandit et des drapeaux sont ajoutés, le code-spaghetti IMHO grandit également.
Diriez-vous qu'il existe des cas où l'utilisation de drapeaux est une bonne pratique ou même nécessaire?, Ou conviendrez-vous que l'utilisation de drapeaux dans le code est ... des drapeaux rouges et devrait être évitée/refactorisée; moi, je me contente de faire des fonctions/méthodes qui vérifient les états en temps réel à la place.
Le problème que j'ai vu lors de la maintenance du code qui utilise des drapeaux est que le nombre d'états augmente rapidement et qu'il y a presque toujours des états non gérés. Un exemple de ma propre expérience: je travaillais sur un code qui avait ces trois drapeaux
bool capturing, processing, sending;
Ces trois ont créé huit États (en fait, il y avait également deux autres drapeaux). Toutes les combinaisons de valeurs possibles n'étaient pas couvertes par le code, et les utilisateurs voyaient des bogues:
if(capturing && sending){ // we must be processing as well
...
}
Il s'est avéré qu'il y avait des situations où l'hypothèse de la déclaration if ci-dessus était fausse.
Les indicateurs ont tendance à s'aggraver au fil du temps et masquent l'état réel d'une classe. C'est pourquoi ils devraient être évités.
Voici un exemple lorsque les indicateurs sont utiles.
J'ai un morceau de code qui génère des mots de passe (en utilisant un générateur de nombres pseudo-aléatoires cryptographiquement sécurisé). L'appelant de la méthode choisit si le mot de passe doit contenir des majuscules, des minuscules, des chiffres, des symboles de base, des symboles étendus, des symboles grecs, des cyrilliques et unicode.
Avec des drapeaux, appeler cette méthode est facile:
var password = this.PasswordGenerator.Generate(
CharacterSet.Digits | CharacterSet.LowercaseLetters | CharacterSet.UppercaseLetters);
et il peut même être simplifié pour:
var password = this.PasswordGenerator.Generate(CharacterSet.LettersAndDigits);
Sans drapeaux, quelle serait la signature de la méthode?
public byte[] Generate(
bool uppercaseLetters, bool lowercaseLetters, bool digits, bool basicSymbols,
bool extendedSymbols, bool greekLetters, bool cyrillicLetters, bool unicode);
appelé comme ceci:
// Very readable, isn't it?
// Tell me just by looking at this code what symbols do I want to be included?
var password = this.PasswordGenerator.Generate(
true, true, true, false, false, false, false, false);
Comme indiqué dans les commentaires, une autre approche consisterait à utiliser une collection:
var password = this.PasswordGenerator.Generate(
new []
{
CharacterSet.Digits,
CharacterSet.LowercaseLetters,
CharacterSet.UppercaseLetters,
});
Ceci est beaucoup plus lisible par rapport à l'ensemble de true
et false
, mais présente toujours deux inconvénients:
L'inconvénient majeur est que pour autoriser des valeurs combinées, comme CharacterSet.LettersAndDigits
, Vous écririez quelque chose comme ça dans la méthode Generate()
:
if (set.Contains(CharacterSet.LowercaseLetters) ||
set.Contains(CharacterSet.Letters) ||
set.Contains(CharacterSet.LettersAndDigits) ||
set.Contains(CharacterSet.Default) ||
set.Contains(CharacterSet.All))
{
// The password should contain lowercase letters.
}
éventuellement réécrit comme ceci:
var lowercaseGroups = new []
{
CharacterSet.LowercaseLetters,
CharacterSet.Letters,
CharacterSet.LettersAndDigits,
CharacterSet.Default,
CharacterSet.All,
};
if (lowercaseGroups.Any(s => set.Contains(s)))
{
// The password should contain lowercase letters.
}
Comparez cela avec ce que vous avez en utilisant des drapeaux:
if (set & CharacterSet.LowercaseLetters == CharacterSet.LowercaseLetters)
{
// The password should contain lowercase letters.
}
Le deuxième inconvénient très mineur est qu'il n'est pas clair comment la méthode se comporterait si elle était appelée comme ceci:
var password = this.PasswordGenerator.Generate(
new []
{
CharacterSet.Digits,
CharacterSet.LettersAndDigits, // So digits are requested two times.
});
Un énorme bloc fonctionnel est l'odeur, pas les drapeaux. Si vous définissez le drapeau sur la ligne 5, ne vérifiez que le drapeau sur la ligne 354, alors c'est mauvais. Si vous placez le drapeau sur la ligne 8 et vérifiez le drapeau sur la ligne 10, ça va. De plus, un ou deux drapeaux par bloc de code conviennent, 300 drapeaux dans une fonction sont mauvais.
Habituellement, les drapeaux peuvent être complètement remplacés par une certaine saveur du modèle de stratégie, avec une implémentation de stratégie pour chaque valeur possible du drapeau. Cela facilite beaucoup l'ajout de nouveaux comportements.
Dans les situations critiques de performances, le coût de l'indirection pourrait faire surface et rendre la déconstruction en drapeaux clairs nécessaire. Cela étant dit, j'ai du mal à me souvenir d'un seul cas où j'ai dû le faire.
Non, les drapeaux ne sont pas mauvais ou un mal qui doivent être refactorisés à tout prix.
Considérez Java Pattern.compile (String regex, int flags) call. Ceci est un bitmask traditionnel et cela fonctionne. Regardez les constantes in Java et partout où vous voyez un tas de 2n vous savez qu'il y a des drapeaux.
Dans un monde refactorisé idéal, on utiliserait à la place un EnumSet où les constantes sont à la place des valeurs dans une énumération et comme le dit la documentation:
Les performances spatiales et temporelles de cette classe devraient être suffisamment bonnes pour permettre son utilisation en tant qu'alternative de haute qualité et sécurisée aux "indicateurs de bits" basés sur l'int.
Dans un monde parfait, cet appel Pattern.compile devient Pattern.compile(String regex, EnumSet<PatternFlagEnum> flags)
.
Cela dit, ses drapeaux fixes. Il est beaucoup plus facile de travailler avec Pattern.compile("foo", Pattern.CASE_INSENSTIVE | Pattern.MULTILINE)
que ce ne serait avec Pattern.compile("foo", new PatternFlags().caseInsenstive().multiline())
ou un autre style pour essayer de faire ce que les drapeaux sont vraiment et bon pour.
Les indicateurs sont souvent visibles lorsque vous travaillez avec des choses au niveau du système. Lors de l'interfaçage avec quelque chose au niveau du système d'exploitation, on est susceptible d'avoir un indicateur quelque part - que ce soit la valeur de retour d'un processus, ou les autorisations d'un fichier, ou les indicateurs pour ouvrir un socket. Essayer de refactoriser ces instances dans une chasse aux sorcières contre une odeur de code perçue se terminera probablement par un code pire que si l'on utilisait acceptait et comprenait le drapeau.
Le problème se produit lorsque les gens utilisent les drapeaux à mauvais escient en les lançant ensemble et en créant un ensemble de fanions de toutes sortes de drapeaux sans rapport ou en essayant de les utiliser là où ils ne sont pas du tout des drapeaux.
Je suppose que nous parlons de drapeaux dans les signatures de méthode.
L'utilisation d'un seul drapeau est déjà assez mauvaise.
Cela ne signifiera rien pour vos collègues la première fois qu'ils le verront. Ils devront regarder le code source de la méthode afin d'établir ce qu'elle fait. Vous serez probablement dans la même position quelques mois plus tard, lorsque vous oublierez ce qu'était votre méthode.
Passer un indicateur à la méthode signifie normalement que votre méthode est responsable de plusieurs choses. À l'intérieur de la méthode, vous effectuez probablement une simple vérification sur les lignes de:
if (flag)
DoFlagSet();
else
DoFlagNotSet();
C'est une mauvaise séparation des préoccupations et vous pouvez normalement trouver un moyen de contourner ce problème.
J'ai normalement deux méthodes distinctes:
public void DoFlagSet()
{
}
public void DoFlagNotSet()
{
}
Cela aura plus de sens avec les noms de méthode applicables au problème que vous résolvez.
Passer plusieurs drapeaux est deux fois plus mauvais. Si vous avez vraiment besoin de passer plusieurs drapeaux, envisagez de les encapsuler dans une classe. Même alors, vous serez toujours confronté au même problème, car votre méthode fait probablement plusieurs choses.
Les drapeaux et la plupart des variables temporaires ont une forte odeur. Très probablement, ils pourraient être refactorisés et remplacés par des méthodes de requête.
Modifié:
Les indicateurs et les variables temporaires lors de l'expression de l'état doivent être refactorisés pour les méthodes de requête. Les valeurs d'état (booléens, ints et autres primitives) devraient presque toujours être masquées dans le cadre des détails d'implémentation.
Les indicateurs utilisés pour le contrôle, le routage et le flux de programme général peuvent également indiquer la possibilité de refactoriser des sections des structures de contrôle dans des stratégies ou des usines distinctes, ou tout ce qui peut être approprié à la situation, qui continuent à utiliser les méthodes de requête.
Lorsque nous parlons de drapeaux, nous devons savoir qu'ils vont être modifiés au cours de l'exécution du programme et qu'ils vont affecter le comportement du programme en fonction de leurs états. Tant que nous aurons un contrôle net sur ces deux choses, elles fonctionneront très bien.
Les drapeaux peuvent très bien fonctionner si
S'il y a beaucoup de drapeaux, un bon travail de conception devrait précéder puisque les drapeaux commencent alors à jouer un rôle clé dans le comportement du programme. Vous pouvez opter pour des diagrammes d'état pour la modélisation. Ces diagrammes servent également de documentation et d'orientation visuelle tout en les traitant.
Tant que ces choses sont en place, je pense que cela ne mènera pas au gâchis.
J'ai supposé à partir de la question que l'AQ signifiait des variables d'indicateur (globales), et non des bits d'un paramètre de fonction.
Il y a des situations où vous n'avez pas beaucoup d'autres possibilités. Par exemple, sans système d'exploitation, vous devez évaluer les interruptions. Si une interruption survient très fréquemment et que vous n'avez pas le temps de faire une longue évaluation dans l'ISR, il est non seulement autorisé, mais parfois même la meilleure pratique de ne définir que quelques indicateurs globaux dans l'ISR (vous devez passer le moins de temps possible dans l'ISR), et d'évaluer ces indicateurs dans votre boucle principale.
Je ne pense pas que quoi que ce soit est un mal absolu en programmation, jamais.
Il y a une autre situation où les drapeaux pourraient être en ordre, qui n'ont pas encore été mentionnés ici ...
Envisagez l'utilisation de fermetures dans cet extrait Javascript:
exports.isPostDraft = function ( post, draftTag ) {
var isDraft = false;
if (post.tags)
post.tags.forEach(function(tag){
if (tag === draftTag) isDraft = true;
});
return isDraft;
}
La fonction interne, transmise à "Array.forEach", ne peut pas simplement "renvoyer true".
Par conséquent, vous devez garder l'État à l'extérieur avec un drapeau.