Faut-il définir des types pour tout?


141

Récemment, j'ai eu un problème de lisibilité de mon code.
J'avais une fonction qui effectuait une opération et renvoyait une chaîne représentant l'ID de cette opération pour référence future (un peu comme OpenFile dans Windows renvoyant un descripteur). L'utilisateur utiliserait cet identifiant ultérieurement pour lancer l'opération et surveiller son achèvement.

L'ID doit être une chaîne aléatoire en raison de problèmes d'interopérabilité. Cela a créé une méthode qui avait une signature très peu claire comme ceci:

public string CreateNewThing()

Cela rend l'intention du type de retour floue. J'ai pensé à envelopper cette chaîne dans un autre type qui rend son sens plus clair comme suit:

public OperationIdentifier CreateNewThing()

Le type ne contiendra que la chaîne et sera utilisé chaque fois que cette chaîne sera utilisée.

Il est évident que l’avantage de ce mode de fonctionnement est une sécurité de type et une intention plus claires, mais cela crée également beaucoup plus de code et de code qui n’est pas très idiomatique. D'une part, j'aime la sécurité supplémentaire, mais cela crée aussi beaucoup d'encombrement.

Pensez-vous qu’il est une bonne pratique d’envelopper des types simples dans une classe pour des raisons de sécurité?


4
Vous pourriez être intéressé à lire sur les types minuscules. J'ai joué avec pendant un moment et je ne le recommanderais pas, mais c'est une approche amusante à laquelle réfléchir. darrenhobbs.com/2007/04/11/tiny-types
Jeroen Vannevel

40
Une fois que vous utilisez quelque chose comme Haskell, vous verrez à quel point l'utilisation de types aide. Les types aident à assurer un programme correct.
Nate Symer

3
Même un langage dynamique comme Erlang bénéficie d’un système de types. C’est pourquoi il dispose d’un système de typespec et d’un outil de vérification de typage bien connus de Haskellers bien qu’il s’agisse d’un langage dynamique. Des langages tels que C / C ++ où le type réel de tout est un entier que nous convenons avec le compilateur de traiter d’une certaine manière, ou Java où vous avez presque des types si vous décidez de prétendre que les classes sont des types qui présentent soit un problème de surcharge sémantique langage (est-ce une "classe" ou un "type"?) ou une ambiguïté du type (est-ce une "URL", "Chaîne" ou "UPC"?). En fin de compte, utilisez tous les outils possibles pour lever l’ambiguïté.
Zxq9

7
Je ne sais pas trop d'où vient l'idée que C ++ a un surcoût sur les types. Avec C ++ 11, aucun fabricant de compilateur ne voyait de problème à donner à chaque lambda son propre type unique. Mais TBH, vous ne pouvez pas vraiment vous attendre à trouver beaucoup d'informations dans un commentaire sur le "système de types C / C ++" comme si les deux partageaient un système de types.
MSalters

2
@JDB - non, ce n'est pas le cas. Pas même pareil.
Davor Ždralo

Réponses:


152

Les primitives telles que stringou intn'ont aucune signification dans un domaine commercial. Une conséquence directe de cela est que vous pouvez utiliser par erreur une URL lorsqu'un ID de produit est attendu ou utiliser une quantité lorsque vous attendez un prix .

C'est aussi la raison pour laquelle le défi Object Calisthenics présente les primitives comme l'une de ses règles:

Règle 3: Envelopper tous les primitifs et les chaînes

En langage Java, int est un objet primitif et non un objet réel. Il obéit donc à des règles différentes de celles des objets. Il est utilisé avec une syntaxe qui n'est pas orientée objet. Plus important encore, un int n'est en soi qu'un scalaire, il n'a donc aucun sens. Lorsqu'une méthode prend un int en tant que paramètre, le nom de la méthode doit effectuer tout le travail d'expression de l'intention. Si la même méthode prend une heure comme paramètre, il est beaucoup plus facile de voir ce qui se passe.

Le même document explique qu'il existe un avantage supplémentaire:

Les petits objets comme Hour ou Money nous donnent également un endroit évident pour mettre en place un comportement qui aurait autrement été jonché autour d'autres classes .

En effet, lorsque des primitives sont utilisées, il est généralement extrêmement difficile de suivre l'emplacement exact du code associé à ces types, ce qui conduit souvent à une duplication importante du code . S'il y a Price: Moneyclasse, il est naturel de trouver la plage en vérifiant l'intérieur. Si, au lieu de cela, un int(pire, un double) est utilisé pour stocker les prix des produits, qui doit valider la fourchette? Le produit? Le rabais? Le panier?

Enfin, un troisième avantage non mentionné dans le document est la possibilité de changer relativement facilement le type sous-jacent. Si aujourd'hui, mon type ProductIdest shortsous-jacent et que je devrais plus tard utiliser int, il est probable que le code à modifier ne couvre pas l'intégralité de la base de codes.

L'inconvénient - et le même argument s'applique à toutes les règles de l'exercice de la calisthénie des objets - est que si cela devient rapidement trop pénible pour créer une classe pour tout . Si Productcontient ProductPricece qui hérite de qui hérite de PositivePricequi hérite Priceà son tour Money, ce n'est pas une architecture propre, mais plutôt un gâchis total dans lequel, pour trouver une seule chose, un responsable doit ouvrir quelques dizaines de fichiers à chaque fois.

Un autre point à considérer est le coût (en termes de lignes de code) de la création de classes supplémentaires. Si les wrappers sont immuables (comme ils devraient l'être habituellement), cela signifie que si nous prenons C #, vous devez avoir, au moins dans les wrappers:

  • Le getter de la propriété,
  • Son champ de soutien,
  • Un constructeur qui attribue la valeur au champ de sauvegarde,
  • Une coutume ToString(),
  • Commentaires de la documentation XML (qui fait beaucoup de lignes),
  • A Equalset a GetHashCodeoutrepasse (aussi beaucoup de LOC).

et éventuellement, le cas échéant:

  • Un attribut DebuggerDisplay ,
  • Une dérogation de ==et des !=opérateurs,
  • Finalement, une surcharge de l'opérateur de conversion implicite pour convertir de manière transparente vers et à partir du type encapsulé,
  • Contrats de code (y compris l'invariant, qui est une méthode assez longue, avec ses trois attributs),
  • Plusieurs convertisseurs qui seront utilisés lors de la sérialisation XML, de la sérialisation JSON ou du stockage / chargement d'une valeur dans / à partir d'une base de données.

Une centaine de LOC pour un emballage simple le rend assez prohibitif, ce qui explique également pourquoi vous pouvez être absolument sûr de la rentabilité à long terme d'un tel emballage. La notion de portée expliquée par Thomas Junk est particulièrement pertinente ici. Ecrire une centaine de LOCs pour représenter une ProductIdutilisation sur votre base de code semble très utile. Écrire une classe de cette taille pour un morceau de code qui fait trois lignes dans une même méthode est beaucoup plus discutable.

Conclusion:

  • Enveloppez les primitives dans des classes qui ont une signification dans un domaine commercial de l’application lorsque (1) cela aide à réduire les erreurs, (2) réduit le risque de duplication de code ou (3) aide à changer le type sous-jacent ultérieurement.

  • N'emballez pas automatiquement toutes les primitives que vous trouvez dans votre code: il existe de nombreux cas d'utilisation stringou d' utilisation intparfaite.

En pratique, public string CreateNewThing()renvoyer une instance de ThingIdclasse au lieu de stringpeut aider, mais vous pouvez aussi:

  • Renvoie une instance de Id<string>classe, c'est-à-dire un objet de type générique indiquant que le type sous-jacent est une chaîne. Vous avez l'avantage de la lisibilité, sans l'inconvénient de devoir conserver de nombreux types.

  • Renvoie une instance de Thingclasse. Si l'utilisateur n'a besoin que de l'identifiant, vous pouvez le faire facilement avec:

    var thing = this.CreateNewThing();
    var id = thing.Id;
    

33
Je pense que le pouvoir de changer librement le type sous-jacent est important ici. C'est une chaîne aujourd'hui, mais peut-être un GUID ou long demain. La création du wrapper de type masque ces informations, ce qui entraîne moins de modifications sur la route. ++ Bonne réponse ici.
RubberDuck

4
Dans le cas de money, assurez-vous que la devise est incluse dans l'objet Money ou que tout votre programme utilise le même (qui doit ensuite être documenté).
paulo Ebermann

5
@Blrfl: Bien qu'il existe des cas valables dans lesquels l'héritage est nécessaire, je vois généralement cet héritage dans un code surdimensionné écrit sans tenir compte de la lisibilité, résultant d'une attitude de paiement obligatoire par LOC. C'est donc le caractère négatif de cette partie de ma réponse.
Arseni Mourzenko

3
@ArTs: C'est l'argument "condamnons l'outil parce que certaines personnes l'utilisent à tort".
Blrfl

6
@MainMa Exemple où le sens aurait pu éviter une erreur coûteuse: en.wikipedia.org/wiki/Mars_Climate_Orbiter#Cause_of_failure
cbojar

60

J'utiliserais la portée comme règle empirique: plus la génération et la consommation d'une telle portée sont restreintes values, moins vous aurez de chances de créer un objet représentant cette valeur.

Disons que vous avez le pseudocode suivant

id = generateProcessId();
doFancyOtherstuff();
job.do(id);

alors la portée est très limitée et je ne verrais aucun sens à en faire un type. Mais disons que vous générez cette valeur dans un calque et que vous le transmettez à un autre calque (ou même à un autre objet), il serait alors parfaitement logique de créer un type pour cela.


14
Un très bon point. Les types explicites sont un outil important pour communiquer l'intention , ce qui est particulièrement important au-delà des limites des objets et des services.
Avner Shahar-Kashtan,

+1 bien que si vous n'avez pas encore refacturé tout le code de doFancyOtherstuff()dans un sous-programme, vous pourriez penser que la job.do(id)référence n'est pas assez locale pour être conservée comme une simple chaîne.
Mark Hurd

C'est totalement vrai! C'est le cas lorsque vous utilisez une méthode privée dans laquelle vous savez que le monde extérieur ne l'utilisera jamais. Après cela, vous pouvez réfléchir un peu plus lorsque la classe est protégée et ensuite lorsque la classe est publique mais ne fait pas partie d'une API publique. Scope scope scope et beaucoup d’art pour placer la ligne à la position idéale entre types trop primitifs et types très personnalisés.
Samuel

34

Les systèmes de types statiques ont tous pour objectif de prévenir les utilisations incorrectes des données.

Il existe des exemples évidents de types faisant cela:

  • vous ne pouvez pas obtenir le mois d'un UUID
  • vous ne pouvez pas multiplier deux chaînes.

Il y a des exemples plus subtils

  • vous ne pouvez pas payer pour quelque chose en utilisant la longueur du bureau
  • vous ne pouvez pas faire de requête HTTP en utilisant le nom de quelqu'un comme URL.

Nous pouvons être tentés d’utiliser doubleà la fois le prix et la longueur, ou d’utiliser un stringpour un nom et une URL. Mais cela compromet notre génial système de caractères et permet à ces utilisations abusives de passer les contrôles statiques du langage.

Obtenir une livre-seconde confondue avec Newton-seconde peut avoir des résultats médiocres au moment de l'exécution .


Ceci est particulièrement un problème avec les chaînes. Ils deviennent souvent le "type de données universel".

Nous sommes habitués principalement aux interfaces de texte avec des ordinateurs, et nous étendons souvent ces interfaces utilisateur (IU) aux interfaces de programmation (API). Nous pensons à 34.25 comme aux personnages 34.25 . Nous pensons à une date comme les personnages 05-03-2015 . Nous pensons à un UUID comme les caractères 75e945ee-f1e9-11e4-b9b2-1697f925ec7b .

Mais ce modèle mental nuit à l'abstraction des API.

Les mots ou la langue, qu'ils soient écrits ou parlés, ne semblent jouer aucun rôle dans mon mécanisme de pensée.

Albert Einstein

De même, les représentations textuelles ne doivent jouer aucun rôle dans la conception des types et des API. Attention le string! (et autres types "primitifs" trop génériques)


Les types communiquent "quelles opérations ont un sens".

Par exemple, j'ai déjà travaillé sur un client pour une API HTTP REST. REST, correctement fait, utilise des entités hypermédia, qui ont des hyperliens pointant vers des entités apparentées. Dans ce client, non seulement les entités tapées (par exemple, utilisateur, compte, abonnement), les liens vers ces entités ont également été tapés (UserLink, AccountLink, SubscriptionLink). Les liens n'étaient guère plus que des wrappers Uri, mais les types distincts rendaient impossible l'utilisation d'un AccountLink pour récupérer un utilisateur. Si tout avait été simple Uriou pire, stringces erreurs n’auraient été constatées qu’au moment de l’exécution.

De même, dans votre cas, vous disposez de données utilisées dans un seul but: identifier un fichier Operation. Il ne devrait pas être utilisé pour autre chose, et nous ne devrions pas essayer d'identifier les Operations avec des chaînes aléatoires que nous avons inventées. La création d'une classe séparée ajoute la lisibilité et la sécurité à votre code.


Bien sûr, toutes les bonnes choses peuvent être utilisées à l'excès. Considérer

  1. combien de clarté cela ajoute à votre code

  2. combien de fois il est utilisé

Si un "type" de données (au sens abstrait) est utilisé fréquemment, à des fins distinctes et entre interfaces de code, il constitue un très bon candidat pour être une classe séparée, aux dépens de la verbosité.


21
"les chaînes de caractères deviennent souvent le type de données" universel "" - c'est l'origine du "langage typé", appelé "langues fortement typées".
MSalters

@MSalters, ha ha, très gentil.
Paul Draper

4
Et bien sûr, qu'est-ce que cela 05-03-2015 signifie quand interprété comme une date ?
un CVn

10

Pensez-vous qu’il est une bonne pratique d’envelopper des types simples dans une classe pour des raisons de sécurité?

Quelquefois.

C’est l’un de ces cas où vous devez peser les problèmes qui pourraient survenir si vous utilisiez stringun système plus concret OperationIdentifier. Quelle est leur gravité? Quelle est leur probabilité?

Ensuite, vous devez considérer le coût d'utilisation de l'autre type. Est-ce douloureux à utiliser? Combien de travail faut-il faire?

Dans certains cas, vous économiserez du temps et des efforts en utilisant un type de béton intéressant. Dans d'autres cas, ça ne vaudra pas la peine.

En général, je pense que ce genre de chose devrait être fait plus qu'aujourd'hui. Si vous avez une entité qui signifie quelque chose dans votre domaine, il est bon de l'avoir comme type propre, car cette entité est plus susceptible de changer / de se développer avec les activités.


10

Je conviens généralement que vous devez souvent créer un type pour les primitives et les chaînes, mais comme les réponses ci-dessus recommandent de créer un type dans la plupart des cas, je vais énumérer quelques raisons pour lesquelles / pour ne pas ne pas:

  • Performance. Je dois me référer aux langues actuelles ici. En C #, si un ID client est court et que vous l'enveloppez dans une classe, vous créez beaucoup de surcharge: de la mémoire, car il s'agit maintenant de 8 octets sur un système 64 bits et de sa vitesse car il est maintenant alloué sur le tas.
  • Lorsque vous faites des hypothèses sur le type. Si un ID client est court et que vous avez une logique qui le compresse - vous faites généralement déjà des hypothèses sur le type. Dans tous ces endroits, vous devez maintenant briser l'abstraction. Si c'est un endroit, ce n'est pas grave, si c'est partout, vous pourriez trouver que la moitié du temps que vous utilisez la primitive.
  • Parce que toutes les langues n'ont pas typedef. Et pour les langues qui ne le sont pas et le code déjà écrit, apporter un tel changement pourrait être une grosse tâche qui pourrait même introduire des bogues (mais tout le monde a une suite de tests avec une bonne couverture, non?).
  • Dans certains cas, cela réduit la lisibilité. Comment puis-je imprimer cela? Comment puis-je valider cela? Devrais-je vérifier pour null? Sont toutes les questions qui nécessitent que vous explorez la définition du type.

3
Si le type de wrapper est une structure plutôt qu'une classe, un short(par exemple) a le même coût, qu'il soit encapsulé ou non. (?)
MathematicalOrchid

1
Ne serait-il pas judicieux qu'un type encapsule sa propre validation? Ou créer un type d'assistance pour le valider?
Nick Udell

1
Emballer ou mung inutilement est un anti-modèle. Les informations doivent circuler de manière transparente dans le code de l'application, sauf lorsque la transformation est explicitement & véritablement nécessaire . Les types sont bons, car ils substituent généralement une petite quantité de code correct, situés dans un emplacement unique, à de grandes quantités de mauvaise mise en œuvre dispersée dans tout le système.
Thomas W

7

Non, vous ne devez pas définir de types (classes) pour "tout".

Mais, comme le soulignent d’autres réponses, il est souvent utile de le faire. Vous devriez développer - consciemment, si possible - un sentiment ou une sensation de trop de friction dû à l' absence d'un type ou d'une classe appropriée, lorsque vous écrivez, testez et mettez à jour votre code. Pour moi, trop de frictions surviennent lorsque je veux consolider plusieurs valeurs primitives en tant que valeur unique ou lorsque je dois valider les valeurs (c'est-à-dire déterminer laquelle de toutes les valeurs possibles du type primitif correspond aux valeurs valides de 'type implicite').

J'ai constaté que des considérations telles que ce que vous avez demandé dans votre question sont responsables de trop de conception dans mon code. J'ai pris l'habitude d'éviter délibérément d'écrire plus de code que nécessaire. J'espère que vous écrivez de bons tests (automatisés) pour votre code. Si vous le savez, vous pouvez facilement refactoriser votre code et ajouter des types ou des classes si cela procure des avantages nets pour le développement et la maintenance continus de votre code .

Les réponses de Telastyn et de Thomas Junk soulignent toutes les deux le très bon point de vue sur le contexte et l'utilisation du code correspondant. Si vous utilisez une valeur dans un seul bloc de code (par exemple, une méthode, une boucle, un usingbloc), il est préférable d'utiliser simplement un type primitif. Il est même bon d'utiliser un type primitif si vous utilisez un ensemble de valeurs à plusieurs reprises et dans de nombreux autres endroits. Mais plus vous utilisez fréquemment et largement un ensemble de valeurs et moins cet ensemble de valeurs correspond aux valeurs représentées par le type primitif, plus vous devriez envisager d'encapsuler les valeurs dans une classe ou un type.


5

En surface, il semble que tout ce que vous avez à faire est d'identifier une opération.

De plus, vous dites ce qu'une opération est supposée faire:

pour démarrer l'opération [et] pour surveiller son achèvement

Vous dites cela comme si c'était "exactement comment l'identification est utilisée", mais je dirais que ce sont des propriétés décrivant une opération. Cela ressemble à une définition de type pour moi, il y a même un motif appelé "motif de commande" qui convient très bien. .

Ça dit

le modèle de commande est utilisé pour [...] encapsuler toutes les informations nécessaires à l' exécution d'une action ou au déclenchement ultérieur d'un événement .

Je pense que cela ressemble beaucoup à ce que vous voulez faire avec vos opérations. (Comparez les phrases que j'ai mises en gras dans les deux guillemets) Au lieu de renvoyer une chaîne, renvoyez un identifiant pour un Operationélément abstrait, un pointeur sur un objet de cette classe dans oop, par exemple.

Concernant le commentaire

Ce serait wtf-y d'appeler cette classe une commande et de ne pas avoir la logique à l'intérieur.

Non ce ne serait pas. Rappelez-vous que les motifs sont très abstraits , si abstraits qu’ils sont un peu méta. En d’autres termes, ils font souvent abstraction de la programmation elle-même et non d’un concept réel. Le modèle de commande est une abstraction d'un appel de fonction. (ou méthode) Comme si quelqu'un appuyait sur le bouton de pause juste après avoir passé les valeurs des paramètres et juste avant l'exécution, pour le reprendre plus tard.

Ce qui suit est une réflexion sur oop, mais la motivation derrière cela devrait être vraie pour tout paradigme. J'aime donner quelques raisons pour lesquelles placer la logique dans la commande peut être considéré comme une mauvaise chose.

  1. Si vous aviez toute cette logique dans la commande, elle serait gonflée à néant. Vous penseriez "eh bien, toutes ces fonctionnalités devraient être dans des classes séparées." Vous voudriez refactoriser la logique en dehors de la commande.
  2. Si vous aviez toute cette logique dans la commande, il serait difficile de tester et de vous gêner lors des tests. "Bon Dieu, pourquoi dois-je utiliser cette commande absurde? Je veux seulement tester si cette fonction crache 1 ou pas, je ne veux pas l'appeler plus tard, je veux le tester maintenant!"
  3. Si vous aviez toute cette logique dans la commande, il ne serait pas très logique de faire un rapport lorsque vous avez terminé. Si vous pensez qu’une fonction doit être exécutée de manière synchrone par défaut, il n’a aucun sens d’être averti à la fin de l’exécution. Peut-être démarrez-vous un autre thread parce que votre opération consiste à convertir un avatar au format cinéma (vous aurez peut-être besoin de thread supplémentaire sur framboise pi), vous devrez peut-être récupérer des données sur un serveur, peu importe la raison, s'il existe une raison de le faire. en rendant compte lorsque vous avez terminé, cela peut être dû au fait qu'il y a une certaine asynchrone (est-ce un mot?). Je pense que lancer un thread ou contacter un serveur est une logique qui ne devrait pas être dans votre commande. C'est un peu un méta argument et peut-être controversé. Si vous pensez que cela n'a aucun sens, dites-le-moi dans les commentaires.

Pour récapituler: le modèle de commande vous permet d’envelopper la fonctionnalité dans un objet, pour l’exécuter ultérieurement. Par souci de modularité (la fonctionnalité existe indépendamment du fait qu'elle soit exécutée via une commande ou non), la testabilité (la fonctionnalité doit être testable sans commande) et tous les autres mots à la mode qui expriment essentiellement la demande d'écrire du bon code, vous ne mettriez pas la logique réelle dans la commande.


Les motifs étant abstraits, il peut être difficile de créer de bonnes métaphores du monde réel. Voici une tentative:

"Hé grand-mère, pourriez-vous s'il vous plaît appuyer sur le bouton d'enregistrement sur la télévision à 12 heures pour que je ne manque pas les simpsons sur le canal 1?"

Ma grand-mère ne sait pas ce qui se passe techniquement quand elle appuie sur le bouton d'enregistrement. La logique est ailleurs (à la télé). Et c'est une bonne chose. La fonctionnalité est encapsulée et les informations masquées de la commande, il s'agit d'un utilisateur de l'API, qui ne fait pas nécessairement partie de la logique.


Vous avez raison, je n'y ai pas pensé comme ça. Mais le modèle de commande ne convient pas à 100%, car la logique de l'opération est encapsulée dans une autre classe et je ne peux pas la placer à l'intérieur de l'objet qui constitue la commande. Ce serait wtf-y d'appeler cette classe une commande et de ne pas avoir la logique à l'intérieur.
Ziv

Cela fait beaucoup de sens. Envisagez de renvoyer une opération et non un identificateur d'opération. Ceci est similaire à la TPL C # dans laquelle vous renvoyez une tâche ou une tâche <T>. @Ziv: Vous dites "la logique de l'opération est encapsulée dans une autre classe". Mais cela signifie-t-il vraiment que vous ne pouvez pas également le fournir d'ici aussi?
Moby Disk

@Ziv je vous prie de différer. Examinez les raisons que j'ai ajoutées à ma réponse et voyez si elles ont un sens pour vous. =)
null

5

Idée derrière les types primitifs,

  • Pour établir un langage spécifique à un domaine
  • Limiter les erreurs commises par les utilisateurs en transmettant des valeurs incorrectes

De toute évidence, il serait très difficile et peu pratique de le faire partout, mais il est important d’envelopper les types le cas échéant,

Par exemple, si vous avez la classe Order,

Order
{
   long OrderId { get; set; }
   string InvoiceNumber { get; set; }
   string Description { get; set; }
   double Amount { get; set; }
   string CurrencyCode { get; set; }
}

Les propriétés importantes pour rechercher une commande sont principalement OrderId et InvoiceNumber. Et le montant et le code de devise sont étroitement liés, et si quelqu'un modifie le code de devise sans modifier le montant, la commande ne peut plus être considérée comme valide.

Ainsi, seuls le wrapping OrderId, InvoiceNumber et l'introduction d'un composite pour la devise ont du sens dans ce scénario et probablement une description du wrapping n'a pas de sens. Résultat donc préféré pourrait ressembler,

    Order
    {
       OrderIdType OrderId { get; set; }
       InvoiceNumberType InvoiceNumber { get; set; }
       string Description { get; set; }
       Currency Amount { get; set; }
    }

Il n'y a donc aucune raison de tout envelopper, mais des choses qui comptent vraiment.


4

Opinion impopulaire:

Vous ne devriez généralement pas définir un nouveau type!

Définir un nouveau type pour envelopper une primitive ou une classe de base est parfois appelé définir un pseudo-type. IBM décrit cela comme une mauvaise pratique ici (ils se concentrent spécifiquement sur l'utilisation abusive de génériques dans ce cas).

Les pseudo-types rendent les fonctionnalités de bibliothèque communes inutiles.

Les fonctions mathématiques de Java peuvent fonctionner avec toutes les primitives numériques. Mais si vous définissez une nouvelle classe Pourcentage (un double pouvant être compris entre 0 et 1), toutes ces fonctions sont inutiles et doivent être entourées par des classes qui (encore pire) doivent connaître la représentation interne de la classe Pourcentage. .

Les pseudo types deviennent viraux

Lorsque vous créez plusieurs bibliothèques, vous constaterez souvent que ces pseudotypes deviennent viraux. Si vous utilisez la classe de pourcentage, mentionnée ci-dessus, dans une bibliothèque, vous devrez soit la convertir à la limite de la bibliothèque (en perdant toute sécurité / signification / logique / autre raison de la création de ce type), soit vous devrez créer ces classes. accessible à l’autre bibliothèque également. Infecter la nouvelle bibliothèque avec vos types là où un simple double aurait suffi.

Enlever le message

Tant que le type que vous emballez n'a pas besoin de beaucoup de logique métier, je vous conseillerais de ne pas l'envelopper dans une pseudo-classe. Vous ne devriez envelopper une classe que s'il existe de sérieuses contraintes commerciales. Dans d’autres cas, nommer correctement vos variables devrait permettre de véhiculer un sens.

Un exemple:

A uintpeut parfaitement représenter un UserId, nous pouvons continuer à utiliser les opérateurs intégrés de Java pour uints (tels que ==) et nous n'avons pas besoin de logique métier pour protéger "l'état interne" de l'ID utilisateur.


Se mettre d'accord. toute la philosophie de la programmation est bonne, mais en pratique, il faut que cela fonctionne aussi
Ewan

Si vous avez vu des annonces de prêts pour les personnes les plus pauvres du Royaume-Uni, vous constaterez que Pourcentage de doubler un double dans la plage 0..1 ne fonctionne pas ...
gnasher729

1
En C / C ++ / Objective C, vous pouvez utiliser un typedef, qui vous donne simplement un nouveau nom pour un type primitif existant. D'autre part, votre code devrait fonctionner quel que soit le type sous-jacent, par exemple si je change OrderId de uint32_t à uint64_t (car je dois traiter plus de 4 milliards de commandes).
gnasher729

Je ne vous réserverai pas votre courage, mais les pseudotypes et les types définis dans la réponse complète sont différents. Ce sont des types qui sont spécifiques aux entreprises. Les Psuedotypes sont des types encore génériques.
0fnt

@ gnasher729: C ++ 11 contient également des littéraux définis par l'utilisateur qui rendent le processus de wrapping très agréable pour l'utilisateur: Distance d = 36.0_mi + 42.0_km; . msdn.microsoft.com/en-us/library/dn919277.aspx
damix911

3

Comme pour tous les conseils, il faut savoir quand appliquer les règles. Si vous créez vos propres types dans une langue de type, vous obtenez une vérification de type. Donc, en général, ce sera un bon plan.

NamingConvention est la clé de la lisibilité. Les deux combinés peuvent clairement exprimer l'intention.
Mais /// est toujours utile.

Alors oui, je dirais créer de nombreux types lorsque leur durée de vie dépasse les limites d'une classe. Pensez également à l’utilisation des deux Structet Classpas toujours de la classe.


2
Qu'est-ce que ça ///veut dire?
Gabe

1
/// est le commentaire de la documentation en C # qui permet à intellisense d'afficher la documentation de la signature au point de la méthode call. Considérez le meilleur moyen de documenter le code. Peut être exploité à l'aide d'outils et offre un comportement d'intelligence. msdn.microsoft.com/en-us/library/tkxs89c5%28v=vs.71%29.aspx
phil soady
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.