our own practical experiences enriched with opinions and knowledge of various esteemed experts and super heroes such as: Katrina Owen Sandi Metz Corey Haines Jay Fields and other respected people Kent Beck Martin Fowler Uncle Bob Martin Michael C. Feathers
Project Leader understood it How the Analyst designed it How the Programmer wrote it How the Business Consultant described it How the project was documented What Operations deployed How the customer was billed How it was supported What the customer really needed
the web. Web applications are more than our daily business. We support our customers in close cooperation from the initial idea through to the launch - and beyond.
the needs of their businesses generating value (usually it’s the main system and the source of income…) people and processes depend on it just because code is old doesn’t mean it has to be thrown away Some people in our industry call it legacy while the codebase is less than 2 years old?! #vintage #oldtimer #classic
referring to the eventual consequences of any system design • complexity • coupling • hard to change • anti patterns *sometimes required to move projects forward
referring to the eventual consequences of any system design the result of unpaid debt • complexity • coupling • hard to change • anti patterns *sometimes required to move projects forward
code smells • metaprogramming madness • long parameter list • shotgun surgery • duplicated code • feature envy • large class • long method • case statements • complex conditionals referring to the eventual consequences of any system design the result of unpaid debt • complexity • coupling • hard to change • anti patterns *sometimes required to move projects forward
code smells refactoring • metaprogramming madness • long parameter list • shotgun surgery • duplicated code • feature envy • large class • long method • case statements • complex conditionals referring to the eventual consequences of any system design the result of unpaid debt • complexity • coupling • hard to change • anti patterns *sometimes required to move projects forward
requirements needless complexity unnecessary / tough deadlines high fluctuation and poor on-boarding not enough training / further education doing agile wrong (yeah, we’re doing scrum/kanban we’re so agile!) The influence of management* on legacy: Even a development process that encourages code reviews and pairing can yield bad technical decisions which effect the whole architecture:
to come up with estimates that are meaningful. even the simplest code changes take a long time to implement (or release) it seems like no amount of time will be enough to understand everything you need to do to make a change Expensive
changes are risky? What changes do we have to make? How will we know that we've done them correctly and haven't broken anything? Risk Often we don't know how much of the existing behaviour is at risk when we make our changes.
points 3. Break dependencies 4. Write tests 5. Make your changes and refactor Algorithm: debugger, inspect, puts, tap, raise, console.log, … internal vs 3rd party, most obvious impediment to testing this can be hard for side effects sometime requires creative and ugly technics Always leave the campground cleaner than you found it Michael C. Feathers
impediment to testing 1. Identify change points 2. Find test points 3. Break dependencies 4. Write tests 5. Make your changes and refactor Algorithm: debugger, inspect, puts, tap, raise, console.log, … this can be hard for side effects sometime requires creative and ugly technics Always leave the campground cleaner than you found it Michael C. Feathers
code that is hard to change, fragile, and non-reusable. Whenever we bring up on our screens a nasty batch of tangled legacy code, we are experiencing the results of poor dependency management.
code that is hard to change, fragile, and non-reusable. When classes depend directly on things that are hard to use in a test, they are hard to modify and hard to work with. Whenever we bring up on our screens a nasty batch of tangled legacy code, we are experiencing the results of poor dependency management.
test for a piece of functionality shouldn’t be too bad. We instantiate a class, call its methods and check the results. What could go wrong? GOALS • complete • stable • fast • few
the test. It could be private or have some other accessibility problem. It might be hard to call the method because it is hard to construct the parameters we need to call it. We might need to understand first some other object that the method uses. The method might have (bad) side effects modifying a database send notification launching a cruise missile … How to GOALS • complete • stable • fast • few <test>
the result of “a process”, and then comparing future runs against the saved golden master version to discover unexpected changes. So you expect, that what it does now, is correct.
some output and save the result (snapshot) and every time you run the test again you compare: * the same: great the test pass, because everything works as before * different: the test fails and the human has to check the output again >>>
undocumented code 2. Write an assertion that you know will fail 3. Let the failure tell you what the behaviour is 4. Change the test so that it expects the behaviour that the code produces 5. Reason about the code and exercise every branch Golden Master Testing: 6. Repeat until the code is covered
refactor A characterization test is not a test you want to keep around. • You use the test to get coverage, • you refactor, • cleanup / write new specs • and then you throw it away! We are trying to put in a mechanism to find bugs later.
code is better than no code) Cyclomatic Complexity ABC Score (Code Smells) Churn Afferent / Efferent Coupling Mutation Coverage Metrics: Usually language agnostic and a helpful tool, but metrics should never be a goal:
the beginning composing methods (extract method, replace method with object) moving features (move method, extract class) organizing data (replace hash with object, replace type code with polymorphism) simplifying conditional (decompose, recompose, null objects) making method calls simpler (separate query from modifier, replace constructor with factory) dealing with generalization (template method, extract module, inheritance) learn when to start, when to stop when you have “refactoring tickets” you're doing it wrong (it’s part of your work!) not having the time for refactoring (because of deadlines?) is usually a sign that you need to do some refactoring Refactoring: pay your bills!
disaster. As ugly as the mess looks now, focus on the real problems. When you need to add a new feature: add some specs to get confidence clean up add new code refactoring hat, feature hat Don't mix an unfinished refactoring with other new tasks. Your goal is to leave the code computing exactly the same, like you found it. Discipline Refactor when it’s necessary! (It will never be finished…)
has to work mostly correctly, before you refactor) if it is full of bugs and you cannot stabilize it if there are big changing requirements, the current app can't support if time and money are no constraints When is it easier to start from scratch, instead of refactoring a big mess? + + + + (For most developers this is the favourite choice, but it’s usually the most expensive)
Favour piece by piece rewrite over big bang rewrite and rebuild incrementally. This strategy will let you: • focus only on the important and critical parts • recover knowledge which got lost in complex code • preserve investments which went into the code already (time for bugfixes, requirements…)
Favour piece by piece rewrite over big bang rewrite and rebuild incrementally. What's the cost of debt? Which parts are less critical? Think of extra costs for maintenance and extension caused by overly complex code. This strategy will let you: • focus only on the important and critical parts • recover knowledge which got lost in complex code • preserve investments which went into the code already (time for bugfixes, requirements…)
for legacy code treat your legacy and former developers with respect the business always wins* (changing requirements) keep your code well tested break dependencies and decouple business logic maintenance == refactoring (pay your debt!)