Suppression des valeurs codées en dur et de la conception défensive par rapport à YAGNI

10

D'abord, un peu de contextualisation. Je code une recherche à partir de Age -> Rate. Il y a 7 tranches d'âge, la table de recherche comporte donc 3 colonnes (de | à | taux) avec 7 lignes. Les valeurs changent rarement - ce sont des taux légiférés (première et troisième colonnes) qui sont restés les mêmes pendant 3 ans. J'ai pensé que le moyen le plus simple de stocker cette table sans codage en dur est dans la base de données dans une table de configuration globale, en tant que valeur de texte unique contenant un CSV (donc "65,69,0.05,70,74,0.06" est la façon dont les niveaux 65-69 et 70-74 seraient stockés). Relativement facile à analyser puis à utiliser.

Ensuite, j'ai réalisé que pour implémenter cela, je devrais créer une nouvelle table, un référentiel pour l'enrouler, des tests de couche de données pour le dépôt, des tests unitaires autour du code qui aplatit le CSV dans la table et des tests autour de la recherche elle-même. Le seul avantage de tout ce travail est d'éviter de coder en dur la table de recherche.

Lorsque vous parlez aux utilisateurs (qui utilisent actuellement la table de recherche directement - en regardant une copie papier), l'opinion est à peu près que «les taux ne changent jamais». De toute évidence, ce n'est pas vraiment correct - les taux ont été créés il y a seulement trois ans et dans le passé, les choses qui "ne changent jamais" avaient l'habitude de changer - donc pour moi de programmer de manière défensive, je ne devrais certainement pas stocker la table de recherche dans L'application.

Sauf quand je pense à YAGNI . La fonctionnalité que j'implémente ne spécifie pas que les taux vont changer. Si les taux changent, ils changeront si rarement que la maintenance n'est même pas prise en compte, et la fonctionnalité n'est en fait pas suffisamment critique pour que quoi que ce soit soit affecté s'il y avait un délai entre le changement de taux et l'application mise à jour.

J'ai à peu près décidé que rien de précieux ne serait perdu si je codais en dur la recherche, et je ne suis pas trop préoccupé par mon approche de cette fonctionnalité particulière. Ma question est la suivante: en tant que professionnel, ai-je correctement justifié cette décision? Le codage en dur des valeurs est une mauvaise conception, mais se donner la peine de supprimer les valeurs de l'application semble violer le principe YAGNI.

EDIT Pour clarifier la question, je ne suis pas préoccupé par la mise en œuvre réelle. Je crains que je ne puisse faire une mauvaise et rapide chose, et la justifier en disant YAGNI, ou que je peux adopter une approche plus défensive et exigeante, qui, même dans le meilleur des cas, a finalement de faibles avantages. En tant que programmeur professionnel, ma décision de mettre en œuvre une conception que je connais est-elle simplement défectueuse se résume-t-elle à une analyse coûts / avantages?

EDIT Bien que toutes les réponses soient très intéressantes car je pense que cela se résume aux choix de conception d'un individu, je pense que les meilleures réponses étaient @ Corbin et @EZ Hart car elles évoquent des choses que je n'avais pas considérées dans la question:

  • la fausse dichotomie consistant à «supprimer correctement les valeurs codées en dur» en les déplaçant vers la base de données vs «appliquer efficacement YAGNI» en utilisant le codage en dur. Il y avait une troisième option de mettre la table de recherche dans la configuration de l'application, ce qui n'entraîne pas la surcharge de la bonne façon, et sans l'efficacité de YAGNI. Nous ne sommes généralement pas limités à l'une ou l'autre des décisions, et cela se résume alors à une décision coûts / avantages.
  • la génération de code peut réduire la surcharge de déplacement des valeurs codées en dur vers la base de données, et d'une manière qui supprime également ma décision trop complexe de traiter un CSV dans la table. Cela ajoute potentiellement un problème de maintenance à long terme avec le code généré si les exigences de base changent pour la méthode de recherche. Tout cela affecte simplement l'analyse coûts / avantages, et il est probable que si j'avais eu cette automatisation disponible, je n'aurais même pas envisagé de coder en dur quelque chose comme ça.

Je marque la réponse de @ Corbin comme correcte car elle modifie mes hypothèses de coût de développement, et j'ajouterai probablement des outils de génération de code à mon arsenal dans un proche avenir.

Rebecca Scott
la source
N'oubliez pas que si les taux changent et que vous ne disposez que de valeurs codées en dur, vous pourriez gâcher les calculs des enregistrements historiques lorsque les taux changent (et ils le feront, peu importe ce que votre client vous dit).
Andy

Réponses:

6

Vous avez trouvé une faille dans votre processus de développement. Quand il est difficile de faire la bonne chose (créer la table, repo, repo tests, flatten tests ...), les développeurs trouveront un moyen de la contourner. Cela implique généralement de faire la mauvaise chose. Dans ce cas, cela vous tente de traiter les données d'application comme une logique d'application. Ne le fais pas. Ajoutez plutôt des automatisations utiles à votre processus de développement. Nous utilisons CodeSmith pour générer le code ennuyeux et standard que personne ne veut écrire. Après avoir créé une table, nous exécutons CodeSmith et il génère les tests unitaires DAO, DTO et stubs out pour chacun.

Selon les technologies que vous utilisez, vous devriez avoir des options similaires. De nombreux outils ORM génèrent des modèles à partir d'un schéma existant. Les migrations de rails fonctionnent dans la direction opposée - les tables des modèles. La méta-programmation dans les langages dynamiques est particulièrement efficace pour éliminer le code passe-partout. Vous devrez travailler un peu plus pour générer tout ce dont vous avez besoin si vous avez une application complexe à plusieurs niveaux, mais cela en vaut la peine. Ne laissez pas le sentiment, "wow, c'est une douleur dans le cou" vous empêcher de faire la bonne chose.

Oh, et ne stockez pas vos données dans un format qui nécessite un traitement supplémentaire (CSV). Cela ajoute simplement des étapes supplémentaires qui nécessitent votre attention et vos tests.

Marche de Corbin
la source
Je ne dirais pas qu'une migration Rails crée des tables à partir de modèles ... Les migrations Rails décrivent les modifications apportées aux tables. L'exécution d'une migration modifie la base de données. Les propriétés du modèle correspondant à la structure de la table sont créées au moment de l'exécution.
kevin cline
Cela m'intéresse, je n'avais pas envisagé d'automatiser une partie de l'effort initial à ce niveau. Et avoir cette automatisation signifierait que je pourrais mettre en place une table complète plutôt que d'utiliser le CSV pour gagner du temps. Ce qui m'inquiète, c'est la maintenance à long terme du code généré. En le générant à l'avance, j'ai fait une première supposition que la méthode de recherche ne changera jamais. Je suppose que si c'est une possibilité, je devrais simplement en tenir compte dans le cadre du rapport coût / avantage, compte tenu du coût réduit du passe-partout. +1
Rebecca Scott
La question était: "devrais-je coder en dur les valeurs lorsque mon client dit qu'elles ne changeront pas?" et votre réponse est "non, et en fait, vous devriez lancer un ORM sur le problème." Je ne suis pas d'accord. Il existe des options plus simples qui permettraient à l'OP d'éviter le codage en dur. Faire de gros ajouts à l'architecture pour prendre en charge des choses explicitement désignées comme inutiles est contraire à l'éthique. Il est regrettable que de nombreux développeurs soient prédisposés à faire de telles choses. Et je dois dire que je suis en désaccord à 100% avec votre suggestion de ne pas "laisser le sentiment," wow, c'est une douleur dans le cou "vous empêcher de faire la bonne chose." Ces sentiments comptent!
user1172763
10

Pour valider et étendre la réponse de @ Thorbjørn Ravn Andersen: Garder le calcul / la recherche au même endroit est un bon début.

Votre processus de pensée défensif contre YAGNI est un problème courant. Dans ce cas, je suggérerais qu'il soit informé par deux autres choses.

Premièrement - comment les besoins des utilisateurs ont-ils été présentés? Ont-ils précisé la possibilité de modification des taux? Sinon, la complexité supplémentaire fait-elle partie de quelque chose que vous pouvez facturer ou non? (ou si vous faites partie du personnel, que vous pouvez passer votre temps à faire d'autres travaux à la place?) Si c'est le cas, alors allez-y définitivement et livrez ce qui était raisonnablement demandé.

Deuxièmement, et peut-être plus important encore, la simple possibilité de modification ne remplira probablement pas une exigence réelle face à un changement législatif. Si et quand les taux changent, il y aura probablement une date limite et un certain traitement avant et après. De plus, si cette même interface doit ensuite effectuer tout type de traitement des demandes de remboursement antidatées, le taux correct devra peut-être être recherché en fonction de la date d'entrée en vigueur, plutôt que de la date réelle.

En bref, je note qu'une exigence modifiable réelle peut être assez complexe, donc à moins qu'elle ne soit étoffée, le simple est probablement meilleur.

Bonne chance

sdg
la source
1
+1 pour votre deuxième point. Je n'essaie pas de deviner ce que les organes législatifs pourraient faire à l'avenir.
David Thornley
Faites-le dans une fonction de bibliothèque. Il est acceptable de n'avoir qu'un seul utilisateur. Quand et si les exigences changent, vous avez un endroit à changer. (Vous devrez peut-être ajouter une fonction pour effectuer des recherches sur la valeur à une date particulière.)
BillThor
Meilleure réponse et la plus complète, +1
Andy
7

Faites de la recherche réelle une fonction de bibliothèque. Vous pouvez ensuite voir tout le code à l' aide de cette recherche dans votre référentiel source, vous savez donc quels programmes doivent être mis à niveau lorsque les taux changent.


la source
La recherche ne sera implémentée qu'en un seul endroit (quelque chose comme une PensionRateLookupclasse peut-être) qui est ensuite utilisé globalement. Je le ferais, qu'il soit stocké en dehors de l'application ou codé en dur, de cette façon seule l'implémentation de la PensionRateLookupclasse doit être maintenue. Mon problème est de savoir comment j'ai utilisé YAGNI pour conclure que le codage en dur de la table de recherche est acceptable.
Rebecca Scott
Merci pour votre réponse. Garder l'implémentation de la recherche au même endroit est certainement une bonne conception.
Rebecca Scott
YAGNI n'est valide que si vous pouvez envoyer de nouveaux fichiers binaires lorsque les taux changent. Si vous ne le pouvez pas, mais vous pouvez modifier la configuration, faites-en une valeur de configuration lue au démarrage avec la représentation textuelle actuelle par défaut.
J'expédie une nouvelle version habituellement chaque semaine, donc la mise à jour de la recherche n'est pas un problème. Le mettre dans la configuration client est en fait pire car je ne peux pas changer la configuration client avec un nouveau binaire. L'alternative telle que je la vois est de la mettre dans la base de données, ce qui signifie beaucoup d'efforts.
Rebecca Scott
Alors quel est le problème? Lorsque les tarifs changent, vous mettez à jour le code et expédiez à tous les clients?
2

Laissez-moi voir si j'ai bien répondu à votre question. Vous avez 2 options pour implémenter une fonctionnalité: soit vous codez en dur une valeur et votre fonctionnalité est facile à implémenter (mais vous n'aimez pas la partie codée en dur) ou vous avez un gros effort pour "refaire" beaucoup de choses qui sont faites afin que vous puissiez développer votre fonctionnalité de manière propre. Est-ce exact?

La première chose qui me vient à l'esprit est "La chose la plus importante pour connaître les bonnes pratiques est de savoir quand il vaut mieux s'en passer".

Dans ce cas, l'effort est très élevé, vous pouvez donc le faire de manière propre, les chances d'effet collatéral de ce changement sont grandes et le rendement que vous obtiendriez est faible (comme vous l'avez expliqué, il semble probable qu'il ne le sera pas changement).

J'utiliserais l'approche du code dur (mais la préparais pour être flexible à l'avenir) et, en cas de changement de ce taux à l'avenir, j'en profiterais pour refactoriser toute cette section de mauvaise conception du code. Ainsi, le temps et le coût pourraient être estimés correctement et le coût de modification de votre valeur codée en dur serait minime.

Ce serait mon approche :)

JSBach
la source
Merci @Oscar. La partie technique de cela est def. correct. Et je suis d'accord avec la façon dont vous aborderiez le problème, en ne refactorisant que lorsque cela est nécessaire. Vous dites donc qu'en tant que professionnel, nous pouvons et devons choisir nos principes de conception, uniquement en fonction des coûts / avantages? Logique.
Rebecca Scott
@Ben Scott: Vous êtes les bienvenus :). Oui, à mon avis, les principes de conception ont été créés / catalogués non pas parce qu'ils sont beaux, mais parce qu'ils apportent des avantages comme la "propreté" du code, la flexibilité, la robustesse, etc. Mais il y a toujours 2 questions: 1- Ai-je besoin de cela ? 2- Mes restrictions (temps, technique, etc.) me permettent-elles de le mettre en œuvre? C'est généralement ce qui m'amène à choisir mon principe de conception. La suringénierie est également mauvaise;) ps: si vous pensez que c'est une réponse intéressante, votez pour que d'autres personnes la lisent également avec une probabilité plus élevée. Merci! :)
JSBach
a voté positivement ;-)
Rebecca Scott
2

Il s'agit d'un élément qui "ne changera pas" tant qu'il ne le sera pas. Il est inévitable que cela va changer, mais ce temps peut être un peu éloigné.

Votre justification est correcte. À ce moment, le client n'a pas demandé la possibilité de modifier facilement ces tarifs. En tant que tel, YAGNI.

Cependant, ce que vous ne voulez pas, c'est le code qui accède à vos tarifs et interprète les résultats dispersés dans la base de code. Une bonne conception OO vous permettrait d'encapsuler la représentation interne de vos tarifs dans une classe et d'exposer uniquement une ou deux méthodes nécessaires pour utiliser les données.

Vous allez avoir besoin de cette encapsulation pour éviter les erreurs de copier-coller, ou devoir effectuer une refactorisation sur tout le code qui utilise les taux lorsque vous devez apporter une modification à la représentation interne. Lorsque vous prenez cette précaution initiale, l'approche la plus compliquée peut être un simple échange et remplacer pour la version plus en vedette.

En outre, appelez la limitation de la conception actuelle au client. Dites-leur que dans l'intérêt de maintenir la planification, vous avez codé en dur les valeurs - ce qui nécessite une modification de codage pour les mettre à jour. De cette façon, lorsqu'ils prennent connaissance des changements de taux en attente en raison de la nouvelle législation, ils peuvent choisir de simplement mettre à jour la table de recherche ou d'effectuer le changement le plus compliqué à ce moment-là. Mais mettez cette décision sur leurs genoux.

Berin Loritsch
la source
Merci @berin, je n'ai pas mentionné SRP dans la question mais c'était dans le plan. Bon point de redonner la propriété du problème au client.
Rebecca Scott
Attribuer le blâme lorsque les choses tournent mal à l'avenir ne me semble pas professionnel.
Andy
@Andy, quelle partie de cela attribue le blâme? Présenter les limites de conception au client lui permet de hiérarchiser le travail compliqué maintenant et de retirer d'autres choses de la table, de repousser le délai ou d'accepter la conception limitée maintenant car d'autres choses sur la table sont plus importantes pour lui. Vous donnez à votre client le choix de son produit. Rendre votre client conscient du risque / récompense des choix que vous voulez faire dans son intérêt rendra le projet plus fluide.
Berin Loritsch
@BerinLoritsch Mais cette décision de conception particulière s'apparente à l'utilisation de pièces de qualité inférieure disant "Je peux utiliser du mastic de plombier pour boucher ce tuyau qui fuit, c'est votre option la moins chère!" Les tarifs changeront. C'est une évidence, et c'est irresponsable pour un professionnel de construire un système qui ne le permet pas. Et les économies sont probablement négligeables dans le grand schéma du coût du projet. Mettez les données dans un tableau; le code qui obtient les données de recherche est légèrement différent, la logique déterminant le taux à utiliser est la même dans les deux cas. La seule autre décision est de savoir comment gérer les données historiques après le ...
Andy
les changements de taux, qui sont généralement traités en copiant simplement le taux dans l'enregistrement correspondant. Aucun écran d'administration n'est nécessaire à ce stade, le système peut alors gérer lorsque les taux changent, et ce sera un script simple pour les modifier. Rien de tout cela ne devrait durer plus de quelques heures, mais cela empêchera le sous-sol d'inonder l'année prochaine lorsque le mastic tombe en panne.
Andy
2

Divisez la différence et placez les données de débit dans un paramètre de configuration. Vous pouvez utiliser le format CSV que vous avez déjà, vous évitez les frais généraux inutiles de la base de données, et si le changement est jamais nécessaire, ce sera un changement que le client devrait pouvoir effectuer sans avoir à recompiler / réinstaller et sans salir dans la base de données - ils peuvent simplement modifier le fichier de configuration.

Habituellement, lorsque vous regardez un choix entre deux extrêmes (violer YAGNI vs données dynamiques codées en dur), la meilleure réponse se situe quelque part au milieu. Méfiez-vous des fausses dichotomies.

Cela suppose que toute votre configuration est dans des fichiers; si c'est quelque part difficile comme le registre, vous devriez probablement ignorer ce conseil :)

EZ Hart
la source
J'ai envisagé de l'avoir dans un paramètre de configuration, mais je l'ai mis de côté parce que les utilisateurs ne sont pas très techniques pour éditer une chaîne CSV, donc je serais celui qui mettrait à jour la configuration pour N utilisateurs (comme je fais tout le support informatique dans l'organisation aussi), en termes d'effort, il serait plus facile de faire le travail initial pour le mettre dans la base de données que je peux administrer à partir d'un seul endroit. Je suppose que si ce n'était pas une considération (si les utilisateurs étaient capables de gérer leur configuration individuelle), je n'aurais pas ce problème. Donc un très bon point, merci.
Rebecca Scott
Si vous avez combiné cela avec les autres suggestions de logique centralisée, c'est une excellente réponse. Il est tout à fait diabolique de vraiment durcir la recherche plutôt que de la mettre de manière à pouvoir la modifier via la configuration. Chaque fois qu'un autre développeur le rencontre, il vous déteste pour cela. Même un atelier de développement individuel devrait considérer ce qui se passe s'il est heurté par un bus et devrait permettre au prochain développeur de changer facilement les valeurs sans une version complète du logiciel. Ce n'est que si vous détestez le client et détestez tous les autres développeurs que vous le hardcore. Donc, fondamentalement jamais.
simbo1905
2

J'ai pensé que le moyen le plus simple de stocker cette table sans codage en dur est dans la base de données dans une table de configuration globale, en tant que valeur de texte unique contenant un CSV (donc "65,69,0.05,70,74,0.06" est de savoir comment les niveaux 65-69 et 70-74 seraient stockés.

Je viens de créer une table DB. (Vous pensiez à stocker une table entière dans une seule, êtes-vous devenu fou?)

Le tableau a besoin de 2 champs PAS 3. Âge et taux. Cette ligne suivante contient la valeur supérieure! Vous dénormalisez sans même le savoir!

Voici le Sql pour obtenir le tarif pour une personne âgée de 67 ans.

Select * from RateTable where Age in (Select max(age) from RateTable where age <=67) 

Ne vous embêtez pas à faire un écran de maintenance car il est hors de portée. S'ils le demandent plus tard, émettez une demande de modification et faites-le.

EDIT: Comme d'autres l'ont dit, gardez le code obtient le taux centralisé, au cas où toute la structure de taux change.

Crétins
la source
Bonjour @Morons, merci pour votre réponse. Le tableau aurait en fait besoin de 3 colonnes. Il s'agit d'un taux unique par tranche d'âge, âge minimum à âge maximum (les 65 à 69 ans sont sur 5%). Et je ne stocke qu'une petite quantité de données dans un but limité, alors qu'est-ce qui ne va pas avec l'hypothèse de la structure et le choix d'un CSV au lieu d'une table entièrement dédiée? Je voudrais probablement ajouter un délimiteur de ligne, et divisé par ligne puis colonne et tirer les colonnes dans les champs requis. Cela va toujours être lu bien plus qu'écrit, donc je ne suis pas trop inquiet pour un tableau complet.
Rebecca Scott
La ligne suivante contient la valeur supérieure de la plage ... Il s'agit de la duplication de données. Dire <69 et> 7, c'est la même chose. La seule raison d'avoir les deux valeurs est si la plage peut avoir des trous. ... Je dis d'utiliser une table car c'est plus facile (et un meilleur design). Je ne comprends pas pourquoi vous pensez que le stockage d'un csv dans une table vous fera économiser du temps ou des efforts, vous avez toujours besoin d'un appel DB juste pour obtenir cette chaîne, alors vous devez l'analyser. Comme je vous l'ai montré ci-dessus, vous pouvez obtenir le tarif avec un seul appel DB.
Morons
Ah oui. Je gardais le tableau lui-même similaire au matériel source ... et j'ai manqué que l'âge soit la limite supérieure dans votre réponse parce que j'avais les tristes que je vous avais données.
Rebecca Scott
1
"Vous démoralisez sans même le savoir!" - au début, je pensais que c'était une faute de frappe et que vous vouliez dire «dénormaliser», mais plus j'y pensais, plus il me semblait que vous aviez raison de toute façon :)
EZ Hart
@ez hart LOL true.
Rebecca Scott
1

Je suis d'accord avec la plupart des réponses données. J'ajouterais également que la cohérence avec le reste de l'application est importante. Si c'est le seul endroit dans le code qui a des valeurs codées en dur, cela surprendra probablement les responsables. Cela est particulièrement vrai s'il s'agit d'une grande base de code stable. S'il s'agit d'un petit programme géré par vous, la décision est moins importante.

J'ai un souvenir lointain de la lecture d'un gars agile / OOP bien connu (comme Dave Thomas ou Kent Beck ou quelqu'un) disant que leur règle de base pour la duplication de code était deux fois et seulement deux: ne refactorisez pas la première fois que vous dupliquez quelque chose depuis vous pourriez ne l'écrire que deux fois dans votre vie. Mais la troisième fois ...

Cela ne répond pas exactement à la question, puisque vous interrogez YAGNI mais je pense que cela parle de la flexibilité générale des règles agiles. Le point de l'agilité est de s'adapter à la situation et d'avancer.

Dave
la source
Merci @Dave, c'est utile. Je suis le seul mainteneur, depuis sa création, mais c'est une base de code importante et relativement stable (des milliers de fichiers, plus de 100 tables, etc.) et je suis toujours constamment surpris (et consterné). La cohérence est certainement l'un de mes objectifs à l'avenir.
Rebecca Scott
0

Code dur dans une fonction.

  • Lorsque les valeurs changent, vous pouvez à nouveau facturer le client
  • Lorsque les valeurs changent, il est probable que le format du tableau devra changer, faire du CSV perdra du temps
  • Plus facile à mettre en œuvre, plus de chances de respecter le budget avec le contrat actuel
  • Peut trouver facilement quand il devra être mis à jour
earlNameless
la source
Permettez-moi d'installer cette valeur inférieure de sorte que lorsque cela se brise très certainement dans un an, je reçois des affaires répétées. Cela semble louche, parce que ça l'est.
Andy