J'ai passé beaucoup de temps à lire différents livres sur le "bon design", les "modèles de design", etc. Je suis un grand fan du SOLIDE approche et chaque fois que je dois écrire un simple morceau de code, je pense à l'avenir. Donc, si l'implémentation d'une nouvelle fonctionnalité ou d'une correction de bogue nécessite simplement d'ajouter trois lignes de code comme ceci:
if(xxx) {
doSomething();
}
Cela ne signifie pas que je vais le faire de cette façon. Si j'ai l'impression que ce morceau de code est susceptible de devenir plus grand dans un avenir proche, je penserai à ajouter des abstractions, à déplacer cette fonctionnalité ailleurs et ainsi de suite. Le but que je poursuis est de maintenir la complexité moyenne la même qu'avant mes modifications.
Je crois que du point de vue du code, c'est une assez bonne idée - mon code n'est jamais assez long et il est assez facile de comprendre les significations pour différentes entités, comme les classes, les méthodes et les relations entre les classes et les objets.
Le problème est que cela prend trop de temps et j'ai souvent l'impression qu'il serait préférable de simplement implémenter cette fonctionnalité "telle quelle". C'est à peu près "trois lignes de code" contre "une nouvelle interface + deux classes pour implémenter cette interface".
Du point de vue du produit (quand nous parlons du résultat ), les choses que je fais sont tout à fait insensées. Je sais que si nous allons travailler sur la prochaine version, avoir un bon code est vraiment génial. Mais d'un autre côté, le temps que vous avez passé pour rendre votre code "bon" a peut-être été consacré à la mise en œuvre de quelques fonctionnalités utiles.
Je me sens souvent très insatisfait de mes résultats - un bon code qui ne peut faire que A est pire qu'un mauvais code qui peut faire A, B, C et D.
Cette approche est-elle susceptible d'entraîner un gain net positif pour un projet logiciel, ou est-ce une perte de temps?
un bon code qui ne peut faire que A est pire qu'un mauvais code qui peut faire A, B, C, D.
Cela me sent comme généralité spéculative . Sans savoir (ou du moins être raisonnablement sûr) que vos clients vont en avoir besoin fonctionnalités B, C et D, vous compliquez inutilement votre conception. Un code plus complexe est plus difficile à comprendre et à maintenir à long terme. La complexité supplémentaire n'est justifiée que par utile fonctionnalités supplémentaires. Mais nous sommes généralement très mauvais pour prédire l'avenir. La plupart des fonctionnalités que nous pensons peut-être nécessaires à l'avenir ne seront jamais demandées dans la vie réelle.
Donc:
Un bon code qui ne peut faire que A (mais il fait cela simplement et proprement) est MIEUX qu'un mauvais code qui peut faire A, B, C, D (dont certains pourrait être nécessaire dans le futur).
Temps d'anecdote:
J'ai eu deux développeurs travaillant pour moi qui se sont penchés sur la suringénierie de cette manière.
Pour l'un d'entre eux, cela a essentiellement stoppé sa productivité, en particulier lors du démarrage d'un nouveau projet. Surtout si le projet était, par nature, assez simple. En fin de compte, un logiciel qui fonctionne maintenant est ce dont nous avons besoin. Ça a tellement mal que j'ai dû le laisser partir.
L'autre développeur enclin à la suringénierie l'a compensé en étant extrêmement productif. En tant que tel, malgré tout le code étranger, il a toujours livré plus rapidement que la plupart. Cependant, maintenant qu'il est parti, je suis souvent irrité par la quantité de travail supplémentaire nécessaire pour ajouter des fonctionnalités car vous devez modifier (entièrement inutile) les couches d'abstraction, etc.
Donc, la morale est que la suringénierie consommera du temps supplémentaire qui pourrait être mieux dépensé pour faire des choses utiles. Et pas seulement votre temps, mais aussi ceux qui doivent travailler avec votre code.
Alors ne le faites pas.
Vous voulez que votre code soit aussi simple que possible (et pas plus simple). Gérer les "maybes" ne simplifie pas les choses, si vous vous trompez, vous aurez rendu le code plus complexe sans aucun gain réel à montrer.
Les principes SOLID et KISS/YAGNI sont des opposés quasi polaires. On vous dira que si doSomething () ne peut pas être justifié en tant que partie intégrante du travail de la classe qui l'appelle, il devrait être dans une classe différente qui est couplée et injectée de manière lâche. L'autre vous dira que si c'est le seul endroit où doSomething est utilisé, même l'extraire dans une méthode peut avoir été exagéré.
C'est ce qui fait que les bons programmeurs valent leur pesant d'or. Une structure "appropriée" est une base au cas par cas, nécessitant une connaissance de la base de code actuelle, de la trajectoire future du programme et des besoins de l'entreprise derrière le programme.
J'aime suivre cette méthodologie simple en trois étapes.
Fondamentalement, c'est ainsi que vous mélangez KISS avec SOLID. Lorsque vous écrivez pour la première fois une ligne de code, pour tout ce que vous savez, ce sera unique; cela doit simplement fonctionner et personne ne se soucie comment, alors ne vous faites pas de fantaisie. La deuxième fois que vous placez votre curseur dans cette ligne de code, vous avez réfuté votre hypothèse d'origine; en revoyant ce code, vous êtes probablement en train de l'étendre ou de vous y accrocher ailleurs. Maintenant, vous devrait le nettoyer un peu; extraire les méthodes pour les petits morceaux couramment utilisés, réduire ou éliminer le codage copier/coller, ajouter quelques commentaires, etc. La troisième fois que vous revenez à ce code, c'est maintenant une intersection majeure pour l'exécution de votre programme Vous devez maintenant le traiter comme une partie essentielle de votre programme et appliquer les règles SOLID lorsque cela est possible).
Exemple: vous devez écrire une simple ligne de texte sur la console. La première fois que cela se produit, Console.WriteLine () est très bien. Ensuite, vous revenez à ce code après que de nouvelles exigences imposent également d'écrire la même ligne dans un journal de sortie. Dans cet exemple simple, il peut ne pas y avoir beaucoup de code "copier/coller" répétitif (ou peut-être qu'il y en a un), mais vous pouvez toujours faire un nettoyage de base, peut-être extraire une méthode ou deux pour éviter l'inline IO opérations dans une logique métier plus profonde. Ensuite, vous revenez lorsque le client veut la même sortie de texte vers un canal nommé pour un serveur de surveillance. Maintenant, cette routine de sortie est un peu compliquée; vous diffusez la même chose Voici trois exemples de texte d'un algorithme qui bénéficierait d'un modèle composite; développer une interface IWriter simple avec une méthode Write (), implémenter cette interface pour créer des classes ConsoleWriter, FileWriter et NamedPipeWriter, et encore une fois pour créer une classe composite "MultiWriter", puis exposez une dépendance IWriter sur votre classe, configurez le composite MultiWriter avec les trois rédacteurs réels et branchez-le. Maintenant, c'est assez SOLIDE; à partir de ce moment, chaque fois que les exigences dictent que la sortie doit allez partout, vous venez de créer te un nouvel IWriter et branchez-le au MultiWriter, pas besoin de toucher un code de travail existant.
un bon code qui ne peut faire que A est pire qu'un mauvais code qui peut faire A, B, C, D.
1) Avoir un code qui ne fait que ce qui est censé faire.
Si j'ai l'impression que ce morceau de code est susceptible de devenir plus grand dans un avenir proche, je penserai à ajouter des abstractions, à déplacer cette fonctionnalité ailleurs et ainsi de suite.
2) Si vous prévoyez que votre code exécute A, B, C et D, le client vous demandera finalement E.
Votre code doit faire ce qui est censé faire, vous ne devriez pas penser maintenant aux futures implémentations, car vous ne cesserez jamais de changer votre code en continu, et plus important encore, vous sur-concevrez votre code. Vous devez refactoriser votre code dès que vous sentez qu'il en a besoin en raison des fonctionnalités actuelles, sans avoir du mal à le préparer pour quelque chose qu'il ne fera pas encore, à moins bien sûr que vous l'ayez planifié dans le cadre de l'architecture du projet.
Je vous suggère de lire un bon livre: The Pragmatic Programmer . Cela vous ouvrira l'esprit et vous apprendra ce que vous devriez faire et ce que vous ne devriez pas faire.
--- Code Complete est une excellente ressource pleine d'informations utiles que chaque développeur (pas seulement un programmeur) devrait lire.
très souvent j'ai besoin d'écrire un simple morceau de code, je pense à l'avenir
Voici peut-être le problème.
Aux premiers stades, vous n'avez aucune idée de ce que serait le produit final. Ou si vous l'avez, c'est faux. Pour sûr. C'est comme un garçon de 14 ans qui a demandé il y a quelques jours sur Programmers.SE s'il devait, pour sa future carrière, choisir entre des applications web et je ne me souviens pas de quoi d'autre: il est assez évident que dans quelques années, les choses il aime va changer, il sera intéressé par d'autres domaines, etc.
Si, pour écrire trois lignes de code, vous créez une nouvelle interface et deux classes, vous êtes en sur-ingénierie. Vous obtiendrez un code qui sera difficile à maintenir et difficile à lire, juste parce que pour chaque ligne de code utile, vous avez deux lignes de code dont vous n'avez pas besoin. Sans compter la documentation XML, les tests unitaires, etc.
Pensez-y: si je veux savoir comment une fonctionnalité est implémentée dans votre code, serait-il plus facile pour moi de lire vingt lignes de code, ou ce serait plus rapide et plus facile d'avoir à ouvrir des dizaines de classes semi-vides et interfaces pour déterminer laquelle utilise, comment sont-elles liées, etc.?
N'oubliez pas: une base de code plus grande signifie plus de maintenance. N'écrivez pas plus de code lorsque vous pouvez l'éviter.
Votre approche est également néfaste des autres côtés:
Si vous devez supprimer une fonctionnalité, n'est-il pas plus facile de déterminer où une méthode spécifique de vingt lignes est utilisée, que de perdre votre temps à comprendre les dépendances entre des dizaines de classes?
Lors du débogage, n'est-il pas plus facile d'avoir une petite trace de pile, ou préférez-vous lire des dizaines de lignes afin de comprendre ce qui est appelé par quoi?
Pour conclure, cela semble similaire à optimisation prématurée. Vous essayez de résoudre le problème sans même être sûr s'il y a un problème en premier lieu et où il se trouve. Lorsque vous travaillez sur la version 1 de votre produit, implémentez les fonctionnalités que vous devez implémenter dès maintenant; ne pensez pas à quelque chose que vous prévoyez de mettre en œuvre dans deux ans dans la version 14.
Écrire beaucoup de code qui ne sera (probablement) jamais utilisé est un très bon moyen d'obtenir un P45 . Vous n'avez pas de boule de cristal et vous n'avez aucune idée de la direction finale que prendra le développement, donc passer du temps sur ces fonctions coûte de l'argent sans retour.
Essayer de prédire ce qui pourrait être nécessaire à partir du code à l'avenir finit souvent par être une ingénierie excessive inutile (une habitude que j'essaie actuellement de secouer). Je dirais juste faire les trois lignes. Lorsque le besoin s'en fait sentir (et pas avant), refactorisez. De cette façon, votre code fait toujours ce dont il a besoin sans être trop compliqué et évolue naturellement de bonne structure grâce au refactoring.
Je dis souvent que le codage est comme le côté clair/côté sombre de la Force - le côté "léger" nécessite plus d'efforts mais donne de meilleurs résultats. Le côté "sombre" est rapide et facile et offre immédiatement de plus grands avantages, mais vous corrompt sur la route. Une fois que vous aurez commencé sur le chemin sombre, il dominera à jamais votre destin.
Je rencontre ça tout le temps, à chaque travail que j'ai jamais eu, c'est comme une malédiction à laquelle je ne peux pas échapper. La culture de l'entreprise est toujours le chemin du côté obscur, et des hacks/correctifs rapides pour pousser de nouvelles fonctionnalités, et mes plaidoyers et mes cris de refactoring et d'écriture de code tombent dans l'oreille d'un sourd, si ce n'est pas le cas '' t menant à ma résiliation pour "essayer de changer les choses" (pas de blague que j'ai eue une poignée de fois, parce que je voulais introduire des modèles de conception et m'éloigner des hacks rapides).
La triste vérité est que souvent la voie stupide/côté obscur est la façon dont vous devez marcher, vous devez juste vous assurer de marcher légèrement. J'ai lentement et tristement réalisé que les programmeurs qui comprennent la façon à droite d'écrire un logiciel, c'est-à-dire suivre SOLID, utiliser des modèles de conception, obéir à SoC, etc. sont beaucoup moins courants que les hacks désemparés qui écriront un if
pour corriger un bogue, et lorsque d'autres bogues surviennent, ajoutez-le simplement au lieu de penser "Peut-être qu'il y a une meilleure façon de le faire" et de refactoriser le code pour qu'il soit correctement extensible.
Être conscient de ce qui pourrait arriver (futur) n'est JAMAIS mauvais. Penser à ce qui pourrait être une meilleure façon de faire quelque chose fait partie de ce qui vous rend bon dans votre travail. La partie la plus difficile consiste à déterminer si le rapport temps passé/gain est justifié. Nous avons tous vu des situations où les gens font le "facile si" pour arrêter le saignement immédiat (et/ou les cris), et que, au fur et à mesure que cela s'accumule, vous obtenez un code déroutant. Beaucoup d'entre nous ont également connu une abstraction exagérée qui est un mystère une fois que le codeur d'origine continue, ce qui produit également du code déroutant.
Je regarderais votre situation et poserais ces questions:
Ce code est-il essentiel à la mission et sera-t-il beaucoup plus stable si je recode? En chirurgie, est-ce que ce refactoring sauve des vies ou est-ce simplement électif et cosmétique?
Est-ce que j'envisage de refactoriser le code que nous allons remplacer dans 6 mois?
Suis-je prêt à prendre autant de temps pour documenter la conception et mon raisonnement pour les futurs développeurs que pour passer à la refactorisation?
En ce qui concerne mon design élégant pour ajouter de futures fonctionnalités, est-ce dans le code que les utilisateurs demandent des modifications chaque semaine, ou est-ce la première fois que je le touche cette année?
Il y a des moments où YAGNI et KISS gagnent la journée, mais il y a des jours où un changement fondamental vous fera sortir de la spirale descendante vers le caca. Tant que vous êtes réaliste dans votre évaluation non seulement ce que vous voulez, mais ce que les autres devront faire pour maintenir votre travail, vous serez mieux en mesure de déterminer quelle situation est laquelle. Oh, et n'oubliez pas d'écrire ce que vous avez fait et pourquoi. ceux qui vous suivent, mais aussi vous-même quand vous devrez revenir plus tard.
Dans la deuxième édition de Stroustrups 'Le langage de programmation C++', (je n'ai pas la page disponible), j'ai lu
N'ajoutez pas de code spontanément en place.
et je suis bien allé en suivant les conseils. Bien sûr, il y a des compromis, et vous devez trouver un équilibre, mais de courts fragments sont mieux testables qu'un gros gâchis de spaghetti.
J'ai souvent expérimenté qu'en différenciant d'un cas à deux cas, si vous considérez 2 comme n-cas, vous ouvrez une porte à de nombreuses nouvelles possibilités, auxquelles vous n'auriez peut-être pas pensé.
Mais alors vous devez poser la question YAGNI: Est-ce que ça vaut le coup? Sera-ce vraiment utile? Être expérimenté signifie que vous vous tromperez rarement et, en tant que débutant, vous aurez plus souvent tort.
Vous devez être suffisamment critique pour reconnaître un modèle et détecter si votre code est difficile à maintenir, en raison d'une trop grande abstraction, ou difficile à maintenir, car tout est résolu en place.
La solution n'est pas ceci ou cela, mais d'y penser. :)
"Un bon code qui ne peut faire que A est pire qu'un mauvais code qui peut faire A, B, C et D."
Cela peut avoir un certain sens dans le développement de produits; Mais la plupart des professionnels de l'informatique travaillent dans des "projets" plutôt que dans le développement de produits.
Dans les "projets informatiques", si vous programmez un bon composant, il fonctionnera sans problème pendant toute la durée de vie du projet - qui ne peut pas durer plus de 5 ou 10 ans d'ici là, le scénario d'entreprise peut être devenu obsolète et un nouveau projet/ERP le produit a pu le remplacer. Au cours de cette durée de vie de 5/10 ans, à moins qu'il n'y ait des défauts dans votre code, personne ne remarquera son existence et les mérites de vos meilleures pensées sont passés inaperçus! (sauf si vous êtes bon en pariant fort votre propre trompette!)
Peu de gens ont la possibilité de programmer le 'Windows Ctl + Alt + Del' et ceux-ci ont cette chance de ne pas réaliser le potentiel futur de leur code!
De nombreux livres sur le développement Lean et/ou Agile aideront à renforcer cette approche: faites ce qui est nécessaire dès maintenant. Si vous savez que vous construisez un cadre, ajoutez des abstractions. Sinon, n'ajoutez pas de complexité jusqu'à ce que vous en ayez besoin. Je recommande Lean Software Development , qui introduira de nombreux autres concepts qui peuvent faire une différence substantielle dans la productivité.
C'est drôle comment les gens parlent de la bonne/mauvaise façon de faire les choses. Dans le même temps, la tâche de programmation est toujours douloureusement difficile et ne donne pas de bonnes solutions pour écrire de gros systèmes complexes.
Il se peut qu'un jour nous, les programmeurs, trouvions enfin comment écrire des logiciels complexes. D'ici là, je suggère que vous commenciez toujours par l'implémentation d'un prototype "stupide" en premier, puis que vous passiez juste assez de temps sur la refactorisation pour que vos collèges puissent suivre votre code.
Ayant vu de telles conceptions généralisées prématurément qui ne correspondaient pas du tout aux exigences réelles qui sont venues plus tard, j'ai conçu une règle pour moi:
Pour les exigences hypothétiques, n'écrivez que du code hypothétique.
C'est-à-dire: vous êtes bien avisé de penser aux changements qui pourraient survenir plus tard. Mais utilisez uniquement ces informations pour choisir une conception pour le code qui peut facilement être modifiée et refactorisée si ces exigences se présentent réellement. Vous pouvez même écrire du code dans votre tête (code hypothétique) que vous voudriez écrire dans ce cas, mais n'écrivez aucun code réel!
Demandez-vous quels sont les avantages d'une bonne conception:
Maintenant, demandez-vous si l'ajout de toutes ces couches d'abstractions ajoute vraiment à l'un des points mentionnés ci-dessus. Si ce n'est pas le cas, vous le faites [~ # ~] mal [~ # ~] .
Si vous pouvez ajouter de nouvelles fonctionnalités en ajoutant 3 lignes de code comme ceci:
if (condition()) {
doSomething()
}
Alors s'il vous plaît, s'il vous plaît faites-le. Cela indique que votre conception précédente était bonne et facile à adapter. Seulement une fois que vos classes commencent à croître au-delà d'une certaine extension, utilisez refactoring pour diviser les fonctions et éventuellement extraire de nouvelles classes.
Ma règle de base est que les nouvelles fonctionnalités doivent être implémentées de manière aussi minimaliste que possible, seulement lorsque quelque chose est trop gros à saisir au départ (disons, cela prend plus d'un jour ou une demi-journée à mettre en œuvre), vous pouvez ajouter une conception globale approximative. À partir de là, ajoutez des couches d'abstraction uniquement lorsque la taille du code augmente. Vous refactorisez ensuite! Après un certain temps, cela devrait même vous sembler naturel lorsque vous avez besoin de concevoir un morceau un peu plus ou d'aller sur la route rapide. Une autre indication qu'un code peut utiliser un nettoyage est lorsque vous le réutilisez. Chaque fois que vous ajoutez une nouvelle fonctionnalité ou appelez l'ancien code dans un nouvel emplacement, c'est le bon moment pour consulter l'ancien code et voir si vous pouvez l'améliorer un peu avant de l'ajouter à nouveau. De cette façon, le code à chaud deviendra lentement plus propre, tandis que les parties inintéressantes pourriront lentement et ne prendront aucun de votre temps précieux. (Si vous utilisez un ancien code et que vous ne le comprenez pas rapidement, cela pourrait être une démotivation pour l'améliorer, mais rappelez-vous que c'est aussi un gros avertissement qu'un nettoyage/une nouvelle conception est approprié).
Si vous travaillez comme ça, vous ne concevrez jamais rien de trop. Il peut prendre un peu de discipline pour revenir à l'ancien code lorsque vous voulez ajouter quelque chose de nouveau, ou pour laisser un nouveau code un peu plus laid que vous le souhaitez, mais vous travaillerez vers quelque chose de productif au lieu d'être trop conçu.
Je pense que la suringénierie semble souvent due à l'insécurité concernant l'écriture de code. Tous les principes et modèles abstraits doivent être traités comme des outils pour vous aider. Ce qui arrive souvent, c'est qu'ils sont traités comme des normes auxquelles il faut se conformer.
Je crois qu'un programmeur est toujours mieux placé pour décider comment abstraire qu'un axiome.
Le reste a déjà été dit par KeithS
Je pense que l'état d'esprit qui vous aidera est de toujours chercher des solutions concrètes aux problèmes de codage au lieu de solutions abstraites. Les abstractions ne doivent être ajoutées que lorsqu'elles contribuent réellement à simplifier la base de code (lorsqu'elles permettent par exemple de réduire la base de code).
De nombreux programmeurs ont constaté que les abstractions émergent presque toutes seules, quand elles SÈCHENT leur code vers le haut. Les modèles de conception et les meilleures pratiques vous aident à trouver des opportunités pour cela, mais ne sont pas en soi des objectifs qui méritent d'être poursuivis.