Quel est le problème avec les chaînes magiques?

164

En tant que développeur de logiciel expérimenté, j'ai appris à éviter les chaînes magiques.

Mon problème est qu'il y a si longtemps que je ne les utilise pas, j'en ai oublié la plupart des raisons. En conséquence, j'ai du mal à expliquer pourquoi ils posent problème à mes collègues moins expérimentés.

Quelles sont les raisons objectives pour les éviter? Quels problèmes causent-ils?

Kramii
la source
38
Qu'est-ce qu'une ficelle magique? Même chose que des nombres magiques ?
Laiv
14
@Laiv: Ils ressemblent aux nombres magiques, oui.J'aime la définition sur deviq.com/magic-strings : "Les chaînes magiques sont des valeurs de chaîne spécifiées directement dans le code de l'application qui ont une incidence sur le comportement de l'application.". (La définition sur en.wikipedia.org/wiki/Magic_string n'est pas ce que j'ai en tête.)
Kramii
17
c'est drôle j'ai appris à détester ... plus tard Quels arguments puis-je utiliser pour convaincre mes juniors ... L'histoire qui n'en finit pas :-). Je n'essaierais pas de "convaincre" que je préfère les laisser apprendre par eux-mêmes. Rien ne dure plus qu'une leçon / idée tirée de votre propre expérience. Ce que vous essayez de faire, c'est endoctriner . Ne faites pas ça sauf si vous voulez une équipe de Lemmings.
Laiv
15
@ Laiv: J'aimerais laisser les gens apprendre de leur propre expérience, mais malheureusement, ce n'est pas une option pour moi. Je travaille pour un hôpital financé par des fonds publics, où des microbes subtils peuvent compromettre les soins des patients et où nous ne pouvons pas nous permettre des coûts de maintenance évitables.
Kramii
6
@ David Arno, c'est exactement ce qu'il fait en posant cette question.
user56834

Réponses:

211
  1. Dans un langage qui compile, la valeur d'une chaîne magique n'est pas vérifiée lors de la compilation . Si la chaîne doit correspondre à un modèle particulier, vous devez exécuter le programme pour garantir qu'il correspond à ce modèle. Si vous avez utilisé quelque chose comme une énumération, la valeur est au moins valide au moment de la compilation, même s'il peut s'agir d'une valeur incorrecte.

  2. Si une chaîne magique est écrite à plusieurs endroits, vous devez tous les changer sans aucune sécurité (telle qu'une erreur de compilation). Cela peut être contré en le déclarant seulement à un endroit et en réutilisant la variable.

  3. Les fautes de frappe peuvent devenir des bugs sérieux. Si vous avez une fonction:

    func(string foo) {
        if (foo == "bar") {
            // do something
        }
    }
    

    et quelqu'un tape accidentellement:

    func("barr");
    

    C’est pire si la chaîne est rare ou complexe, surtout si vous avez des programmeurs qui ne connaissent pas la langue maternelle du projet.

  4. Les chaînes magiques sont rarement auto-documentées. Si vous voyez une chaîne, cela ne vous dit rien de ce que la chaîne pourrait / devrait être d'autre. Vous devrez probablement examiner l'implémentation pour vous assurer que vous avez sélectionné la bonne chaîne.

    Ce type d'implémentation présente des fuites , nécessitant soit une documentation externe, soit un accès au code pour comprendre ce qui doit être écrit, d'autant plus qu'il doit s'agir d'un caractère parfait (comme au point 3).

  5. En dehors des fonctions de recherche de chaîne dans les IDE, un petit nombre d'outils prend en charge le modèle.

  6. Il se peut que vous utilisiez par hasard la même chaîne magique à deux endroits différents, alors si vous effectuez une recherche et un remplacement et que vous les modifiez tous les deux, l'un d'eux risque de se rompre pendant que l'autre fonctionne.

Erdrik Ironrose
la source
34
En ce qui concerne le premier argument: TypeScript est un langage compilé qui permet de typer les littéraux de chaîne. Cela invalide également l'argument deux à quatre. Par conséquent, le problème ne réside pas dans les chaînes elles-mêmes, mais dans l'utilisation d'un type qui autorise trop de valeurs. Le même raisonnement peut s’appliquer à l’utilisation d’entiers magiques pour les énumérations.
Yogu
11
Étant donné que je n'ai aucune expérience avec TypeScript, je m'en remettrai à votre jugement. Ce que je dirais alors, c'est que ce sont les chaînes non vérifiées (comme c'est le cas pour toutes les langues que j'ai utilisées) qui posent problème.
Erdrik Ironrose
23
@Yogu Typescript ne renommera pas toutes vos chaînes si vous modifiez le type littéral de chaîne statique que vous attendez. Vous obtiendrez des erreurs de compilation pour vous aider à toutes les retrouver, mais ce n'est qu'une amélioration partielle sur 2. Ne dites pas que c'est absolument incroyable (parce que c'est ça, et j'adore la fonctionnalité), mais cela ne le fait certainement pas. éliminer carrément l'avantage des enums. Dans notre projet, quand utiliser des énums et quand ne pas en rester reste une sorte de question ouverte dont nous ne sommes pas sûrs; les deux approches ont des inconvénients et des avantages.
Kryan
30
Un gros problème que j'ai vu moins pour les chaînes que pour les chiffres, mais pourrait se produire avec des chaînes, est lorsque vous avez deux valeurs magiques avec la même valeur. Puis l'un d'entre eux change. Maintenant, vous utilisez du code qui modifie l'ancienne valeur pour qu'elle corresponde à la nouvelle valeur, mais vous effectuez également un travail EXTRA pour vous assurer que vous ne modifiez pas les mauvaises valeurs. Avec des variables constantes, non seulement vous n'avez pas à le parcourir manuellement, mais vous ne craignez pas d'avoir changé le mauvais.
CorsiKa
35
@Yogu J'ajouterais que si la valeur d'un littéral de chaîne est vérifiée au moment de la compilation, elle cesse alors d'être une chaîne magique . À ce stade, il s’agit simplement d’une valeur normale const / enum qui s’écrit de manière amusante. Dans cette perspective, je dirais en fait que votre commentaire appuie les arguments d'Erdrik au lieu de les réfuter.
GrandOpener
89

Le point culminant de ce que les autres réponses ont compris n'est pas que les "valeurs magiques" sont mauvaises, mais qu'elles devraient l'être:

  1. définis de manière reconnaissable comme des constantes;
  2. définis une seule fois dans tout leur domaine d’utilisation (si possible du point de vue de l’architecture);
  3. définies ensemble si elles forment un ensemble de constantes qui sont en quelque sorte liées;
  4. définis à un niveau de généralité approprié dans l'application dans laquelle ils sont utilisés; et
  5. définis de manière à limiter leur utilisation dans des contextes inappropriés (par exemple, susceptibles de vérification de type).

Ce qui distingue généralement les "constantes" acceptables des "valeurs magiques" est une violation d'une ou plusieurs de ces règles.

Bien utilisées, les constantes nous permettent simplement d’exprimer certains axiomes de notre code.

Ce qui m'amène au dernier point, à savoir un usage excessif de constantes (et donc d'un nombre excessif d'hypothèses ou de contraintes exprimées en termes de valeurs), même s'il respecte par ailleurs les critères ci-dessus (mais surtout s'il s'en écarte), Cela peut impliquer que la solution en cours d’élaboration n’est pas suffisamment générale ou structurée (nous ne parlons donc plus vraiment du pour et du contre des constantes, mais du pour et du contre d’un code bien structuré).

Les langages de haut niveau ont des constructions pour les modèles dans les langages de bas niveaux qui devraient employer des constantes. Les mêmes modèles peuvent également être utilisés dans le langage de niveau supérieur, mais ne devraient pas l'être.

Mais cela peut être un jugement d'expert basé sur une impression de toutes les circonstances et sur ce à quoi une solution devrait ressembler, et la façon dont ce jugement sera justifié dépendra beaucoup du contexte. En effet, il pourrait ne pas être justifié en termes de principe général, si ce n’est d’affirmer "je suis assez vieux pour avoir déjà vu ce genre de travail, avec lequel je suis familier, mieux fait"!

EDIT: après avoir accepté une édition, en rejeter une autre, et ayant maintenant effectué ma propre édition, puis-je maintenant considérer le style de formatage et de ponctuation de ma liste de règles à régler une fois pour toutes haha!

Steve
la source
2
J'aime cette réponse. Après tout, "struct" (et tous les autres mots réservés) est une chaîne magique destinée au compilateur C. Il y a de bonnes et de mauvaises manières de les coder.
Alfred Armstrong
6
Par exemple, si quelqu'un voit «X: = 898755167 * Z» dans votre code, il ne saura probablement pas ce que cela signifie et encore moins susceptible de savoir que c'est faux. Mais s'ils voient «Speed_of_Light: constant Integer: = 299792456», quelqu'un le recherchera et suggérera la valeur correcte (et peut-être même un meilleur type de données).
WGroleau
26
Certaines personnes oublient complètement le problème et écrivent COMMA = "," au lieu de SEPARATOR = ",". Le premier ne rend pas plus clair, tandis que le second énonce l'utilisation prévue et vous permet de changer le séparateur plus tard dans un seul endroit.
Marc
1
@marcus, en effet! Il est bien sûr judicieux d’utiliser des valeurs littérales simples sur place: par exemple, si une méthode divise une valeur en deux, il peut être plus simple et plus simple d’écrire value / 2, plutôt que value / VALUE_DIVISORde la définir comme 2ailleurs. Si vous avez l'intention de généraliser une méthode de gestion de fichiers CSV, vous souhaiterez probablement que le séparateur soit passé en tant que paramètre et non défini en tant que constante. Mais c’est une question de jugement dans le contexte. L’exemple de @ WGroleau SPEED_OF_LIGHTest quelque chose que vous voudriez nommer explicitement, mais tous les littéraux n’ont pas besoin de cela.
Steve
4
La meilleure réponse est meilleure que cette réponse si besoin de convaincre que les cordes magiques sont une "mauvaise chose". Cette réponse est préférable si vous savez et acceptez qu’ils sont une "mauvaise chose" et que vous devez trouver le meilleur moyen de répondre aux besoins auxquels ils répondent de manière maintenable.
CorsiKa
34
  • Ils sont difficiles à suivre.
  • Tout modifier peut nécessiter la modification de plusieurs fichiers dans plusieurs projets (difficile à gérer).
  • Parfois, il est difficile de dire quel est leur but simplement en regardant leur valeur.
  • Pas de réutilisation.
Jason
la source
4
Que signifie "pas de réutilisation"?
revoir
7
Au lieu de créer une variable / constante, etc. et de la réutiliser dans tout votre projet / code, vous créez une nouvelle chaîne dans chaque code, ce qui entraîne une duplication inutile.
jason
Donc, les points 2 et 4 sont les mêmes?
Thomas
4
@ThomasMoors Non, il ne parle pas de la façon dont vous devez construire une nouvelle chaîne chaque fois que vous souhaitez utiliser une chaîne magique existante . Le point 2 concerne le changement de la chaîne elle
Pierre Arlaud le
25

Exemple concret: je travaille avec un système tiers dans lequel les "entités" sont stockées avec des "champs". Fondamentalement, un système EAV . Comme il est assez facile d'ajouter un autre champ, vous pouvez y accéder en utilisant le nom du champ sous forme de chaîne:

Field nameField = myEntity.GetField("ProductName");

(notez la chaîne magique "ProductName")

Cela peut entraîner plusieurs problèmes:

  • Je dois me référer à la documentation externe pour savoir que "ProductName" existe même et son orthographe exacte
  • De plus, je dois consulter cette documentation pour savoir quel est le type de données de ce champ.
  • Les fautes de frappe dans cette chaîne magique ne seront pas interceptées tant que cette ligne de code n'aura pas été exécutée.
  • Lorsque quelqu'un décide de renommer ce champ sur le serveur (difficile tout en empêchant la perte de données, mais pas impossible), alors je ne peux pas facilement rechercher dans mon code pour voir où je dois ajuster ce nom.

Ma solution a donc été de générer des constantes pour ces noms, organisées par type d’entité. Alors maintenant je peux utiliser:

Field nameField = myEntity.GetField(Model.Product.ProductName);

C'est toujours une constante de chaîne et compile exactement le même binaire, mais présente plusieurs avantages:

  • Après avoir tapé "Modèle", mon IDE affiche uniquement les types d'entités disponibles, ce qui me permet de sélectionner "Produit" facilement.
  • Ensuite, mon IDE fournit uniquement les noms de champs disponibles pour ce type d'entité, également sélectionnables.
  • La documentation générée automatiquement indique la signification de ce champ, ainsi que le type de données utilisé pour stocker ses valeurs.
  • À partir de la constante, mon IDE peut trouver tous les endroits où cette constante exacte est utilisée (par opposition à sa valeur)
  • Les fautes de frappe seront interceptées par le compilateur. Ceci s'applique également lorsqu'un nouveau modèle (éventuellement après avoir renommé ou supprimé un champ) est utilisé pour régénérer les constantes.

Suivant sur ma liste: masquer ces constantes derrière des classes fortement typées - le type de données est également sécurisé.

Hans Keing
la source
+1 vous apportez beaucoup de points
positifs
Si certaines parties de votre type d'entité sont suffisamment statiques pour que la définition d'un nom constant en vaille la peine, je pense qu'il serait plus à même de définir un modèle de données approprié pour pouvoir le faire nameField = myEntity.ProductName;.
Lie Ryan
@LieRyan - il était beaucoup plus facile de générer des constantes simples et de mettre à niveau les projets existants pour les utiliser. Cela dit, je suis travaille sur la génération de types statiques pour que je puisse faire précisément
Hans Ke st ing
9

Les chaînes magiques ne sont pas toujours mauvaises , ce qui explique pourquoi vous ne pouvez pas trouver une raison générale de les éviter. (Par "chaîne magique", je suppose que vous entendez littéral chaîne dans le cadre d'une expression et non défini comme une constante.)

Dans certains cas particuliers, les chaînes magiques devraient être évitées:

  • La même chaîne apparaît plusieurs fois dans le code. Cela signifie que vous pourriez avoir une faute d'orthographe à l'un des endroits. Et ce sera un souci des changements de chaîne. Faites de la chaîne une constante et vous éviterez ce problème.
  • La chaîne peut changer indépendamment du code où elle apparaît. Par exemple. si la chaîne est un texte affiché à l'utilisateur final, elle changera probablement indépendamment de tout changement de logique. Séparer cette chaîne dans un module séparé (ou une configuration externe ou une base de données) facilitera la modification indépendante
  • La signification de la chaîne n'est pas évidente d'après le contexte. Dans ce cas, l'introduction d'une constante facilitera la compréhension du code.

Mais dans certains cas, les "chaînes magiques" vont bien. Disons que vous avez un analyseur simple:

switch (token.Text) {
  case "+":
    return a + b;
  case "-":
    return a - b;
  //etc.
}

Il n’ya vraiment pas de magie ici, et aucun des problèmes décrits ci-dessus ne s’applique. Il n'y aurait aucun avantage à mon humble avis à définir string Plus="+"etc. Restez simple.

JacquesB
la source
7
Je pense que votre définition de "chaîne magique" est insuffisante, elle doit avoir un concept de dissimulation / obscurcissement / mystification. Je ne qualifierais pas le "+" et le "-" dans ce contre-exemple de "magie", pas plus que je dirais que le zéro est magique if (dx != 0) { grad = dy/dx; }.
Rupe
2
@Rupe: Je suis d'accord, mais l'OP utilise la définition " valeurs de chaîne spécifiées directement dans le code de l'application qui ont une incidence sur le comportement de l'application ", ce qui n'exige pas que la chaîne soit mystérieuse. la réponse.
JacquesB
7
En vous référant à votre exemple, j'ai vu des déclarations de switch qui remplaçaient "+"et "-"par TOKEN_PLUSet TOKEN_MINUS. A chaque fois que je le lisais, j'avais l'impression qu'il était plus difficile de le lire et de le déboguer! C'est certainement un endroit où je conviens que l'utilisation de chaînes simples est préférable.
Cort Ammon
2
Je conviens qu'il y a des moments où les chaînes magiques sont appropriées: les éviter est une règle de base, et toutes les règles de base ont des exceptions. Espérons que, quand nous comprendrons pourquoi ils peuvent être une mauvaise chose, nous pourrons faire des choix intelligents plutôt que de faire des choses parce que (1) nous n’avons jamais compris qu’il pouvait exister un meilleur moyen, ou (2) nous 'un développeur senior ou une norme de codage nous ont dit de faire les choses différemment.
Kramii
2
Je ne sais pas ce qui est "magique" ici. Celles-ci ressemblent à des littéraux de base pour moi.
tchrist
6

Pour ajouter aux réponses existantes:

Internationalisation (i18n)

Si le texte à afficher à l'écran est codé en dur et enfoui dans des couches de fonctions, vous aurez beaucoup de difficulté à fournir la traduction de ce texte dans d'autres langues.

Certains environnements de développement (par exemple, Qt) gèrent les traductions en recherchant une chaîne de texte de la langue de base dans la langue traduite. Les chaînes magiques peuvent généralement y survivre - jusqu'à ce que vous décidiez d'utiliser le même texte ailleurs et d'obtenir une faute de frappe. Même dans ce cas, il est très difficile de déterminer quelles chaînes magiques doivent être traduites lorsque vous souhaitez prendre en charge une autre langue.

Certains environnements de développement (par exemple, MS Visual Studio) adoptent une autre approche et exigent que toutes les chaînes traduites soient conservées dans une base de données de ressources et relues pour les paramètres régionaux actuels par l'ID unique de cette chaîne. Dans ce cas, votre application avec des chaînes magiques ne peut tout simplement pas être traduite dans une autre langue sans retouche majeure. Un développement efficace nécessite que toutes les chaînes de texte soient entrées dans la base de données de ressources et se voient attribuer un identifiant unique lors de la première écriture du code, et que la suite est relativement facile. Essayer de le remplacer après coup demandera généralement un très gros effort (et oui, j'y suis allé!), Il est donc préférable de faire les choses correctement.

Graham
la source
3

Ce n'est pas une priorité pour tout le monde, mais si vous voulez pouvoir calculer automatiquement les métriques de couplage / cohésion sur votre code, les chaînes magiques rendent cela presque impossible. Une chaîne située à un endroit fait référence à une classe, une méthode ou une fonction située à un autre endroit. Il n'existe pas de moyen automatique simple pour déterminer que la chaîne est couplée à la classe / méthode / fonction simplement en analysant le code. Seul le cadre sous-jacent (angulaire, par exemple) peut déterminer qu’il existe un lien - et il ne peut le faire qu’au moment de l’exécution. Pour obtenir vous-même les informations de couplage, votre analyseur devrait tout savoir sur le cadre que vous utilisiez, au-delà de la langue de base que vous codez.

Mais encore une fois, ce n’est pas quelque chose qui intéresse beaucoup les développeurs.

utilisateur3511585
la source