Dans quels cas moins de code n'est-il pas meilleur? [fermé]

55

J'ai récemment refactorisé du code au travail et je pensais avoir fait du bon travail. J'ai abandonné 980 lignes de code à 450 et divisé par deux le nombre de classes.

En montrant cela à mes collègues, certains n'étaient pas d'accord pour dire qu'il s'agissait d'une amélioration.

Ils ont dit - "moins de lignes de code n'est pas nécessairement meilleur"

Je peux voir qu'il peut y avoir des cas extrêmes où les gens écrivent de très longues lignes et / ou mettent tout en une seule méthode pour économiser quelques lignes, mais ce n'est pas ce que j'ai fait. À mon avis, le code est bien structuré et plus simple à comprendre / à maintenir car il est deux fois plus petit.

J'ai du mal à comprendre pourquoi quiconque voudrait travailler avec le double du code requis pour accomplir un travail, et je me demande si quelqu'un ressent la même chose que mes collègues et peut défendre avec succès le fait d'avoir plus de code par rapport à moins ?

PiersyP
la source
145
La taille du code est mesurée en temps que vous devez lire et comprendre, pas en lignes ou en nombre de caractères.
Bergi
13
Votre question telle que rédigée est catégoriquement trop large. Nous vous recommandons d’en écrire un autre concernant les modifications spécifiques que vous avez apportées.
jpmc26
8
Considérez l' algorithme rapide de racine carrée inverse . La mise en œuvre de la méthode de Newton intégrale avec une dénomination correcte des variables serait beaucoup plus claire et plus facile à lire, même si elle contiendrait probablement plus de lignes de code. (Notez que dans ce cas particulier, l’utilisation de code intelligent était justifiée par les préoccupations de la performance).
Maciej Piechotka
65
Il existe un site d'échange complet de piles dédié à la réponse à votre question: codegolf.stackexchange.com . :)
Federico Poloni

Réponses:

123

Une personne maigre n'est pas nécessairement en meilleure santé qu'une personne en surpoids.

Une histoire d’enfants de 980 lignes est plus facile à lire qu’une thèse de physique de 450 lignes.

De nombreux attributs déterminent la qualité de votre code. Certaines sont simplement calculées, comme la complexité cyclomatique et la complexité de Halstead . D'autres sont définies de manière plus vague, telles que la cohésion , la lisibilité, la compréhensibilité, l'extensibilité, la robustesse, la correction, l'autodocumentation, la propreté, la testabilité et bien d'autres.

Par exemple, si vous réduisez la longueur totale du code, vous pouvez introduire une complexité supplémentaire injustifiée et rendre le code plus crypté.

Diviser un long morceau de code en méthodes minuscules peut être aussi néfaste que bénéfique .

Demandez à vos collègues de vous expliquer précisément pourquoi, à leur avis, vos efforts de refactorisation ont produit un résultat indésirable.

MA Hanin
la source
1
@PiersyP, juste une FYI, une des directives qui m'a été enseignée sur une bonne refactorisation est que nous devrions voir la complexité cyclomatique réduite à une racine carrée de ce qu'elle était à l'origine.
MA Hanin
4
@PiersyP également, je ne dis pas que votre code est pire ou meilleur que ce qu'il était. En tant qu'étranger, je ne peux pas vraiment dire. Il se peut également que vos collègues soient trop conservateurs et craignent votre changement simplement parce qu'ils n'ont pas fait l'effort requis pour l'examiner et le valider. C'est pourquoi j'ai suggéré que vous leur demandiez des commentaires supplémentaires.
MA Hanin
6
Bon travail, les gars - vous avez établi qu'il y a un "bon" poids quelque part (le nombre exact peut varier). Même les messages originaux de @Neil disent "surpoids" par opposition à "plus lourd qu'une personne est", et c'est parce qu'il y a un endroit idéal, tout comme avec la programmation. Ajouter du code au-delà de la "bonne taille" est un fouillis, et supprimer des lignes en-dessous sacrifie simplement la compréhension par souci de brièveté. Savoir où se situe exactement ce point ... C’est la partie difficile.
AC
1
Ce n’est pas parce que ce n’est pas nécessaire que cela n’a pas de valeur.
Chris Wohlert
1
@Neil Vous avez généralement raison, mais "l'équilibre" toujours insaisissable auquel vous faites allusion est en quelque sorte un mythe, objectivement . Tout le monde a une idée différente de ce qu'est un "bon équilibre". Clairement, OP pensait qu'il avait fait quelque chose de bien, pas ses collègues, mais je suis sûr qu'ils se sont tous dit qu'ils avaient le "bon équilibre" lorsqu'ils ont rédigé le code.
code_dredd
35

Fait intéressant, un de mes collègues et moi-même sommes actuellement au milieu d’un refactor qui augmentera le nombre de classes et de fonctions d’un peu moins du double, bien que les lignes de code restent à peu près identiques. J'ai donc un bon exemple.

Dans notre cas, nous avions une couche d'abstraction qui aurait vraiment dû être deux. Tout était entassé dans la couche ui. En le divisant en deux couches, tout devient plus cohérent, et le test et la maintenance des pièces individuelles deviennent beaucoup plus simples.

Ce n'est pas la taille du code qui gêne vos collègues, c'est autre chose. S'ils ne peuvent pas l'articuler, essayez de regarder le code vous-même comme si vous n'aviez jamais vu l'ancienne implémentation, et évaluez-le selon ses propres mérites plutôt que simplement en comparaison. Parfois, lorsque je fais un long refactor, je perds de vue l'objectif initial et je prends les choses trop loin. Examinez la situation dans son ensemble et remettez-le sur les rails, peut-être avec l'aide d'un programmeur en binôme dont vous appréciez les conseils.

Karl Bielefeldt
la source
1
Oui, bien séparer l'interface utilisateur des autres choses, cela en vaut toujours la peine. En ce qui concerne votre objectif de perdre de vue l’objectif initial, je suis plutôt d’accord, mais vous pouvez également redéfinir pour améliorer, ou sur la voie de l’amélioration. Comme dans le vieil argument sur l'évolution ("à quoi sert une aile?"), Les choses ne s'améliorent pas si vous ne prenez jamais le temps de les améliorer. Vous ne savez pas toujours où vous allez jusqu'à ce que vous soyez sur la route. Je suis d’accord pour essayer de découvrir pourquoi les collègues sont mal à l’aise, mais c’est peut-être vraiment «leur problème», pas le vôtre.
17

Une citation, souvent attribuée à Albert Einstein, me vient à l’esprit:

Rendez chaque chose aussi simple que possible, mais pas plus simple.

Lorsque vous réduisez les choses à la mer, cela peut rendre le code plus difficile à lire. Comme "facile / difficile à lire" peut être un terme très subjectif, j'expliquerai exactement ce que je veux dire par ceci: une mesure du degré de difficulté qu'un développeur expérimenté aura pour déterminer "qu'est-ce que ce code fait?" en regardant simplement la source, sans l'aide d'outils spécialisés.

Des langages comme Java et Pascal sont tristement célèbres pour leur verbosité. Les gens parlent souvent de certains éléments syntaxiques et disent avec dérision: "Ils ne sont là que pour faciliter le travail du compilateur". C'est plus ou moins vrai, sauf pour la partie "juste". Plus les informations sont explicites, plus le code est facile à lire et à comprendre, non seulement par un compilateur, mais également par un être humain.

Si je dis var x = 2 + 2;, il est immédiatement évident que cela xest supposé être un entier. Mais si je dis var foo = value.Response;, il est beaucoup moins clair ce qui fooreprésente ou quelles sont ses propriétés et capacités. Même si le compilateur peut facilement en déduire, cela met beaucoup plus d'effort cognitif sur une personne.

Rappelez-vous que les programmes doivent être écrits pour que les gens puissent les lire et, accessoirement, pour que les machines puissent les exécuter. (Ironiquement, cette citation provient d’un manuel consacré à une langue réputée extrêmement difficile à lire!) C’est une bonne idée de supprimer les éléments redondants, mais ne supprimez pas le code qui facilite la tâche de vos semblables. comprendre ce qui se passe, même si ce n'est pas strictement nécessaire pour le programme en cours d'écriture.

Maçon Wheeler
la source
7
l’ varexemple n’est pas particulièrement bon en termes de simplification, car la plupart du temps, la lecture et la compréhension du code impliquent de déterminer le comportement à un certain niveau d’abstraction. Par conséquent, connaître les types réels de variables spécifiques ne change en général rien vous aide à comprendre les abstractions inférieures). Un meilleur exemple serait plusieurs lignes de code simple écrasées en une seule déclaration compliquée - par exemple, il if ((x = Foo()) != (y = Bar()) && CheckResult(x, y)) faut du temps pour parler, et connaître les types de xou n'aider à rien y.
Ben Cottrell
15

Un code plus long peut éventuellement être plus facile à lire. C'est généralement le contraire, mais il existe de nombreuses exceptions, dont certaines sont décrites dans d'autres réponses.

Mais regardons sous un angle différent. Nous supposons que le nouveau code sera considéré comme supérieur par la plupart des programmeurs expérimentés qui verront les deux éléments de code sans avoir une connaissance supplémentaire de la culture, de la base de code ou de la feuille de route de la société. Même dans ce cas, il existe de nombreuses raisons de s’opposer au nouveau code. Par souci de brièveté, je vais appeler «Les personnes critiquant le nouveau code». Pecritenc :

  • La stabilité. Si l'ancien code était réputé stable, la stabilité du nouveau code est inconnue. Avant que le nouveau code puisse être utilisé, il doit encore être testé. Si, pour une raison quelconque, des tests appropriés ne sont pas disponibles, le changement constitue un gros problème. Même si des tests sont disponibles, Pecritenc peut penser que l'effort ne vaut pas l'amélioration (mineure) du code.
  • Performance / mise à l'échelle. L'ancien code a peut-être mieux évolué et Pecritenc présume que les performances vont devenir un problème pour les futurs clients * et les fonctionnalités *.
  • Extensibilité. L’ancien code aurait peut-être permis d’introduire facilement certaines fonctionnalités que Pecritenc suppose d’être bientôt ajoutées *.
  • Familiarité. L'ancien code peut avoir réutilisé des modèles utilisés à 5 autres endroits de la base de code de la société. Dans le même temps, le nouveau code utilise un modèle sophistiqué dont seulement la moitié de l'entreprise a déjà entendu parler à ce stade.
  • Rouge à lèvres sur un cochon. Pecritenc peut penser que l'ancien et le nouveau code sont des ordures, ou sont sans importance, rendant ainsi toute comparaison entre eux inutile.
  • Fierté. Pecritenc est peut-être l'auteur original du code et n'aime pas les personnes qui apportent des modifications massives à son code. Il pourrait même voir les améliorations comme une insulte légère, car elles impliquent qu'il aurait dû faire mieux.
Peter
la source
4
+1 pour 'Pecritenc', et un très beau résumé d'objections raisonnables qu'il convient de prendre en compte avant le préfactoring.
1
Et +1 pour "extensibilité" - je pensais que le code d'origine pouvait avoir des fonctions ou des classes destinées à être utilisées dans un projet futur, de sorte que les abstractions peuvent sembler redondantes ou inutiles, mais uniquement dans le contexte d'un programme unique.
Darren Ringer
En outre, le code en question peut ne pas être un code critique, il est donc considéré comme un gaspillage de ressources d'ingénierie pour le nettoyer.
Erik Eidt
@nocomprende Avez-vous utilisé le terme «praisonnable», préconçu ou préfactorisé? Méthode similaire à Pecritenc peut-être?
Milind R
@MilindR Probablement une préconception, une prédilection ou peut-être une préférence personnelle? Ou peut-être juste aucune raison du tout, une confluence cosmique de cofacteurs, confondant des conditions de conspiration. Aucune idée, vraiment. Et vous?
1

Le type de code qui convient le mieux peut dépendre de l’expertise des programmeurs ainsi que des outils qu’ils utilisent. Par exemple, voici pourquoi ce qui serait normalement considéré comme du code mal écrit peut être plus efficace dans certaines situations que du code bien écrit orienté objet qui utilise pleinement l’héritage:

(1) Certains programmeurs n'ont tout simplement pas une compréhension intuitive de la programmation orientée objet. Si votre métaphore pour un projet logiciel est un circuit électrique, vous vous attendez à beaucoup de duplication de code. Vous aimerez voir plus ou moins les mêmes méthodes dans de nombreuses classes. Ils vous feront sentir à la maison. Et un projet dans lequel vous devez rechercher des méthodes dans les classes de parents ou même dans les classes de grands-parents pour voir ce qui se passe peut sembler hostile. Vous ne voulez pas comprendre le fonctionnement de la classe parente, ni comprendre en quoi la classe actuelle est différente. Vous voulez comprendre directement le fonctionnement de la classe actuelle et vous trouvez que le fait que les informations soient réparties sur plusieurs fichiers est source de confusion.

De même, lorsque vous souhaitez simplement résoudre un problème spécifique dans une classe spécifique, vous n'aurez peut-être pas envie de vous demander s'il faut résoudre le problème directement dans la classe de base ou écraser la méthode de votre classe d'intérêt actuelle. (Sans héritage, vous n'auriez pas à prendre une décision consciente. Par défaut, les problèmes similaires dans des classes similaires sont ignorés jusqu'à ce qu'ils soient signalés comme des bogues.) Ce dernier aspect n'est pas vraiment un argument valable, bien qu'il puisse expliquer certaines des opposition.

(2) Certains programmeurs utilisent beaucoup le débogueur. Même si en général je suis moi-même fermement du côté de l'héritage de code et de la prévention de la duplication, je partage une partie de la frustration que j'ai décrite dans (1) lors du débogage de code orienté objet. Lorsque vous suivez l'exécution de code, il saute parfois entre les classes (ancêtres) même s'il reste dans le même objet. En outre, lorsque vous définissez un point d'arrêt dans un code bien écrit, il est plus susceptible de se déclencher lorsqu'il ne vous est pas utile. Vous devrez donc peut-être vous efforcer de le rendre conditionnel (si possible), ou même de le poursuivre manuellement plusieurs fois avant le déclencheur approprié.


la source
3
"cours de grands-parents"! Haw Haw! Faites juste attention aux classes d'Adam et Eve. (Et la classe de Dieu bien sûr) Avant cela, c'était sans formes et vide.
1

Cela dépend totalement. Je travaille sur un projet qui n'autorise pas les variables booléennes en tant que paramètres de fonction, mais nécessite à la place une enumoption dédiée pour chaque option.

Alors,

enum OPTION1 { OPTION1_OFF, OPTION1_ON };
enum OPTION2 { OPTION2_OFF, OPTION2_ON };

void doSomething(OPTION1, OPTION2);

est beaucoup plus verbeux que

void doSomething(bool, bool);

cependant,

doSomething(OPTION1_ON, OPTION2_OFF);

est beaucoup plus lisible que

doSomething(true, false);

Le compilateur devrait générer le même code pour les deux. Il n’ya donc rien à gagner en utilisant le formulaire le plus court.

Simon Richter
la source
0

Je dirais que la cohésion pourrait être un problème.

Par exemple, dans une application Web, disons que vous avez une page d'administration dans laquelle vous indexez tous les produits, qui est essentiellement le même code (index) que celui que vous utiliseriez dans une page d'accueil, pour .. simplement indexer les produits.

Si vous décidez de tout partialiser pour rester au sec et élégant, vous devrez ajouter de nombreuses conditions si l'utilisateur navigant est administrateur ou non, et encombrer le code de choses inutiles qui le rendront très illisible, disons un designer!

Donc, dans une situation comme celle-ci, même si le code est à peu près le même, juste parce qu’il peut évoluer et que les cas d’utilisation peuvent légèrement changer, il serait mauvais de s’attaquer à chacun d’eux en ajoutant des conditions et des if. Une bonne stratégie consisterait donc à abandonner le concept DRY et à diviser le code en parties gérables.

gâteau
la source
0
  • Quand moins de code ne fait pas le même travail que plus de code. Il est bon de refactoriser pour plus de simplicité, mais vous devez veiller à ne pas trop simplifier l’espace de problème rencontré par cette solution. 980 lignes de code pourraient traiter plus de cas de virage que 450.
  • Quand moins de code échoue moins bien que plus de code. J'ai déjà vu quelques tâches de "ref *** toring" effectuées sur du code afin de supprimer les tentatives "inutiles" d'interception et de traitement des erreurs. Le résultat inévitable était au lieu d'afficher une boîte de dialogue avec un beau message sur l'erreur et ce que l'utilisateur pouvait faire, l'application plantée ou YSODed.
  • Quand moins de code est moins maintenable / extensible que plus de code. Le refactoring pour la concision du code supprime souvent les constructions de code "inutiles" dans l’intérêt de la LoC. Le problème, c’est que ces constructions de code, telles que les déclarations d’interface parallèle, les méthodes / sous-classes extraites, etc., sont nécessaires si ce code doit jamais faire plus que ce qu’il fait actuellement, ou le faire différemment. À la limite, certaines solutions personnalisées en fonction du problème spécifique risquent de ne pas fonctionner du tout si la définition du problème change légèrement.

    Un exemple; vous avez une liste d'entiers. Chacun de ces entiers a une valeur en double dans la liste, sauf un. Votre algorithme doit trouver cette valeur non appariée. La solution générale consiste à comparer chaque nombre à tous les autres nombres jusqu'à ce que vous trouviez un nombre sans duplication dans la liste, ce qui correspond à une opération N ^ 2 fois. Vous pouvez également créer un histogramme à l'aide d'une table de hachage, mais c'est très inefficace. Cependant, vous pouvez le faire en temps linéaire et en espace constant en utilisant une opération XOR au niveau du bit; XOR chaque entier contre un "total" courant (commençant à zéro), et à la fin, la somme courante sera la valeur de votre entier non apparié. Très élégant. Jusqu'à ce que les exigences changent et que plusieurs numéros de la liste soient non appariés ou que les nombres entiers incluent zéro. Maintenant, votre programme retourne des résultats incompréhensibles ou ambigus (s'il renvoie zéro, cela signifie-t-il que tous les éléments sont appariés ou que l'élément non apparié est égal à zéro?). Tel est le problème des implémentations "intelligentes" dans la programmation en situation réelle.

  • Quand moins de code est moins auto-documenté que plus de code. Etre capable de lire le code lui-même et de déterminer ce qu'il fait est essentiel au développement de l'équipe. Donner un algorithme brain-f *** que vous avez écrit et qui fonctionne très bien pour un développeur junior et lui demander de le modifier pour modifier légèrement la sortie ne vous mènera pas très loin. Beaucoup de développeurs expérimentés auraient également des problèmes avec cette situation. Pouvoir comprendre à tout moment ce que fait le code et ce qui pourrait mal tourner est la clé d'un environnement de développement d'équipe (et même en solo; je vous garantis que l'éclair de génie que vous aviez lorsque vous avez écrit 5 méthode en ligne pour guérir le cancer va disparaître depuis longtemps lorsque vous reviendrez à cette fonction en cherchant à le guérir aussi.)
KeithS
la source
0

Le code informatique doit faire un certain nombre de choses. Un code "minimaliste" qui ne fait pas ces choses n'est pas un bon code.

Par exemple, un programme informatique devrait couvrir tout ce qui est possible (ou au minimum, tous les cas probables). Si un morceau de code ne couvre que le "cas de base" et en ignore les autres, ce n'est pas un bon code, même s'il est bref.

Le code informatique devrait être "évolutif". Un code crypté peut ne fonctionner que pour une seule application spécialisée, alors qu'un programme plus long mais plus ouvert peut faciliter l'ajout de nouvelles applications.

Le code informatique devrait être clair. Comme un autre intervenant l'a démontré, il est possible pour un codeur à noyau dur de produire une fonction de type "algorithmique" sur une ligne qui effectue le travail. Mais le one-liner a dû être divisé en cinq "phrases" différentes avant que cela soit clair pour le programmeur moyen.

Tom Au
la source
Le devoir est dans l'oeil du spectateur.
-2

Performances informatiques. Lors de l'optimisation parallèle ou parallèle de certaines parties de votre code, il peut s'avérer avantageux, par exemple, de ne pas créer de boucles de 1 à 400, mais de 1 à 50 et de placer 8 instances de code similaire dans chaque boucle. Je ne présume pas que ce soit le cas dans votre cas, mais c’est un exemple où plus de lignes sont meilleures (performances).

Hans Janssen
la source
4
Un bon compilateur devrait savoir mieux qu’un programmeur moyen comment dérouler des boucles pour une architecture informatique spécifique, mais le point général est valable. Une fois, j'ai examiné le code source d'une routine de multiplication de matrice à partir d'une bibliothèque haute performance Cray. La matrice est multipliée par trois boucles imbriquées et environ 6 lignes de code au total, non? Wrong - cette routine de bibliothèque comportait environ 1100 lignes de code, plus un nombre similaire de lignes de commentaires expliquant pourquoi il était si long!
alephzero
1
@alephzero wow, j'aimerais voir ce code, il doit être juste Cray Cray.
@alephzero, les bons compilateurs peuvent faire beaucoup, mais malheureusement pas tout. Le bon côté des choses, c’est que ce sont ces choses qui rendent la programmation intéressante!
Hans Janssen
2
@alephzero En effet, un bon code de multiplication de matrice ne fait pas que gagner un peu de temps (c'est-à-dire le réduire d'un facteur constant), il utilise un algorithme totalement différent avec une complexité asymptotique différente, par exemple l'algorithme de Strassen est approximativement O (n ^ 2,8) plutôt que O (n ^ 3).
Arthur Tacca