Parfois, vous rencontrez une situation dans laquelle vous devez étendre / améliorer du code existant. Vous voyez que l'ancien code est très maigre, mais il est également difficile à étendre et prend du temps à lire.
Est-ce une bonne idée de le remplacer par du code moderne?
Il y a quelque temps, j'aimais l'approche Lean, mais maintenant, il me semble qu'il vaut mieux sacrifier de nombreuses optimisations au profit d'abstractions plus élevées, de meilleures interfaces et d'un code plus lisible et extensible.
Les compilateurs semblent également s'améliorer. Ainsi, des choses comme par exemple struct abc = {}
sont transformées silencieusement en memset
s, shared_ptr
s produisent à peu près le même code que le twiddling au pointeur brut, les modèles fonctionnent très bien parce qu'ils produisent du code très maigre, etc.
Néanmoins, vous voyez parfois des tableaux basés sur des piles et de vieilles fonctions C avec une logique obscure, et ils ne sont généralement pas sur le chemin critique.
Est-ce une bonne idée de changer ce code si vous devez en toucher un petit morceau de toute façon?
Réponses:
Où?
Sur une page d'accueil d'un site Web à l'échelle de Google, cela n'est pas acceptable. Gardez les choses aussi vite que possible.
Dans une partie d'une application utilisée par une personne une fois par an, il est parfaitement acceptable de sacrifier les performances pour gagner en lisibilité du code.
En général, quelles sont les exigences non fonctionnelles pour la partie du code sur laquelle vous travaillez? Si une action doit être exécutée sous 900 ms. dans un contexte donné (machine, charge, etc.) 80% du temps, et en réalité, il exécute moins de 200 ms. Bien sûr, cela rend le code plus lisible 100% du temps, même si cela peut avoir un impact négatif sur les performances. Si par contre la même action ne s'est jamais exécutée en moins de dix secondes, eh bien, vous devriez plutôt essayer de voir ce qui ne va pas avec la performance (ou l'exigence en premier lieu).
En outre, en quoi l'amélioration de la lisibilité diminuera les performances? Souvent, les développeurs adaptent le comportement à une optimisation prématurée: ils craignent d’augmenter la lisibilité, estimant que cela va détruire radicalement les performances, alors que le code plus lisible nécessitera quelques microsecondes de plus pour effectuer la même action.
la source
goto
c'était plus rapide que pour les boucles. Ironiquement, l'optimiseur fonctionnait mieux avec les boucles for, il a donc rendu le code à la fois plus lent et plus difficile à lire.Habituellement non .
La modification du code peut entraîner des problèmes inattendus dans le système (ce qui peut parfois passer inaperçu bien plus tard dans un projet si vous ne disposez pas de tests unitaires et de test de fumée en place). Je passe habituellement par la mentalité "si ce n'est pas cassé, ne le répare pas".
L'exception à cette règle est si vous implémentez une nouvelle fonctionnalité qui touche ce code. Si, à ce stade, cela n'a pas de sens et que la refactorisation doit vraiment avoir lieu, allez-y tant que le temps de refactorisation (et que suffisamment d'essais et de solutions de sécurité pour traiter les problèmes de contrefaçon) est pris en compte dans les estimations.
Bien sûr, profil, profil, profil , surtout s'il s'agit d'un domaine de chemin critique.
la source
En bref: ça dépend
Allez-vous vraiment avoir besoin ou utiliser votre version refactorisée / améliorée?
A-t-il vraiment besoin d'être optimisé?
En détails
Allez-vous avoir besoin de trucs nettoyés et brillants?
Il y a des choses à être prudent ici, et vous devez identifier la limite entre ce qui est réel, un gain mesurable et ce qui est juste votre préférence personnelle et votre mauvaise habitude de toucher au code qui ne devrait pas être.
Plus précisément, sachez ceci:
Il y a une telle chose comme sur-ingénierie
C'est un anti-modèle, et il y a des problèmes intégrés:
Certains pourraient aussi citer le principe KISS comme référence, mais ici, c'est contre-intuitif: la voie optimisée est-elle simple ou la voie propre? La réponse n'est pas nécessairement absolue, comme expliqué dans le reste ci-dessous.
Vous n'en aurez pas besoin
Le principe de YAGNI n’est pas complètement orthogonal à l’autre problème, mais il est utile de se poser la question suivante: allez-vous en avoir besoin?
L'architecture plus complexe présente-t-elle vraiment un avantage pour vous, en plus de donner l'impression d'être plus facile à maintenir?
Si ce n'est pas cassé, ne le répare pas
Ecrivez ceci sur une grande affiche et accrochez-le à côté de votre écran ou dans la cuisine au travail ou dans la salle de réunion du développeur. Bien sûr, il y a beaucoup d'autres mantras qui méritent d'être répétés, mais celui-ci est important lorsque vous essayez d'effectuer un "travail de maintenance" et que vous ressentez le besoin de "l'améliorer".
Il est naturel que nous voulions «améliorer» le code ou même simplement le toucher, même inconsciemment, en le lisant pour essayer de le comprendre. C'est une bonne chose, car cela signifie que nous avons une opinion et que nous essayons de mieux comprendre les internes, mais cela dépend également de notre niveau de compétence, de nos connaissances (comment décidez-vous de ce qui est meilleur ou non? Eh bien, voir les sections ci-dessous ...), et toutes les hypothèses que nous faisons sur ce que nous pensons connaître le logiciel ...:
A-t-il vraiment besoin d'être optimisé?
Tout cela étant dit, pourquoi a-t-il été "optimisé"? Ils disent que l' optimisation prématurée est la racine de tous les maux, et si vous voyez un code non documenté et apparemment optimisé, vous pouvez généralement supposer qu'il n'a probablement pas suivi les règles d'optimisation n'a pas besoin chèrement l'effort d'optimisation et qu'il était le L'orgueil du développeur habituel entre en jeu. Encore une fois, c'est peut-être juste à vous de parler maintenant.
Si c'est le cas, dans quelles limites devient-il acceptable? Si vous en avez besoin, cette limite existe et vous donne la possibilité d'améliorer les choses, ou une ligne dure pour décider de laisser tomber.
Aussi, méfiez-vous des caractéristiques invisibles. Il est fort probable que votre version "extensible" de ce code vous permettra de disposer de plus de mémoire au moment de l'exécution et de présenter une empreinte mémoire statique encore plus grande pour l'exécutable. Les fonctionnalités OO brillantes entraînent des coûts non intuitifs comme ceux-ci et peuvent avoir une incidence sur votre programme et l'environnement sur lequel il est supposé fonctionner.
Mesurer, mesurer, mesurer
En tant que Google, tout est une question de données! Si vous pouvez le sauvegarder avec des données, alors c'est nécessaire.
Il n’ya pas si vieux discours que pour chaque dollar dépensé en développement, il sera suivi d’ au moins un dollar en tests et d’ au moins un dollar en support (mais en réalité, c’est beaucoup plus).
Le changement affecte beaucoup de choses:
Il ne faut donc pas mesurer ici uniquement la consommation de ressources matérielles (vitesse d'exécution ou empreinte mémoire), mais également la consommation de ressources de l'équipe . Les deux doivent être prédits pour définir un objectif, pour être mesurés, comptabilisés et adaptés en fonction du développement.
Et pour vous, manager, cela signifie que cela rentre dans le plan de développement actuel, alors communiquez à ce sujet et n’entrez pas dans le code furieux de cow-boy / sous-marin / black-ops.
En général...
Oui mais...
Ne vous méprenez pas, en général, je serais en faveur de ce que vous suggérez, et je le préconise souvent. Mais vous devez être conscient du coût à long terme.
Dans un monde parfait, c'est la bonne solution:
En pratique:
vous pouvez aggraver
Vous avez besoin de plus de globes oculaires pour le regarder, et plus vous le complexifiez, plus vous avez besoin de globes oculaires.
vous ne pouvez pas prédire l'avenir
Vous ne pouvez pas savoir avec une certitude absolue si vous en aurez jamais besoin et même si les "extensions" dont vous aurez besoin auraient été plus faciles et plus rapides à mettre en œuvre dans l'ancien formulaire, et si elles auraient besoin d'être super optimisées. .
Du point de vue de la direction, cela représente un coût énorme sans aucun gain direct.
Faites-en partie du processus
Vous mentionnez ici qu'il s'agit d'un changement relativement modeste et que vous avez des problèmes spécifiques à l'esprit. Je dirais que c'est généralement OK dans ce cas, mais la plupart d'entre nous ont aussi des histoires personnelles de petits changements, des retouches presque chirurgicales, qui ont fini par devenir un cauchemar d'entretien et des délais presque manqués ou dépassés parce que Joe Programmer n'en a pas vu. des raisons derrière le code et touché quelque chose qui n'aurait pas dû être.
Si vous avez un processus pour gérer de telles décisions, vous en prenez l'avantage personnel:
La couverture des tests, le profilage et la collecte de données sont difficiles
Mais, bien sûr, votre code de test et vos métriques peuvent souffrir des mêmes problèmes que vous essayez d'éviter pour votre code réel: testez-vous les bonnes choses, et sont-elles la bonne pour l'avenir, et mesurez-vous le bon des choses?
Néanmoins, en général, plus vous testez (jusqu’à une certaine limite) et mesurez, plus vous collectez de données et plus vous êtes en sécurité. Mauvaise analogie: pensez-y à la conduite (ou à la vie en général): vous pouvez être le meilleur pilote du monde, si la voiture tombe en panne ou si quelqu'un décide de se suicider en conduisant dans sa voiture avec le leur les compétences pourraient ne pas suffire. Des facteurs environnementaux peuvent vous frapper et les erreurs humaines importent également.
Les revues de code sont les tests de couloir de l'équipe de développement
Et je pense que la dernière partie est la clé ici: faire des revues de code. Vous ne saurez pas la valeur de vos améliorations si vous les faites en solo. Les revues de code sont nos "tests de couloir": suivez la version de Raymond de la loi de Linus, à la fois pour détecter les bugs et pour détecter les sur-techniques et autres anti-patterns, et pour vous assurer que le code correspond aux capacités de votre équipe. Il est inutile d'avoir le "meilleur" code si personne d'autre que vous ne pouvez le comprendre et le maintenir, et cela vaut à la fois pour les optimisations cryptiques et les conceptions architecturales profondes à 6 couches.
En guise de conclusion, rappelez-vous:
la source
En général, vous devez vous concentrer d'abord sur la lisibilité, puis sur les performances beaucoup plus tard. La plupart du temps, ces optimisations de performances sont négligeables, mais les coûts de maintenance peuvent être énormes.
Certes, toutes les "petites" choses devraient être changées au profit de la clarté puisque, comme vous l'avez souligné, la plupart d'entre elles seront optimisées par le compilateur.
En ce qui concerne les optimisations plus larges, il est possible que les optimisations soient réellement essentielles pour atteindre des performances raisonnables (bien que ce ne soit pas le cas étonnamment souvent). Je voudrais faire vos modifications puis profiler le code avant et après les modifications. Si le nouveau code présente des problèmes de performances importants, vous pouvez toujours revenir à la version optimisée, sinon vous pouvez vous en tenir à la version de code plus propre.
Ne modifiez qu'une partie du code à la fois et voyez comment il affecte les performances après chaque cycle de refactoring.
la source
Cela dépend de la raison pour laquelle le code a été optimisé, de l'impact de sa modification et de l'impact éventuel du code sur les performances globales. Cela dépend aussi de votre capacité à charger les modifications de test.
Vous ne devez pas effectuer cette modification sans profilage avant et après et, idéalement, sous une charge similaire à celle que l’on verrait en production. Cela signifie ne pas utiliser un sous-ensemble de données sur une machine de développement ou effectuer des tests lorsqu'un seul utilisateur utilise le système.
Si l'optimisation était récente, vous pourrez peut-être contacter le développeur pour savoir exactement quel était le problème et quelle était la lenteur de l'application avant l'optimisation. Cela peut vous en dire beaucoup sur la rentabilité de l'optimisation et sur les conditions dans lesquelles l'optimisation était nécessaire (un rapport couvrant une année entière, par exemple, peut ne pas être devenu lent avant septembre ou octobre, si vous testez votre modification. en février, la lenteur pourrait ne pas encore être apparente et le test invalide).
Si l'optimisation est plutôt ancienne, les méthodes les plus récentes pourraient même être plus rapides et plus lisibles.
En fin de compte, c'est une question pour votre patron. Il faut beaucoup de temps pour refactoriser quelque chose qui a été optimisé et pour s'assurer que le changement n'affecte pas le résultat final et qu'il fonctionne aussi bien ou du moins de manière acceptable par rapport à l'ancien. Il voudra peut-être que vous passiez votre temps dans d’autres domaines au lieu d’assumer une tâche à haut risque afin de gagner quelques minutes de temps de programmation. Il peut également convenir que le code est difficile à comprendre et qu’il a eu besoin d’interventions fréquentes et que de meilleures méthodes sont maintenant disponibles.
la source
si le profilage montre que l'optimisation n'est pas nécessaire (ce n'est pas dans une section critique) ou a même une exécution pire (résultat d'une mauvaise optimisation prématurée), alors remplacez-le avec du code lisible qui est plus facile à gérer
assurez-vous également que le code se comporte de la même manière avec les tests appropriés
la source
Pensez-y du point de vue des entreprises. Quels sont les coûts du changement? Combien de temps avez-vous besoin pour effectuer le changement et combien économiserez-vous à long terme en rendant le code plus facile à étendre ou à maintenir? Attachez maintenant une étiquette de prix à cette période et comparez-la à l’argent perdu en réduisant les performances. Peut-être devez-vous ajouter ou mettre à niveau un serveur pour compenser la perte de performances. Peut-être que le produit ne répond plus aux exigences et ne peut plus être vendu. Peut-être qu'il n'y a pas de perte. Peut-être que le changement augmente la robustesse et fait gagner du temps ailleurs. Maintenant, prenez votre décision.
Par ailleurs, dans certains cas, il peut être possible de conserver les deux versions d’un fragment. Vous pouvez écrire un test générant des valeurs d'entrée aléatoires et vérifier les résultats avec l'autre version. Utilisez la solution "intelligente" pour vérifier le résultat d'une solution parfaitement compréhensible et évidemment correcte, et vous rassurez ainsi (sans aucune preuve) le fait que la nouvelle solution est équivalente à l'ancienne. Ou bien faites l'inverse et vérifiez le résultat du code compliqué avec le code verbal et documentez ainsi l'intention derrière le piratage de manière non ambiguë.
la source
Fondamentalement, vous demandez si la refactorisation est une entreprise rentable. La réponse à cette question est très certainement oui.
Mais...
... vous devez le faire avec soin. Vous avez besoin de tests unitaires, d'intégration, fonctionnels et de performances solides pour tout code que vous refactoring. Vous devez être sûr qu'ils testent vraiment toutes les fonctionnalités requises. Vous devez avoir la capacité de les exécuter facilement et à plusieurs reprises. Une fois que vous avez cela, vous devriez pouvoir remplacer les composants par de nouveaux composants contenant une fonctionnalité équivalente.
Martin Fowler a écrit le livre à ce sujet.
la source
Vous ne devez pas modifier le code de production ou de travail sans raison valable. Le "refactoring" n'est pas une raison suffisante à moins que vous ne puissiez faire votre travail sans ce refactoring. Même si vous corrigez des bogues dans le code difficile lui-même, vous devez prendre le temps de le comprendre et d’apporter le changement le plus petit possible. Si le code est si difficile à comprendre, vous ne pourrez pas le comprendre complètement. Ainsi, toute modification apportée aura des effets secondaires imprévisibles - des bogues, en d’autres termes. Plus le changement est important, plus vous risquez de causer des problèmes.
Il y aurait une exception à cela: si le code incompréhensible avait un ensemble complet de tests unitaires, vous pouvez le refactoriser. Comme je n'ai jamais vu ni entendu parler de code incompréhensible avec des tests unitaires complets, vous écrivez d'abord les tests unitaires, obtenez l'accord des personnes nécessaires pour que ces tests unitaires représentent réellement ce que le code devrait faire, et ALORS effectuez les modifications de code. . Je l'ai fait une ou deux fois. C'est une douleur dans la nuque, et très cher, mais produit finalement de bons résultats.
la source
S'il ne s'agit que d'un petit morceau de code qui fait quelque chose de relativement simple d'une manière difficile à comprendre, je décalerais la "compréhension rapide" dans un commentaire étendu et / ou une implémentation alternative inutilisée, comme
la source
La réponse est, sans perte de généralité, oui. Ajoutez toujours du code moderne lorsque vous voyez du code difficile à lire et supprimez le code incorrect dans la plupart des cas. J'utilise le processus suivant:
<function>_clean()
. Ensuite, "faites la course" de votre code contre le mauvais code. Si votre code est meilleur, supprimez l'ancien code.CQFD
la source
Si je pouvais enseigner au monde une chose (à propos du logiciel) avant de mourir, je lui apprendrais que "Performance versus X" est un faux dilemme.
Le refactoring est généralement considéré comme un avantage en termes de lisibilité et de fiabilité, mais il peut tout aussi bien prendre en charge l'optimisation. Lorsque vous traitez l'amélioration des performances sous la forme d'une série de modifications, vous pouvez respecter la règle de camping tout en accélérant l'application. En fait, du moins à mon avis, il vous incombe éthiquement de le faire.
Par exemple, l'auteur de cette question a rencontré un morceau de code fou. Si cette personne lisait mon code, elle trouverait que la partie folle compte 3-4 lignes. C'est dans une méthode en soi, et le nom de la méthode et sa description indique CE QUE fait la méthode. La méthode contiendrait 2 à 6 lignes de commentaires en ligne décrivant COMMENT le code fou obtient la bonne réponse, malgré son apparence discutable.
Compartimenté de cette manière, vous êtes libre d’échanger à votre guise les implémentations de cette méthode. En effet, c’est probablement pour cela que j’ai écrit la version folle pour commencer. Vous êtes invités à essayer, ou au moins demander des alternatives. La plupart du temps, vous découvrirez que la mise en œuvre naïve est sensiblement pire (en général, je ne dérange que pour une amélioration de 2 à 10 fois), mais les compilateurs et les bibliothèques changent constamment, et qui sait ce que vous pouvez trouver aujourd'hui qui n'était pas disponible quand la fonction a été écrite?
la source
Ce n'est probablement pas une bonne idée de le toucher - si le code était écrit de cette façon pour des raisons de performances, cela signifierait que le modifier pourrait ramener des problèmes de performances qui avaient été résolus auparavant.
Si vous ne décidez de changer les choses pour être plus lisible et extensible: Avant de faire un changement, référence l'ancien code sous forte charge. Mieux encore, si vous pouvez trouver un ancien document ou un ticket de dépannage décrivant le problème de performances que ce code étrange est censé résoudre. Après avoir effectué vos modifications, exécutez à nouveau les tests de performance. Si ce n'est pas très différent, ou toujours dans les paramètres acceptables, c'est probablement OK.
Il peut arriver que lorsque d'autres parties d'un système changent, ce code optimisé en termes de performances ne nécessite plus d'optimisations aussi lourdes, mais il est impossible de le savoir avec certitude sans tests rigoureux.
la source
Le problème ici est de distinguer "optimisé" de lisible et extensible, ce que nous considérons comme code optimisé et ce que le compilateur considère comme optimisé sont deux choses différentes. Le code que vous envisagez de modifier ne constitue peut-être pas un goulot d'étranglement. Par conséquent, même si le code est "maigre", il n'est même pas nécessaire de "l'optimiser". Ou, si le code est suffisamment ancien, le compilateur peut effectuer des optimisations pour intégrer des éléments intégrés rendant l'utilisation d'une structure intégrée plus récente plus simple ou plus efficace que l'ancien code.
Et "maigre", le code illisible n'est pas toujours optimisé.
J'avais l'habitude de penser que le code intelligent / lean était un bon code, mais tirant parfois parti de règles obscures du langage blessé plutôt que d'aide à la création de code, j'ai été piqué plus que tout dans un travail intégré lorsque j'essayais de soyez intelligent, car le compilateur transforme votre code intelligent en quelque chose de totalement inutilisable par le matériel intégré.
la source
Je ne remplacerai jamais le code optimisé par du code lisible car je ne peux pas compromettre les performances et je choisirai d'utiliser des commentaires appropriés dans chaque section afin que tout le monde puisse comprendre la logique implémentée dans cette section optimisée qui résoudra les deux problèmes.
Par conséquent, le code sera optimisé + les commentaires appropriés le rendront également lisible.
REMARQUE: Vous pouvez rendre un code optimisé lisible à l'aide de commentaires appropriés, mais vous ne pouvez pas en faire un code lisible optimisé.
la source
Voici un exemple pour voir la différence entre un code simple et un code optimisé: https://stackoverflow.com/a/11227902/1396264
vers la fin de la réponse, il remplace simplement:
avec:
Pour être juste, je ne sais pas du tout quelle déclaration a été remplacée par if, mais comme le répondeur le dit, certaines opérations au niveau des bits donnent le même résultat (je vais juste le croire sur parole) .
Ceci s’exécute en moins d’un quart du temps initial (11.54s contre 2.5s)
la source
La principale question est la suivante: l’optimisation est-elle requise?
Si c'est le cas, vous ne pouvez pas le remplacer par un code plus lent, plus lisible. Vous devrez ajouter des commentaires, etc. pour le rendre plus lisible.
Si le code n'a pas besoin d'être optimisé, il ne devrait pas l'être (au point d'affecter la lisibilité) et vous pouvez le reformater pour le rendre plus lisible.
CEPENDANT - assurez-vous de savoir exactement ce que fait le code et comment le tester complètement avant de commencer à changer les choses. Cela inclut l'utilisation maximale, etc. Si vous n'avez pas à composer un ensemble de cas de test et à les exécuter avant et après, vous n'avez pas le temps de refactoriser.
la source
Voici comment je fais les choses: d'abord, je le fais fonctionner en code lisible, puis je l'optimise. Je conserve la source d'origine et documente mes étapes d'optimisation.
Ensuite, lorsque je dois ajouter une fonctionnalité, je retourne à mon code lisible, puis ajoute la fonctionnalité et suis les étapes d'optimisation décrites. Parce que vous avez documenté, il est très rapide et facile de réoptimiser votre code avec la nouvelle fonctionnalité.
la source
La lisibilité à mon humble avis est plus importante que le code optimisé car dans la plupart des cas, la micro-optimisation n'en vaut pas la peine.
Article sur les micro-optimisations sans sens :
la source
L'optimisation est relative. Par exemple:
Cette hypothèse:
mène à:
Références
L'analyse coûts-avantages des champs de bits pour une collection de booléens - The Old New Thing
Mauvais champ de bits - Hardwarebug
Champs de bits lisibles et maintenables en C | pagetable.com
la source