Refactoring
20 Feb 2022It's 2022, and I haven't written a single blog after I created this website! So here I am finally writing my second blog. As a natural extention of my first blog on how to write clean code, this blog is a summary on how to refactor code based on the book Refactoring by Martin Fowler
What is refactoring? When to refactor?
When you have to add a feature to a program but the code is not structured in a convinient way, refactor the program to make it easy to add the feature, then add the feature. Before you start refactoring, make sure you have a solid suite of tests. These tests must be self - checkings. Refactoring changes the programs in small steps, so if you make a mistake, it is easy to find where the bug is. Sometimes refactoring will have a significant impact on the performance. Even then, do it, because it's much easier to tune the performance of well-factored code.
Principles in refactoring
If someone says their code was broken for a couple of days whilte they are refactoring, you can be pretty secure they were not refactoring. Refactoring is always done to make the code "easier to understand and cheaper to modify. The first time you want to do something, you just do it. The second time you do something similar, you wince at the duplication, but you do the duplicate thing anyway. The third time you do something similar, you refactor. You need to refactor when you run into ugly code, but excellent code needs plenty of refactoring too. The hwole purpose of refactoring is to make programs run faster, producing more value with less effort. Even if you you know exactly what is going on in your system, measure performance, don't speculate. You'll learn something and 9 out of 10 times, it won't be that you were right.
Building Tests
- Make sure all the tests are fully automatic and that they check their own results.
- A suite of tests is a powerful bug detector that decapitates the time it takes to find bugs.
- Always make sure a test will fail when it should.
- Run tests frequently. Run those exercising the code you are working on at least every few minutes, run ll tests at least daily.
- It is better to write and run incomplete tests than not to run complete tests.
- Always test boundary conditions.
- Don't let the fear that testing can't catch all bugs stop you from writing tests that catch most bugs.
- When you get a bug report, start by writing a unit test that exposes the bug.
Set of refactoring
- Extract function: inverse of inline function. If you have to spend effort looking at a fragment of code and figuring out what it's doing, then you should extract it into a function and name the function after the "what".
- Inline function: inverse of extract function.
- Extract variable: inverse of inline variable - add name to an expression.
- Inline variable: inverse of extract variable.
- Rename function: change a bad function declaration.
- Encapsulate variable: To move widely accessed data, often the best approach is to first encapsulate it by routing all it's access through functions. That way we turn the difficult task of reorganizing data into simpler tasks of reorgnizing functions.
- Rename variable: rename a badly named variable.
- Introduce parameter object.
- Combine functions into class: a group of functions that operates closely on common body of data can form a class.
- Encapsulation: The most important criteria to be used in decomposing modules is to identify secrets that modules should hide from the rest of the system.
- Encapsulate record: turn record into class.
- Encapsulate collection: provide add/remove methods for collection.
- Replace primitive with object.
- Replace temporary variables with query.
- Extract class: When a subset of data and a subset of methods seem to go together.
- Hide delegate: inverse of middle man. If a client calls a method defined on an object, the client needs to know about this delegate object. If delegate changes it's interface, changes propogate to all client of the server that use delegate. We can remove this dependency by placing a simple delegating method on the server that hides the delegate.
- Remove middle man: inverse of hide delegate.
- Move functions: One of the reasons to move a function is when it references elements in other contexts more than the one it currently resides in. Move function to other class.
- Move field: If a change in one record cause a filed in another record to change too, that's a sign of a field in the wrong place.
- Move statements into functions.
- Replace inline code with function call.
- Split loop: Do one thing in the loop.
- Replace loop with pipeline.
- Remove dead code.
- Split variable: Remove assignments to parameters. Any variable should have just one responsibility.
- Replace derived variable with query.
- Change reference to value and vice-versa.
- Replace nested conditional with guard clauses.
- Replace conditional with polymorphism.
- Introduce special case: A common case of duplicated code is when many users of a data structure check a specific value and then most of them do the same thing.
- Introduce assertion: When you see that a condition is assumed to be true, add an assertion to state it.
- Separate query from modifier: A function that returns a value should not have observable side effects.
- Remove flag argument: replace parameters with explicit methods.
- Replace parameter with query: If a function is able to derive the value, don't send it exclusively.
- Replace query with parameter: Replace internal reference with a parameter, shifting the responsibility of resolving the reference to the caller of the function.
- Replace constructor with factory function.
- Replace function with command.
- Pull up method/field/constructor body.
- Pull down method: if method is only relavant to one subclass, pull down;field.
- Replace subclass with delegate: Favor object composition over inheritance.
- Replace superclass with delegate: If functions of the superclass don't make sense on the subclass, that's a sign that we shouldn't be using inheritance to use superclass's functionality.
Bad smells in code
The below table lists out the bad smells in code and which refactoring could be used to change it.
Smell | Refactoring technique |
---|---|
Mysterious name | Change function declaration, rename variable/field. |
Duplicated code | Extract function, slide statements, pull up method. |
Long function | Extract function, replace temp with query, introduce parameter object, preserver whole object, replace function with command, decompose conditional, split loop. |
Long parameter list | Replace parameter with query, preserver whole object, introduce parameter object, remove flag argument, combine functions into class. |
Global data | Encapsulate variable. |
Mutable data | Encapsulate variable, split variable, slide statements, extract function, separate query from modifier, remove setting method, replace derived variable with query, combine functions into class, change reference to value. |
Divergent change | Split phase, move function, extract function/class. |
Shotgun surgery | Move function, move field, combine functions into class, split phase, inline functions, inline class. |
Feature envy | Move function, extract function. |
Data clumps | Extract class, introduce parameter object, preserver whole object. |
Primitive obsession | Replace primitive with object, replace type code with subclasses, replace conditional with polymorphism, extract class, introduce parameter object. |
Repeated switches | Replace conditional with polymorphism. |
Loops | Replace loop with pipeline. |
Lazy elements | Inline function, inline class, collapse hierarchy. |
Speculative generality | Collapse hierarchy, inline function, inline class, change function declaration, remove dead code. |
Temporary field | Extract class, move function, introduce special case. |
Message chains | Hide delegate, extract function, move function. |
Middle man | Remove middle man, inline function, replace superclass with delegate. |
Insider trading | Move function/field, hide delegate, replace subclass/superclass with delegate. |
Large class | Extract class, extract superclass, replace type code with subclasses. |
Alternative classes with different interfaces | Change function declaration, move function, extract superclass. |
Data class | Encapsulate record, remove setting method, move function, extract function, split phase. |
Refused bequest | Push down method/field, replace subclass/superclass with delegate. |
Comments | Extract function, change function declaration, introduce assertion. |