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 du code propre de Nice au code légèrement plus laid pour le rendre plus rapide.
Quand est-ce OK de franchir cette ligne?
Vous franchissez la ligne lorsque
Voici un exemple concret: un système expérimental que je dirige produisait des données trop lentement, prenant plus de 9 heures par exécution et n'utilisant que 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 laid, et maintenant l'utilisation du CPU est supérieure à 98%. Problème résolu; aucune laideur requise.
C'est une fausse dichotomie. Vous pouvez rendre le code rapide et facile à maintenir.
La façon dont vous le faites est de l'écrire propre, en particulier avec une structure de données aussi simple que possible.
Ensuite, vous découvrez où sont les vidanges de temps (en l'exécutant, après vous l'avez écrit, pas avant), et les corrigez un par un. (Voici un exemple.)
Ajouté: Nous entendons toujours parler de compromis, n'est-ce pas, comme un compromis entre le temps et la mémoire, ou un compromis entre la vitesse et la maintenabilité? Bien que de telles courbes puissent bien exister, il ne faut pas supposer qu'un programme donné est sur la courbe, ni même n'importe où près de lui.
Tout programme qui est sur la courbe peut facilement (en le donnant à un certain type de programmeur) être rendu à la fois beaucoup plus lent et beaucoup moins maintenable, et il sera alors loin de la courbe. Un tel programme a alors beaucoup de place pour être rendu plus rapide et plus facile à maintenir.
D'après mon expérience, c'est là que de nombreux programmes commencent.
Dans mon existence OSS, je fais beaucoup de travail de bibliothèque visant à la performance, qui est profondément lié à la structure de données appelant (c'est-à-dire externe à la bibliothèque), sans (par conception) aucun mandat sur les types entrants. Ici, la meilleure façon de rendre cela performant est la méta-programmation, qui (puisque je suis en .NET-land) signifie IL-emit. C'est du code moche et moche, mais très rapide.
De cette façon, j'accepte avec plaisir bibliothèque le code peut être "plus laid" que application code, simplement parce que il a moins ( ou peut-être pas) de contrôle sur les entrées, doit donc réaliser certaines tâches à travers différents mécanismes. Ou comme je l'ai exprimé l'autre jour:
"coder sur la falaise de la folie, pour que vous n'ayez pas à le faire"
Maintenant application le code est légèrement différent, car c'est là que les développeurs "réguliers" (sensés) investissent généralement une grande partie de leur temps collaboratif/professionnel; les objectifs et les attentes de chacun sont (OMI) légèrement différents.
OMI, les réponses ci-dessus qui suggèrent qu'il peut être rapide et facile à entretenir font référence à application code où 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 quand vous voulez activement, positivement, vraiment qu'il gère données inattendues de la manière la plus rapide; ça peut devenir moche. Traitez-le; p
Lorsque vous avez profilé le code et vérifié qu'il provoque réellement un ralentissement important.
Le code propre n'est pas nécessairement exclusif avec le code à exécution rapide. Le code normalement difficile à lire a été écrit parce qu'il était plus rapide à écrire, pas parce qu'il s'exécute plus rapidement.
Il est sans doute imprudent d'écrire du code "sale" pour tenter de le rendre plus rapide, car vous ne savez pas avec certitude que vos modifications améliorent réellement quoi que ce soit. Knuth le dit le mieux:
"Nous devons oublier les petites efficacités, disons environ 97% du temps: l'optimisation prématurée est la racine de tout mal. Pourtant, nous ne devons pas laisser passer nos opportunités dans ces 3% critiques. Un bon programmeur ne pas être bercé de complaisance par un tel 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 d'abord le code propre. Ensuite, profilez le programme résultant et voyez si ce segment est en fait un goulot d'étranglement des performances. Si tel est le cas, optimisez la section si nécessaire et assurez-vous d'inclure de nombreux commentaires sur la documentation (y compris éventuellement le code d'origine) pour expliquer les optimisations. Ensuite, profilez le résultat pour vérifier que vous avez réellement apporté une amélioration.
Comme la question dit "code rapide difficile à lire ", la réponse simple n'est jamais. Il n'y a jamais d'excuse pour écrire du code difficile à lire. Pourquoi? Deux raisons.
Quand c'est du code jetable. Je veux dire littéralement: lorsque vous écrivez un script pour effectuer un calcul ou une tâche ponctuelle, et que vous savez avec une telle certitude que vous n'aurez plus jamais à refaire cette action que vous pouvez `` rm source-file '' sans hésitation, alors vous pouvez choisir la route laide.
Sinon, c'est une fausse dichotomie - si vous pensez que vous avez besoin de faire du mal pour le faire plus rapidement, vous le faites mal. (Ou vos principes sur ce qu'est un bon code doivent être révisés. L'utilisation de goto est en fait assez élégante quand c'est la bonne solution au problème. Elle l'est rarement cependant.)
Chaque fois que le coût estimé d'une baisse des performances 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 encore des codes SSE/NEON/etc torsadés codés à la main. Assemblage pour essayer de battre le logiciel de certains concurrents sur la puce CPU populaire de cette année.
N'oubliez pas que vous pouvez rendre le code difficile à lire facile à comprendre par une documentation et des commentaires appropriés.
En général, profilez après avoir écrit du code facile à lire qui fait la fonction souhaitée. Les goulots d'étranglement peuvent vous obliger à faire quelque chose qui rend les choses plus compliquées, mais vous pouvez résoudre ce problème en vous expliquant.
Pour moi, c'est une proportion de stabilité (comme dans le ciment cimenté, l'argile cuite au four, sertie dans la pierre, écrite à l'encre permanente). Plus votre code est instable, car plus il est probable que vous devrez le changer à l'avenir, plus il doit être facilement pliable, comme l'argile humide, pour rester productif. J'insiste également sur la flexibilité 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 de lire et de comprendre facilement les détails de l'implémentation s'il s'agit d'un cauchemar à changer? À moins qu'il ne s'agisse que d'un exercice académique, le but de pouvoir facilement comprendre le code dans une base de code de production est généralement de pouvoir le modifier plus facilement selon les besoins. S'il est difficile de changer, alors la plupart des avantages de la lisibilité disparaissent. La lisibilité n'est généralement utile que dans le contexte de la flexibilité, et la flexibilité n'est utile que dans le contexte de l'instabilité.
Naturellement, même le code le plus difficile à maintenir imaginable, quelle que soit sa facilité ou sa difficulté de lecture, ne pose pas de problème s'il n'y a jamais de raison de le modifier, il suffit 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 ont souvent tendance à compter le plus. J'ai un 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 énormément, écrit dans les jours de tripotage, et je le comprends à peine. Pourtant, il est toujours applicable aujourd'hui, et je n'ai pas besoin de comprendre sa mise en œuvre pour en tirer le meilleur parti.
Une rédaction approfondie des tests est un moyen d'améliorer la stabilité. Un autre est le découplage. Si votre code ne dépend de rien d'autre, alors la seule raison pour laquelle il change est s'il doit lui-même changer. Parfois, une petite quantité de duplication de code peut servir de mécanisme de découplage pour améliorer considérablement la stabilité d'une manière qui en fait un compromis valable si, en échange, vous obtenez du code qui est maintenant complètement indépendant de toute autre chose. Maintenant, ce code est invulnérable aux modifications du monde extérieur. Pendant ce temps, 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, peut-être même de la construire séparément, comme vous pouvez le faire pour les bibliothèques tierces (qui sont également destinées à être utilisées, non modifiées, du moins pas par votre équipe). Ce type d'organisation peut empêcher les gens de la falsifier.
Un autre est le minimalisme. Moins votre code essaie de faire, plus il est susceptible de faire ce qu'il fait bien. Les conceptions monolithiques sont presque instables en permanence, car de plus en plus de fonctionnalités leur sont ajoutées, plus elles semblent incomplètes.
La stabilité devrait être votre objectif principal chaque fois que vous essayez d'écrire du code qui sera inévitablement difficile à modifier, comme le code SIMD parallélisé qui a été micro-réglé à mort. Vous contrecarrez la difficulté de maintenir le code en maximisant la probabilité que vous n'aurez pas à changer le code, et donc ne devrez pas le maintenir à l'avenir. Cela réduit les coûts de maintenance à zéro, quelle que soit la difficulté de maintenance du code.