Refactoring Legacy Code - true story

Preview:

DESCRIPTION

 

Citation preview

Refactoring legacy code

True story

Juha.aaltonen@ambientia.fi

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: aki.salmi@iki.fi / aki.salmi@ambientia.fi

Twitter: @rinkkasatiainen

Aki Salmi

Recommended