Est-ce une mauvaise pratique de stocker certaines valeurs sous forme de chaînes?


19

C'est un titre très vague mais je ne pouvais pas penser à une meilleure façon de le formuler. Mais, juste à titre d'exemple, pensez à la direction dans laquelle un personnage dans un jeu se déplace if(character.direction == "left"). Il me semble que cela laisse trop de place aux erreurs idiotes, comme l'utilisation accidentelle Leftou lou quoi que ce soit à la place de left. Mes soupçons sont-ils corrects? Si oui, quelle est la meilleure façon de réaliser quelque chose comme ça?


10
vous voulez dire, outre les énumérations si votre langue les prend en charge?
hoffmale

12
Vous voulez dire Stringly Typed ?
RubberDuck


Certaines langues ne sont pas en mesure de stocker des valeurs autres que sous forme de chaînes, par exemple, bash.
mouviciel

Je ne sais pas pourquoi mais je me suis souvenu du "définir" en C. Vous pouvez faire des choses comme: #define false true
linuxunil

Réponses:


28

Si la langue que vous utilisez prend en charge l'utilisation des énumérations, je les utiliserais. Il vous permet de limiter le nombre d'options disponibles pour un type donné. par exemple en Java:

public enum Direction {
    LEFT,
    RIGHT,
    UP,
    DOWN
}

2
Cette réponse ne contribue pas à comprendre le problème OP; Je ne vois aucun raisonnement ici, seulement une opinion limitée dans sa portée aux langues avec des classes enumcomme java. De nombreuses langues (par exemple VBA) en ont enum, mais ce n'est peut-être pas la meilleure option car elles n'ont pas la même pérennité. Voir ma réponse pour une discussion de pourquoi enumseul est une solution incomplète, souvent fragile et spécifique à la langue.
2016

12

C'est une terrible pratique d'utiliser des chaînes littérales (ou des nombres magiques) dans le code.

Les énumérations sont bonnes, ou du moins utilisent des constantes (les énumérations ne sont bien sûr qu'un type d'encapsuleur pour une ou plusieurs constantes liées).

const string LEFT = "left"
if (someThing == LEFT) { doStuff(); }

Ce simple interrupteur a tellement d'avantages que je ne saurais même pas par où commencer pour les expliquer (du moins pas sans plus de café). Lisez sur les nombres magiques, c'est le même concept exact, appliqué uniquement à une valeur numérique au lieu d'une valeur de chaîne.

Voici une référence rapide, je suis sûr qu'il y en a des centaines d'autres: /programming/47882/what-is-a-magic-number-and-why-is-it-bad


1
Le problème que j'ai avec cette approche est que vous obtenez des bogues typographiques comme --const string LEFT = "letf" - alors que si vous travaillez avec des énumérations qui sont capturées au moment de la compilation.
Pieter B

@PieterB - Je pensais que la question OP était assez large, et l'exemple de "gauche" n'était qu'un exemple arbitraire - où tous les cas ne correspondraient pas à une énumération. Je suis d'accord que pour un ensemble de directions en particulier, une énumération est le meilleur choix, mais ma réponse était censée être plus générique en ce que "si vous la mettez dans un littéral, faites-en au moins une constante" (énumérations, bien sûr, étant un type de constante)
jleach

3
J'ai eu le plaisir de travailler avec une application qui attribuait les jours du week-end comme ces jours dont le nom commence par un "s". Ils étaient très surpris du "fait" que, lorsqu'ils ont internationalisé la demande, la France n'avait qu'un week-end d'une journée.
Pieter B

4
Appelez cela de la microoptimisation, mais certaines langues peuvent l'interpréter comme une comparaison de valeurs et perdre beaucoup de temps à vérifier chaque caractère de la chaîne. Encore plus probable si vous faites comme le demandeur l'a fait et comparez avec la chaîne codée en dur "gauche". S'il n'est pas nécessaire que la valeur constante soit une chaîne (c'est-à-dire que vous ne l'imprimez pas), il serait préférable d'utiliser un const int à la place.
Devsman

4

Réponse courte: Oui, les chaînes ne sont idéales pour aucune tâche autre que le stockage et l'accès à une séquence de caractères textuels, et même si les bits sous-jacents d'une abstraction sont des chaînes, il y a des avantages à les référencer en tant que variables ou constantes .

Réponse longue: la plupart des langues proposent des types plus proches du domaine de votre problème, et même si ce n'est pas le cas, elles ont probablement une méthode par laquelle vous pouvez définir leften tant qu'entité différente de right(etc etc). La transformer en chaîne en utilisant "left"est simplement la perte de la langue et des fonctionnalités utiles de l'EDI telles que la vérification des erreurs. Même si vous devez utiliser

left = "left";

ou quelque chose d'équivalent, il a des avantages à utiliser la chaîne lorsque vous y faites référence dans votre code. Par exemple,

if (player.direction == Left)

serait détecté comme une erreur au moment de la compilation (dans le meilleur des cas, java et autres) ou serait signalé par n'importe quel IDE valant ses bits comme étant mauvais (dans le pire des cas, de base et autres).

De plus, le fait d'avoir la notion d' leftentité référençable vous aidera à s'adapter à la direction que vous décidez d'aller avec le type de direction. En utilisant "left", la direction s'engage à être a String. Si vous trouvez ou créez une meilleure abstraction pour le type, vous devez parcourir tout le corps de votre code en changeant chaque instance de "left". Une constante ou une variable ne nécessitera aucune modification si le type est élaboré.

Le type que vous voulez vraiment est particulier à votre domaine. Qu'est-ce que votre code prévoit de faire avec vous left? Peut-être voudrez-vous mettre en miroir l'axe x d'une texture ou d'un sprite qui fait face à gauche ou qui avance; si tel est le cas, vous souhaiterez peut-être créer leftun objet avec une propriété ou une méthode qui reflète ce comportement, votre rightobjet ayant le résultat non inversé opposé. Vous pouvez faire cette logique dans un objet qui représente les sprites ou les textures, auquel cas vous souffrirez à nouveau pour l'utilisation "left"au lieu des leftraisons indiquées ci-dessus.

En Java (et probablement dans d'autres langages que je ne connais pas encore), enumc'est une bonne alternative car en Java, enumc'est aussi utile final classavec un ensemble fini d'instances. Vous pouvez définir des comportements si vous en avez besoin, et vous pouvez basculer vers une classe ouverte sans tracas en dehors du fichier qui déclare le enum, mais de nombreuses langues voient un enumcomme un type primitif ou nécessitent une syntaxe spéciale à utiliser. Cela fuit le enumcapot vers un code qui n'a rien à voir avec cela et peut-être besoin d'être corrigé plus tard. Assurez-vous de bien comprendre le concept de votre langue enumavant de considérer l'option.


2

Vous n'avez pas spécifié votre langue, mais pour donner une réponse générique, vous devez utiliser des chaînes lorsque vous avez réellement besoin du texte, pas pour représenter quelque chose. L'exemple que vous avez donné, par exemple, ne serait tout simplement pas acceptable dans les logiciels de production.

Chaque langue possède différents types de données de base et vous devez utiliser celle qui convient le mieux à la tâche. Pour utiliser votre exemple, vous pouvez utiliser des constantes numériques pour représenter différents états. Donc, gauche pourrait avoir une valeur de zéro et droite serait un. Outre la raison que vous avez mentionnée, les valeurs numériques occupent moins d'espace (mémoire) et il est toujours plus rapide de comparer des nombres que de comparer plusieurs chaînes de caractères.

Comme cela a été suggéré, utilisez des énumérations si votre langue le permet. Dans votre exemple spécifique, c'est clairement le meilleur choix.


Il est peu probable que la vitesse et la mémoire importent. Cela dit, préférez les énumérations ou les constantes. Les chaînes ont plus de sens si les données proviennent d'un format texte, par exemple XML, mais même alors, elles doivent être cohérentes avec toutes les majuscules ou toutes les décapsulations. Ou, toujours convertir , par exemple. if (uppercase(foo) == "LEFT")
user949300

2
@ user949300 La conversion de chaînes en majuscules ou en minuscules n'est pas non plus fiable. Il est possible que quelqu'un décide de visiter un autre lieu (ou d'y développer ses activités), comme la Turquie , et de rompre les comparaisons naïves de chaînes de boîtier supérieur (ou boîtier inférieur).
8bittree

Prendre soin de la vitesse et de la mémoire est toujours une bonne pratique, ce ne sont pas des ressources infinies. Bien qu'il soit acceptable de le sacrifier consciemment pour des raisons de lisibilité ou pour un autre compromis, ce qui n'est pas le cas ici. Mais ne pas s'en soucier peut facilement conduire à des applications lentes ou inutiles.
Nagev

La comparaison des chaînes est probablement 50 fois plus lente que la comparaison des énumérations. La plupart du temps, cela n'a pas d'importance. Mais si votre code est déjà un peu lent, cette différence peut le rendre inacceptable. Le plus important est bien sûr le fait que le compilateur ne peut pas vous aider avec les fautes d'orthographe si vous utilisez des chaînes.
gnasher729

1

Utilisez simplement le type de données qui convient à votre situation.

L'utilisation du stringtype comme communication inter / intra-application sous-jacente n'est pas intrinsèquement un problème. En fait, il est parfois préférable d'utiliser des chaînes: si vous avez une API publique, il peut être souhaitable que les valeurs entrant et sortant de l'API soient lisibles par l'homme. Cela facilite l'apprentissage et le débogage pour les utilisateurs de votre API.

Le vrai problème avec votre code réside dans la répétition de la valeur.

Ne faites pas ça:

if (direction == 'left') {
  goLeft();
}

Si vous avez fait une faute de frappe dans l'une de ces conditions ou d'autres utilisations de "left" quelque part, vous ne pourrez peut-être pas effectuer efficacement une recherche à l'échelle du code, car vous l'avez mal saisie!

Au lieu de cela, codez par rapport à une énumération ou une constante - selon la prise en charge du type de données dont vous avez besoin dans la ou les langues que vous utilisez.

Cela signifie que LEFTvous devez attribuer une seule et unique fois à votre demande. Et, le reste de votre code devrait exploiter ces énumérations ou constantes:

const string LEFT = "left";

if (direction == LEFT) {
  goLeft();
}

Cela vous permet également de modifier plus facilement le type de données que vous utilisez pour vos directions ultérieurement, si vous le souhaitez.


1

Est-ce une mauvaise pratique?

Probablement oui, mais cela dépend exactement de ce que vous faites, ainsi que du langage de programmation que vous utilisez.

Dans votre exemple, nous pouvons déduire qu'il existe un petit ensemble de valeurs fixes pour toujours qu'une direction pourrait prendre. Dans ce cas, il n'y a aucun avantage à utiliser une chaîne et certains inconvénients certains. Pour cet exemple:

  • Un enumtype serait préférable si votre langage de programmation le prend en charge.
  • Sinon, les constantes entières nommées sont la voie à suivre, avec une définition unique pour les constantes.

Mais la réponse peut être différente si l'ensemble de valeurs est modifiable dynamiquement ou doit évoluer dans le temps. Dans la plupart des langues, la modification d'un enumtype (par exemple pour ajouter ou supprimer une valeur) nécessite une recompilation complète. (Par exemple, la suppression d'une valeur d'énumération en Java rompt la compatibilité binaire.) Et il en va de même pour les constantes entières nommées.

Dans des scénarios comme celui-ci, une autre solution peut être requise et l'utilisation de chaînes est l'une des options. (Et vous pouvez combiner des littéraux de chaîne et des constantes nommées ... si le scénario implique un noyau fixe avec des extensions dynamiques.)


Si vous implémentez en Java, character.direction == "left"c'est une mauvaise pratique pour une autre raison. Vous ne devez pas utiliser ==pour comparer des chaînes, car il teste l'identité des objets plutôt que l'égalité des chaînes. (Cela fonctionnerait si vous pouviez garantir que toutes les instances de la "left"chaîne ont été internées, mais cela nécessite une analyse de l'ensemble de l'application.)


1
Ce qui semble fixe pour toujours s'avère souvent pas du tout fixe. Quatre directions peuvent par exemple devenir huit.
Jimmy T.20

@JimmyT. - Cela dépend du contexte. Par exemple, dans un jeu, changer le nombre de directions qu'un personnage peut déplacer de 4 à 8 entraînerait une refonte complète des règles du jeu et du code qui met en œuvre les règles. Utiliser Stringpour représenter les directions ferait une différence proche de zéro à la quantité de travail nécessaire pour effectuer les changements. Une approche plus sensée consiste à supposer que le nombre de directions ne changera pas et à reconnaître que vous auriez beaucoup de travail à faire de toute façon si les règles du jeu changeaient si fondamentalement.
Stephen C

@JimmyT - Tout à fait d'accord, j'ai vu cela se produire plusieurs fois, vous obtenez également que les développeurs prennent le raccourci de redéfinir la chaîne, par exemple, le produit = "Option" devient le produit = "Option_or_Future" qui dévalorise totalement la signification du code.
Chris Milburn

-3

Comme suggéré, vous devez utiliser Enums. Cependant, le simple fait d'utiliser l'Enum en remplacement des Strigns n'est pas suffisant à mon humble avis. Dans votre exemple particulier, la vitesse du joueur devrait en fait être un vecteur au moyen de la physique:

class Vector {
  final double x;
  final double y;
}

Ce vecteur doit ensuite être utilisé pour mettre à jour la position du joueur à chaque tick.

Vous pouvez ensuite combiner cela avec une énumération pour une meilleure lisibilité:

enum Direction {
  UP(new Vector(0, 1)),
  RIGHT(new Vector(1, 0)),
  DOWN(new Vector(0, -1)),
  LEFT(new Vector(-1, 0));

  private Vector direction;

  Direction(Vector v) {
    this.direction = v;
  }

  public Vector getVector() { return this.direction; }
}

4
-1 Vous allez beaucoup trop loin dans l'exemple qu'il donne.
Pieter B
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.