Je suis tombé sur une entrée de blog décourageant l'utilisation de Strings dans Java pour rendre votre code dépourvu de sémantique, suggérant que vous devriez utiliser des classes de wrapper mince à la place. et après des exemples, ladite entrée fournit pour illustrer la question:
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);
Dans mon expérience de lecture de blogs de programmation, je suis arrivé à la conclusion que 90% est un non-sens, mais je me demande si c'est un point valable. D'une certaine manière, cela ne me semble pas juste, mais je ne pouvais pas exactement identifier ce qui ne va pas avec le style de programmation.
L'encapsulation est là pour protéger votre programme contre changer. 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 fondamentale. Le problème est qu'il l'a fait beaucoup trop loin. Quelque chose comme String orderId
is vraiment mauvais, car vraisemblablement "!"£$%^&*())ADAFVF
n'est pas un orderId
valide. Cela signifie que String
représente beaucoup plus de valeurs possibles qu'il n'y a de orderId
s valides. Cependant, pour quelque chose comme un name
, vous ne pouvez pas prévoir ce qui pourrait ou non être un nom valide, et tout String
est un name
valide.
Dans le premier cas, vous réduisez (correctement) les entrées possibles uniquement à des entrées valides. Dans le second cas, vous n'avez pas réduit les entrées valides possibles.
Modifier à nouveau: considérez le cas d'une entrée non valide. Si vous écrivez "Gareth Gobulcoque" comme votre nom, ça va paraître idiot, ça ne va pas être la fin du monde. Si vous entrez un OrderID invalide, il est probable que cela ne fonctionnera tout simplement pas.
C'est juste fou :)
Je suis principalement d'accord avec l'auteur. S'il y a tout comportement particulier à un champ, comme la validation d'un identifiant de commande, alors 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 qui représentent un concept comme une adresse, alors créez une classe pour ce concept. Si vous programmez en Java, vous payez un prix élevé pour la saisie statique. Vous pouvez aussi bien obtenir toute la valeur que vous pouvez.
Ne le faites pas, cela compliquera les choses et vous n'en aurez pas besoin
... est la réponse que j'aurais écrite 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 celles existantes. Je comprends les averses automatiques que d'autres ici ont en voyant ce code, mais je pense que c'est quelque chose qui mérite une réflexion sérieuse.
Le principal avantage est la possibilité de modifier et é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 pour de nombreuses interfaces - il devient alors beaucoup plus facile d'ajouter plus tard une autre dimension. Ou changez le type en double
. Si vous utilisez List<Author> authors
Ou List<Person> authors
Au lieu de List<String> authors
, Il devient plus facile par la suite d'ajouter plus d'informations à ce que représente un auteur. En l'écrivant comme ça, j'ai l'impression de dire l'évidence, mais en pratique, je me suis souvent rendu coupable d'utiliser des cordes de cette façon, surtout dans les cas où ce n'était pas évident au début, alors 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 ressens la douleur: \
Au-delà de cela, je suis d'accord avec l'auteur du blog qu'il contient plus d'informations sémantiques, ce qui facilite la compréhension du lecteur. Bien que les paramètres reçoivent souvent des noms significatifs et obtiennent une ligne de documentation dédiée, ce n'est souvent pas le cas avec les champs ou les sections locales.
L'avantage final est type sécurité, pour des raisons évidentes, mais à mes yeux, c'est une chose mineure ici.
L'écriture prend plus de temps. É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 être un réel préjudice pour votre concentration. J'aimerais penser, cependant, que cet état d'effort ne se produira généralement qu'au premier stade de l'écriture d'un morceau de code; Je peux généralement avoir une assez bonne idée rapidement des entités qui devront être impliquées.
Cela peut impliquer beaucoup de setters (ou constructions) et 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'une utilisation 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 le sentiment que beaucoup de code déplace les objets, et que seules les méthodes de sélection le créent, et encore moins ont besoin d'accéder à son graveleux détails (ce qui n'est pas OOPy de toute façon).
Je dirais si 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 à moins d'aller lire sa documentation.
Comme pour la plupart des autres écoles de pensée en programmation, je pense qu'il est malsain de pousser celle-ci à l'extrême. Je ne me vois jamais séparer les coordonnées x et y pour être chacune d'un type différent. Je ne pense pas que Count
soit nécessaire lorsqu'un int
devrait suffire. Je n'aime pas l'utilisation de unsigned int
En C - bien que théoriquement bon, il ne vous donne tout simplement pas assez d'informations, et il interdit d'étendre votre code plus tard pour prendre en charge ce magique -1. Parfois, vous avez besoin de simplicité.
Je pense que ce billet de blog est un peu extrême. Mais dans l'ensemble, j'ai appris par une expérience douloureuse que l'idée de base derrière elle est faite à partir des bonnes choses.
J'ai une profonde aversion pour le code sur-conçu. Vraiment. Mais utilisé correctement, je ne pense pas que cette ingénierie excessive.
Bien que ce soit un peu exagéré, je pense souvent que la plupart des choses que j'ai vues sont sous-conçues à la place.
Ce n'est pas seulement "Sécurité", L'une des choses vraiment sympa à propos de Java est qu'il vous aide beaucoup à vous souvenir/comprendre exactement ce dont tout appel de méthode de bibliothèque donné a besoin/attend.
La pire (de loin) Java avec laquelle j'ai travaillé a été écrite par quelqu'un qui aimait beaucoup Smalltalk et a modélisé la bibliothèque GUI après pour la faire ressembler 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 que l'objet de base pouvait être casté, vous étiez donc revenu à deviner quoi passer dans les méthodes et à ne pas savoir si vous aviez échoué jusqu'à l'exécution (quelque chose que j'ai utilisé pour doivent faire face à chaque fois quand je travaillais en C).
Un autre problème - Si vous passez des chaînes, des entiers, des collections et des tableaux sans objets, tout ce que vous avez est des boules de données sans signification. Cela semble naturel lorsque vous pensez en termes de bibliothèques que "certaines applications" utiliseront, mais lors de la conception d'une application entière, il est beaucoup plus utile d'attribuer un sens (code) à toutes vos données à l'endroit où les données sont définies et de ne penser qu'à termes de la façon dont ces objets de haut niveau interagissent. Si vous passez des primitives au lieu d'objets, vous modifiez par définition des données dans un endroit différent de celui où elles sont définies (c'est aussi pourquoi je n'aime vraiment pas les Setters et les Getters - même concept, vous opérez sur des données qui ne vous appartiennent pas).
Enfin, si vous définissez des objets séparés pour tout, vous avez toujours un endroit idéal pour tout valider - par exemple, si vous créez un objet pour le code postal et constatez plus tard que vous devez vous assurer que le code postal comprend toujours l'extension à 4 chiffres, vous avez un 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é trop conçu, il est juste plus facile de travailler avec presque tous les moyens - la seule exception étant la prolifération de petites classes mais Java les classes sont si légères et faciles à écrire que ce n'est même pas un coût (elles peuvent même être générées).
Je serais vraiment intéressé de voir un Java Java qui avait en fait trop de classes définies (où cela a rendu la programmation plus difficile), je commence à penser qu'il n'est pas possible de avoir trop de classes.
Je pense que vous devez regarder ce concept à partir d'un autre point de départ. Jetez un œil 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, et encore moins de manière utile.
public void bookTicket(
String name,
String firstName,
String film,
int count,
String cinema);
Deux paramètres sont nécessaires pour spécifier le parrain 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 peut avoir différentes succursales, alors comment allez-vous gérer cela de manière cohérente (par exemple, utilisez-vous $chain ($city)
ou $chain in $city
Ou même quelque chose d'autre et comment allez-vous vous assurer que cela est utilisé de manière cohérente. Le pire est en fait de spécifier votre client au moyen de deux paramètres, le fait que le prénom et le nom soient fournis ne garantit pas un client valide (et vous ne pouvez pas distinguer deux John Doe
s).
La réponse à cela est de déclarer des types, mais ce 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 à une sorte de base de données. Ainsi, un objet Cinema
aura probablement un nom, un emplacement, ... et ainsi vous vous débarrasserez de ces ambiguïtés. S'il s'agit d'emballages fins, ils le sont par coïncidence.
Donc, à mon humble avis, le billet de blog dit simplement "assurez-vous de passer les bons types", son auteur vient de faire le choix trop contraint de choisir les types de données de base en particulier (ce qui est le mauvais message).
L'alternative proposée est meilleure:
public void bookTicket(
Name name,
FirstName firstName,
Film film,
Count count,
Cinema cinema);
D'un autre côté, je pense que le blog va trop loin avec tout envelopper. Count
est trop générique, je pourrais compter des pommes ou des oranges avec ça, les ajouter et avoir encore une situation où le système de type 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 les types CountOfOranges
etc., mais c'est aussi tout simplement idiot.
Pour ce que ça vaut, j'écrirais en fait quelque chose comme
public Ticket bookTicket(
Person patron,
Film film,
int numberOfTickets,
Cinema cinema);
Pour faire court: vous ne devez pas transmettre de 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, c'est lorsque vous exécutez une requête (par exemple public Collection<Film> findFilmsWithTitle(String title)
) ou lorsque vous préparez une preuve de concept. Gardez votre système de types propre, donc n'utilisez pas un type trop général (par exemple un film représenté par un String
) ou trop restrictif/spécifique/artificiel (par exemple Count
au lieu de int
). Utilisez un type qui définit votre objet de manière unique et sans ambiguïté chaque fois que cela est possible et viable.
edit : un résumé encore plus court. Pour les petites applications (par exemple la preuve de concepts): pourquoi s'embêter avec une conception compliquée? Utilisez simplement String
ou int
et continuez.
Pour les grandes applications: est-il vraiment probable que vous ayez beaucoup de classes composées d'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ée pour les petites applications, pas assez complète pour les grandes applications.
Pour moi, cela revient à utiliser les régions en C #. En général, si vous pensez que cela est nécessaire pour rendre votre code lisible, vous avez de plus gros problèmes sur lesquels vous devriez passer votre temps.
Je dirais que c'est une très bonne idée dans une langue avec une fonctionnalité de type typedef fortement typée.
En Java vous n'avez pas cela, donc créer une toute nouvelle classe pour ces choses signifie que le coût l'emporte probablement sur l'avantage. Vous pouvez également obtenir 80% de l'avantage en faisant attention à votre variable/paramètre appellation.
Ce serait bien, IF String (end Integer, et ... ne parlant que de String) n'était pas final, donc ces classes pourraient être des chaînes (restreintes) avec du sens et pourraient toujours être envoyées à un objet indépendant qui sait comment gérer le type de base (sans converser d'avant en arrière).
Et les "goodnes" augmentent quand il y a par exemple. restrictions sur tous les noms.
Mais lors de la création d'une application (pas d'une bibliothèque), il est toujours possible de la refactoriser. Je préfère donc commencer sans.
Pour donner un exemple: dans notre système actuellement développé, il existe de nombreuses entités différentes qui peuvent être identifiées par plusieurs types d'identifiants (en raison de l'utilisation de systèmes externes), parfois même le même type d'entité. Tous les identifiants sont des chaînes - donc si quelqu'un confond quel type d'ID doit être passé en paramètre, aucune erreur de temps de compilation n'est affichée, mais le programme explosera 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 se protéger contre le changement (bien qu'il serve cela aussi), mais de nous protéger contre les bugs. Et de toute façon, si quelqu'un conçoit une API, elle doit refléter les concepts du domaine, il est donc conceptuellement utile de définir des classes spécifiques au domaine - tout dépend de l'ordre dans l'esprit des développeurs, et cela peut être grandement facilité par le système de typage!