Refactoring legacy code
True story
Aki Salmi@rinkkasatiainen
• Hiking guide• Supervisor • Programmer
• Blipper – but no pics today.
Refactoring legacy code
True story
My goals for the talk
• Show how what I have learned at coderetreats have been taken into production use• GOAL 1: Encourage us to practice the craft
• Show what I’ve done – Test Driving my design• Show steps rather than finished ‘product’• GOAL 2: initiate discussions on what I’ve done
• Learn• GOAL 3: reflect on my work and decisions and how
they actually seem now.
Tips for listeners
• There’s going to be a lot of code
• Rather than reading the code, try to smell it.
• How clean the code seems to be?
What I’ve done – what I believe in
• Is not either good or bad. • It has bugs (I’ve seen those)• It provides value to the customer every day
What is valuable (for someone)
• Client• clients of the Client• Myself & fellow ambientians
• How likely this code is going to change?• How likely the change I make is going to introduce
bugs in future additions to this feature
Key process decisions
• TDD as design tool.• Unit tests for changes• Practice TDD in real environment. Try to get feedback
• Refactor often• Keep tests clean
• ”if it is not important for the test, it is important not to be in the test!”
• Note: Builder pattern
Key process decisions
• Use proper tools• GIT, IntelliJ IDEA, VIM
• Hamcrest, mockito, various other open-source components
4 elements of simple design
1. Passes its tests
2. Minimizes duplication
3. Maximizes clarity
4. Has fewer elements
Coderetreat
• 1 day of coding• Pair programming, 6 different pairing partner• Learn through pairing• Deliberate practice• experiment
Key takeaways from coderetreats
• Baby steps – commit to git often. Rebase to keep git log clean
• TDD-as-if-you-meant-it• Avoid conditionals, switches, try/catch• Only 4 lines per method• Avoid naked primitives• Only one assert/behavior per test• Sapir-Whorf hypothesis• Tell – Don’t ask: no getters/setters for objects
Object calisthenics
1. One level of indentation per method
2. Don’t use the ELSE keyword
3. Wrap all primitives and Strings
4. First class collections
5. One dot per line
6. Don’t abbreviate
7. Keep all entities small
8. No classes with more than two instance variables
9. No getters / setters / properties
Background of the system
Domain logicPOJO, Spring, Hibernate
UI (Grails) Controllers + Views
Database
A live service since 2008
Providing value to Client and end-users every day since
Case 1: adding a search parameter
Search for a product
The method had 21 parameters and 200LOC
And there’s more
..and more
..you see the pattern.
What to doadd yet another parameter?
What I did was a brief study
• How the methods are used in the service?• Grails-based service (200LOC) uses it
• Determine the current responsibilities• Service builds valid parameters, DAO consumes it.
• Where the changes could be made?• Both the service & DAO
• Is the method likely to change later?• YES
Step 1
An integration test, See the builder-pattern
If it’s not important for the test, it is important not to be on the test
I changed the signature
The first model
And part of the service
Key decisions
• Factory to hide implementation details• Sometimes Criteria handled Date, sometimes Calendar
• Make it first to work, refactor then.
• Create a ProductSearchCriteria per type – think about the name of the object
• There were no tests before – try to make minimal impact on code.
Test on localhost.
• The context is a bit more complex than I originally thought:• In one case, it was not enough to limit on Date/Calendar
• Thus, the original factory-idea would turn into a bit more complicated problem
Step 2
Firstly: factory into builder
• Build what you need – add items to CompositeSearchCriteria as need arises
• Again – do the minimal changes to the grails-service in order to minimize errors
Second try
Second try
Tests for builder
Buildermaybe too many responsibilities?
Key decisions
• Do the smallest amount that is needed• Builder to support only those methods that are used.
Step 3
Minor changes to fix a bug
Bug # 2
Minor changes to fix a bug
Bug # 3
Sometimes maths just is too much
Bug # 4
Minor changes to fix a bug
Learnings
• Fast to refactor• Next time: start from integration test
• But one cannot integration test the whole – too many parameters
• Minor changes somewhere in the controller code caused it to fail on other places.
• Later, a colleague joined me. His first task was to add a new concept to domain.• Also for search
Case 2: Attachment handling
Targets
• System used to have ~ 20 different attachment types.
• Only 5 were needed.• Earlier supported only 1 attachment / type /
product. Now should support more.
AttachmentService(Dump?)
Decision to be made
• Where to start?• The only (business) knowledge came from the
previous changes
• I decided to tackle the switch-case structure• It was spread 4 times throughout the code• To ease the change from 20 to 5
A story of FileType
AttachmentFileType–RepositoryTest
AttachmentFileTypeRepos…
FileType
Step 2
Get rid of switch-case
From …
… to
Or in picture
Data-driven tests
ProductService from…
… to
• Private method, thus no tests for this.• How am I sure this works?
• I am not. Now. I was pretty sure. I think. Hope.
AttachmentService from…
… to
Step 3
Work with attachment directory/name
Things not to do
• Have a constructor to throw an Exception.
• How would I change this now:• Factory to create the directory• Create AttachmentDirectory only if dir exists
• ready?
AttachmentDirectory
AttachmentDirFactory
AttDirFactoryTest
AttachmentFile(Factory)
• Similar concept with AttachmentFile and AttachmentFileFactory
• With one difference• AttachmentFile is interface and has two concrete
classes PlainAttachmentFile and WebImageAttachmentFile
• Use of Template-pattern (which changed a lot later)
AttachmentFileTemplTest
AttachmentFileTemplatestarting to get messy
verify: AttachmentService
Step 4
do similar changes to AttachmentService#moveToAttachmentDir
AttachmentFile
AttachmentFile
• Too many responsibilities.• Would turn up with lot of methods. I needed to
do something.• Did try to not to repeat myself (not shown – ask
for more info later)
The first draft
Step 5
Copying and moving files
>150 LOC @ AttachmentService
… to 4*4
The classes within the process
Steps
• Factory to create an AttachmentFile• Factory creates a AttachmentFileProcess –
process to move/copy/clone/delete the AttachmentFile
• For specific AttachmentFileType, it does different things
• The execute method takes one argument, AttachmentFileAction, which either is move/copy/clone/delete/rename
Some steps
AttchmntFileProcessFactory
PlainAttachmentFileProcess
ASpecificImageProcess
ASpecificImageProcess
FileCopyAction
FileDeleteAction
A mysterious test
Later steps
• Renaming classes, reordering packages• Using template
One last thing – listen the tests
The difference:
And that led to
• New class: AttachmentFileName• A domain logic for handling name of the Attachment
• I’m working with this. Now.
Learnings
• Slow to refactor• Test-Driven design can work even in brown-field
projects• Integration to old system required integration tests
• How valuable the changes were to the customer• With my current understanding:
• Split the changes to two – deploy both separately.
Tests
0
50
100
150
200
250
PassedFailed
Questions?
Puhelin: +358 50 341 5620
email: [email protected] / [email protected]
Twitter: @rinkkasatiainen
Aki Salmi