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

Une correction de bogue récente m'a obligé à passer en revue le code écrit par d'autres membres de l'équipe, où j'ai trouvé ceci (c'est C #):

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

Maintenant, admettant qu'il y ait une bonne raison pour 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 lors de la révision du code, et son approche est que raccourcir est presque toujours préférable. Et bien sûr, il y a de la valeur à cela: nous avons tous vu des chaînes inutilement complexes de logique conditionnelle qui pourraient être mises de l'ordre avec quelques opérateurs bien placés. Mais il est clairement plus habile que moi à suivre des chaînes d'opérateurs regroupés dans une seule déclaration.

C’est bien sûr, en fin de compte, une question de style. Mais est-ce que quelque chose a été écrit ou fait des recherches pour reconnaître le point où s'efforcer d'obtenir une concision du code cesse d'être utile et devient un obstacle à la compréhension?

Note de bas de page:

La raison des 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 converti.

100
ajouté édité
Vues: 1
Quand la brièveté l'emporte sur la lisibilité.
ajouté l'auteur sgwill, source
De plus, la logique peut en réalité être fausse. Supposons que CostOut soit égal à Double.Epsilon et par conséquent supérieur à zéro. Mais (décimal) CostOut est dans ce cas nul, et nous avons une erreur division par zéro. La première étape devrait être d’obtenir le code correct , ce qui, à mon avis, ne l’est pas. Corrigez-le, créez des scénarios de test, puis rendez-le élégant . Le code élégant et le code bref ont beaucoup en commun, mais parfois, la brièveté n'est pas l'âme de l'élégance.
ajouté l'auteur CodeCharming, source
En particulier, il semble étrange qu’il soit nécessaire de convertir CostIn en décimal pour le comparer à zéro, mais pas CostOut; pourquoi donc? Quel est le type de CostIn sur terre qui ne peut être comparé à zéro qu'en le convertissant en décimal? Et pourquoi CostOut n'est-il pas du même type que CostIn?
ajouté l'auteur CodeCharming, source
En regardant votre exemple spécifique: une distribution est soit (1) un endroit où le développeur en sait plus que le compilateur et doit dire au compilateur un fait qui ne peut pas être déduit, ou (2) où certaines données sont stockées dans le "mauvais" "type pour les types d'opérations que nous devons effectuer dessus. Les deux sont des indicateurs forts que quelque chose pourrait être refactorisé. La meilleure solution ici consiste à trouver un moyen d'écrire le code sans transtypage.
ajouté l'auteur CodeCharming, source
Je passe beaucoup trop de temps dans le débogueur, donc je soutiens également que la brièveté n'est plus une vertu lorsque vous ne pouvez pas facilement déboguer le code, ce n'est peut-être qu'un corollaire de la lisibilité. Bien entendu, avec cet exemple fourni, l’exactitude du code peut être obtenue avec unittests et est facile à voir dans le débogueur.
ajouté l'auteur saulspatz, source
Il semble qu'il existe certaines règles commerciales/comptables permettant de renvoyer un zéro si CostIn est égal à zéro ou moins, mais vous ne pouvez pas savoir à partir du code (calcul du bénéfice standard). La vérification de la division par zéro a du sens, mais elle n’est pas très explicite.
ajouté l'auteur bstpierre, source
N'optimisez pas votre code pour le compilateur à moins que cela ne soit absolument nécessaire: écrivez-le pour d'autres humains. Le compilateur comprendra quand même.
ajouté l'auteur Flávio Filho, source
La devise norstrilienne - "ne soyez pas trop malin" - m'a toujours bien servi.
ajouté l'auteur user251748, source
La brièveté peut être vue par certains comme un code supérieur ou intelligent. Votre agent de code aime probablement jouer au golf et l'applique à son code quotidien. Une ligne> [n] lignes ...
ajouté l'auteur Anup Chaudhary, source
Tout cela pour éviter un if, qui aurait été plus facile à comprendre et à modifier.
ajouté l'auteur Thorbjørn Ravn Andersen, source
____ quand cela ____
ajouté l'auteur Joshua, source
Je pense que la logique que vous avez évoquée dans la question mélange deux choses. Le code devient difficile à suivre, pas parce que c'est long. La longueur ou la brièveté du code n’a rien à voir avec sa lisibilité. Ce sont deux axes distincts. Efforcez-vous de la lisibilité, pas de la "brièveté" artificielle.
ajouté l'auteur Dennis, source
La brièveté est toujours une vertu. Mais notre fonction objective combine la brièveté avec d’autres vertus. Si l'on peut être plus bref sans nuire aux autres vertus, on devrait toujours le faire.
ajouté l'auteur Solomonoff's Secret, source
Pour moi, l'épiphanie est arrivée quand j'ai dû trouver le code que je savais avoir écrit il y a des années. Si votre code n'est pas maintenable même par vos propres normes ...
ajouté l'auteur Droj, source
Il semble que l’auteur original ait également été confus par la brièveté et a donc introduit un bogue dans le code.
ajouté l'auteur tad, source
Cela me semble clair. Si les coûts sont positifs, obtenez le pourcentage de différence entre eux. J'ai vu du code qui tente de le casser et il devient trop verbeux.
ajouté l'auteur Tyler Raber, source
@ RobertHarvey Votre commentaire est tout ce qu'il y a à dire sur le sujet, et c'est l'incarnation de la brièveté.
ajouté l'auteur sdenham, source
Récemment, écoutez cette citation de Brian Kernighan: «Tout le monde sait que le débogage est deux fois plus difficile que d’écrire un programme. Alors, si vous êtes aussi intelligent que vous le pouvez lorsque vous l'écrivez, comment allez-vous le déboguer?
ajouté l'auteur xdhmoore, source
" Maintenant ."
ajouté l'auteur Anjali Nair, source
La brièveté et la lisibilité sont (IMHO) quelque peu liées: elles sont toutes deux corrélées au nombre d'éléments distincts dans le code. Cramming tous sur une seule ligne ne réduit pas le nombre d'entre eux, et les rend souvent plus obscur.
ajouté l'auteur Sujan, source
Je dois dire qu'il devrait y avoir un raccourci clavier/une fonction permettant de démêler ce genre de choses dans chaque IDE, puis de les reprogrammer pour éviter qu'elles ne prennent 12 lignes à la fin.
ajouté l'auteur zzbomb, source

14 Réponses

Pour répondre à votre question sur la recherche existante

Mais est-ce que quelque chose a été écrit ou recherché 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 des travaux dans ce domaine.

Pour comprendre cela, vous devez trouver un moyen de calculer une métrique afin que les comparaisons puissent être effectuées de manière quantitative (au lieu de simplement effectuer la comparaison en fonction de l’esprit et de l’intuition). les autres réponses le font). 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.

La SATC a constaté que l’évaluation la plus efficace était une combinaison de taille et de complexité [Cyclomatic]. Les modules avec une complexité élevée et une taille importante tendent à avoir la fiabilité la plus basse. Les modules de taille réduite et de complexité élevée présentent également un risque de fiabilité, car ils ont tendance à être un code très succinct, difficile à modifier ou à modifier.

Lien

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

McCabe, T. et A. Watson (1994), Complexité logicielle (CrossTalk: le journal du génie logiciel de la défense).

Watson, A. H., & McCabe, T. J. (1996). Structured Testing: A Testing Methodology Using the Cyclomatic Complexity Metric (NIST Special Publication 500-235). Retrieved May 14, 2011, from McCabe Software web site: http://www.mccabe.com/pdf/mccabe-nist235r.pdf

Rosenberg, L., Hammer, T., Shaw, J. (1998). Software Metrics and Reliability (Proceedings of IEEE International Symposium on Software Reliability Engineering). Retrieved May 14, 2011, from Penn State University web site: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.104.4041&rep=rep1&type=pdf

Mon avis et solution

Personnellement, jamais je n’ai jamais apprécié la brièveté, mais uniquement la lisibilité. Parfois, la brièveté améliore la lisibilité, parfois non. Le plus important est que vous écriviez le code vraiment évident (ROC) au lieu du code en écriture seule (WOC).

Juste pour le plaisir, voici comment je l'écrirais et demanderais 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 l’effet secondaire heureux de déclencher une arithmétique en virgule fixe au lieu d’une arithmétique entière, éliminant ainsi la nécessité de tous ces conversions en décimal .

159
ajouté
+1 Woo hoo, Wu! Et en particulier: "Personnellement, je n’ai jamais apprécié la brièveté, mais uniquement la lisibilité". Cependant, ironiquement, je dois souligner que votre solution doit être méthodisée. Deux symptômes de "brièveté": le dumping "bref" du code dans d'autres méthodes inappropriées. Et vider les méthodes "brèves" dans des classes inappropriées -> échec total à appliquer le SRP à tous les niveaux du code.
ajouté l'auteur jbu, source
Pourquoi les parenthèses supplémentaires? À moins qu'il y ait quelques bizarreries avec ce langage, vous pouvez simplement écrire si (costIn <= 0 || costOut <= 0) non?
ajouté l'auteur wilhelmtell, source
@ Doc Brown Il existe en fait un meilleur moyen pour la clause de protection: introduisez simplement un objet de valeur Cost spécifique au domaine qui empêche un coût d'être négatif plutôt que de traiter avec des valeurs décimales. En réalité, cela réduirait la valeur à return costOut.minus (costIn) .percentOf (costOut);
ajouté l'auteur PepperBob, source
Je ne jetterais pas sur un coût négatif. Plus d'une fois j'ai acheté un article à un prix négatif. (Je veux un X. Pour satisfaire aux conditions d'une offre promotionnelle, je reçois également un Y, de sorte que X + Y est moins cher que X seul. Dans ce cas, je considérerais que Y a un prix négatif.)
ajouté l'auteur Sander Rijken, source
La complexité cyclomatique a été montrée, sur le code réel, très corrélée au SLOC brut. Cela signifie que la complexité cyclomatique/SLOC aura très peu d’utilité. Considérons deux routines: une avec une complexité cyclomatique de 30, contenant 300 SLOC, et une avec une complexité cyclomatique de 3, contenant 30 SLOC. Dans les deux cas, votre métrique renverra 10, mais je pense que nous pouvons convenir que ces deux routines n'ont rien de comparable, ni même une complexité comparable.
ajouté l'auteur Mark McKinstry, source
@ DocBrown: C'est une bonne suggestion, mais je voudrais déterminer si le chemin de code exceptionnel peut être exercé par un test. Si oui, alors écrivez un scénario de test qui utilise ce chemin de code. Si non, changez le tout en assert.
ajouté l'auteur CodeCharming, source
+1 J'ajouterais probablement quelque chose comme si ((costIn <0) || (costOut <0)) jette une nouvelle exception ("les coûts ne doivent pas être négatifs");
ajouté l'auteur Peter LeFanu Lumsdaine, source
@Doc Brown J'aime votre idée d'exception en général, mais voyant ce code, je me demande s'ils utilisent des nombres négatifs pour signaler des cas particuliers. (Mauvaise idée mais pourrait être le cas) Ma réponse ci-dessous tente d'être plus explicite. (Et on peut soutenir qu’il devrait lancer une exception au lieu de renvoyer 0).
ajouté l'auteur MrG, source
J'aime beaucoup la clause de garde pour le cas inférieur à zéro. Peut-être un commentaire digne de ce nom: comment un coût peut-il être inférieur à zéro, quel cas particulier est-ce?
ajouté l'auteur MrG, source
Eh bien, si je veux vraiment parler de la meilleure lecture et compréhension possible, ce ((decOut - decIn)/decOut) * 100 est bien meilleur que les deux lignes, car je reconnais immédiatement le motif simple de un calcul de taux. Sur vos deux lignes, c'est un peu plus lent pour moi.
ajouté l'auteur Walfrat, source
Aucune métrique n'est parfaite. Personnellement, je n'ai pas peur d'utiliser beaucoup d'espaces dans la base de code tant que cela ne crée pas de fonctions aussi longtemps qu'il faut beaucoup de défilement.
ajouté l'auteur John Wu, source
Bien que tous ces points soient positifs, je pense que la portée de cette question concerne le style du code et non la logique. Mon code snip est un effort d'équivalence fonctionnelle du point de vue d'une boîte noire. Lancer une exception n'est pas la même chose que renvoyer 0, donc cette solution ne serait pas fonctionnellement équivalente.
ajouté l'auteur John Wu, source
Soyez prudent avec vos termes. Ils ne présentent pas une complexité équivalente (vous venez de dire qu’un en a 30 et l’autre en 3). Mais la métrique prédit la même fiabilité si vous en croyez les auteurs.
ajouté l'auteur John Wu, source
Merci, M. Wu, pour cette excellente réponse avec un exemple de la façon dont elle devrait être écrite pour la lisibilité.
ajouté l'auteur div1, source
"Personnellement, je n'ai jamais apprécié la brièveté, uniquement la lisibilité. Parfois, la brièveté améliore la lisibilité, parfois non." Excellent . +1 juste pour ça. J'aime votre solution de code, aussi.
ajouté l'auteur Wildcard, source
sûrement par votre mesure suggérée que si la déclaration devrait être étendue à if .. elseif..else avec beaucoup d'accolades et de sauts de ligne?
ajouté l'auteur Ewan, source
@Walfrat J'aurais utilisé (1 - decIn/decOut) * 100 pour la même raison.
ajouté l'auteur Mathieu Guindon, source
Très belle réponse. Mon seul nit à choisir est que l'attribution de la valeur de retour semble un peu prolixe. Le nom de la méthode doit prendre en charge cette partie de la lisibilité, et en faisant de cette dernière partie de la logique deux lignes au lieu de une, je pense que vous augmentez la charge mentale. Mais, assez mineur nit et vous avez toujours mon vote.
ajouté l'auteur anon, source
Il suffit de le calculer en utilisant les branches dans le CIL généré, le fragment de code d'origine a une valeur métrique de 4, alors que votre modification a une valeur de métrique de 0,5.
ajouté l'auteur user113445, source

La brièveté est bonne quand elle réduit l'encombrement autour des objets importants, mais quand elle devient laconique , contenant trop de données pertinentes trop étroitement pour être facilement suivies, les données pertinentes deviennent elles-mêmes encombrées et vous avez un problème.

Dans ce cas particulier, les conversions vers décimal sont répétées à maintes reprises; dans l’ensemble, il serait probablement préférable de le réécrire comme suit:

var decIn = (decimal)CostIn;
var decOut = (decimal)costOut;
return decIn > 0 && CostOut > 0 ? ((decOut - decIn )/decOut) * 100 : 0;

Soudain, la ligne contenant la logique est beaucoup plus courte et s'adapte à une ligne horizontale, de sorte que vous pouvez tout voir sans avoir à faire défiler l'écran, et la signification est beaucoup plus évidente.

46
ajouté
Mon mauvais - je me concentrais trop sur le code et pas assez sur la question réelle.
ajouté l'auteur David Espart, source
possible faute de frappe - vous vouliez peut-être dire return decIn> 0 && decOut> 0 ... ?
ajouté l'auteur David Espart, source
@FrustratedWithFormsDesigner: Si vous extrayez ceci avant que le conditionnel ignore le contrôle de division par zéro ( CostOut> 0 ), vous devez développer le conditionnel en un if . -déclaration. Cela n’a rien de mal à cela, mais cela ajoute plus de verbosité que la simple introduction d’un local.
ajouté l'auteur mR_fr0g, source
@FrustratedWithFormsDesigner J'irais même plus loin et encapsuler la vérification conditionnelle entre parenthèses.
ajouté l'auteur xandy, source
"Il suit le code d'origine qui n'a pas de casting sur ce CostOut." Exactement. Je voulais seulement simplifier le code tel quel, sans rien faire qui puisse en changer le sens.
ajouté l'auteur JanC, source
@wchargin ... c'est pourquoi je ne l'ai pas fait dans ma version.
ajouté l'auteur JanC, source
Je serais probablement allé plus loin et refactored ((decOut - decIn)/decOut) * 100 vers une autre variable.
ajouté l'auteur FrustratedWithFormsDesigner, source
L'assembleur était beaucoup plus clair: une seule opération par ligne. Doh!
ajouté l'auteur user251748, source
Pour être honnête, le simple fait de se débarrasser des moulages semble être comme si vous aviez diffusé assez d'IMO, si quelqu'un avait du mal à lire parce qu'il ne pouvait pas reconnaître un calcul simple du taux de base, c'est son problème, pas le mien.
ajouté l'auteur Walfrat, source
@nocomprende il y a différents assembleurs: les programmes fragment/vertex ARB, par exemple, peuvent être écrits en deux lignes: la première ligne est l'en-tête comme !! ARBfp1.0 , la seconde est le programme complet. Les instructions y sont séparées par des points-virgules et les nouvelles lignes sont facultatives.
ajouté l'auteur Ruslan, source
"La brièveté est bonne quand elle réduit l'encombrement autour des choses qui importent" J'aime vraiment cette déclaration.
ajouté l'auteur user48329, source
@DanPichelman Il suit le code original qui n'a pas de casting sur ce CostOut. Pour ma part, cependant, votre suggestion est meilleure.
ajouté l'auteur Robert Grant, source
Je suggérerais même d'évaluer et de stocker le conditionnel. Cela augmente non seulement la lisibilité, mais est sans doute plus flexible. Après, retour costPositive? (decOut - decIn)/decOut * 100: 0; est un bon compromis entre les deux idéologies.
ajouté l'auteur Legato, source

Bien que je ne puisse citer aucune recherche particulière sur le sujet, je suggérerais que tous les moulages de votre code enfreignent le principe «Ne vous répétez pas». Ce que votre code semble essayer de faire est de convertir costIn et costOut en type Decimal , puis effectuez des vérifications de sécurité sur les résultats de ces conversions et l'exécution d'opérations supplémentaires sur ces valeurs converties si les contrôles aboutissent. En fait, votre code effectue l'une des vérifications de sécurité sur une valeur non convertie, ce qui augmente la possibilité que costOut puisse contenir une valeur supérieure à zéro, mais inférieure à la moitié de la taille de la plus petite valeur non nulle que Décimal 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 en conséquence.

Il semble curieux que le rapport entre les représentations décimale de costIn et costOut soit inférieur au rapport entre les valeurs réelles de costIn et costOut , sauf si le code va également utiliser les représentations décimales à une autre fin. Si le code doit utiliser davantage ces représentations, ce serait un argument supplémentaire pour la création de variables destinées à contenir ces représentations, plutôt que d'avoir une séquence continue de transtypages dans le code.

7
ajouté
Les conversions (décimales) doivent probablement satisfaire à certaines exigences de la règle de gestion. Lorsque vous traitez avec des comptables, vous devez parfois franchir des obstacles stupides. Je pensais que le CFO allait avoir une crise cardiaque quand il a trouvé la ligne "Utiliser la correction de l'arrondi de l'impôt - 0,01 $" qui était inévitable compte tenu de la fonctionnalité demandée. (Fourni: prix après taxe. Je dois calculer le prix avant taxe. Les chances qu'il n'y ait pas de réponse sont égales au taux de taxe.)
ajouté l'auteur Sander Rijken, source
Bon point - j'avais oublié les détails du type décimal parce que je n'ai jamais eu l'occasion de vouloir quelque chose comme ça. Je pense maintenant que c’est une obéissance des règles commerciales au culte des cargos - c’est-à-dire que l’argent ne doit pas être un élément flottant.
ajouté l'auteur Sander Rijken, source
Argh, mal lire les docs. Je pensais que c'était du type à virgule fixe avec décimales. C’est quelque chose que je n’ai jamais eu l’occasion d’utiliser - j’évite autant que possible toute virgule flottante (et le choix approprié des unités évite presque toujours) et je n’ai encore aucune occasion de regarder au-delà de 64 bits. types entiers.
ajouté l'auteur Sander Rijken, source
Le type Decimal occupe 16 octets, ce qui serait amplement suffisant pour un type à virgule fixe avec 18 chiffres à droite du séparateur décimal et 20 à gauche. Decimal est un type à virgule flottante qui a environ 27 chiffres de précision, ce qui signifie que pour les valeurs inférieures à un milliard, il a plus de précision qu'un type à virgule fixe, mais le fait de fixer la précision absolue est souvent plus important que sa magnitude exacte, car addition et soustraction transitive.
ajouté l'auteur supercat, source
@ LorenPechtel: Malheureusement, Decimal est un type à virgule flottante, bien qu'assez maladroit et inefficace. À mon humble avis, un type à virgule fixe de 16 octets aurait été meilleur à tous les égards.
ajouté l'auteur supercat, source
@ LorenPechtel: Etant donné que la précision avec laquelle une conversion décimale dépendra de l'ampleur de la valeur en question, il m'est difficile d'imaginer des règles commerciales qui imposeraient un déroulement réel des conversions se comporter.
ajouté l'auteur supercat, source
@ LorenPechtel: Pour clarifier mon dernier point: un type à point fixe peut garantir que x + y-y débordera ou donnera y. Le type Decimal ne l’est pas. La valeur 1.0d/3.0 aura plus de chiffres à la droite de la décimale que ce qui peut être maintenu en utilisant des nombres plus grands. Donc, ajouter et soustraire le même nombre plus grand entraînera une perte de précision. Les types à point fixe peuvent perdre en précision avec la multiplication ou la division fractionnelle, mais pas avec l'addition, la soustraction, la multiplication ou la division avec le reste (par exemple 1,00/7 est 0,14 reste 0,2; 1,00 div 0,15 est 6 reste 0,10).
ajouté l'auteur supercat, source
@ Hulk: Oui, bien sûr. Je me demandais s'il fallait utiliser x + y-y donnant x, x + y-x donnant y, ou x + y-x-y, et j'ai fini par mélanger les deux premiers. Ce qui est important, c’est que les types à virgule fixe puissent garantir que de nombreuses séquences d’opérations ne provoqueront jamais d’erreurs d’arrondis non détectées, de grandeur quelconque . Si le code additionne les éléments de différentes manières pour vérifier la concordance des totaux (par exemple, en comparant la somme des sous-totaux de lignes à la somme des sous-totaux de colonnes), il est bien mieux d'avoir des résultats parfaitement égaux que de les faire "fermer".
ajouté l'auteur supercat, source
@supercat "x + y-y débordera ou cédera y" - vouliez-vous dire "céder x"?
ajouté l'auteur Dan, source

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

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

Je devine en ce qui concerne les noms ici: changez BothCostsAreValidProducts et NO_PROFIT selon les cas.

5
ajouté
Bien sûr, les coûts peuvent être nuls (pensez aux cadeaux de Noël). Et en période de taux d’intérêt négatifs, les coûts négatifs ne devraient pas non plus être trop surprenants et au moins le code devrait être prêt à y faire face (et ce en jetant des erreurs)
ajouté l'auteur bubbleking, source
Cela est bête. if (CostIn <= 0 || CostOut <= 0) est tout à fait correct.
ajouté l'auteur Miles Rout, source
Vous étes sur le bon chemin.
ajouté l'auteur danny117, source

La brièveté cesse d’être une vertu quand on oublie que c’est un moyen d’atteindre un but plutôt qu’une vertu en soi. Nous aimons la brièveté parce que cela correspond à la simplicité, et nous aimons la simplicité, car un code 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. Répondez aux exigences de l'entreprise avec le moins de travail possible

  2. Évitez les bugs

  3. Permettez-nous d’apporter à l’avenir des modifications conformes aux numéros 1 et 2

Ce sont les objectifs. Tous les principes ou méthodes de conception (qu’il s’agisse de KISS, YAGNI, TDD, SOLID, preuves, systèmes de types, métaprogrammation dynamique, etc.) ne sont vertueux que dans la mesure où ils nous aident à atteindre ces objectifs.

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

La ligne a trois préoccupations: gardes (boîtier spécial 0), type casting et calcul. Chaque problème est assez simple lorsqu'il est pris isolément, mais parce qu'il a été mélangé dans la même expression, il devient difficile à suivre.

La raison pour laquelle CostOut n'est pas converti lors de sa première utilisation n'est pas claire, alors que CostIn l'est. Il peut y avoir une bonne raison, mais l'intention n'est pas claire (du moins sans contexte), ce qui signifie qu'un responsable serait prudent de ne pas modifier ce code car il pourrait y avoir des hypothèses cachées. Et ceci est un anathème à 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 nombre à virgule flottante, le code pourrait masquer un bogue obscur de division par zéro, puisqu’une valeur en virgule flottante peut être petite mais non nulle, mais nulle lorsqu’elle est convertie en décimal (au moins Je crois que cela serait possible.)

Le problème n'est donc pas la brièveté ni l'absence de concision, le problème est une logique répétée et une confusion des préoccupations menant à un code difficile à maintenir.

L'introduction de variables pour conserver les valeurs exprimées augmenterait probablement la taille du code compté en nombre de touches, mais réduirait la complexité, dissocierait les préoccupations et améliorerait la clarté, ce qui nous rapprocherait de l'objectif d'un code plus facile à comprendre et à gérer.

5
ajouté

Au fil de mes années d’expérience, j’ai fini par croire que la brièveté ultime est celle du temps - le temps domine tout le reste. Cela inclut à la fois le temps d'exécution - le temps requis par un programme pour effectuer un travail - et le temps nécessaire à la maintenance - le temps nécessaire pour ajouter des fonctionnalités ou corriger des bogues. (La manière dont vous équilibrez ces deux facteurs dépend de la fréquence à laquelle le code en question est exécuté par rapport à l'amélioration. N'oubliez pas que l'optimisation prématurée est toujours la racine de tout mal .) des deux; Un code plus court est généralement plus rapide, plus facile à comprendre et donc à maintenir. Si ce n'est pas le cas, alors c'est un net négatif.

Dans le cas présenté ici, je pense que la brièveté du texte a été mal interprétée comme une 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, en fonction de la façon dont le lancer 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 conversions décimales répétitives nuisent à la lisibilité, car il est voyez 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: il s'agit du même code que l'autre réponse, alors voilà.)

Je suis fan de l'opérateur ternaire ? : , je laisserais donc cela dans.

2
ajouté
Mort à l'opérateur ternaire !! (aussi, mort aux onglets, ni espaces et tout modèle de bracketing et d'indentation sauf Allman (en fait, The Donald (tm) a tweeté que ce seront les trois premières lois qu'il édictera le 20)
ajouté l'auteur Rytis, source
Je suis d'accord sur l'opérateur ternaire. Je ne l'aime pas non plus. De plus, le concept de "optimisation prématurée" est discutable. Parfois, le bon moment pour "optimiser" est immédiatement lorsque le besoin et la méthode sont évidents. Parfois, "optimisation" revient à corriger une erreur.
ajouté l'auteur Wells, source
il y a un bug dans le code .. cela signifie que personne n'a vérifié pour s'assurer qu'il fonctionnait complètement .. c'est un problème plus important que la façon dont l'exigence a été implémentée
ajouté l'auteur Vidushraj Chandresekaran, source
Vraiment pas sûr. Je ne vous ai pas voté. Je n'aime pas les ternaries, car il est difficile de savoir ce qui se trouve de chaque côté du : . if-else lit comme anglais: ne peut pas rater ce que cela signifie.
ajouté l'auteur div1, source
Les ternaires sont difficiles à lire, en particulier s'il y a des expressions au-delà d'une valeur unique ou d'une variable dans les valeurs renvoyées.
ajouté l'auteur div1, source
Je n'ai qu'une chose à dire à cela: le froid? morts: mains
ajouté l'auteur Droj, source
Je me demande si c'est ce qui motive les votes négatifs. Sauf que ce que j'ai écrit est en accord avec Mason Wheeler, actuellement à 10 voix. Il a également quitté le ternaire. Je ne sais pas pourquoi tant de gens ont un problème avec ? : - Je pense que l'exemple ci-dessus est suffisamment compact, en particulier. comparé à un si-alors-sinon.
ajouté l'auteur Droj, source
FWIW Je suppose que vous obtenez des votes défavorables car il s’agit d’une réponse très similaire à celle de Mason Wheeler, mais la sienne a eu son premier.
ajouté l'auteur Robert Grant, source

Comme presque toutes les réponses ci-dessus, la lisibilité devrait toujours être votre objectif principal. Cependant, je pense aussi que le formatage peut être un moyen plus efficace d’atteindre cet objectif en créant 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 assez petite et assez simple pour être mieux traitée avec un bon cas de test. Par curiosité, pourquoi la nécessité de convertir en décimal?

2
ajouté
@Almo: Je suis surpris par votre "pas du tout". Personnellement, je préfère les '?' Et ':' au début des lignes suivantes et suivis par des tabulations, pour obtenir le «ballast syntaxique» dans des positions prévisibles, mais le motif de base de _condition_ _when-true_ _when-false _ est son. Mais je conviens que l'extraction des conversions (décimal) et l'ajout de commentaires peuvent être utiles.
ajouté l'auteur Rocket Hazmat, source
Je dois expliquer à mes étudiants que les types de données SQL sont étrangement différents de ceux utilisés dans les langages de programmation. Je n'ai pas pu leur expliquer pourquoi . "Je ne sais pas!" "Petit endian!"
ajouté l'auteur user251748, source
Je ne trouve pas cela lisible du tout.
ajouté l'auteur div1, source
Je programme depuis des décennies et je n'ai jamais trouvé de ternaires lisibles. Peut-être que j'ai juste un problème personnel. :)
ajouté l'auteur div1, source
@ pinkfloydx33 oui! Taper sur un téléphone avec seulement la moitié d'un cerveau engagé :)
ajouté l'auteur Robert Grant, source
La raison des conversions est Entity Framework. La base de données doit les stocker en tant que types nullables. Double? n’est pas équivalent à Double en C# et doit être converti.
ajouté l'auteur Robert Grant, source
@MattThrower Vous voulez dire décimal , n'est-ce pas? double ! = décimal , il y a une grande différence.
ajouté l'auteur pinkfloydx33, source

Pour moi, il semble qu'un gros problème de lisibilité réside 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 de CostOut est un type à virgule flottante ou un type intégral, certains transtypages peuvent également être inutiles. À la différence de float et de double , les valeurs intégrales sont implicitement promues en décimal .

1
ajouté
Je suis d’accord sur les opérateurs, comme je l’ai remarqué dans un commentaire sur l’autre réponse - je n’avais pas remarqué que vous l’aviez fait comme je le préfère.
ajouté l'auteur Rocket Hazmat, source
Je suis désolé de voir que cela a été voté sans explication, mais cela me semble être identique à la réponse de backpackcodes moins certaines de ses remarques, donc je suppose que cela était justifié.
ajouté l'auteur Rocket Hazmat, source
@PJTraill J'ai dû rater ça, c'est presque identique. Cependant, je préfère fortement avoir les opérateurs sur les nouvelles lignes, c'est pourquoi je vais laisser ma version en attente.
ajouté l'auteur tgecho, source

Brevity is not a virtue at all. Readability is THE virtue.

La brièveté peut être un outil pour atteindre la vertu, ou, comme dans votre exemple, peut être un outil permettant de réaliser quelque chose de totalement 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" - elles sont toutes également dénuées de sens et peuvent être dommageables si elles ne servent pas un objectif plus ambitieux.

De plus, le code que vous avez posté ne suit même pas la règle de brièveté. Si les constantes avaient été déclarées avec le suffixe M, la plupart des calculs (décimaux) pourraient être évités, car le compilateur encouragerait le int restant en décimal 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.

1
ajouté

Le code peut être écrit rapidement, mais le code ci-dessus devrait dans mon monde être écrit avec des noms de variables bien meilleurs.

Et si je lis correctement le code, il essaie de faire un calcul de grossmargin.

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
ajouté
et c’est pourquoi il est difficile de modifier le code de quelqu'un d’autre qui est optimisé par souci de concision et pourquoi il est bon d’avoir des commentaires supplémentaires pour expliquer pourquoi/comment les choses fonctionnent.
ajouté l'auteur Manishearth, source
Vous avez perdu la division par zéro renvoie zéro.
ajouté l'auteur danny117, source

Le code est écrit pour être compris par les gens; la brièveté dans ce cas n’achète pas beaucoup et alourdit la charge du mainteneur. Pour cette brièveté, vous devez absolument le développer en rendant le code plus auto-documentant (de 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 risque de poser problème demain lorsque les exigences changent. La maintenance doit toujours être prise en compte et une meilleure compréhension du code est essentielle.

0
ajouté
C’est là que les pratiques et les principes du génie logiciel entrent en jeu. Prérogatives non fonctionnelles
ajouté l'auteur Vidushraj Chandresekaran, source

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

De toute évidence, un bogue dans le code indique qu’il ya un autre problème avec Q/A qui était un oubli, alors le fait qu’un bogue n’ait pas été détecté est une source de préoccupation.

Lorsque des exigences non fonctionnelles, telles que la "lisibilité" du code, doivent être définies par le responsable du développement, gérées par le développeur principal et respectées par les développeurs afin de garantir une implémentation correcte.

Essayez d’assurer une mise en œuvre normalisée du code (normes et conventions) afin d’assurer la "lisibilité" et la facilité de "maintenabilité". Mais si ces attributs de qualité ne sont pas définis et appliqués, vous obtiendrez un code comme dans l'exemple ci-dessus.

Si vous n'aimez pas voir ce type de code, essayez de faire en sorte que l'équipe soit d'accord sur les normes et les conventions de mise en œuvre, écrivez-le et faites des examens de code aléatoires ou des contrôles inopinés pour valider que la convention est respectée.

0
ajouté

Je suppose que CostIn * CostOut sont des entiers
C'est comme ça que je l'écrirais
M (Money) est décimal

return CostIn > 0 && CostOut > 0 ? 100M * (CostOut - CostIn)/CostOut : 0M;
0
ajouté
en espérant qu'ils ne soient pas tous deux pensées négatives: p
ajouté l'auteur Walfrat, source
Je ne veux pas mettre à jour la question et la rendre active. Le 100M et le 0M forcent la décimale. Je pense que (CostOut - CostIn) sera exécuté sous forme de calcul mathématique entier, puis la différence est convertie en décimal.
ajouté l'auteur Flamewires, source
@ danny117 Lorsque la brièveté donne une réponse fausse, elle est allée trop loin.
ajouté l'auteur Flamewires, source
La division par zéro est-elle toujours là?
ajouté l'auteur danny117, source

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

  • Il existe une division sans contrôle préalable de zéro.
  • Il n'y a pas de vérification de la valeur null.
  • Il n'y a pas de nettoyage.
  • ESSAYEZ CATCH par rapport à la chaîne alimentaire où l'erreur peut être traitée.
  • Des hypothèses sont formulées sur l'ordre d'exécution des tâches asynchrones
  • Tâches utilisant le délai au lieu d'une nouvelle planification
  • Des entrées/sorties inutiles sont utilisées
  • Ne pas utiliser la mise à jour optimiste
0
ajouté
Ok, cela aurait dû être un commentaire, pas une réponse. Mais c’est un nouveau mec, alors au moins expliquer; ne vous contentez pas de vous déplacer et de vous enfuir! Bienvenue à bord, Danny. J'ai annulé un vote négatif, mais la prochaine fois, faites un commentaire :-)
ajouté l'auteur Rytis, source
Quand il n'y a pas assez longtemps Réponse.
ajouté l'auteur user251748, source
Je viens de modifier via Android et il n'a pas demandé une description de la modification. J'ai ajouté la mise à jour optimiste (détecter modifié et avertir)
ajouté l'auteur danny117, source
Ok, j'ai élargi la réponse pour inclure des choses plus complexes que j'ai apprises à la dure et la manière facile d'écrire un code bref.
ajouté l'auteur danny117, source
Merci pour l'accueil @Mawg Je tiens à souligner le contrôle de null est ce que je rencontre les problèmes les plus générateurs de code bref.
ajouté l'auteur danny117, source