lundi 7 novembre 2022

Breaking out of legacy with 3P

Protect, Prepare, Produce - 3P. A recipe to break out of legacy

If you prefer you can read this in French 

Photo by Ugne Vasyliute on Unsplash

Maybe you've already had experiences like

  • A refactoring project over several months was been cancelled
  • Tests that don't really help, perhaps even slowing you down
  • Refactorings that don't turn out that good 
  • Code that continuously degrades, despite the stories dedicated to refactoring and testing that you do regularly.

If this is the case you would probably be interested in knowing why these efforts are not bringing the expected benefit and why the 3Ps would help you.

Let's first consider the classic approaches of either doing refactoring and testing in dedicated stories, or doing most of the refactoring and testing at the end of the story. These approaches seem attractive at first glance, but they have a big ROI problem. Indeed, they lengthen the time between investment (refactoring) and return on investment (a new feature, facilitated by said refactoring). 

Precisely, when we refactor in a separate project or story, or even at the end of a story: who reaps the value, and when

It's the next person who touches the code, hopefully, sometime later. And that's provided that the refactoring actually turns out to facilitate the future work that we don't know beforehand... Basically the ROI will be for someone else, maybe. And this in a potentially distant future. Not ideal, but we can notice that the ROI is closer if we do it at the end of the story than if we do it in a separate project.

Same question for tests. Who reaps the value of tests written after the fact? And when will that be?

The answer is essentially the same. It's the next person who touches the code, hopefully, sometime in the future. Provided that the tests are still relevant with regard to the new need, which we didn't know about when we wrote the tests.

In short, we can see that there is a time that can be more or less long between the investment and the benefit. The shorter the delay, the better. But that's not all. In my experience, we don't always do the ideal refactoring and we don't always do the ideal tests. One of the reasons is that we don't know the future very well. Could we minimise these "mistakes"?

So we should find a way to get feedback on the quality of our tests and our refactoring as quickly as possible, and to minimise the uncertainty about the future. If we did this, we'd have refactoring and tests  that would bring much more for the same amount of time invested. Given that our investments pays off better, we're likely to increase the investments, possibly by a lot!

3P

This is where the idea of the 3Ps comes in, where we not only bring the investment closer to the benefit, but also we are the foremost beneficiaries of the tests just wrote and the refactoring we just did! This has the added benefit that any ill conceived tests and refactoring are immediately brought to the attention of just the right person to fix the issue.  

The idea is simple, for each story break down the work into 

  1. Protect
  2. Prepare
  3. Produce

Protect

Setting up the missing tests

First we have to protect the changes with tests. To do this, we need to identify the code that needs to be touched in order to add any missing tests (for the upcoming refactoring). We don't worry about covering anything but the code that will be changed, that would be a distraction

The code that will be affected must be perfectly covered with tests. Any less and we wouldn't dare to thoroughly refactor in the next phase. However, we can completely ignore the maintainability of the tests, we're only concerned with protecting the refactoring phase. So this phase is way shorter than what we might expect.

We need tests that will not break during refactoring, so low-level tests are not always the best. Typically, duplicating and adapting some high level tests, even end-to-end tests, works quite well. Quite often the code is not testable at all, but with high level tests anything is testable.

Prepare

Refactor in depth to make the code fit for the new feature. Address testability

We make sure that the new functionality can be inserted in the code in an elegant way. 

We also make the code easily testable. Maybe we wrote end-to-end tests in the protect phase. This is the time to refactor to move those tests to a level where they're fast, predictable and easy to understand. Basically improve maintainability of the tests without sacrificing much of confidence and refactorability.

Produce

Code the new functionality in TDD

Since the code is clean and testable, this phase can be done using TDD, regardless of the code at the start.

Conclusion

With the 3Ps we write the tests, to do ourselves the refactoring behind. We minimize the time between refactoring and introducing the new feature. We know the functionality before we start. We can work in TDD to benefit from the new tests ourselves. How could the ROI be better? And if it is, wouldn't we do more of it, a lot more?

End notes

I'm not saying to avoid refactoring at the end of the story, just that if that's all we're doing, we're missing a lot.  

I'm aware don't go into much detail about how the 3Ps work in practice. I'll try to make it clearer in a subsequent post.

Translated (mostly) with www.DeepL.com/Translator. Originally posted in French



mercredi 2 novembre 2022

3P pour sortir du Legacy

Protéger, Préparer, Produire - 3P. Une machine pour sortir du legacy.

Photo by Ugne Vasyliute on Unsplash
Photo by Ugne Vasyliute on Unsplash

Peut-être que avez-vous déjà vécu des expériences du style

  • Un projet de refactoring sur plusieurs mois a été annulé
  • Des tests qui n'aident pas vraiment le dev, voire le ralentit
  • Des refactorings qui finalement se montrent pas terribles 
  • Le code qui se dégrade continuellement, malgré des stories dédiées au refactoring et aux tests que vous faites régulièrement.

Si c'est bien le cas vous seriez probablement intéressés par savoir pourquoi ces efforts n'apportent pas le bénéfice escompté et pourquoi les 3P vous aiderait.

Considérons d'abord les approches classiques de faire du refactoring ou tests dans des stories dédiés, ou le simple fait de faire la majorité du refactoring et les tests en fin de story. Certes ces approches semblent attrayantes à première vue,  mais elles ont un gros problème de ROI. En effet, elles rallongent le temps entre investissement (le refactoring) et retour sur investissement (une nouvelle fonctionnalité, facilité par ledit refactoring). 

Justement, quand on fait du refactoring dans un projet ou story à part, ou même en fin de story : qui récolte la valeur, et ce sera quand

C'est la prochaine personne qui touche le code, d'ici qq temps, avec un peu de chance. Et ce à condition que le refactoring facilitera bien le futur travail qu'on ne connaît pas... En gros le ROI sera pour quelqu'un d'autre, peut-être. Et ce dans un futur potentiellement lointain. Pas idéal, mais on peut remarquer que le ROI est plus proche si on le fait en fin de story que si on le fait dans un projet à part.

Même question pour les tests. Qui récolte la valeur des tests écrits après coup? Et ce sera quand?

La réponse est essentiellement la même. C'est la prochaine personne qui touche le code, d'ici qq temps, avec un peu de chance. A condition que les tests seront encore pertinents face à la nouvelle demande, qu'on ne connaissait pas lors de l'écriture des tests.

Bref on voit bien qu'il y un temps qui peut être plus ou moins long entre l'investissement et la récolte du bénéfice. Plus ce temps est court plus on va y trouver un intérêt. Mais ce n'est pas tout. Dans mon expérience, on ne fait pas toujours le refactoring idéal et pas toujours les tests idéaux. Entre autres parce qu'on connaît pas bien le futur.

On aurait donc intérêt à trouver une façon de récolter le plus rapidement le feedback sur la qualité de nos tests et notre refactoring, ainsi que de minimiser l'incertitude par rapport au futur. Si jamais on le faisait, on aurait alors des actions de refactoring et de tests qui apporteraient nettement plus pour un même temps investi. Dans ces conditions on aura tendance à en faire plus, voire beaucoup plus.

3P

C'est là que se trouve l'intérêt des les 3P, où nous rapprochons non seulement l'investissement du bénéfice, mais en plus c'est nous même qui sommes les premiers bénéficiaires des tests que nous venons d'écrire et du refactoring que nous venons d'opérer!  

L'idée est simple, pour chaque story décomposer le travail en 

  1. Protéger
  2. Préparer
  3. Produire

Protéger

Mise en place des tests manquants

D'abord il faut protéger les modifications avec des tests. Pour cela il faut identifier le code qu'on doit toucher pour compléter avec des tests manquants. Attention il s'agit uniquement des tests pour le code qui sera touché, tester plus serait une distraction

Le code qui sera touché doit être parfaitement couvert avec des tests bétons. Sans quoi on n'osera pas faire de refactoring digne de ce nom.

On fait plutôt des tests haut niveau dans cette phase afin qu'elles ne cassent pas lors du refactoring. Pour gagner beaucoup de temps, on ignore complètement l'aspect maintenabilité dans cette phase (voir la suite)

Préparer

Refactoring en profondeur pour rendre plus facile la nouvelle fonctionnalité

On fait en sorte que la nouvelle fonctionnalité puisse s'insérer de façon élégante dans le code. 

On rend également le code facilement testable. Peut-être qu'on a écrit des tests end-to-end dans la phase protéger. C'est le moment de refactorer pour rendre possible de déplacer ces tests à un niveau où ils seront plus rapides et où il sera plus facile de maintenir les tests.

Produire

Coder la nouvelle fonctionnalité en TDD

Puisque le code est propre et testable, cette phase peut se faire en TDD, quel que soit le code au départ.

Conclusion

Avec les 3P nous écrivons les tests, pour faire nous même le refactoring derrière. Nous pouvons travailler en TDD pour bénéficier nous même des nouveaux tests. Nous minimisons le temps entre refactoring et introduction de la nouvelle fonctionnalité. Nous connaissons la fonctionnalité avant de commencer. Comment le ROI pourrait-il être plus idéal?

Et toi lecteur, tu fais comment?

Notes de fin

Je ne dis pas qu'il faut éviter le refactoring en fin de story, simplement c'est pas la part principale.  

Je ne donne pas beaucoup de détails sur comment s'articule les 3P concrètement. Je vais tâcher à rendre ça plus claire dans un autre post.