Je suis un grand fan de la vérification de type statique. Cela vous empêche de faire des erreurs stupides comme ceci:
// java code
Adult a = new Adult();
a.setAge("Roger"); //static type checker would complain
a.setName(42); //and here too
Mais cela ne vous empêche pas de faire des erreurs stupides comme celle-ci:
Adult a = new Adult();
// obviously you've mixed up these fields, but type checker won't complain
a.setAge(150); // nobody's ever lived this old
a.setWeight(42); // a 42lb adult would have serious health issues
Le problème survient lorsque vous utilisez le même type pour représenter des types d'informations évidemment différents. Je pensais qu'une bonne solution à cela serait d'étendre la Integer
classe, juste pour éviter les erreurs de logique métier, mais pas pour ajouter des fonctionnalités. Par exemple:
class Age extends Integer{};
class Pounds extends Integer{};
class Adult{
...
public void setAge(Age age){..}
public void setWeight(Pounds pounds){...}
}
Adult a = new Adult();
a.setAge(new Age(42));
a.setWeight(new Pounds(150));
Est-ce considéré comme une bonne pratique? Ou y a-t-il des problèmes d'ingénierie imprévus en cours de route avec une conception aussi restrictive?
a.SetAge( new Age(150) )
compilerez toujours pas ?new Age(...)
objet, vous ne pouvez pas l'affecter de manière incorrecte à une variable de typeWeight
à d'autres endroits. Cela réduit le nombre d'endroits où des erreurs peuvent se produire.Réponses:
Vous demandez essentiellement un système unitaire (non, pas des tests unitaires, "unité" comme dans "unité physique", comme les compteurs, les volts, etc.).
Dans votre code
Age
représente le temps etPounds
représente la masse. Cela conduit à des choses comme la conversion d'unités, les unités de base, la précision, etc.Il y a eu / il y a eu des tentatives pour mettre une telle chose en Java, par exemple:
Les deux derniers semblent vivre dans cette chose github: https://github.com/unitsofmeasurement
C ++ a des unités via Boost
LabView est livré avec un tas d'unités .
Il existe d'autres exemples dans d'autres langues. (les modifications sont les bienvenues)
Comme vous pouvez le voir ci-dessus, plus un langage est utilisé pour gérer des valeurs avec des unités, plus il prend en charge nativement les unités. LabView est souvent utilisé pour interagir avec des appareils de mesure. En tant que tel, il est logique d'avoir une telle fonctionnalité dans la langue et son utilisation serait certainement considérée comme une bonne pratique.
Mais dans tout langage de haut niveau à usage général, où la demande est aussi faible pour une telle rigueur, c'est probablement inattendu.
Ma conjecture serait: performance / mémoire. Si vous traitez avec beaucoup de valeurs, la surcharge d'un objet par valeur peut devenir un problème. Mais comme toujours: l' optimisation prématurée est la racine de tout mal .
Je pense que le plus gros "problème" est de s'y habituer, car l'unité est généralement implicitement définie comme suit:
Les gens seront confus lorsqu'ils devront passer un objet comme valeur pour quelque chose qui pourrait apparemment être décrit avec un simple
int
, lorsqu'ils ne sont pas familiers avec les systèmes d'unités.la source
int
..." --- pour lequel nous avons ici le principal de Least Surprise comme guide. Bonne prise.Contrairement à la réponse nulle, la définition d'un type pour une "unité" peut être bénéfique si un entier n'est pas suffisant pour décrire la mesure. Par exemple, le poids est souvent mesuré en plusieurs unités dans le même système de mesure. Pensez «livres» et «onces» ou «kilogrammes» et «grammes».
Si vous avez besoin d'un niveau de mesure plus granulaire, définir un type pour l'unité est avantageux:
Pour quelque chose comme "l'âge", je recommande de calculer qu'au moment de l'exécution en fonction de la date de naissance de la personne:
la source
Ce que vous semblez rechercher est connu sous le nom de types balisés . C'est une façon de dire "c'est un entier qui représente l'âge" alors que "c'est aussi un entier mais ça représente du poids" et "tu ne peux pas assigner l'un à l'autre". Notez que cela va plus loin que les unités physiques telles que les mètres ou les kilogrammes: je peux avoir dans mon programme "les hauteurs des gens" et les "distances entre les points sur une carte", mesurés tous les deux en mètres, mais non compatibles les uns avec les autres depuis l'attribution d'un à l'autre n'a pas de sens du point de vue de la logique métier.
Certaines langues, comme Scala, supportent assez facilement les types balisés (voir le lien ci-dessus). Dans d'autres, vous pouvez créer vos propres classes wrapper, mais cela est moins pratique.
La validation, par exemple vérifier que la taille d'une personne est "raisonnable" est un autre problème. Vous pouvez mettre un tel code dans votre
Adult
classe (constructeur ou setters), ou dans vos classes de type / wrapper marquées. D'une certaine manière, les classes intégrées telles queURL
ouUUID
remplissent un tel rôle (entre autres, par exemple, fournir des méthodes utilitaires).Que l'utilisation de types balisés ou de classes wrapper contribue réellement à améliorer votre code, cela dépendra de plusieurs facteurs. Si vos objets sont simples et ont peu de champs, le risque de les affecter incorrectement est faible et le code supplémentaire nécessaire pour utiliser les types balisés ne vaut pas la peine. Dans les systèmes complexes avec des structures complexes et beaucoup de champs (surtout si beaucoup d'entre eux partagent le même type primitif), cela peut en fait être utile.
Dans le code que j'écris, je crée souvent des classes wrapper si je passe des cartes. Les types tels que
Map<String, String>
sont très opaques par eux-mêmes, donc les envelopper dans des classes avec des noms significatifs tels que celaNameToAddress
aide beaucoup. Bien sûr, avec les types balisés, vous pouvez écrireMap<Name, Address>
et ne pas avoir besoin de l'encapsuleur pour toute la carte.Cependant, pour les types simples tels que Strings ou Integers, j'ai trouvé que les classes wrapper (en Java) étaient trop gênantes. La logique métier habituelle n'était pas si mauvaise, mais il y a eu un certain nombre de problèmes avec la sérialisation de ces types en JSON, leur mappage vers des objets DB, etc. Vous pouvez écrire des mappeurs et des hooks pour tous les grands frameworks (par exemple Jackson et Spring Data), mais le travail supplémentaire et la maintenance liés à ce code compenseront tout ce que vous gagnerez en utilisant ces wrappers. Bien sûr, YMMV et dans un autre système, l'équilibre peut être différent.
la source