web-dev-qa-db-fra.com

Dans quels cas moins de code n'est-il pas meilleur?

J'ai refactorisé du code au travail ces derniers temps et je pensais avoir fait du bon travail. J'ai laissé tomber 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 mieux"

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 dans une seule méthode pour enregistrer quelques lignes, mais ce n'est pas ce que j'ai fait. Le code est à mon avis bien structuré et plus simple à comprendre/à entretenir car il fait la moitié de sa taille.

J'ai du mal à voir pourquoi quelqu'un voudrait travailler avec le double du code nécessaire pour faire un travail, et je me demande si quelqu'un ressent la même chose que mes collègues et peut faire de bons arguments pour avoir plus de code sur moins ?

55
PiersyP

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

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

Il existe de nombreux attributs qui déterminent la qualité de votre code. Certains sont simplement calculés, comme Complexité cyclomatique , et Complexité Halstead . D'autres sont définis de manière plus lâche, tels que cohésion , lisibilité, compréhensibilité, extensibilité, robustesse, exactitude, auto-documentation, propreté, testabilité et bien d'autres.

Il se pourrait, par exemple, que si vous réduisiez la longueur totale du code - vous introduisiez une complexité supplémentaire injustifiée et rendiez le code plus cryptique.

Diviser un long morceau de code en petites méthodes pourrait être aussi dangereux que bénéfique .

Demandez à vos collègues de vous fournir des commentaires spécifiques sur les raisons pour lesquelles ils pensent que vos efforts de refactoring ont produit un résultat indésirable.

122
M.A. Hanin

Fait intéressant, un collègue et moi sommes actuellement au milieu d'un refactor qui va augmenter le nombre de classes et de fonctions d'un peu moins du double, bien que les lignes de code restent à peu près les mêmes. Il m'arrive donc d'avoir un bon exemple.

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

Ce n'est pas la taille du code qui dérange 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-la selon ses propres mérites plutôt que simplement en comparaison. Parfois, lorsque je refaçonne longtemps, je perds en quelque sorte l'objectif initial et je vais trop loin. Jetez un regard critique sur la situation et remettez-le sur la bonne voie, peut-être avec l'aide d'un programmeur de paires dont vous appréciez les conseils.

35
Karl Bielefeldt

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

Faites tout aussi simple que possible, mais pas plus simple.

Lorsque vous allez trop loin pour réduire les choses, cela peut rendre le code plus difficile à lire. Comme "facile/difficile à lire" peut être un terme très subjectif, je vais expliquer exactement ce que je veux dire par là: une mesure du degré de difficulté qu'un développeur qualifié aura à déterminer "que fait ce code? 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 pointent souvent vers certains éléments syntaxiques et disent de manière dérisoire "ils sont juste là pour faciliter le travail du compilateur." C'est plus ou moins true, à l'exception de la partie "juste". Plus les informations sont explicites, plus le code est facile à lire et à comprendre, non seulement par un compilateur, mais aussi par un être humain.

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

N'oubliez pas que les programmes doivent être écrits pour que les gens puissent les lire, et accessoirement que les machines s'exécutent. (Ironiquement, cette citation provient d'un manuel consacré à une langue tristement célèbre pour être extrêmement difficile à lire!) C'est une bonne idée de supprimer les choses redondantes, mais ne supprimez pas le code qui facilite la tâche de vos compagnons humains. comprendre ce qui se passe, même si ce n'est pas strictement nécessaire pour le programme en cours d'écriture.

17
Mason Wheeler

Un code plus long peut être plus facile à lire. C'est généralement le contraire, mais il existe de nombreuses exceptions - certaines d'entre elles étant 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 qualifiés qui voient les 2 morceaux de code sans avoir de connaissances supplémentaires sur la culture, la base de code ou la feuille de route de l'entreprise. Même alors, il existe de nombreuses raisons de s'opposer au nouveau code. Par souci de concision, j'appellerai "Les gens critiquant le nouveau code" Pecritenc:

  • La stabilité. Si l'ancien code était connu pour être 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, les tests appropriés ne sont pas disponibles, le changement est 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/évolutivité. L'ancien code a peut-être mieux évolué, et Pecritenc suppose que les performances deviendront un problème à mesure que les clients et les fonctionnalités s'accumuleront bientôt.
  • Extensibilité. L'ancien code aurait pu permettre une introduction facile de certaines fonctionnalités que Pecritenc suppose être ajoutées bientôt *.
  • Familiarité. L'ancien code peut avoir des modèles réutilisés qui sont utilisés à 5 autres endroits de la base de code de l'entreprise. Dans le même temps, le nouveau code utilise un modèle sophistiqué dont seule la moitié de l'entreprise n'a jamais entendu parler à ce stade.
  • Rouge à lèvres sur un cochon. Pecritenc peut penser que l'ancien et le nouveau code sont des déchets ou non pertinents, rendant ainsi toute comparaison entre eux inutile.
  • Fierté. Pecritenc est peut-être l'auteur original du code et n'aime pas que les gens apportent des modifications massives à son code. Il pourrait même voir les améliorations comme une légère insulte, car elles impliquent qu'il aurait dû faire mieux.
14
Peter

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

Donc,

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 doit générer le même code pour les deux, il n'y a donc rien à gagner en utilisant le formulaire plus court.

1
Simon Richter

Quel type de code est préférable peut dépendre de l'expertise des programmeurs et également 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 qu'un code orienté objet bien écrit 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 d'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 vous sentir chez vous. Et un projet où vous devez rechercher des méthodes dans les classes parentales ou même dans les classes de grands-parents pour voir ce qui se passe peut sembler hostile. Vous ne voulez pas comprendre comment fonctionne la classe parente, puis comprendre en quoi la classe actuelle diffère. Vous voulez comprendre directement comment fonctionne la classe actuelle et vous trouvez que les informations sont réparties sur plusieurs fichiers confus.

En outre, lorsque vous souhaitez simplement résoudre un problème spécifique dans une classe spécifique, vous pouvez ne pas aimer avoir à penser s'il faut résoudre le problème directement dans la classe de base ou remplacer la méthode dans votre classe d'intérêt actuelle. (Sans héritage, vous n'auriez pas à prendre une décision consciente. La valeur par défaut est d'ignorer simplement des problèmes similaires dans des classes similaires 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 certains 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 du code orienté objet. Lorsque vous suivez l'exécution de code, il continue parfois de sauter entre les classes (ancêtres) même s'il reste dans le même objet. De plus, lors de la définition d'un point d'arrêt dans un code bien écrit, il est plus susceptible de se déclencher lorsqu'il n'est pas utile, vous devrez donc peut-être consacrer des efforts à le rendre conditionnel (si possible), ou même à continuer manuellement plusieurs fois avant le déclencheur concerné.

1
user144228
  • Quand moins de code ne fait pas le même travail que plus de code. Refactoriser pour la simplicité est une bonne chose, mais vous devez faire attention à ne pas simplifier trop l'espace de problème que cette solution rencontre. 980 lignes de code pourraient gérer plus de cas d'angle que 450.
  • Quand moins de code n'échoue pas aussi gracieusement que plus de code. J'ai vu quelques travaux de "ref *** toring" effectués sur du code pour supprimer les tentatives de capture "inutiles" et autres cas d'erreur. Le résultat inévitable était au lieu d'afficher une boîte de dialogue avec un joli message sur l'erreur et ce que l'utilisateur pouvait faire, l'application s'est bloqué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 LoC. Le problème est que ces constructions de code, comme les déclarations d'interface parallèle, les méthodes/sous-classes extraites, etc. sont nécessaires si ce code a besoin de faire plus qu'il ne le fait actuellement, ou de le faire différemment. À l'extrême, certaines solutions adaptées au problème spécifique peuvent ne pas fonctionner du tout si la définition du problème change un peu.

    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 jusqu'à ce que vous trouviez un nombre qui n'a pas de dupe dans la liste, qui est 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 peu d'espace. Cependant, vous pouvez le rendre linéaire et à espace constant en utilisant une opération au niveau du bit XOR; XOR chaque entier par rapport à un "total" en cours d'exécution (en commençant par zéro), et à la fin, la somme cumulée sera la valeur de votre entier non apparié. Très élégant. Jusqu'à ce que les exigences changent et que plusieurs nombres de la liste puissent être non appariés, ou que les entiers incluent zéro. Maintenant, votre programme soit renvoie des ordures ou des résultats 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 nul?). Tel est le problème des implémentations "intelligentes" dans la programmation du monde réel.

  • Quand moins de code est moins auto-documenté que plus de code. Être 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 qui fonctionne magnifiquement à 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 seniors auraient également des problèmes avec cette situation. Être capable de 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'éclat de génie que vous avez eu lorsque vous avez écrit un 5 La méthode en ligne pour guérir le cancer sera révolue depuis longtemps lorsque vous reviendrez à cette fonction pour la guérir également de la maladie de Parkinson.)
0
KeithS

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 tous les possibles (ou au strict minimum, tous les cas probables). Si un morceau de code ne couvre que le "cas de base" et en ignore d'autres, ce n'est pas du bon code, même s'il est bref.

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

Le code informatique doit être clair. Comme un autre répondeur l'a démontré, il est possible pour un codeur à noyau dur de produire une fonction de type "algorithmique" d'une seule ligne qui fait le travail. Mais le one-liner devait être divisé en cinq "phrases" différentes avant qu'il ne soit clair pour le programmeur moyen.

0
Tom Au

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 vous utiliseriez dans une situation de page d'accueil, pour .. simplement indexer les produits.

Si vous décidez de tout partialiser afin de pouvoir rester DRY et élégant, vous devrez ajouter de nombreuses conditions pour savoir si la navigation de l'utilisateur est un administrateur ou non et encombrer le code avec des trucs inutiles ce qui le rendra très illisible, disons d'un designer!

Donc, dans une situation comme celle-ci, même si le code est à peu près le même, simplement parce qu'il pourrait évoluer vers autre chose et que les cas d'utilisation pourraient légèrement changer, il serait mauvais de poursuivre chacun d'eux en ajoutant des conditions et des if. Une bonne stratégie serait donc d'abandonner le concept DRY et de diviser le code en parties maintenables.

0
frcake