Ne jamais utiliser des chaînes en Java? [fermé]

73

Je suis tombé par hasard sur une entrée de blog décourageant l'utilisation de Strings en Java pour rendre votre code dépourvu de sémantique, suggérant d'utiliser plutôt des classes wrappers minces. Voici les exemples d’avant et d’après que cette entrée fournit pour illustrer le propos:

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

En lisant des blogs sur la programmation, je suis arrivé à la conclusion que 90% est un non-sens, mais je me demande si c'est un argument valable. En quelque sorte, cela ne me convient pas, mais je ne peux pas déterminer exactement ce qui ne va pas avec le style de programmation.

DPM
la source
Discussion pertinente sur le wiki original: "qu'est-ce que le toString () de?"
Christoffer Hammarström
5
Oh wow, ce blog m'a fait me sentir physiquement malade
Rob
5
J'ai lu au moins un livre (qui était peut-être la première édition de Code Complete) recommandant de saisir tous les types primitifs pour avoir des noms appartenant au domaine posant problème. Même idée.
user16764
9
une déclaration commençant par "jamais" est "toujours" fausse!
Florents Tselai
1
Ce mec est un CTO?!
Hyangelo

Réponses:

88

L'encapsulation est là pour protéger votre programme contre les changements . La représentation d'un nom va-t-elle changer? Sinon, vous perdez votre temps et YAGNI s'applique.

Edit: J'ai lu le blog et il a une bonne idée. Le problème, c'est qu'il a beaucoup trop évolué. Quelque chose String orderId est vraiment mauvais, parce que vraisemblablement "!"£$%^&*())ADAFVFn'est pas valide orderId. Cela signifie que Stringreprésente beaucoup plus de valeurs possibles qu'il n'y a de orderIds valides . Cependant, pour quelque chose comme un name, alors vous ne pouvez pas prédire ce qui pourrait ou non être un nom valide, et any Stringest valide name.

En premier lieu, vous réduisez (correctement) les entrées possibles en données valides uniquement. Dans le second cas, vous n'avez pas réduit le nombre d'entrées valides possibles.

Modifier à nouveau: considérons le cas d'une entrée invalide. Si vous écrivez "Gareth Gobulcoque" comme votre nom, ça aura l'air ridicule, ce ne sera pas la fin du monde. Si vous entrez un OrderID invalide, il y a de grandes chances qu'il ne fonctionne tout simplement pas.

DeadMG
la source
5
Avec les identifiants de commande, je préférerais probablement encore ajouter une vérification dans le code acceptant les identifiants et laisser la chaîne. Les classes sont supposées fournir des méthodes pour utiliser des données. Si une classe ne fait que vérifier la validité de certaines données et ne fait rien ensuite, cela ne me semble pas approprié. OOP est bon, mais vous ne devriez pas en faire trop.
Malcolm
13
@ Malcolm: Mais vous n'avez aucun moyen de savoir quelles chaînes sont validées, sauf pour les valider encore et encore.
DeadMG
7
"[...] vous ne pouvez pas prédire ce qui pourrait être ou ne pas être un nom valide, et toute chaîne est un nom valide." J'imagine que l'un des points forts de cette technique est que, si votre paramètre est de type Name, vous ne pouvez pas transmettre accidentellement une autre valeur String non liée. Lorsque vous vous attendez à un Name, seul un Nametestament est compilé et la chaîne "Je vous dois 5 $" ne peut être acceptée accidentellement. (Notez que ceci n’est pas lié à une validation de nom!)
Andres F.
6
La création de types spécifiques à une utilisation particulière ajoute une richesse sémantique et une sécurité accrue. De plus, la création de types pour représenter des valeurs spécifiques aide beaucoup lorsque vous utilisez un conteneur IoC pour lier automatiquement vos classes - il est facile de résoudre le bon bean à utiliser lorsqu'il est le seul bean enregistré pour une classe particulière. Beaucoup plus d'efforts sont nécessaires lorsqu'il ne s'agit que de l'une des nombreuses chaînes enregistrées.
Dathan
3
@DeadMG C'est argumentatif, et pas quelque chose que nous pouvons résoudre dans les commentaires. Je dirais que des valeurs mal placées de types primitifs se produisent, et le renforcement de vos interfaces est un moyen d'améliorer la situation.
Andres F.
46

C'est juste fou :)

Makach
la source
2
C'est aussi mon avis, mais le fait est que je me souviens de cette suggestion dans "Code Complete". Bien sûr, tout ce que contient ce livre n’est pas incontestable, mais au moins cela me fait réfléchir à deux fois avant de rejeter une idée.
DPM
8
Vous venez de mentionner des slogans sans véritable justification. Pouvez-vous préciser votre opinion? Certains modèles acceptés et certaines fonctionnalités linguistiques, par exemple, peuvent sembler complexes, tout en offrant quelque chose de précieux en retour (par exemple: typage statique)
Andres F.
12
Le bon sens est tout sauf commun.
Kaz Dragon
1
+1, j'ajouterais que lorsqu'un langage supporte des paramètres nommés, et que l'IDE est bon, c'est le cas de C # et de VS2010, il n'est pas nécessaire de compenser le manque de fonctionnalités dans le langage avec les motifs fous. . Il n’est pas nécessaire d’utiliser une classe nommée X et une classe nommée Y, si l’on peut écrire. var p = new Point(x: 10, y:20);En outre, ce n’est pas comme si le cinéma était si différent d’une chaîne. Je comprendrais si on devait traiter des quantités physiques, telles que la pression, la température, l’énergie, où les unités diffèrent et certaines ne peuvent pas être négatives. L'auteur du blog doit essayer de fonctionner.
Job
1
+1 pour "Ne pas trop d'ingénieur!"
Ivan
23

Je suis d'accord avec l'auteur, principalement. S'il existe un comportement spécifique à un champ, comme la validation d'un identifiant de commande, je créerais une classe pour représenter ce type. Son deuxième point est encore plus important: si vous avez un ensemble de champs représentant un concept comme une adresse, créez une classe pour ce concept. Si vous programmez en Java, vous payez un prix élevé pour le typage statique. Vous pouvez aussi bien obtenir toute la valeur que vous pouvez.

Kevin Cline
la source
2
Est-ce que le descendant peut commenter?
kevin cline
Quel est le prix élevé que nous payons pour le typage statique?
Richard Tingle
@RichardTingle: l'heure de recompiler le code et de redémarrer la machine virtuelle Java à chaque changement de code. Le temps perdu lorsque vous apportez une modification compatible avec la source mais incompatible avec les programmes binaires et les éléments devant être recompilés ne le sont pas et que vous obtenez "MissingMethodException".
kevin cline
Je n'ai jamais rencontré de problème avec les applications java (mais avec la compilation continue, il est généralement déjà compilé au moment où je clique sur Run), mais c'est un bon point avec les WAR Web pour lesquelles le redéploiement semble un peu plus lent
Richard Tingle,
16

Ne le fais pas; ça compliquera trop les choses et vous n'en aurez pas besoin

... est la réponse que j'aurais écrit ici il y a 2 ans. Maintenant, cependant, je n'en suis pas si sûr. En fait, ces derniers mois, j'ai commencé à migrer l'ancien code vers ce format, non pas parce que je n'ai rien de mieux à faire, mais parce que j'en avais vraiment besoin pour implémenter de nouvelles fonctionnalités ou modifier les existantes. Je comprends les aversions automatiques des autres ici à voir ce code, mais je pense que c'est quelque chose qui mérite une réflexion sérieuse.


Avantages

L'un des principaux avantages est la possibilité de modifier et d'étendre le code. Si tu utilises

class Point {
    int x,y;
    // other point operations
}

au lieu de simplement passer quelques entiers, ce qui est malheureusement le cas de nombreuses interfaces, il devient alors beaucoup plus facile d'ajouter ultérieurement une autre dimension. Ou changez le type en double. Si vous l'utilisez List<Author> authorsou le List<Person> authorsremplace List<String> authorsplus tard, il devient beaucoup plus facile d'ajouter plus d'informations à ce qu'un auteur représente. En écrivant cela comme cela, je me sens comme énonçant une évidence, mais en pratique, j’ai été coupable d’utiliser des cordes de cette façon plusieurs fois moi-même, surtout dans les cas où ce n’était pas évident au début, j’aurais besoin de plus de un string.

J'essaie actuellement de refactoriser une liste de chaînes qui est entrelacée dans tout mon code car j'ai besoin de plus d'informations là-bas et je sens la douleur: \

Au-delà de cela, je suis d'accord avec l'auteur du blog pour dire qu'il contient davantage d'informations sémantiques , ce qui facilite la compréhension du lecteur. Bien que les paramètres reçoivent souvent des noms significatifs et une ligne de documentation dédiée, ce n'est souvent pas le cas avec les champs ou les sections locales.

Le dernier avantage est la sécurité du type , pour des raisons évidentes, mais à mes yeux, c'est une chose mineure ici.

Désavantages

Cela prend plus de temps pour écrire . Écrire une petite classe est rapide et facile, mais pas sans effort, surtout si vous avez besoin de beaucoup de ces classes. Si vous vous arrêtez toutes les 3 minutes pour écrire une nouvelle classe de wrapper, cela peut aussi nuire à votre concentration. J'aimerais cependant penser que cet effort ne se produira généralement qu'à la première étape de l'écriture d'un morceau de code; Je peux généralement avoir rapidement une bonne idée de ce que les entités devront impliquer.

Cela peut impliquer un grand nombre de setters (ou de constructions) et de getters redondants . L'auteur du blog donne l'exemple vraiment laid de new Point(x(10), y(10))au lieu de new Point(10, 10), et j'aimerais ajouter qu'un usage peut également impliquer des choses comme Math.max(p.x.get(), p.y.get())au lieu de Math.max(p.x, p.y). Et le code long est souvent considéré comme plus difficile à lire, et à juste titre. Mais en toute honnêteté, j'ai l'impression que beaucoup de code déplace des objets, et que seules des méthodes choisies le créent, et encore moins ont besoin d'accéder à ses détails précis (ce qui n'est pas OOPy de toute façon).

Discutable

Je dirais que cela aide ou non à la lisibilité du code est discutable. Oui, plus d'informations sémantiques, mais un code plus long. Oui, il est plus facile de comprendre le rôle de chaque section locale, mais il est plus difficile de comprendre ce que vous pouvez en faire si vous ne lisez pas sa documentation.


Comme avec la plupart des écoles de pensée de programmation, je pense qu'il est malsain de pousser celui-ci à l'extrême. Je ne me vois jamais séparer les coordonnées x et y d'un type différent. Je ne pense pas que Countnécessaire quand un intdevrait suffire. Je n'aime pas l' unsigned intutilisation en C - bien que théoriquement bon, cela ne vous donne tout simplement pas assez d'informations, et cela interdit l'extension ultérieure de votre code pour prendre en charge cette valeur magique -1. Parfois, vous avez besoin de simplicité.

Je pense que ce blog est un peu extrême. Mais dans l’ensemble, j’ai appris d’une expérience douloureuse que l’idée de base est faite à partir des éléments appropriés.

J'ai une profonde aversion pour le code trop élaboré. Je le fais vraiment. Mais bien utilisé, je ne pense pas que cette sur-ingénierie.

Chêne
la source
5

Bien que ce soit un peu excessif, je pense souvent que la plupart des éléments que j'ai vus sont plutôt sous-développés.

Il ne s’agit pas uniquement de "sécurité". L’un des avantages de Java est qu’il vous aide beaucoup à vous rappeler et à déterminer exactement ce dont une méthode de bibliothèque a besoin / attend.

La bibliothèque Java TRÈS (de loin) avec laquelle j'ai travaillé a été écrite par une personne qui aimait beaucoup Smalltalk et qui a modelé la bibliothèque GUI d'après elle pour qu'elle ressemble davantage à smalltalk - le problème étant que chaque méthode prenait le même objet de base, mais ne pouvait pas réellement utiliser tout ce sur quoi l'objet de base pouvait être transtypé. Vous deviez donc de nouveau deviner ce qu'il fallait passer dans les méthodes et ne pas savoir si vous aviez échoué jusqu'au moment de l'exécution (Quelque chose que j'avais l'habitude de gérer à chaque fois travaillait en C).

Un autre problème - Si vous passez des chaînes, des ints, des collections et des tableaux sans objets, tout ce que vous avez, ce sont des boules de données sans signification. Cela semble naturel lorsque vous pensez en termes de bibliothèques que "certaines applications" vont utiliser, mais lors de la conception d'une application complète, il est beaucoup plus utile d'attribuer une signification (code) à toutes vos données à l'endroit où les données sont définies et en y réfléchissant uniquement. termes de la façon dont ces objets de haut niveau interagissent. Si vous transmettez des primitives plutôt que des objets, vous modifiez (par définition) des données dans un endroit différent de celui défini (c’est aussi pourquoi je n’aime vraiment pas les Setters et les Getters - le même concept, vous utilisez sur des données qui ne vous appartiennent pas).

Enfin, si vous définissez des objets distincts pour tout, vous avez toujours la possibilité de tout valider - par exemple, si vous créez un objet avec un code postal et que vous constatez ensuite que vous devez vous assurer que le code postal comprend toujours l’extension à 4 chiffres que vous avez. endroit parfait pour le mettre.

Ce n'est pas vraiment une mauvaise idée. En y réfléchissant, je ne suis même pas sûr que je dirais qu'il a été surinformé du tout, il est simplement plus facile de travailler avec à peu près tout, à la seule exception de la prolifération de petites classes, mais les classes Java sont si légères et faciles à utiliser. d’écrire que c’est à peine un coût (ils peuvent même être générés).

Je serais vraiment intéressé de voir un projet Java bien écrit qui définit trop de classes (où cela rend plus difficile la programmation), je commence à penser qu'il n'est pas possible d'avoir trop de classes.

Bill K
la source
3

Je pense que vous devez regarder ce concept d'un autre point de départ. Regardez du point de vue d’un concepteur de base de données: les types passés dans le premier exemple ne définissent pas vos paramètres de manière unique, encore moins utile.

public void bookTicket(
  String name,
  String firstName,
  String film,
  int count,
  String cinema);

Deux paramètres sont nécessaires pour spécifier le client réel qui réserve les billets, vous pouvez avoir deux films différents avec des noms identiques (par exemple, des remakes), vous pouvez avoir le même film avec des noms différents (par exemple, des traductions). Une certaine chaîne de salles de cinéma pourrait avoir des bureaux différents, alors comment allez - vous traiter que dans une chaîne et d'une manière cohérente (par exemple , vous utilisez $chain ($city)ou $chain in $cityou même quelque chose d' autre et comment allez - vous pour vous assurer que ce Le pire est en fait de spécifier votre client au moyen de deux paramètres, le fait qu’un prénom et un nom de famille soient fournis ne garantit pas un client valide (et vous ne pouvez pas distinguer deux personnes John Doe).

La réponse à cette question est de déclarer les types, mais ceux-ci seront rarement des enveloppes minces comme je le montre ci-dessus. Très probablement, ils fonctionneront comme votre stockage de données ou seront couplés avec une sorte de base de données. Ainsi, un Cinemaobjet aura probablement un nom, un emplacement, ... et ainsi vous vous débarrasserez de telles ambiguïtés. Si ce sont des emballages minces, ils le sont par coïncidence.

Ainsi, à mon humble avis, l’article de blog dit simplement "assurez-vous de passer les types corrects", son auteur vient de faire le choix trop contraignant de choisir des types de données de base en particulier (ce qui est un message erroné).

L'alternative proposée est meilleure:

public void bookTicket(
  Name name,
  FirstName firstName,
  Film film,
  Count count,
  Cinema cinema);

D'autre part, je pense que l'article de blog va trop loin en emballant tout. Countest trop générique, je pourrais compter des pommes ou des oranges avec cela, les ajouter et avoir toujours une situation entre mes mains où le système de typographie me permet de faire des opérations absurdes. Vous pouvez bien sûr appliquer la même logique que dans le blog et définir des types, CountOfOrangesetc., mais c’est aussi ridicule.

Pour ce que ça vaut, j'écrirais en fait quelque chose comme

public Ticket bookTicket(
  Person patron,
  Film film,
  int numberOfTickets,
  Cinema cinema);

Longue histoire courte: vous ne devriez pas passer des variables absurdes; les seules fois où vous spécifiez réellement un objet avec une valeur qui ne détermine pas l'objet réel, sont lorsque vous exécutez une requête (par exemple public Collection<Film> findFilmsWithTitle(String title)) ou lorsque vous créez une preuve de concept. Gardez votre système de caractères propre, évitez donc d'utiliser un type trop général (par exemple, un film représenté par un String) ou trop restrictif / spécifique / artificiel ( Countau lieu de int). Utilisez un type qui définit votre objet de manière unique et non ambiguë chaque fois que cela est possible et viable.

edit : un résumé encore plus court. Pour les petites applications (par exemple, preuve de concept): pourquoi s'embarrasser d'un design compliqué? Il suffit d'utiliser Stringou intet passer à autre chose.

Pour les grandes applications: est-il vraiment probable que vous ayez beaucoup de classes consistant en un seul champ avec un type de données de base? Si vous avez peu de telles classes, vous avez juste des objets "normaux", rien de spécial ne s'y passe.

Je pense que l’idée d’encapsuler des chaînes,… n’est qu’une conception incomplète: trop compliqué pour les petites applications, pas assez complet pour les grandes applications.

Egon
la source
Je vois ce que vous voulez dire, mais je dirais que vous supposez plus que l’auteur ne dit. Pour autant que nous sachions, son modèle n’a qu’une chaîne pour un mécène, une chaîne pour un film, etc. Cette fonctionnalité supplémentaire hypothétique est vraiment le noeud du problème, aussi était-il vraiment "distrait" d'omettre que lorsqu'il plaidait pour son cas ou il pensait que nous devions fournir plus de puissance sémantique, simplement parce que. Encore une fois, pour autant que nous sachions, c'est ce dernier qu'il avait en tête.
DPM le
@Jubbat: en effet, je suppose plus que ce que l'auteur dit. Mais ce que je veux dire, c'est que vous avez une application simple, auquel cas la procédure est trop compliquée. Pour cette échelle, la facilité de maintenance n'est pas un problème et la distinction sémantique empêche votre vitesse de codage. Si, en revanche, votre application est volumineuse, il devient intéressant de définir correctement vos types (mais il est peu probable que ce soit de simples wrappers). IMHO, ses exemples ne sont tout simplement pas convaincants ou ont des défauts de conception majeurs qui vont au-delà de ce qu’il essaie de dire.
Egon
2

Pour moi, cela revient à utiliser des régions en C #. Généralement, si vous estimez que cela est nécessaire pour rendre votre code lisible, vous aurez de plus gros problèmes sur lesquels vous devriez passer votre temps.

Tom Squires
la source
2
+1 pour impliquer le traitement des symptômes plutôt que la cause.
Job
2

Je dirais que c'est une très bonne idée dans une langue avec une fonctionnalité typedef fortement typée.

En Java, ce n’est pas le cas. La création d’une toute nouvelle classe pour ces choses signifie que le coût est probablement supérieur au bénéfice. Vous pouvez également obtenir 80% des bénéfices en faisant attention à la dénomination de vos variables / paramètres.

jk.
la source
0

Ce serait bien, IF String (fin Integer, et ... ne parlant que de String) n'étant pas définitif, ces classes pourraient donc être une chaîne (restreinte) ayant une signification, tout en pouvant être envoyées à un objet indépendant qui sait comment gérer le type de base (sans converser d'avant en arrière).

Et "goodnes" de celui-ci augmentent quand il y a par exemple. restrictions sur tous les noms.

Mais lors de la création d'une application (pas de bibliothèque), il est toujours possible de la refactoriser. Je préfère donc commencer sans elle.

utilisateur470365
la source
0

Pour donner un exemple: dans notre système actuellement développé, de nombreuses entités différentes peuvent être identifiées par plusieurs types d'identificateurs (en raison de l'utilisation de systèmes externes), parfois même du même type d'entité. Tous les identifiants sont des chaînes. Ainsi, si quelqu'un spécifie quel type d'identifiant doit être passé en paramètre, aucune erreur de compilation ne s'affiche, mais le programme explose au moment de l'exécution. Cela arrive assez souvent. Je dois donc dire que l'objectif principal de ce principe n'est pas de protéger contre le changement (même s'il sert également à cela), mais de nous protéger contre les insectes. Et de toute façon, si quelqu'un conçoit une API, elle doit refléter les concepts du domaine. Il est donc utile d'un point de vue conceptuel de définir des classes spécifiques à un domaine - tout dépend de la question de savoir s'il y a un ordre dans l'esprit des développeurs,

thSoft
la source
@ downvoter: pouvez-vous s'il vous plaît donner une explication?
ThSoft