
Imagine that you were given the task to fix a part of the code. Many thoughts will arise in your head. Who wrote it? When? Or maybe he - legacy? Where is the documentation? Let's try to deal with the "legacy" thoroughly and from all sides. Andrey Solntsev
@asolntsev (
http://asolntsev.imtqy.com/ ), a developer from Tallinn-based Codeborne, will help us in this matter. Let's start.
- Andrei, are you familiar with the writings of Michael Feathers, for example, “Working Effectively with Legacy Code”? The book focuses on the importance of testing and highlights one of the key differences between legacy and non-legacy code — the presence of tests. Do you agree with this opinion?')
Absolutely agree! I will say more: unit tests are a necessary but not sufficient condition. And with unit tests you can pile in such a way that Heracles himself will not rake.
What the real Jedi mast has to do is:
- TDD - that is, tests up to the code .
- Clean code (and clean tests).
I love Robert C. Martin’s Clean Code. This is a tabletop bible for me. I strongly advise everyone. By the way, his
blog is great too.
- With clean code is understandable. And what have the TDD? Code without bugs - this is not the same as good code?Everyone thinks that TDD is about tests, which means it's boring.
Nonsense!
TDD - this is about development (test driven DEVELOPMENT). This is a way to create code so that it is NOT legacy. So that it can be maintained: debug, fix, refactor, refine. At the moment it is the only way known to mankind. Everything else - from the evil one, do not let yourself be deceived.
- There is an opinion that TDD is for weaklings, and for strong developers it’s enough just to think hard and think through the code in advance. Then there will be no bugs.TDD is not needed to find bugs.
A test is the first use of your code. Sherlock Holmes said: “First you collect the facts, and only then you build the theory. Otherwise, you will subconsciously begin to juggle the facts under your theory. ” You will not even notice how this happens! It is the same with the code: when you write a test before the code, you have to think about how it is more convenient to use it, what to call a class, a method; what parameters to pass. And if you first write the code, then you begin to juggle tests for it. As a result, the share of tests will be too complicated, the other is difficult to correct, and some will remain unwritten. And here we have the legacy code in person!
- Continuing the conversation about testing - in your opinion, which tests are correct and in what situations, for example, unit tests?Let's start with unit tests. They are needed in every project, here without options.
Of course, other types of tests are also important: integration tests, UI, performance tests. This is already in different projects in different ways. One thing is important: they should be an order of magnitude smaller than unit tests. About the
pyramid of testing everything in the course, I hope.
- What should be the code coverage tests? Is 70% Enough? How not to overdo it with the "diagnosis" of the project?Of course, you need to strive for maximum coverage. All this talk about 30% or 70% coverage is from misunderstanding. We must strive for 100% (although this is an unattainable limit).
Everyone knows that 100% coverage is impossible, but not everyone correctly understands why. It’s not at all because “only critical things have to be tested,” “there’s no need to test getters,” and “we have more important things to do.”
Full coverage is not possible because in any system the code communicates with the outside world. Drawing pixels on the screen, device management, and so on - for this code, unit tests are impossible, you need to check it with your own eyes. Our professionalism is to keep this layer of “communication with the outside world” as thin as possible, and to take all the logic out of it. And it needs to be tested.
Roughly speaking, if overdue loans need to be highlighted in red, then the “what kind of overdue” logic should be put into a separate method with unit tests, and “highlighting” - separately. Well there, css or whatever. And TDD stimulates the separation of these layers, that's what's great. This is his strength! And tests are just a side effect.
- How much time do you need to devote to the creation of unit tests, and at what stage of the project do they play a key role?Unit tests play a key role at all stages. There is no stage where “it's time to start writing unit tests” or “you can no longer write unit tests”.
If you do everything right, you can never answer the question of how long it takes to write unit tests. This is part of the development. Nobody asks how long it takes to apply brick with mortar, and how much - laying. These are all necessary steps and that's it. You can not throw one of them, otherwise your wall will fall apart on the second day, and you will get a bunch of legacy bricks.
- How does the process of writing tests look like?You write a red test for 10 minutes, make it green for 10 minutes, refactor 10 minutes. No one, of course, measures this time for sure - sometimes it is 5: 30: 0, and sometimes 180: 5: 60. Never mind. It is important that you will be able to change the code at the same rate and in a month, and in a year with the same pace, at a predictable rate. Constant speed in the long run is much more important than high instantaneous speed at the start.
I advise this video where TDD is clearly and cheerfully shown: “The
boy was naked - the boy protested! "Do not be intimidated by the length, there is only unit tests for the first 30 minutes.
- Andrei, if TDD is so useful, why is it used so little?It's simple. TDD is a discipline. It's like a sport: everyone knows that sport is useful, but lazy. The most honest ones admit that they are lazy, the rest find excuses, and the most notorious begin to invent explanations why the sport, it turns out, is even harmful. The same with TDD.
The funny difference is that people lying on the couches do not call themselves athletes. It's all fair. And coders do not write tests and at the same time call themselves developers. Incredible, right? But it always ends the same way. The athlete who misses training quickly loses the competition. Similarly, the code written not through tests, instantly becomes legacy. It's all fair.
TDD is a discipline. It is not enough to know TDD, it should be used every day, at each code change. Even the most vehement fans of TDD are continually forgotten and find themselves in an unattractive position, writing publicly the code before the test. Pair
programming helps a lot with this. This is another practice that I strongly advise.
TDD is a theorem that needs to be proved every day.- We discussed issues related to test coverage. It is important to address other legacy issues, such as compatibility. There are projects where the code does not change for the sake of compatibility with even more ancient libraries. If you draw analogies with the automotive industry, do not you think that attempts to use outdated components and assemblies in new designs are destructive, and the average code level in the project must meet the expectations of time?I'll tell you one bike. My brother worked in the chemical laboratory of the University of Tartu. A delegation from Europe came to them. Everything was shown to them: new premises, new equipment, everything. And then they noticed the old Soviet unit somewhere in the corner. The delegates made round eyes and asked, they say, why don't you throw it away? To which the answer followed: “Do not believe it! Because he ... WORKS! ”
There is no single answer. You can update all dependencies daily and treat it as necessary hygiene. You can even ask Maven or Gradle to do this automatically. And you can make a fork of the original library and sit for twenty years on the old version. Perhaps it depends on dependencies. If I strongly depend on Selenium, use new features, then I update it often. If I don’t depend on new log4j features, I’m sitting on the ancient version. Everything works.
The older I get, the more I tend to the fact that you should not chase after updates. The new Play framework was no better than Struts2. Maven is no better (and even worse) than Ant. TestNG is definitely worse than JUnit (and after all the “new generation” in the title!). Hibernate3 was harder than Hibernate 2. Spring boot is harder than Spring MVC. But 10 years have passed! I am silent about new JavaScript libraries.
So not everything is new, that smeared with grease.- Have you encountered similar situations in your practice (in addition to the previous question)? How did you get out of these situations? What strategy do you follow, rewrite everything (the term greenfield is often used) or do refactoring?Of course, met.
Yes, it's just a matter of organization. If you want to update some kind of dependency, which is backward incompatible, you just need to allocate time for this, agree with the team so that no one has anything urgent at that moment, and refactor. Many will begin to cry: "We do not give time for it." Well, I do not know, maybe not so need to be updated? It would be critical - would give time.
As for greenfield, I'm pretty skeptical.
Of course, the bad soldier who does not want to rewrite everything from scratch.
But in my entire life I have never seen a system rewritten from scratch and become better. Usually it turns out the same or worse. The rewriter starts with great excitement, then he faces one problem, on the other; he understands that he no longer has time ... Leaves technical debt in one place, in another - and now you already have a legacy code in the new project, although half of it is not written.
In order to rewrite something from scratch, you must have a very clear understanding of how and with what you can do better. And why your predecessors did not succeed.
- Maybe there is a proven "hybrid" option?There is a hybrid option. You can rewrite from scratch any part of the system, the most
legacy . And leave the old version too, so that they work in parallel. And transfer to a new version of all the pieces of the system gradually, one by one. Yes, it requires patience and time, but this is the only way. Reluctance? Then don't start at all. Haste is not the place.
- How do you distinguish a bad legacy code from the most “overclocked”? Should I chase performance to the detriment of future support?The overclocked code is a type of a dozen lines of abstruse operations with bytices in order to work faster? I think that such a code is in real life from the category of tales. Everyone thinks that only steep peppers do that, and they also want to learn. In application programming, this is almost never needed.
And if you still need it, then no one bothers to make this code "overclocked", and well readable, and tested. Come on, you're a professional. First write unit tests, and then overclock. Didn't master unit tests? There is nothing to disperse - the nose is not old enough.
- Do you think the legacy code can slow down the development of the entire system?Of course it can! And slow!
Actually, this is the most important thing that usually slows down. Every next change is made more difficult, the risk of breaking something more.
- Can you give advice on the need to get rid of the terrible and slow "monster" in the project, if you see it? How did they react (if such situations occurred)? Changes led to a positive effect (if such situations occurred)?You've probably heard about the scout rule: leave the clearing a bit cleaner than it was before you. That is, do not just change the desired line, but also a sensitive factor, add the missing test. This way your project will be constantly improved.
But here it is important not to go too far. It is necessary to make a clearer clearing, and not to plow the entire forest without looking back. I saw this (and did) many times: the next hot head decides to rewrite everything, goes away for a few hours / days in a hard refactoring, gets stuck, sinks and as a result does only worse. At best, it rolls back its changes and gets off with a little waste of time.
Repairing the legacy code is definitely an extremely useful exercise. Excellent pumping skills of analytical thinking, refactoring and understanding of good code. As an exercise - I advise.
BUT.
Repairing the legacy code is the same as hacking the hydra head. Grow three new. And yet it turns out that the first one was not completely cut off, and not only that you were bitten, so something sticky and green spilled out of it and soiled everything around. And then the manager came and said: “What the hell did you touch, did it work?”
Therefore, if you repair the legacy code, seriously hoping to improve something in the project - good luck, good health and good mood. Something somewhere sawed, but by and large nothing will change. This is the case when it is necessary to treat the symptom, and the disease. Do not squeeze the pimple, and go to the street to run. Understand what the problem really is. Why do you have bad code in your team? Are people in a hurry? Do not understand the architecture? Conflict with each other? Do not know how to negotiate? I met all this in real projects with smart adults. And this should be treated, and not renamed variables.
- You are of the opinion that the beauty of the code is not yet an indicator of legacy status?The "beauty" of the code is evil! Never call code “beautiful,” even mentally. The most sophisticated govnokod is obtained from the desire to "make beautiful." The code can be readable, predictable, concise - but not beautiful! “Beautiful code” is like a “good hero” in an essay on literature. Make an effort to find a more accurate wording - and at the same time answer many other questions.
- Is it possible to assert that a violation of the standard code design agreement is legacy?Standard conventions are useful, but do not overestimate them. Any code convention becomes legacy even faster than the code itself. Nobody ever reads them. Did the swan, the crab and the pike have strings of a standard length - and did it help them much?
And you should not waste your strength for them. Set up once the IDE of all project participants so that it itself formats the code as it should, and no longer think about it.
- As part of the legacy, we discussed issues of testing, performance, dependencies in projects, beauty code, and touched upon the topic of development. Your opinion about the legacy code and the reasons for its occurrence did not change over time?Of course, changed. At first, I, like everyone, thought that the bad code was written by Hindus and stupid colleagues. Or "Junior". But everything is not so simple.
I do not like too simple explanations. It’s too easy to blame it on those assholes. And the legacy code arises from the most intelligent people, and in the best projects.
- And you have?Oh yeah. Oh yeah!
So here I am broadcasting, so smart, about the legacy-code, and I myself have an open-source project
Selenide , which I haven’t blinked with legacy-code. No, it works fine, users are happy, but it's harder for me to change it over time. There are tickets that have been hanging for a couple of years, because it's really hard for me to make them. Change in one place - breaks in another. Yes, there are integration tests, thanks to which I am not afraid to break something. But there is no help from them in the sense that changing the code fails.
Once I understood: the key problem is that in this project I did not initially write unit tests. It seemed, why tests if this library itself is intended for writing tests? She and so will be tested along and across. But it turned out - there it is, Mikhalych! Unit tests are needed to avoid legacy code.
Well, that's what I do: I sit down every six months or a year and rewrite some module to hell with the dog. Since this is a hobby, I am from it. But if it was work and was expressed in money - would it be worth rewriting? Oh, I do not know ...
—How do you think, in what shades should a legacy code be perceived? When is it justified?I have a simple recipe. Take a break from lambdas and brackets for a second and imagine yourself in the client's place. This is a great cleansing brain.
As a customer, you bring your car to a car-care center, ask to see what's knocking there.
And the mechanic says to you: “I changed what was knocking, but you also have a leaky fuel pump, will we change it?” This is a good mechanic. A bad mechanic would change the pump right away. A good mechanic reported a problem and wrote down the options: you can replace it now - it will cost so much. You can not change, then the winter is still reaching, but then all the hoses will fly - it will be three times more expensive. And you yourself as a client decide. One will say: Why go twice, change everything! Another will say: “Now there is no money, but I still need winter tires. Let's wait until spring. ”
Do you know what the worst mechanic does? Keep silent Will only do what they asked.
And how do we all behave with you, dear programmers?
Those very hotheads who strive to immediately change everything - they are like who? Right, like a bad mechanic. And those "matured", they decide to leave everything as it is? Alas, as the worst. What does a good mechanic have to do with legacy code? And how often do we do that? Think about it.
- Andrei, TDD, unit tests - all this sounds good, but you understand that for 99% of our readers it is very heavy due to various reasons, often independent of them. Can you finally give a simple recipe how to deal with legacy code?You want a recipe, but when I give a recipe - proven, reliable, you say that you do not have time for it. And keep complaining about legacy code. What are you waiting for me, the red pill - drank, and you're already in the matrix? There will be no pills.
People are like people. They love to hurry, but it has always been ... Humanity loves simple recipes ... In general, they remind the old ones ... the microservice question only spoiled them ...In general, the magic will not get rid of legacy-code. But you stay there, good luck, good mood!
- Thank you so much for the frank and positive conversation. Let's hope that readers will discover for themselves, albeit not simple, but still solutions in the battle with legacy. One of the recent articles is direct confirmation of the importance of testing. Personally, you convinced me.Thank you for the invitation.
And I will take advantage of the situation and invite all readers to visit us in Tallinn. We have a beautiful medieval city, good craft beer and great events for IT people, where anyone can come for free:
devclub.euThank you for your attention and patience!
More interesting reports, technical, hardcore, you will find in the program Joker 2016 . If you are working with Legacy, you should pay attention to the following reports:
« legacy enterprise Java 8 — , « »».