Le code plus ancien doit-il être mis à jour pour utiliser des constructions de langage plus récentes, ou des constructions obsolètes doivent-elles être bloquées?

15

Je veux apporter quelques améliorations à du code encore fonctionnel qui a été écrit il y a longtemps, avant que le langage de programmation dans lequel il est écrit ne se développe. En théorie, l'ensemble du projet utilise la version à jour de la langue; cependant, ce module particulier (et en fait, de nombreux autres modules) est toujours écrit dans l'ancien dialecte.

Devrais-je:

  • Ne pas toucher les parties du code que je n'ai pas à toucher, mais écrire mon patch en utilisant les nouvelles fonctionnalités du langage qui facilitent l'écriture du patch mais qui ne sont pas utilisées dans des situations analogues ailleurs dans le module? (C'est la solution que je choisirais intuitivement.)
  • Ignorer le fait que des années se sont écoulées et refléter le style utilisé dans le reste du code lors de l'écriture de mon correctif, se comportant effectivement comme si je faisais la même tâche plusieurs années plus tôt? (Cette solution serait intuitivement idiote, mais étant donné la quantité de bruit que tous ceux qui parlent de «bon code» font pour garder la cohérence à tout prix, c'est peut-être ce que je devrais faire.)
  • Mettre à jour l'ensemble du module pour utiliser les nouvelles constructions et conventions de langage? (C'est probablement la meilleure solution, mais cela pourrait nécessiter beaucoup de temps et d'énergie qui pourraient mieux être consacrés à une tâche différente.)
gaazkam
la source
1
@JerryCoffin Je ne vais pas continuer à discuter dans les commentaires: j'ai posté sur meta où nous pouvons avoir une discussion appropriée.

Réponses:

10

Il est impossible de donner une réponse définitive à cette question, car elle dépend trop des particularités de la situation.

La cohérence dans le style de la base de code est importante car elle aide à rendre le code plus facile à comprendre, ce qui est l'un des aspects les plus importants de la maintenabilité.
Vous ne seriez pas le premier programmeur à avoir été maudit en enfer pour avoir rendu le code difficile à comprendre en mélangeant différents styles. Il se peut même que ce soit vous-même qui maudissiez dans un an.

D'un autre côté, l'utilisation de nouvelles constructions de langage qui n'existaient pas lors de la première écriture du code n'implique pas automatiquement que vous réduisez la maintenabilité ou même que vous cassez le style du code. Tout dépend des fonctionnalités de langage particulières que vous souhaitez utiliser et de la familiarité de l'équipe avec l'ancien style du code et les nouvelles fonctionnalités.

Par exemple, il ne serait généralement pas conseillé de commencer à introduire des concepts de programmation fonctionnels comme mapper / réduire à une base de code qui est complètement dans le style OO, mais cela pourrait fonctionner pour ajouter une fonction lambda ici et là à un programme qui n'a pas utilisé quelque chose comme ça avant.

Bart van Ingen Schenau
la source
1
Je suis partiellement en désaccord. Nous devons suivre le principe d'ouverture et de fermeture. Nous devons donc essayer d'isoler le nouveau code autant que possible, et en même temps écrire le nouveau code avec la dernière construction de langage possible.
Anand Vaidya
3
@AnandVaidya: c'est à mon humble avis une attente irréaliste, car il suppose que l'ancien code suit l'OCP ou peut être facilement modifié pour suivre l'OCP, ce qui est rarement le cas ou ne vaut tout simplement pas la peine.
Doc Brown
1
Quand j'ai dit ocp, je ne veux pas dire oops ocp, mais ocp en général. C'est essentiellement essayer de ne pas modifier le code existant autant que possible, et d'avoir votre propre code isolé. C'est même possible avec l'ancien code de procédure. Je comprends que les codes spaghetti ne peuvent pas être modifiés de cette façon, mais pour un code bien conçu dans n'importe quel paradigme, la fermeture ouverte devrait être facilement applicable. Que ce soit oups ou procédural ou fonctionnel.
Anand Vaidya
2
@AnandVaidya: quand vous ne voulez pas dire OCP, vous ne devriez pas l'appeler ainsi. Je suppose que ce que vous voulez vraiment dire, c'est ce que Feather appelle l'écriture de nouveau code dans une méthode de germination , donc on peut restreindre le nouveau style de code à une nouvelle méthode séparée. Lorsque cela est possible et raisonnable, vous avez raison, c'est bien. Malheureusement, cette approche n'est pas toujours applicable, du moins pas si vous souhaitez conserver l'ancien code SEC.
Doc Brown
@DocBrown: Je ne pense pas que le principe ouvert / fermé soit limité à la programmation orientée objet. Vous pouvez avoir des modules dans lesquels vous ajoutez de nouvelles procédures mais ne modifiez pas celles existantes, c'est-à-dire que vous gardez intacte la sémantique du code existant et, si vous avez besoin de nouvelles fonctionnalités, vous écrivez du nouveau code.
Giorgio
5

Dans une large mesure, le code et son apparence ne sont pas pertinents . Qu'est - ce que le code n'est ce qui importe.

Si vous pouvez garantir que la modification / réécriture du code ne changera pas ce que le code fait, alors vous pouvez continuer et refactoriser le code selon le contenu de votre cœur. Cette «garantie» est un ensemble exhaustif de tests unitaires que vous pouvez exécuter avant et après vos modifications, sans différence perceptible.

Si vous ne pouvez pas garantir cette stabilité (vous n'avez pas obtenu ces tests), laissez-la bien tranquille.

Personne ne vous remerciera d'avoir "cassé" un logiciel critique pour l'entreprise, même si vous essayiez de le "rendre meilleur". "Travailler" l'emporte "mieux" à chaque fois.

Bien sûr, il n'y a rien pour vous empêcher de créer un ensemble de ces tests dans la préparation d'un tel exercice ...

Phill W.
la source
4

Presque tout peut être analysé en termes de coûts et d'avantages, et je pense que cela s'applique ici.

Les avantages évidents de la première option sont qu'elle minimise le travail à court terme et les chances de casser quelque chose en réécrivant le code de travail. Le coût évident est qu'il introduit une incohérence dans la base de code. Lorsque vous effectuez une opération X, cela se fait d'une manière dans certaines parties du code et d'une manière différente dans une autre partie du code.

Pour la deuxième approche, vous avez déjà noté l'avantage évident: la cohérence. Le coût évident est que vous devez vous efforcer de travailler d'une manière que vous auriez autrement abandonnée il y a des années, et le code reste toujours illisible.

Pour la troisième approche, le coût évident est simplement d'avoir à faire beaucoup plus de travail. Un coût moins évident est que vous pouvez casser des choses qui fonctionnaient. Cela est particulièrement probable si (comme c'est souvent le cas) l'ancien code a des tests inadéquats pour s'assurer qu'il continue de fonctionner correctement. L'avantage évident est que (en supposant que vous réussissiez), vous disposez d'un nouveau code agréable et brillant. Parallèlement à l'utilisation de nouvelles constructions de langage, vous avez la possibilité de refactoriser la base de code, qui apportera presque toujours des améliorations en soi, même si vous utilisez toujours le langage tel qu'il existait lors de son écriture - ajoutez de nouvelles constructions qui rendent le travail plus facile, et il pourrait bien être une victoire majeure.

Un autre point majeur cependant: en ce moment, il semble que ce module ait eu une maintenance minimale pendant longtemps (même si le projet dans son ensemble est maintenu). Cela tend à indiquer qu'il est assez bien écrit et relativement exempt de bogues - sinon, il aurait probablement subi plus de maintenance dans l'intervalle.

Cela conduit à une autre question: quelle est la source du changement que vous apportez maintenant? Si vous corrigez un petit bogue dans un module qui, dans l'ensemble, répond toujours bien à ses exigences, cela tendrait à indiquer que le temps et les efforts de refactorisation de l'ensemble du module seront probablement largement gaspillés - au moment où quelqu'un doit jouer avec encore une fois, ils pourraient bien se trouver à peu près dans la même position que vous, maintenant, en maintenant un code qui ne répond pas aux attentes "modernes".

Il est également possible, cependant, que les exigences aient changé, et vous travaillez sur le code pour répondre à ces nouvelles exigences. Dans ce cas, il y a de fortes chances que vos premières tentatives ne répondent pas réellement aux exigences actuelles. Il y a également une plus grande chance que les exigences subissent quelques cycles de révision avant de se stabiliser à nouveau. Cela signifie que vous êtes beaucoup plus susceptible de faire un travail important dans ce module à court (relativement) court terme, et beaucoup plus susceptible de faire des changements dans le reste du module, pas seulement dans le seul domaine que vous connaissez bien maintenant. Dans ce cas, la refactorisation de l'ensemble du module est beaucoup plus susceptible d'avoir des avantages tangibles à court terme qui justifient le travail supplémentaire.

Si les exigences ont changé, vous devrez peut-être également examiner le type de changement impliqué et les raisons de ce changement. Par exemple, supposons que vous modifiiez Git pour remplacer son utilisation de SHA-1 par SHA-256. Il s'agit d'un changement dans les exigences, mais la portée est clairement définie et assez étroite. Une fois que vous l'avez fait stocker et utiliser SHA-256 correctement, il est peu probable que vous rencontriez d'autres changements qui affectent le reste de la base de code.

Dans l'autre sens, même quand cela commence petit et discret, un changement à une interface utilisateur a tendance à monter en flèche, donc ce qui a commencé comme "ajouter une nouvelle case à cocher à cet écran" finit plus comme: "changer cette interface utilisateur fixe pour prendre en charge les modèles définis par l'utilisateur, les champs personnalisés, les schémas de couleurs personnalisés, etc. "

Pour le premier exemple, il est probablement plus judicieux de minimiser les changements et d'errer du côté de la cohérence. Pour ces derniers, le refactoring complet est beaucoup plus susceptible de porter ses fruits.

Jerry Coffin
la source
1

J'irais pour vous la première option. Lorsque je code, je respecte la règle pour le laisser dans un meilleur état qu'il ne l'était.

Le nouveau code suit donc les meilleures pratiques, mais je ne toucherai pas à un code sans rapport avec les problèmes sur lesquels je travaille. Si vous le faites bien, votre nouveau code devrait être plus lisible et plus facile à gérer que l'ancien code. J'ai trouvé que la maintenabilité totale s'améliore, car si vous continuez ainsi, le code que vous devez toucher pour votre travail est de plus en plus souvent le "meilleur" code. Ce qui accélère la résolution des problèmes.

Pieter B
la source
1

La réponse, comme tant d'autres choses, est que cela dépend . S'il y a des avantages majeurs à mettre à jour les anciennes constructions, comme une maintenabilité à long terme considérablement améliorée (en évitant l'enfer de rappel, par exemple), allez-y. S'il n'y a pas d'avantage majeur, la cohérence est probablement votre ami

En outre, vous souhaiterez également éviter d'incorporer deux styles dans la même fonction plus que vous ne voudriez l'éviter dans deux fonctions distinctes.

Pour résumer: votre décision finale doit être basée sur une analyse «coût» / «avantage» de votre cas particulier.


la source