web-dev-qa-db-fra.com

À quel moment la brièveté n'est-elle plus une vertu?

Un correctif de bogue récent m'a obligé à parcourir le code écrit par les autres membres de l'équipe, où j'ai trouvé cela (c'est C #):

return (decimal)CostIn > 0 && CostOut > 0 ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 : 0;

Maintenant, s'il y a une bonne raison à tous ces lancers, cela semble toujours très difficile à suivre. Il y avait un bug mineur dans le calcul et j'ai dû le démêler pour résoudre le problème.

Je connais le style de codage de cette personne grâce à l'examen du code, et son approche est que plus court est presque toujours meilleur. Et bien sûr, il y a de la valeur: nous avons tous vu des chaînes de logique conditionnelle inutilement complexes qui pourraient être rangées avec quelques opérateurs bien placés. Mais il est clairement plus apte que moi à suivre des chaînes d'opérateurs entassées dans une seule déclaration.

C'est, bien sûr, finalement une question de style. Mais a-t-on écrit ou fait des recherches sur la reconnaissance du point où la recherche de la brièveté du code cesse d'être utile et devient un obstacle à la compréhension?

La raison de ces conversions est Entity Framework. La base de données doit les stocker en tant que types nullables. Décimal? n'est pas équivalent à Decimal en C # et doit être casté.

102
Bob Tway

Pour répondre à votre question sur les recherches existantes

Mais a-t-on écrit ou fait des recherches sur la reconnaissance du point où la recherche de la brièveté du code cesse d'être utile et devient un obstacle à la compréhension?

Oui, il y a eu du travail dans ce domaine.

Pour comprendre ce genre de choses, vous devez trouver un moyen de calculer une métrique afin que les comparaisons puissent être faites sur une base quantitative (plutôt que de simplement effectuer la comparaison basée sur l'esprit et l'intuition, comme le font les autres réponses). Une mesure potentielle qui a été examinée est

Complexité cyclomatique ÷ Lignes de code source ( SLOC )

Dans votre exemple de code, ce rapport est très élevé, car tout a été compressé sur une seule ligne.

Le SATC a trouvé que l'évaluation la plus efficace est une combinaison de taille et de complexité [cyclomatique]. Les modules à la fois d'une grande complexité et d'une grande taille ont tendance à avoir la plus faible fiabilité. Les modules de petite taille et de grande complexité sont également un risque de fiabilité car ils ont tendance à être très concis, ce qui est difficile à changer ou à modifier.

Lien

Voici quelques références si vous êtes intéressé:

McCabe, T. et A. Watson (1994), Software Complexity (CrossTalk: The Journal of Defense Software Engineering).

Watson, A. H., et McCabe, T. J. (1996). Essais structurés: une méthodologie d'essai utilisant la métrique de complexité cyclomatique (NIST Special Publication 500-235). Extrait le 14 mai 2011 du site Web de McCabe Software: http://www.mccabe.com/pdf/mccabe-nist235r.pdf

Rosenberg, L., Hammer, T., Shaw, J. (1998). Mesures et fiabilité des logiciels (Actes du Symposium international de l'IEEE sur l'ingénierie de la fiabilité des logiciels). Extrait le 14 mai 2011 du site Web de la Penn State University: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.4041&rep=rep1&type=pdf

Mon avis et ma solution

Personnellement, j'ai jamais apprécié la brièveté, seulement la lisibilité. Parfois, la brièveté aide à la lisibilité, parfois non. Ce qui est plus important, c'est que vous écrivez Really Obvious Code (ROC) au lieu de Write-Only Code (WOC).

Juste pour le plaisir, voici comment je l'écrirais et demander aux membres de mon équipe de l'écrire:

if ((costIn <= 0) || (costOut <= 0)) return 0;
decimal changeAmount = costOut - costIn;
decimal changePercent = changeAmount / costOut * 100;
return changePercent;

Notez également que l'introduction des variables de travail a pour effet secondaire de déclencher l'arithmétique à virgule fixe au lieu de l'arithmétique entière, de sorte que la nécessité de tous ces transtypages en decimal est éliminée.

162
John Wu

La concision est bonne quand elle réduit l'encombrement autour des choses qui comptent, mais quand elle devient laconique, emballant trop de données pertinentes trop densément pour les suivre facilement, alors les données pertinentes deviennent l'encombrement lui-même et vous avez un problème.

Dans ce cas particulier, les transtypages vers decimal sont répétés encore et encore; il serait probablement préférable dans l'ensemble de le réécrire comme quelque chose comme:

var decIn = (decimal)CostIn;
var decOut = (decimal)CostOut;
return decIn > 0 && CostOut > 0 ? (decOut - decIn ) / decOut * 100 : 0;
//                  ^ as in the question

Soudain, la ligne contenant la logique est beaucoup plus courte et tient sur une seule ligne horizontale, de sorte que vous pouvez tout voir sans avoir à faire défiler, et la signification est beaucoup plus évidente.

49
Mason Wheeler

Bien que je ne puisse citer aucune recherche particulière sur le sujet, je dirais que toutes ces transtypages dans votre code violent le principe Don't Repeat Yourself. Ce que votre code semble essayer de faire, c'est de convertir les costIn et costOut en type Decimal, puis d'effectuer des vérifications d'intégrité sur les résultats de ces conversions, et d'effectuer opérations supplémentaires sur ces valeurs converties si les vérifications réussissent. En fait, votre code effectue l'une des vérifications d'intégrité sur une valeur non convertie, ce qui augmente la possibilité que costOut puisse contenir une valeur supérieure à zéro, mais inférieure de moitié à la taille du plus petit non nul qui Decimal peut représenter. Le code serait beaucoup plus clair s'il définissait des variables de type Decimal pour contenir les valeurs converties, puis agissait sur celles-ci.

Il semble curieux que vous soyez plus intéressé par le rapport des représentations Decimal de costIn et costOut que par le rapport des valeurs réelles de costIn et costOut, sauf si le code va également utiliser les représentations décimales à d'autres fins. Si le code va utiliser davantage ces représentations, ce serait un argument supplémentaire pour créer des variables pour contenir ces représentations, plutôt que d'avoir une séquence continue de transtypages dans le code.

7
supercat

Je regarde ce code et demande "comment un coût peut-il être 0 (ou moins)?". Quel cas particulier cela indique-t-il? Le code doit être

bool BothCostsAreValidProducts = (CostIn > 0) && (CostOut > 0);
if (!BothCostsAreValidProducts)
  return NO_PROFIT;
else {
   // that calculation here...
}

Je devine les noms ici: changez BothCostsAreValidProducts et NO_PROFIT selon le cas.

5
user949300

La brièveté cesse d'être une vertu lorsque nous oublions qu'elle est signifie une fin plutôt qu'une vertu en soi. Nous aimons la brièveté parce qu'elle est en corrélation avec la simplicité, et nous aimons la simplicité parce que le code plus simple est plus facile à comprendre et à modifier et contient moins de bogues. En fin de compte, nous voulons que le code atteigne ces objectifs:

  1. Satisfaire les exigences de l'entreprise avec le moins de travail

  2. Évitez les bugs

  3. Permettez-nous d'apporter des modifications à l'avenir qui continuent de respecter les points 1 et 2

Ce sont les objectifs. Tout principe ou méthode de conception (que ce soit KISS, YAGNI, TDD, SOLID, preuves, systèmes de type, métaprogrammation dynamique, etc.) ne sont vertueux que dans la mesure où ils nous aident à atteindre ces objectifs.

La ligne en question semble avoir raté le but final. Bien qu'il soit court, ce n'est pas simple. Il contient en fait une redondance inutile en répétant la même opération de coulée plusieurs fois. La répétition du code augmente la complexité et la probabilité de bogues. Le mélange du casting avec le calcul réel rend également le code difficile à suivre.

La ligne a trois préoccupations: les gardes (boîtier spécial 0), la coulée de type et le calcul. Chaque préoccupation est assez simple lorsqu'elle est prise isolément, mais parce qu'elle a toutes été mêlées dans la même expression, elle devient difficile à suivre.

Il n'est pas clair pourquoi CostOut n'est pas lancé la première fois qu'il est utilisé alors que CostIn l'est. Il peut y avoir une bonne raison, mais l'intention n'est pas claire (du moins pas sans contexte), ce qui signifie qu'un responsable se méfierait de changer ce code car il pourrait y avoir des hypothèses cachées. Et c'est un anathème pour la maintenabilité.

Puisque CostIn est converti avant d'être comparé à 0, je suppose qu'il s'agit d'une valeur à virgule flottante. (Si c'était un int, il n'y aurait aucune raison de lancer). Mais si CostOut est un flottant, le code peut masquer un bug obscur de division par zéro, car une valeur à virgule flottante peut être petite mais non nulle, mais nulle lorsqu'elle est convertie en décimale (du moins je crois ce serait possible.)

Le problème n'est donc pas la brièveté ou son absence, le problème est la logique répétée et la confusion des préoccupations conduisant à un code difficile à maintenir.

L'introduction de variables pour conserver les valeurs castées augmenterait probablement la taille du code compté en nombre de tokes, mais diminuerait la complexité, séparerait les préoccupations et améliorerait la clarté, ce qui nous rapproche de l'objectif du code qui est plus facile à comprendre et à maintenir.

5
JacquesB

La concision n'est pas du tout une vertu. La lisibilité est LA vertu.

La brièveté peut être un outil pour réaliser la vertu, ou, comme dans votre exemple, peut être un outil pour réaliser quelque chose d'exactement opposé. De cette façon ou d'une autre, il n'a presque aucune valeur en soi. La règle selon laquelle le code doit être "aussi court que possible" peut également être remplacée par "aussi obscène que possible".

En outre, le code que vous avez publié ne respecte même pas la règle de concision. Si les constantes avaient été déclarées avec le suffixe M, la plupart des horribles (decimal) les transtypages pourraient être évités, car le compilateur favoriserait les int en decimal restants. Je crois que la personne que vous décrivez utilise simplement la brièveté comme excuse. Probablement pas délibérément, mais quand même.

3
Agent_L

Dans mes années d'expérience, j'en suis venu à croire que le la concision ultime est celle du temps - le temps domine tout le reste. Cela inclut à la fois le temps de performance - combien de temps un programme prend pour faire un travail - et le temps de maintenance - combien de temps il faut pour ajouter des fonctionnalités ou corriger des bogues. (La façon dont vous équilibrez ces deux dépend de la fréquence à laquelle le code en question est exécuté vs amélioré - rappelez-vous que l'optimisation prématurée est toujours la racine de tout mal.) La concision du code est afin d'améliorer la concision des deux; un code plus court s'exécute généralement plus rapidement et est généralement plus facile à comprendre et donc à maintenir. Si ce n'est pas le cas non plus, alors c'est un net négatif.

Dans le cas illustré ici, je pense que la brièveté du texte a été mal interprétée comme la brièveté du nombre de lignes, au détriment de la lisibilité, ce qui peut augmenter le temps de maintenance. (Cela peut également prendre plus de temps, selon la façon dont le casting est effectué, mais à moins que la ligne ci-dessus ne soit exécutée des millions de fois, ce n'est probablement pas un problème.) Dans ce cas, les transitions décimales répétitives nuisent à la lisibilité, car il est plus difficile de voir quel est le calcul le plus important. J'aurais écrit comme suit:

decimal dIn = (decimal)CostIn;
decimal dOut = (decimal)CostOut;
return dIn > 0 && CostOut > 0 ? ((dOut - dIn) / dOut) * 100 : 0;

(Edit: c'est le même code que l'autre réponse, alors voilà.)

Je suis fan de l'opérateur ternaire ? :, donc je laisserais ça dedans.

2
Paul Brinkley

Comme presque toutes les réponses ci-dessus, la lisibilité devrait toujours être votre objectif principal. Cependant, je pense également que le formatage peut être un moyen plus efficace d'y parvenir plutôt que de créer des variables et de nouvelles lignes.

return ((decimal)CostIn > 0 && CostOut > 0) ?
       100 * ( (decimal)CostOut - (decimal)CostIn ) / (decimal)CostOut:
       0;

Je suis tout à fait d'accord avec l'argument de la complexité cyclomatique dans la plupart des cas, mais votre fonction semble être suffisamment petite et simple pour mieux répondre avec un bon cas de test. Par curiosité, pourquoi la nécessité de convertir en décimales?

2
backpackcoder

Pour moi, il semble qu'un gros problème de lisibilité réside ici dans l'absence totale de formatage.

Je l'écrirais comme ceci:

return (decimal)CostIn > 0 && CostOut > 0 
            ? (((decimal)CostOut - (decimal)CostIn) / (decimal)CostOut) * 100 
            : 0;

Selon que le type de CostIn et CostOut est un type à virgule flottante ou un type intégral, certains transtypages peuvent également être inutiles. Contrairement à float et double, les valeurs intégrales sont implicitement promues en decimal.

1
Felix Dombek

La brièveté n'est plus une vertu quand

  • Il y a division sans contrôle préalable pour zéro.
  • Il n'y a pas de vérification pour null.
  • Il n'y a pas de nettoyage.
  • TRY CATCH versus lance la chaîne alimentaire où l'erreur peut être gérée.
  • Des hypothèses sont faites sur l'ordre d'achèvement des tâches asynchrones
  • Tâches utilisant le retard au lieu de replanifier à l'avenir
  • Inutile IO est utilisé
  • Ne pas utiliser de mise à jour optimiste
0
danny117

Je suppose que CostIn * CostOut sont des entiers
Voici comment je l'écrirais
M (Money) est décimal

return CostIn > 0 && CostOut > 0 ? 100M * (CostOut - CostIn) / CostOut : 0M;
0
paparazzo

Le code peut être écrit à la hâte, mais le code ci-dessus devrait dans mon monde être écrit avec de bien meilleurs noms de variables.

Et si je lis le code correctement, il essaie de faire un calcul de la marge brute.

var totalSales = CostOut;
var totalCost = CostIn;
var profit = (decimal)(CostOut - CostIn);
var grossMargin = 0m; //profit expressed as percentage of totalSales

if(profit > 0)
{
    grossMargin = profit/totalSales*100
}
0
Thomas Koelle

Le code est écrit pour être compris par les gens; la brièveté dans ce cas n'achète pas grand-chose et augmente la charge du mainteneur. Pour cette brièveté, vous devez absolument développer cela soit en rendant le code plus auto-documenté (meilleurs noms de variables) ou en ajoutant plus de commentaires expliquant pourquoi cela fonctionne de cette façon.

Lorsque vous écrivez du code pour résoudre un problème aujourd'hui, ce code pourrait être un problème demain lorsque les exigences changent. La maintenance doit toujours être prise en compte et l'amélioration de la compréhension du code est essentielle.

0
Rudolf Olah

Si cela réussissait les tests unitaires de validation, alors ce serait bien, si une nouvelle spécification était ajoutée, un nouveau test ou une implémentation améliorée était nécessaire, et il était nécessaire de "démêler" la lourdeur du code, c'est-à-dire lorsque le un problème se poserait.

Évidemment, un bogue dans le code montre qu'il y a un autre problème avec Q/A qui était une erreur, donc le fait qu'il y avait un bogue qui n'a pas été détecté est une source de préoccupation.

Lorsqu'il s'agit d'exigences non fonctionnelles telles que la "lisibilité" du code, il doit être défini par le responsable du développement et géré par le développeur principal et respecté par les développeurs pour garantir une implémentation appropriée.

Essayez d'assurer une implémentation normalisée du code (normes et conventions) pour assurer la "lisibilité" et la facilité de "maintenabilité". Mais si ces attributs de qualité ne sont pas définis et appliqués, vous vous retrouverez avec du code comme l'exemple ci-dessus.

Si vous n'aimez pas voir ce type de code, essayez d'obtenir l'accord de l'équipe sur les normes et conventions de mise en œuvre et écrivez-le et faites des révisions de code aléatoires ou des vérifications ponctuelles pour valider la convention est respectée.

0
hanzolo