Nettoyer le code lisible vs rapide difficile à lire le code. Quand traverser la ligne?


67

Lorsque j'écris du code, j'essaie toujours de rendre mon code aussi propre et lisible que possible.

De temps en temps, il arrive un moment où vous devez franchir la ligne et passer d'un code propre et agréable à un code un peu plus laid pour le rendre plus rapide.

Quand est-il possible de franchir cette ligne?


69
Vous avez répondu à votre propre question, vous franchissez la ligne quand vous devez franchir la ligne
gnibbler

6
En outre, votre "code sale" fonctionnera peut-être aussi rapidement que le "code vierge" sur le matériel informatique dans 6 mois. N'exagérez pas comme Windows, cependant. :)
Mateen Ulhaq

21
Il existe une différence significative entre un algorithme difficile à comprendre et un code difficile à comprendre. Parfois, l'algorithme que vous devez implémenter est compliqué et le code sera nécessairement déroutant, simplement parce qu'il exprime une idée complexe. Mais si le code lui-même est le point difficile, alors le code doit être corrigé.
Tylerl

8
Dans de nombreux cas, un compilateur / interprète intelligent peut optimiser un code propre et lisible afin d’avoir les mêmes performances que du code "moche". Donc, il y a peu d'excuse, sauf indication contraire du profilage.
Dan Diplo

1
En ce qui concerne les compilateurs de nos jours, votre code laid sera probablement le même que votre code propre (en supposant que vous ne faites pas de choses vraiment bizarres). Surtout dans .NET, ce n'est pas comme les journées C ++ / MFC où la définition de vos variables aura un impact sur les performances. Ecrire un code maintenable. certains codes finiront par être complexes, mais cela ne veut pas dire que c'est moche.
DustinDavis

Réponses:


118

Vous franchissez la ligne quand

  • Vous avez mesuré que votre code est trop lent pour l'usage auquel il est destiné .
  • Vous avez essayé d’autres améliorations qui ne nécessitent pas d’abandonner le code.

Voici un exemple concret: un système expérimental que je suis en train de produire produisait des données trop lentement, prenant plus de 9 heures par cycle et utilisant seulement 40% du processeur. Plutôt que de trop gâcher le code, j'ai déplacé tous les fichiers temporaires vers un système de fichiers en mémoire. Ajout de 8 nouvelles lignes de code non moche, et l’utilisation du processeur est supérieure à 98%. Problème résolu; aucune laideur requise.


2
Vous vous assurez également de conserver le code original, plus lent et plus propre, à la fois en tant qu'implémentation de référence et sur laquelle vous pouvez vous appuyer lorsque le matériel change et que votre code plus rapide et plus hackier ne fonctionne plus.
Paul R

4
@ PaulR Comment conservez-vous ce code? Sous forme de commentaires? C'est faux, imo - les commentaires sont obsolètes, personne ne les lit. Personnellement, si je vois du code commenté, je le supprime généralement - c'est à cela que sert le contrôle de source. Un commentaire sur une méthode expliquant ce qu’il fait est préférable, imo.
Evgeni

5
@ Eugène: Je garde généralement la version originale d'une routine nommée fooet la renomme foo_ref- généralement, elle habite immédiatement au-dessus foodans le fichier source. Dans mon harnais de test que j'appelle fooet foo_refpour la validation et la mesure de la performance relative.
Paul R

5
Si vous faites cela, @Paul pourrait être une bonne idée de rater le test si la version optimisée est toujours plus lente que la fonction ref. Cela peut arriver si les hypothèses que vous avez avancées pour accélérer le processus ne sont plus vraies.
user1852503

58

C'est une fausse dichotomie. Vous pouvez rendre le code rapide et facile à maintenir.

Vous le faites en écriture propre, en particulier avec une structure de données aussi simple que possible.

Ensuite, vous découvrez où se trouvent les drains temporels (en les exécutant, après les avoir écrits, pas avant), et vous les corrigez un par un. (Voici un exemple.)

Ajouté: Nous entendons toujours parler de compromis, n'est-ce pas, comme un compromis entre temps et mémoire, ou un compromis entre vitesse et facilité de maintenance? Bien que de telles courbes puissent bien exister, il ne faut pas supposer qu'un programme donné est sur la courbe , ni même à proximité.

Tout programme qui se trouve sur la courbe peut facilement (en le confiant à un certain type de programmeur) être rendu beaucoup plus lent et beaucoup moins gérable, et il sera alors loin de la courbe. Un tel programme a alors beaucoup de place pour être rendu plus rapide et plus facile à gérer.

D'après mon expérience, de nombreux programmes ont vu le jour.


Je suis d'accord. En effet, le code rapide qui n'est pas propre va devenir plus lent, car vous ne pouvez pas le modifier correctement.
EdA-Qa mort-ora-y

22
Je ne suis pas d' accord pour dire qu'il s'agit d'une fausse dichotomie. IMO il existe des scénarios en particulier dans le code de la bibliothèque (pas tellement dans le code de l' application ) où la division est très réelle. Voir ma réponse pour plus.
Marc Gravell

1
Marc, vous pouvez créer un lien vers les réponses dans les commentaires avec l’URL "link". programmers.stackexchange.com/questions/89620/…

Nous avons tous trouvé des moments où nous devions essayer de rendre le code plus rapide. Mais après avoir expérimenté avec le profileur pour trouver la meilleure solution (si le code devenait moche), cela ne veut pas dire que le code doit rester laid. Il s’agit de trouver la meilleure solution qui, au premier abord, ne semble pas évidente mais qui, une fois trouvée, peut être codée proprement. Ainsi, je crois que c’est une fausse dichotomie et une excuse pour ne pas ranger votre chambre après vous être amusés avec vos jouets. Je dis sucer et ranger votre chambre.
Martin York

Je ne suis pas d'accord que c'est une fausse dichotomie. Parce que je fais beaucoup de travail graphique, l'exemple le plus évident à mes yeux est dans les boucles graphiques serrées: je ne sais pas à quelle fréquence, cela est toujours fait, mais il était courant que les moteurs de jeu écrits en C utilisent Assembly pour le rendu principal. des boucles afin de faire sortir la dernière goutte de vitesse. Cela me fait aussi penser à des situations où vous programmez en Python mais utilisez des modules écrits en C ++. "Difficile à lire" est toujours relatif; chaque fois que vous passez à un langage de niveau inférieur pour la vitesse, ce code est plus difficile à lire que les autres.
Jhocking

31

Dans mon existence de logiciel libre, je fais beaucoup de travail de bibliothèque axé sur la performance, qui est profondément lié à la structure de données de l' appelant (c'est-à-dire externe à la bibliothèque), sans (par définition) aucun mandat sur les types entrants. Ici, le meilleur moyen de rendre cette performance performante est la méta-programmation, qui (depuis que je suis en .NET-land) signifie IL-emit. C’est un code moche, moche, mais très rapide.

De cette façon, j’accepte volontiers que le code de bibliothèque puisse être "plus laid" que le code d’ application , simplement parce qu’il a moins (ou peut-être pas) de contrôle sur les entrées , il doit donc exécuter certaines tâches à travers différents mécanismes. Ou comme je l'ai dit l'autre jour:

"coder sur la falaise de la folie, pour que vous n'ayez pas à le faire "

Maintenant , le code des applications est légèrement différent, car c’est là que les développeurs «normaux» (raisonnables) investissent généralement une grande partie de leur temps collaboratif / professionnel; les objectifs et les attentes de chacun sont (IMO) légèrement différents.

OMI, les réponses ci-dessus suggérant qu'il peut être rapide et facile à gérer font référence à du code d' application dans lequel le développeur a plus de contrôle sur les structures de données et n'utilise pas d'outils comme la méta-programmation. Cela dit, il existe différentes façons de faire de la méta-programmation, avec différents niveaux de folie et différents niveaux de frais généraux. Même dans ce domaine, vous devez choisir le niveau d'abstraction approprié. Mais lorsque vous souhaitez activement, positivement et sincèrement, gérer de manière inattendue les données inattendues . ça peut devenir moche. Traiter avec elle


4
Ce n'est pas parce que le code est laid que ça doit être intolérable. Les commentaires et l'indentation sont gratuits, et un code laid peut généralement être encapsulé dans une entité gérable (classe, module, package, fonction, selon la langue). Le code peut être tout aussi moche, mais au moins, les gens seront en mesure de juger de l’impact des changements qu’ils sont sur le point d’apporter.
tdammers

6
@tdammers en effet, et j'essaie de le faire autant que possible; mais c'est un peu comme mettre du rouge à lèvres sur un cochon.
Marc Gravell

1
Eh bien, peut-être faudrait-il faire une distinction entre la syntaxe laide et les algorithmes laids - des algorithmes laids sont parfois nécessaires, mais la syntaxe laide est généralement inexcusable à l’OMI.
tdammers

4
La syntaxe moche de @IMO est assez inévitable si vous faites par nature plusieurs niveaux d'abstraction sous le niveau de langage habituel.
Marc Gravell

1
@marc ... c'est intéressant. Ma première réaction vis-à-vis du fait que le méta / abstrait soit laid est la suspicion que le langage / la plate-forme spécifique ne soit pas propice au méta-codage plutôt qu’une loi sous-jacente liant les deux. Ce qui m'a fait croire que c'était l'exemple des niveaux de métaux progressifs en mathématiques aboutissant à une théorie des ensembles dont l'expression est à peine supérieure à l'algèbre ou même à l'arithématique concrète. Mais alors, la notation d'ensemble est probablement une langue totalement différente et chaque niveau d'abstration situé au-dessous a sa propre langue ....
explorest

26

Lorsque vous avez profilé le code et vérifié qu'il provoque effectivement un ralentissement important.


3
Et qu'est-ce qui est "significatif"?
Rook

2
@ hotpaw2: c'est la réponse intelligente - cela suppose que les développeurs sont au moins quelque peu compétitifs. Sinon, utiliser quelque chose de plus rapide que le tri à bulles est (généralement) une bonne idée. Mais trop souvent, quelqu'un va (pour continuer à trier) échanger un quicksort contre une amplitude minime pour une différence de 1%, seulement pour voir quelqu'un d'autre le remplacer six mois plus tard pour la même raison.

1
Il n'y a jamais de raison de créer un code non propre. Si vous ne pouvez pas rendre votre code efficace propre et facile à gérer, vous faites quelque chose de mal.
EdA-Qa mort-ora-y

2
@SF. - le client le trouvera toujours trop lent s'il peut être plus rapide. Il ne se soucie pas de "nettoyer" du code.
Rook

1
@Rook: le client peut trouver le code d'interface (trivial) trop lent. Quelques astuces psychologiques assez simples améliorent l'expérience utilisateur sans accélérer le code - reporter les événements de bouton dans une routine en arrière-plan au lieu d'exécuter les actions à la volée, afficher une barre de progression ou quelque chose du genre, poser des questions insignifiantes pendant que l'activité est effectuée en arrière-plan ... c'est quand cela ne suffit pas, vous pouvez envisager une optimisation réelle.
SF.

13

Un code propre n'est pas nécessairement exclusif avec un code à exécution rapide. Le code normalement difficile à lire était écrit parce qu'il était plus rapide à écrire, pas parce qu'il s'exécutait plus rapidement.

Écrire du code "sale" pour tenter de l'accélérer est sans doute peu judicieux, car vous ne savez pas avec certitude que vos modifications améliorent réellement quelque chose. Knuth le dit mieux:

"Nous devrions oublier les petites efficacités, disons environ 97% du temps: l' optimisation prématurée est la racine de tous les maux . Cependant, nous ne devrions pas laisser passer nos opportunités dans ce seuil critique de 3%. raisonnement, il sera sage de regarder attentivement le code critique, mais seulement après que ce code a été identifié . "

En d'autres termes, écrivez le code propre en premier. Ensuite, profilez le programme résultant et voyez si ce segment est en fait un goulot d'étranglement en termes de performances. Si tel est le cas, optimisez la section si nécessaire et veillez à inclure de nombreux commentaires dans la documentation (y compris éventuellement le code d'origine) pour expliquer les optimisations. Puis profilez le résultat pour vérifier que vous avez réellement apporté une amélioration.


10

Puisque la question dit "vite difficile à lire le code", la réponse simple n'est jamais. Il n’ya jamais d’excuse pour écrire du code difficile à lire. Pourquoi? Deux raisons.

  1. Que se passe-t-il si vous êtes frappé par un bus en rentrant chez vous ce soir? Ou (plus optimiste, et plus généralement) retiré de ce projet et réaffecté à autre chose? Le petit avantage que vous imaginez avoir avec votre fouillis de code est totalement contrebalancé par le fait que personne d'autre ne peut le comprendre . Le risque que cela pose pour les projets logiciels est difficile à surestimer. J'ai travaillé une fois avec un PBX majeurfabricant (si vous travaillez dans un bureau, vous avez probablement un de leurs téléphones sur votre bureau). Leur chef de projet m'a dit un jour que leur produit principal - le logiciel propriétaire transformant une machine Linux standard en un échange téléphonique complet - était connu dans l'entreprise comme "le blob". Personne ne l'a plus compris. Chaque fois, ils ont mis en œuvre une nouvelle fonctionnalité. ils avaient appuyé sur Compile, puis se tenaient en retrait, fermaient les yeux, comptaient jusqu'à vingt, puis jetaient un coup d'œil à travers leurs doigts pour voir si cela fonctionnait. Aucune entreprise n’a besoin d’un produit de base qu’elle ne contrôle plus, mais c’est un scénario terriblement courant.
  2. Mais j'ai besoin d'optimiser! OK, vous avez donc suivi tous les excellents conseils formulés dans les autres réponses à cette question: votre code échoue dans ses cas de test de performance, vous l’avez bien profilé, identifié les goulots d’étranglement, proposé une solution ... et il en va de même. impliquer un peu de tournoiement . Bien: maintenant allez-y et optimisez. Mais voici le secret (et vous voudrez peut-être vous asseoir pour celui-ci): optimisation et réduction de la taille du code source ne sont pas la même chose. Les commentaires, les espaces, les parenthèses et les noms de variables significatifs sont des aides énormes à la lisibilité qui ne vous coûtent absolument rien car le compilateur les jettera. (Ou si vous écrivez un langage non compilé comme JavaScript - et oui, il existe des raisons très valables d'optimiser JavaScript - ils peuvent être traités par un compresseur .) De longues lignes de code minimaliste et étroit (comme celui que muntoo a posté ici ) n’a rien à voir avec l’optimisation: c’est un programmeur qui essaie de montrer à quel point ils sont intelligents en incorporant le plus de code possible dans le moins de caractères possible. Ce n'est pas intelligent, c'est stupide. Un programmeur vraiment intelligent est celui qui peut communiquer clairement ses idées aux autres.

2
Je ne peux pas accepter que la réponse est "jamais". Certains algorithmes sont par nature très difficiles à comprendre et / ou à mettre en œuvre efficacement. La lecture du code, quel que soit le nombre de commentaires, peut être un processus très difficile.
Rex Kerr

4

Quand c'est du code jetable. Je veux dire cela littéralement: quand vous écrivez un script pour effectuer un calcul ou une tâche ponctuelle et que vous savez avec une telle certitude que vous ne devrez plus jamais faire cette action, vous pouvez «rm le fichier source» sans hésiter, alors vous pouvez choisir la route laide.

Sinon, c'est une fausse dichotomie - si vous pensez que vous devez faire en sorte que vous ne le fassiez pas pour le faire plus vite, vous le faites mal. (Ou vos principes sur ce qui est un bon code doivent être révisés. Utiliser goto est en fait assez élégant quand c'est la solution appropriée au problème. Cependant, c'est rarement le cas.)


5
Il n'y a pas de code jetable. Si j'avais un centime à chaque fois que "le code à jeter" était mis en production parce que "ça marche, on n'a pas le temps de le réécrire", je serais millionnaire. Chaque ligne de code que vous écrivez doit être écrite de manière à ce qu'un autre programmeur compétent puisse le récupérer demain après vous être frappé par la foudre ce soir. Sinon, ne l'écris pas.
Mark Whitaker

Je ne suis pas d'accord pour dire qu'il s'agit d'une fausse dichotomie. IMO il existe des scénarios en particulier dans le code de la bibliothèque (pas tellement dans le code de l'application) où la division est très réelle. Voir ma réponse pour plus.
Marc Gravell

@ mark, si le "autre programmeur compétent" est vraiment compétent, le code de mise au

@ Mark - Facile. Il suffit d'écrire le code jetable pour qu'il échoue à tous les tests de production, peut-être d'une manière irréversible.
hotpaw2

@ Mark, si votre «code à jeter» passe en production, il ne s'agit pas d' un code à jeter. Notez que dans ma réponse, j’ai pris le temps de préciser que je parle de code qui est littéralement à jeter: c’est-à-dire, supprimer après la première utilisation. Sinon, je suis d’accord avec votre sentiment et je le répète dans ma réponse.
Maaku

3

Chaque fois que le coût estimé d'une performance inférieure sur le marché est supérieur au coût estimé de la maintenance du code pour le module de code en question.

Les gens font toujours SSE / NEON / etc. Assemblée pour essayer de battre les logiciels de certains concurrents sur la puce à processeur populaire de cette année.


Un bon point de vue commercial, les programmeurs doivent parfois aller au-delà du purement technique.
this.josh

3

N'oubliez pas que vous pouvez rendre le code difficile à lire facile à comprendre grâce à une documentation appropriée et à des commentaires.

En général, profilez après avoir écrit un code facile à lire qui remplit la fonction souhaitée. Les goulots d'étranglement peuvent vous obliger à faire quelque chose qui rend les choses plus compliquées, mais vous corrigez cela en vous expliquant.


0

Pour moi, c'est une proportion de stabilité (comme dans le béton, l'argile cuite au four, gravée dans la pierre, écrite à l'encre permanente). Plus votre code est instable, plus il est probable que vous deviez le changer à l'avenir, plus il est pliable, comme l'argile humide, pour rester productif. J'insiste également sur la souplesse et non sur la lisibilité. Pour moi, la facilité de changement de code est plus importante que la facilité de lecture. Le code peut être facile à lire et un cauchemar à changer, et à quoi sert-il de pouvoir lire et comprendre facilement les détails de la mise en œuvre s'il s'agit d'un cauchemar à changer? Sauf s'il s'agit simplement d'un exercice académique, le point essentiel pour pouvoir comprendre facilement le code dans une base de code de production consiste à pouvoir le modifier plus facilement au besoin. S'il est difficile de changer, alors, une bonne partie des avantages de la lisibilité disparaissent. La lisibilité n’est généralement utile que dans le contexte de la souplesse et celle-ci n’est utile que dans le contexte de l’instabilité.

Naturellement, même le code le plus difficile à gérer que l'on puisse imaginer, qu'il soit facile ou difficile à lire, ne pose pas de problème s'il n'y a jamais de raison de le modifier, mais de l'utiliser. Et il est possible d'obtenir une telle qualité, en particulier pour le code système de bas niveau où les performances tendent souvent à compter le plus. J'ai le code C que j'utilise toujours régulièrement et qui n'a pas changé depuis la fin des années 80. Il n'a pas eu besoin de changer depuis. Le code est fugly, écrit dans les jours difficiles, et je le comprends à peine. Pourtant, il est toujours applicable aujourd'hui, et je n'ai pas besoin de comprendre son implémentation pour en tirer le meilleur parti.

Bien écrire des tests est un moyen d’améliorer la stabilité. Un autre est le découplage. Si votre code ne dépend pas d’autre chose, sa seule raison de changer est si elle-même doit changer. Parfois, une petite quantité de duplication de code peut servir de mécanisme de découplage pour améliorer considérablement la stabilité, ce qui en fait un bon compromis si, en échange, vous obtenez un code qui est maintenant complètement indépendant de tout le reste. Maintenant, ce code est invulnérable aux changements du monde extérieur. En attendant, le code qui dépend de 10 bibliothèques externes différentes a 10 fois plus de raisons de changer à l’avenir.

Une autre chose utile dans la pratique est de séparer votre bibliothèque des parties instables de votre base de code, voire même de la construire séparément, comme vous le feriez pour des bibliothèques tierces (qui sont également destinées à être simplement utilisées, pas modifiées, du moins pas par votre équipe). Seulement ce type d'organisation peut empêcher les gens de le manipuler.

Un autre est le minimalisme. Moins votre code essaie de faire, plus il est probable qu'il puisse faire ce qu'il fait bien. Les conceptions monolithiques sont presque définitivement instables, car plus elles ajoutent de fonctionnalités, plus elles semblent incomplètes.

La stabilité doit être votre objectif principal lorsque vous écrivez un code qui, inévitablement, sera difficile à modifier, comme le code SIMD parallélisé qui a été mis au point jusqu'à la mort. Vous surmontez la difficulté de maintenir le code en maximisant la probabilité que vous n'ayez pas à le modifier, et donc qu'il ne soit pas obligé de le conserver à l'avenir. Cela ramène les coûts de maintenance à zéro, quelle que soit la difficulté du code à maintenir.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.