working effectively with legacy code object mentor, inc. copyright 2003-2004 by object mentor, inc...

175
Working Effectively with Legacy Code Object Mentor, Inc. Copyright 2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Upload: amice-sparks

Post on 02-Jan-2016

214 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Working Effectively with Legacy Code

Object Mentor, Inc.

Copyright 2003-2004 by Object Mentor, IncAll Rights Reserved

V1.0

Page 2: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor2

Introduction

What is Legacy Code?Change and Risks

Page 3: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor3

What is Legacy Code?

Legacy Systems

(1) An un-refactored system with few to no automated tests. (2) A large working, but hard to modify monolithic system that you’ve inherited, (3) A system implemented in a language or platform that doesn’t contribute to your resume

You may be writing legacy code right now(!)

Page 4: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor4

Challenges in Software Change

Seen one way, changing software is easy

Page 5: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor5

Change and Risks

Page 6: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor6

Two Forms of Software Change

Whenever you are confronted with changes to make in a system you have a choice between modifying the code in place and introducing new classes or methods.

In legacy systems, many programmers modify code in place. There are several reasons why:

It seems easier

It’s the least invasive thing you can do, changes don’t seem as risky

Page 7: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor7

Risk in Legacy Systems

Is modifying code “in place” the least invasive thing you can do in a legacy system?

Probably not

It is easy to introduce bugs when you edit in place when you have no way to test

You have to be aware of all the effects that you introduce or alter when you make the change

Maybe the safest thing you can do is not modify the code at all!

Page 8: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor8

Changing Code

Changing code can introduce bugs, but not changing code causes more bugs (eventually)

Imagine a system that is never refactoredIt’s hard to understand

It’s hard to add to

Changes are expensive and that leads to incredible stress under schedule pressure

And stress leads to… bugs

Has a new bug ever gotten in your way in a crunch?

Page 9: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor9

The Paradox of Change

Code changes can degrade quality so it is better not to make changes

Not changing code (refactoring) degrades quality so it is better to change code

To escape this dilemma we need confidence in our changes

How do we know we have it right?

How do we know we didn’t break existing behavior?

Page 10: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor10

A Change Heuristic for Difficult Code

Look at the places you need to change

Ask…What new code will I write?

Can I put it in a new method or a new class?

If so, do it

Positive EffectsNew responsibilities are separated from old ones

Writing a test for a new method or class is often easier than writing it for an old one

Page 11: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor11

Whoa!

Isn’t this bad advice?

What about putting code where it really belongs?

Answer:When you are changing difficult code, it pays to be cautious

The first task is to be able to make changes without hurting the system

Then we can make the system better

Doctor’s oath: “first of all, do no harm!”

Page 12: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor12

Safe Changes (the easy cases)Sprout Method

Sprout ClassWrap Method

Wrap Class

Page 13: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor13

A Simple System, a Simple Change

Simple system to generate a page of HTML

Feature RequestWe need to add in a

table header

+ PageGenerator()+ generate() : string

PageGenerator

+ queryResults() : vector<Result>

ResultsDatabase

Result

1

*

Page 14: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor14

Sprout Method Steps

1. Identify where you need to make your code change.2. If the change can formed as a single sequence of

statements in a method, write a call for a new method in that place, then comment it out

3. Determine the local variables you need from the source method and make them part of the call

4. Determine whether the sprouted method needs to return values, if so assign the return of the call to a variable.

5. Develop the sprout method test-first6. Remove the comment in the source method to enable the

call

Page 15: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor15

Sprout Method

If the code you need to add to a method is localizable, put it in a new method

Just like extract method, but done preemptively

Page 16: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor16

Sprout Method Advantages

You can write tests directly against the new methodThe distance between cause and effect is smaller, so your tests can be more focused

Less chance of gumming up the old code, you just delegate to the new method

The things that the new method needs to depend on can be far less than what its calling methods depends upon.

We can make dependencies very explicit

You can use TDD on the new code and the new code is a “covered area” You can write tests for all new code that you add there

Page 17: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor17

Sprout Method Disadvantages

More methods (but is that really a disadvantage?)

You need to add a prototype for the method in the header

(But you can use Sprout Class)

You aren’t testing how the new method is used

Page 18: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor18

PageGenerator Example

What could we do if adding a function prototype is hard?

We could introduce a non-member function

But these don’t evolve well

We could introduce a new class that just provides the table header text

Page 19: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor19

Sprout Class

If the code you need to add to a method is localizable, put it in a new class

Just like extract class but preemptively

Only bring along the data that you need to do the work you need to do.

Have the calling code query for results of your computation

Page 20: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor20

Sprout Class Discussion

Often Sprout Method is the better choice, but if the new code is new area of responsibility, it is better to start it in a new class. This moves your classes in line with the Single Responsibility Principle

Sprout Class is a good way of managing headersUnlike Sprout Method you don’t have to modify the header

You introduce new headers and dependencies can be allocated more finely in the system

Page 21: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor21

Sprout Class Discussion

Sprout Class does have the downside of introducing a new concept into the application: the new class

In this case, we bias towards doing it to get out of a bad legacy situation

Often when you create a new concept you won’t see anything that it is related to

Over time concepts tend to group (Page Generator example)

Page 22: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor22

Sprout Method or Class: Choosing

If you can create an instance of the class in a test harness, and the new code and is not really a new responsibility, use sprout method

If the new code is a new responsibility or creating an instance of the class is impractical, use sprout class

Consider “sprout class” even if you use “sprout method” instead

May lead to new design insights

Page 23: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor23

Wrap Method

Don’t add code to a method, write it in a wrapping method and delegate to the old one

1. Find the method you have to add code to

2. Rename it

3. Write a new method with the original name, add your new code and a call to the renamed method

Page 24: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor24

Wrap Class

Create a class which wraps your original class.

Add code to it and delegate to the old class

In many cases, this is very much like the Decorator pattern.

Page 25: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor25

When to Wrap?

When you just can’t stand to add more code to the original method or class

When you sense that the code you are adding is an orthogonal responsibility

Page 26: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor26

We’re Done With The Easy Stuff

Many changes require several changes to a single method

They are will be tangled with the existing logic of the class

They can’t be narrowed down

And, even if we could put the responsibility in another class, it would be better to get the original class under test

The rest of the class is about these problems

But..Use don’t hesitate to use sprouts and wraps

Page 27: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor27

Testing as a Programmer’s Tool

Page 28: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor28

What Can Break?

This is a simple system, but the things that can go wrong are the same as those that could go wrong in difficult code:

We could write code that doesn’t do the right thing

We could put the code in a place where it doesn’t have the intended effect

We could alter the existing behavior in a bad way

Page 29: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor29

Tests Make Work Easier

Shorter cycle time.

When you have tests for a class, in a separate harness, your build time for that class is far shorter.

It makes more work practical.

The idea of an Interactive Top Level

Page 30: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor30

Qualities of a Good Harness

Easy to understand

Easy to use

Creating a new test is a one-step process

Page 31: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor31

The xUnit Testing Framework

The first xUnit Framework was written by Kent Beck for Smalltalk

JUnit was developed for Java by Kent Beck and Erich Gamma

There are dozens of variants of xUnit for nearly every imaginable OO language

Page 32: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor32

Basic xUnit Architecture

+ run(TestResult)+ assert(bool)+ assertEquals()

Test

+ run(TestResult)# runTest(TestResult)# setUp()# teardown()

TestCase

+ addTest(Test)+ run(TestResult)

TestSuite

+ addError(Failure)+ addFailure(Failure)

TestResult

*

Page 33: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor33

Three Types of Tests

Unit test – for new behavior

Use Test – to make sure the behavior is enabled, mini-integration test

Characterization Test – to make sure old behavior is preserved.

Page 34: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor34

Sprout Side-Effects

Remember the things that can go wrong:We could write code that doesn’t do the right thingWe could put the code in a place where it doesn’t have the intended effectWe could alter the existing behavior in a bad way

When we “sprout” we can verify that the code does the right thing, the point of change is identifiable (it is a method call), and we’ve separated the code so we can reason about it independently in a small “chunk”

Page 35: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor35

Deciding How Far to Go

RiskDoes this piece of code have history which warrants extra careDo I understand this code, is this code understandable?What are the ramifications of a bug?

RewardWhat is the payoff for making it easier to work in this code and maintain it?Do we have an opportunity as we make this change to make the code much more maintainable by widening what we cover with tests?

Cost Can we afford this investment now? Will the cost be higher the next time we revisit it?

FearAre you just tired of being scared of this piece of code?

Page 36: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor36

Sprout Side-Effects

Sprouting is good, but if you don’t get the source class under test, you are pretty much “giving up” on that class

Later in the course, we’ll talk about how to progressively get a class under test

Page 37: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor37

Getting a Class Under Test

Page 38: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor38

The Goal

The best case: Changing class goes into a test harness

Allows refactoring, more testing

Next best case: client class goes into a harness and uses the class we need to change

Page 39: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor39

Testing Challenges

Unbuildable Class

Uninstantiable Class

Uncallable Method

Page 40: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor40

Unbuildable Class

The changing class can’t be compiled or linked into a test harness

CausesToo many includes

Deep transitive dependencies

Page 41: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor41

Uninstantiable Class

Class compiles in test harness but can be instantiated

CausesTransitive construction dependencies

Constructor call takes too long

Side effects of constructor are inappropriate

Page 42: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor42

Uncallable Method

Able to instantiate class, but unable to call methods under test

CausesTrouble creating method arguments

Call takes too long

Bad side effects

Page 43: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor43

Seams

A Seam is a place in a program where you can replace behavior by remote control

Seams are a different way of looking at software

Page 44: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor44

The Build Process

Different languages have different build stages

In C and C++, we have: preprocessing, compilation, and link

When we look at a method call in a source file, how do we know what is really called?

At each build stage (and even at run-time) we can substitute one call for another

Page 45: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor45

Seam Example

Suppose we had to test perimeter and distance was a very expensive operation (not really, but we’re using a simple example)

double perimeter(vector<point*> polygon){ double result = 0; int n; for (n = 0; n < polygon.size(); n++) { point *next = polygon [(n + 1) %

polygon.size()]; result += distance(polygon [n], *next); } return result;}

Page 46: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor46

Seam Example

How is point connected to the rest of the code?

How could we test perimeter independently?

First, let’s look at how it is usedThe distance function depends on point

Consider an alternative

Page 47: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor47

Seam Example

How is this code different?

double perimeter(vector<point*> polygon){ double result = 0; int n; for (n = 0; n < polygon.size(); n++) { point *next = polygon [(n + 1) %

polygon.size()]; result += polygon [n]->distanceFrom(*next); } return result;}

Page 48: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor48

Seam Example

In this case, if we really wanted put another class in the place of point, we could do the following:

Subclass Point

Override its distance function

We can do this because a polymorphic call is a seam.

Page 49: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor49

Seam Example

How else could we put in a less expensive distance function?

Think about the build stagesWe can substitute in another call during preprocessing

We can substitute in another function during link

We can’t modify the code we are compiling because… that is the code we want to test

Page 50: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor50

Seam Types

Polymorphic CallsText Substitution

Linkage

Page 51: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor51

Polymorphic Call Seam

There are many reasons to use inheritance

Inheritance can reduce duplication code

It can allow us to call talk to objects of different classes in the same way, this is a form of code reuse. The calling code can be used over many classes

Polymorphic calls also let us use “fake” objects when we test

Page 52: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor52

Polymorphic Call Seam

The place where we send a message to an object is a seam

We can vary the code that is executed by instantiating objects of different classes

void ConnectionPool::reestablishConnections() { for(Connection::iterator it = connections.begin();

it != connections.end(); ++it) {

if (it->isDisconnected())it->reconnect();

}}

Page 53: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor53

Virtuals vs Non-Virtuals

This is true if we have virtual member-functions

You may have virtual functions in your code for other reasons

The need to test is a valid reason to have a virtual function

Page 54: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor54

Polymorphic Call Seam

AdvantagesLeverages features of the language to do the work

Making a method virtual opens up other possibilities in your design

DisadvantagesYou have to be careful when making a function virtual

When non-virtual overrides are present it could break the code

Not possible when objects are passed by value

When to usePreferred method of breaking dependencies. Use when the recompilation isn’t too costly

Page 55: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor55

Subclass To Override

A valid use of inheritanceSubclass and override the things that get in the way in a testTest the subclass

You really are testing the code in the original class, but you are hiding code that you don’t care about in the test

ClassToTest

Subclass

YourTestCase

Production Code

Test Code

Page 56: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor56

void Account::deposit(Money value) {balance += value;logger.log(“deposited: “ + value);

}

Subclass To Override

Inheritance is sort of the 3rd dimension in programming

Any code that you can separate into a method in one class can be replaced with another method when you subclass:

void TestAccount::deposit(Money value) {balance += value;

}

Page 57: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor57

Text Substitution Seam

In languages with a preprocessor, you can use it to trick your code into using alternative definitions of:

Functions

Global Variables (eeeewww!)

Defines

Page 58: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor58

Text Substitution Seam

To use1. Include headers for the program elements you need in

the test cpp file

2. After the includes provide definitions for declarations or redefinitions of macros.

If you provide non-inline definitions for declarations, you’ll have to create a separate executable for the tests.

Page 59: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor59

Text Substitution Seam

AdvantagesUseful when you have many dependencies around a class and you need to get that class under test

DisadvantagesMay require the creation of many fake implementationsDoesn’t really break dependencies in the production code

When to UseUse when you have a very large class and you need to get it into a separate harness to do critical work

Page 60: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor60

Linkage Seam

One of the last steps of the build process

Resolving calls to particular functions with the functions themselves

We can create alternative libraries to link to

These libraries don’t provide real functionality, they just provide something to link to. They allow us to break dependencies to external software

May make builds easier

Page 61: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor61

Linkage Seam

AdvantagesCan separate dependencies over a large set of calls without altering any source

DisadvantagesHave to manage the fake libraries, make sure you can’t release them into productionMakes more sense for extern “C” functions. The work to duplicate a full class interface exactly is hard

When to useWhen dealing with existing libraries that are used extensively throughout an application

Page 62: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor62

Seam Exploitation

Sensing

Separation

Page 63: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor63

Sensing and Separation

There are two reasons to break dependencies in a design

SensingWe need to understand what the values are at a particular point in the program flow

SeparationWe need to break a dependency to make testing easier

Page 64: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor64

Sensing

Programs are usually opaqueWe can try to tell what value a variable has at a certain point by “playing computer” or using a debugger

And we are never wrong! Right!

When we exploit a seam we can put code in place to sense conditions

We can write tests against those conditions

Page 65: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor65

Separation

Sometimes a collaborator isn’t appropriate when testing

The collaborator may take too long to execute

May have side-effects you may not want under test:

i.e. retract the control rods of nuclear reactor

May cause the link to take too long

May cause the compile to take too long (transitive dependencies)

You may not be able to sense easily through the interface of the collaborator

Page 66: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor66

Separation

In all of these cases, the remedy is the same

Use a seam to replace one implementation with another

The implementation may only need to behave in very simple ways:

Commands – no behavior

Queries – return a default value

For some tests, they may have to return canned values for particular scenarios

Page 67: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor67

Sensing and Separation

You don’t always have to use a seam to senseOne alternative is to inject logging code into your application

The big issue is: how do you keep yourself from having to look at the logs over and over againCan you write tests against the logging valuesProbes

Invasive, you have to modify the code (possibly obscuring it)Tests will forever be dependent on log calls

Page 68: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor68

Dependency Breaking Techniques

Manifest Dependencies

Hidden Dependencies

Practices

Page 69: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor69

Dependencies

They cause the trouble you have when you try toLink a class into a test harness

Create an instance of the class

Execute a method of the class

Dependencies also make it hard to sense the effects of your actions when testing

Page 70: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor70

Dependencies

Two main types:Manifest – dependencies that are present at the interface to a class i.e., types of arguments to constructors and methods

Hidden – dependencies internal to a class. Things like calls to globals or singletons, constructing objects of other classes

Page 71: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor71

Manifest Dependencies

Pretty easy to separateTry to instantiate an object in a test harness.

When you have to pass an instance to a constructor or a method you have to decide whether to create the real object or make a fake one

Use Extract Interface if you need to make fakes

Page 72: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor72

Hidden Dependencies

Hidden dependencies are far tougher to deal with

They often force you to link in more than you want to

These dependencies are often transitive and sometimes they force you to make invasive changes in code you want to test

Page 73: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor73

Manifest and Hidden Dependencies

Manifest dependencies are the easiest to deal with

Most common technique: Extract Interface

Page 74: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor74

Practices

Signature Preservation

Leaning on the Compiler

Pair Programming

Single Goal Editing

Page 75: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor75

Signature Preservation

When you are trying to get tests in place favor refactorings which allow you to cut/copy and paste signatures without modification

Less error prone

Page 76: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor76

Leaning on the Compiler

You can use the compiler to navigate to needed points of change by deliberately producing errors

Most common case:Change a declaration

Compile to find the references that need changes

Page 77: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor77

Pair Programming

Vital when working to get tests in place

An extra set of eyes helps

Breaking apart code for test is like surgeryDoctors never operate alone

Page 78: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor78

Single Goal Editing

Be deliberate about each step you take with your pair partner.

Avoid trying to do more than one thing at a time.

Have a running conversation to make sure you aren’t missing anything

Page 79: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor79

Writing Tests

Page 80: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor80

Characterization Testing

The first tests that you write for a class you are changing.

These tests characterize existing behavior.

They hold it down like a vise when you are working

Page 81: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor81

Characterization Testing

What kind of tests do you need?

It’s great to write as many as you need toFeel confident in the class

Understand what it does

Know that you can easily add more tests later

But,The key thing is to detect the existence of behavior that could become broken

Page 82: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor82

Characterization Testing Example

What kind of tests do we need if we are going to move part of this method to BillingPlan?

class ResidentialAccountvoid charge(int gallons, date readingDate){ if (billingPlan.isMonthly()) {

if (gallons < RESIDENTIAL_MIN) balance += RESIDENTIAL_BASE;

else balance += 1.2 *

priceForGallons(gallons);billingPlan.postReading(readingDate,

gallons); }}

Page 83: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor83

Characterization Testing Example

If we are going to move method to the BillingPlan class is there any way this code will change?Do we have to test all of its boundary conditions?

if (gallons < RESIDENTIAL_MIN) balance += RESIDENTIAL_BASE;

else balance += 1.2 *

priceForGallons(gallons);billingPlan.postReading(readingDate,

gallons);

Page 84: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor84

Extract Interface

Page 85: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor85

Extract Interface

A key refactoring for legacy code

SafeWith a few rules in mind you can do this without a chance of breaking your software

Can take a little time

Page 86: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor86

Extract Interface

InterfaceA class which contains only pure virtual member-functions

Can safely inherit more than one them to present different faces to users of your class

Page 87: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor87

Extract Interface Steps

1. Find the member-functions that your class uses from the target class2. Think of an interface name for the responsibility of those methods3. Verify that no subclass of target class defines those functions non-

virtually4. Create an empty interface class with that name5. Make the target class inherit from the interface class6. Replace all target class references in the client class with the name

of the interface7. Lean on the Compiler to find the methods the interface needs8. Copy function signatures for all unfound functions to the new

interface. Make them pure virtual

Page 88: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor88

The Non-Virtual Override Problem

In C++, there is a subtle bug that can hit you when you do an extract interface

class EventProcessor{public:

void handle(Event *event);};

class MultiplexProcessor : public EventProcessor{public:

void handle(Event *event);};

Page 89: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor89

The Non-Virtual Override Problem

When you make a function virtual, every function in a subclass with the same signature becomes virtual too

In the code on the previous slide, when someone sends the handle message through an EventProcessor reference to a MultiplexProcessor, EventProcessor’s handle method will be executed

This can happen when people assign derived objects to base objects. Generally this is bad.

Something to watch out for

Page 90: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor90

The Non-Virtual Override Problem

So…Before you extract an interface, check to see if the subject class has derived classes

Make sure the functions you want to make virtual are not non-virtual in the derived class

If they are introduce a new virtual method and replace calls to use it

Page 91: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor91

Breaking Dependencies 1

Parameterize Method

Extract and Override Getter

Page 92: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor92

Parameterize Method

If a method has a hidden dependency on a class because it instantiates it, make a new method that accepts an object of that class as an argument

Call it from the other method

void TestCase::run() { m_result =

new TestResult; runTest(m_result);}

void TestCase::run() {run(new TestResult);

}

void TestCase::run(TestResult *result)

{ m_result = result; runTest(m_result);}

Page 93: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor93

Steps – Parameterize Method

Create a new method with the internally created object as an argument

Copy the code from the original method into the old method, deleting the creation code

Cut the code from the original method and replace it with a call to the new method, using a “new expression” in the argument list

Page 94: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor94

Extract and Override Getter

If a class creates an object in its constructor but doesn’t use it, you can extract a getter and override it in a testing subclass

Page 95: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor95

Steps – Extract and Override Getter

Write a protected getter for the object created in the constructor

Add a first time switch to the getter

Override it in a subclass and provide another object

Page 96: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor96

Scheduler Exercise #1

Feature - Reject events for dates earlier than the current date in Scheduler.addEvent

Get the class under test and make the changes

Page 97: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor97

Breaking Dependencies 2

Expose Static Method

Page 98: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor98

Expose Static Method

Here is a method we have to modify

How do we get it under test if we can’t instantiate the class?

class RSCWorkflow {public void validate(Packet& packet) {

if (packet.getOriginator() == “MIA” || !packet.hasValidCheckSum()) {throw new InvalidFlowException;

}…

}}

Page 99: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor99

Expose Static Method

Interestingly, the method doesn’t use instance data or methods

We can make it a static method

If we do we don’t have to instantiate the class to get it under test

Page 100: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor100

Expose Static Method

Is making this method static bad?

No. The method will still be accessible on instances. Clients of the class won’t know the difference.

Is there more refactoring we can do?Yes, it looks like validate() belongs on the packet class, but our current goal is to get the method under test. Expose Static Method lets us do that safely

We can move validate() to Packet afterward

Page 101: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor101

Steps – Expose Static Method

Write a test which accesses the method you want to expose as a public static method of the class.Extract the body of the method to a static method. Often you can use the names of arguments to make the names different as we have in this example: validate -> validatePacketIncrease the method’s visibility to make it accessible to your test harness.Compile.If there are errors related to accessing instance data or methods, take a look at those features and see if they can be made static also. If they can, then make them static so system will compile.

Page 102: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor102

Scheduler Exercise #2

Feature – Remove HTML paragraph markup (<p></p>) in Meeting descriptions

Page 103: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor103

Breaking Dependencies 3

Introduce Instance Delegator

Page 104: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor104

Introduce Instance Delegator

Static methods are hard to fake because you don’t have a polymorphic call seamYou can make one

static void BankingServices::updateAccountBalance(int userID, Money amount) {

…}

Page 105: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor105

Introduce Instance Delegator

After introduction..

class BankingServicespublic static void updateAccountBalance(

int userID, Money amount) {

…}public void updateBalance(

int userID, Money amount) {

updateAccountBalance(userID, amount);}

}

Page 106: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor106

Steps –Introduce Instance Delegator

Identify a static method that is problematic to use in a test.

Create an instance method for the method on the class. Remember to preserve signatures.

Make the instance method delegate to the static method.

Find places where the static methods are used in the class you have under test and replace them to use the non-static call.

Use Parameterize Method or another dependency breaking technique to supply an instance to the location where the static method call was made.

Page 107: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor107

Scheduler Exercise #3

Change Scheduler.getMeeting so that it returns meetings only if it is a workday (using the TimeServices.IsWorkday method)

Page 108: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor108

Dependency Breaking 6

Encapsulate Global References

Page 109: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor109

Encapsulate Global References

Free functions and variables can be faked if they are references from a class

The reference provides a seam

Page 110: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor110

Steps – Encapsulate References

Identify the globals that you want to encapsulate.

Create a class you’d like to reference them from.

Copy the globals into the class as instance variables and handle their initialization.

Comment out the original declarations of the globals.

Declare a global instance of the new class

Lean on the compiler to find all of the unresolved references to the old globals.

Precede each unresolved reference with the name of the global instance of the new class.

Page 111: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor111

Scheduler Exercise #6

Feature – We need to change the way email messages are sent for flextime. The date has to be appended to the message.

Page 112: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor112

Dependency Breaking 7

Break Out Method Object

Page 113: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor113

Break Out Method Object

Alternative to Expose Static Method

When you have instance data and the method is long.. Would be easier to work with in its own class

Page 114: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor114

Steps – Break Out Method Object

Create a class that will house the method code. Create a constructor for the class.For each argument in the constructor, declare an instance variable and give it exactly the same type as the variable. Use preserve signatures to do this. Assign all of the arguments to the instance variables in the constructor.Create an empty method named run or something domain-specific on the new class.Copy the body of the old method into the run() method and compile to lean on the compiler.Once the new class compiles, go back to the original method and change it so that it creates an instance of the new class and delegates its work to it.If needed, use Extract Interface to break the dependency on the original class.

Page 115: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor115

Scheduler Exercise #7

Change the Scheduler so that Flextime isn’t allowed on any day that has meetings

Page 116: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor116

Naming Issues

Page 117: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor117

Naming Interfaces

Name them any old thing… names don’t matter

It all compiles away anyway

Use binary for your class names

class B10110111101{

…};

// Just kidding ;-)

Page 118: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor118

Interface Naming

Names are very important

Code is meant to be understood by humans

Page 119: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor119

Interface Naming

There are two ways to handle the naming of extracted interfaces

Use the class name and push down the implementation

Find a name based upon the cohesion of the functions that define the interface

Page 120: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor120

Additional Dependency Breaking Techniques

Shift Up Feature

Introduce Static Setter

Parameterize Constructor

Page 121: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor121

Shift Up Feature

If the breaking dependencies in a class looks too hard, consider pulling the code you want to test up into a new abstract superclass that you can testAll of the code that you need is placed in NewAbstractSuperclass. NewSuperclass is just a concrete class for testing

NewSuperclass

OldClass YourTestClass

NewAbstractSuperclass

Page 122: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor122

Introduce Static Setter

Tests should produce the same results every time they run

References to global variables and singletons can cause state to leak across tests

How do you set the state on a singleton?

Page 123: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor123

Introduce Static Setter

Typical Singleton Code

SomeSingleton *SomeSingleton::s_instance = 0;SomeSingleton *SomeSingleton::instance() {

if (s_instance == 0) {s_instance = new SomeSingleton;

}return s_instance;

}

No opportunity to create a fake one

Page 124: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor124

Introduce Static Setter

The solution is simple:

SomeSingleton *SomeSingleton::s_instance = 0;SomeSingleton *SomeSingleton::instance() {

if (s_instance == 0) {s_instance = new SomeSingleton;

}return s_instance;

}

void SomeSingleton::setInstance(SomeSingleton *newInstance) {

delete s_instance;s_instance = newInstance;

}

Page 125: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor125

Steps - Introduce Static Setter

If the constructor of the singleton is private, make it protected

Subclass the singleton, if needed, to substitute good testing behavior (subclass to override)

Add the setting method (make sure you have a way of deleting the old instance and the new one)

Use the setter in your test setup

Page 126: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor126

Introduce Static Setter

Wait… isn’t this bad?

The goal of the singleton pattern is to make sure that only one instance of a class can be created in a system, but this goal conflicts with the ability to make tested changes in code.

Tested change wins

Approaches for maintaining the “singleton property”:Team rule: no one calls the setter in the production code

Assert in the production code if the setter is called

Page 127: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor127

Parameterize Constructor

If a constructor has a hidden dependency on a class because it instantiates it, make a constructor that accepts an object of that class as an argument

Dispatcher::Dispatcher(): m_logger(new Logger(E_WARNING)){}

Dispatcher::Dispatcher(Logger *logger): m_logger(logger){}

Page 128: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor128

Parameterize Constructor

AdvantagesVery easy to do

Don’t break existing clients

DisadvantagesIf the parameterized constructor is used in production code, it can make clients dependent on those classes

Page 129: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor129

Parameterize Constructor

In C++, you can’t call one constructor from another, so if there is duplicate code in the constructors, consider factoring out an init method

Page 130: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor130

Dealing with Long Methods

Page 131: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor131

Long Methods

Biggest challenge when refactoring

Once methods grow to too large they are nearly impossible to test

The distance between the setup and the code you are exercising becomes too large

Can we break down large methods safely without comprehensive tests?

Page 132: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor132

Sensing Variables

What can a long method affect?Parameters, return value, instance variables

We can introduce instance variables to sense conditions

Page 133: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor133

Extract What You Know

Extract Small PiecesSmall = Count of three or less

Count def. how many values will pass through the boundary of the new method

Parameters

Return values

Instance vars don’t count

Count of zero is ideal

Dangerous Move: Statement Inversion

Page 134: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor134

Extract What You Know

Possible Errors for Extract MethodType Conversion

Unassigned Return Value

Dropped Parameters

Statement inversion to set up the extraction

Page 135: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor135

Breakout Method Object

Local variables hide state that could be used for sensing

If you create a new class for a method all locals can be come instance variables

You may end up with a class that has a verb name, but it allows you to move forward

Page 136: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor136

Breakout Method Object

1. Create a class for the method

2. Give the class instance variables for all method parameters (preserve signatures)

3. Create constructor and run() method for the class

4. Lean on the compiler to resolve all references

Page 137: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor137

Big Picture: Long Method Strategies

SkeletonizeExtract bodies of conditionals and loops into separate methods

Create SequencesExtract conditions and loops into separate methods

Scratch RefactoringAgree with your partner to throw the first refactoring session awayDo it to discover what direction you want to move towards

Page 138: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor138

Refactoring: Improving Design

Page 139: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor139

Where Did Refactoring Come From?

Conceived in Smalltalk circles Kent Beck & Ward Cunningham

Recognized refactoring as a key element of the software process

Used in developing software frameworks Ralph Johnson/Bill Opdyke, University of Illinois

Smalltalk refactoring toolJohn Brant/Don Roberts

Refactoring: Improving the Design of Existing CodeMartin Fowler

Page 140: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor140

What Are The Goals Of Refactoring?

Some goals:Extend the lifetime of applications (maintainability)

Deal efficiently with fast changing requirements without paying exorbitant costs

Ship software faster: reduce schedule slips, stop those tail chasing debugging sessions

Page 141: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor141

How Does Refactoring Help?

Improves the design of softwareBad/decaying design makes new functionality harder to add

We don’t have to be right the first time…

Makes software easier to understandCode communicates its intent as clearly as possible

Helps us find defectsClarifying code/structure gives deeper understanding

Allows us to spot bugs more efficiently

Page 142: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor142

Is Refactoring a Silver Bullet?

No! A valuable tool

“A pair of silver pliers that help you keep a good grip on your code”

Page 143: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor143

What is Refactoring?

Move from one valid state to anotherTests let you know whether a state is valid or not

Refactoring is not:Adding new functionality (two different hats)Performance tuning

A series of small steps, each of which changes a program’s internal structure without changing its external behavior

A series of small steps, each of which changes a program’s internal structure without changing its external behavior

Page 144: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor144

Definitions of Refactoring

Loose UsageReorganize a program (or something)

Refactoring (n.)a change made to the internal structure of some software to make it easier to understand and cheaper to modify, without changing the observable behavior of that software

Refactoring (v.)the activity of restructuring software by applying a series of refactorings without changing the observable behavior of that software.

Page 145: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor145

When Should You Refactor?

To add new functionalityExisting structure may not allow it

“Shoehorning in” a feature makes other features harder to add

To fix a defectCode was probably not clear enough if the bug managed to slip through

When doing code reviews

AlwaysAs part of a write-test, write-code, refactor cycle

Page 146: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor146

The Video Store Demo

Example of cleaning up procedural code

Calculate and print a statement of a customer’s charges at a video store

Tracks movies a customer rented and for how long

Three types of movies: regular, children’s and new releases

Keeps track of frequent renter points

Charges and frequent renter points can change depending on movie type

Page 147: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor147

The Video Store Demo

New requirements:Add HTML statement

Make changes to movie classification. Changes will affect the way charges and frequent renter points are calculated. But they don’t know yet…

Page 148: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor148

Code Smells

Many things make a design hard to maintainDevelop a “nose” for bad structurings

Long MethodLarge ClassFeature EnvyData classDuplicated Code…

[Refactoring] p.75

Page 149: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor149

What to look for when Refactoring

Make the code easy to read:Minimize the amount of work the reader has to do to understand the forest and the trees

Small methods, small classes

Make sure there is no duplicated logicAdding new behavior should not require changing existing codeKeep design simple

Minimize the story you would tell to explain the systemWatch out for complex conditional logicYAGNI

Page 150: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor150

Code Smell: Long Method

Long methods usually do more than one thing

The intent can often not be determined from the method name.

Take a look at the code in the ExtractMethod directory

How many things does ExpenseReport.printOn () do?

It runs the application, but how?

[Refactoring] p76

Page 151: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor151

Code Smell: Long Method (cont)

The name of a method is an abstraction, the body is an implementation

Implementations are details

Raise your “talking level” in a systemHow do you make a method communicate at a higher level?

Name its parts

Extract Method

Page 152: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor152

Refactoring: Extract Method

You have a code fragment that can be grouped togetherTurn the fragment into a method whose name describes your

intention.

void printOwing () {printBanner ();printDetails (getOutstanding ());

}

void printDetails (double outstanding) {System.out.println (“name “ + _name);System.out.println (“amount “ + outstanding);

}

void printOwing () {printBanner ();

// print detailsSystem.out.println (“name “ + _name);System.out.println (“amount “ + getOutstanding ());

}

Page 153: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor153

Extract Method - Example

public void accept (Session newSession) {User id = newSession.getID ();if (_users.contains (id)) {

newSession.logOnFailed ();return;

}

_users.add (id);newSession.logOnSucceeded ();newSession.setLogOnTime (serverTimeStamp);newSession.notifyUser (serverState);

int latency = 0;for (Iterator it = superusers.iterator(); it.hasNext ();) {

Session superSession = (Session)it.next ();

if (superSession.knows (newSession))latency = Math.max (latency,

superSession.getLatency ());}

_expectedTime = latency * EXPECTATIONFACTOR;_latency = latency;

}

Page 154: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor154

Steps for Extract Method

Create method named after intention of code

Copy extracted code

Look for local variables and parametersturn into parameter

turn into return value

declare within method

Compile

Replace code fragment with call to new method

Compile and test

Page 155: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor155

Extract Method – Step 1

Create a method named after the intention of the code

Private, accepts void, returns void

private void maxLatency () {

}

Page 156: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor156

Extract Method – Step 2

Copy extracted code into the new method

private void maxLatency () {int latency = 0;for (Iterator it = superusers.iterator(); it.hasNext ();) {

Session superSession = (Session)it.next ();

if (superSession.knows (newSession))latency = Math.max (latency,

superSession.getLatency ());}

}

Page 157: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor157

Extract Method – Step 3

Look for local variables and parameterspass as parameters

is a return value needed?

private void maxLatency () {int latency = 0;for (Iterator it = superusers.iterator(); it.hasNext ();) {

Session superSession = (Session)it.next ();if (superSession.knows (newSession))

latency = Math.max (latency, superSession.getLatency ());

}}

Page 158: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor158

Extract Method – Step 3 (cont)

Adding the parameter and return value

private int maxLatency (Session newSession) {int latency = 0;for (Iterator it = superusers.iterator(); it.hasNext ();) {

Session superSession = (Session)it.next ();if (superSession.knows (newSession))

latency = Math.max (latency, superSession.getLatency ());

}return latency;

}

Page 159: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor159

Extract Method – Step 4

Compile

(grind, grind, grind)

Page 160: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor160

Extract Method – Step 5

Replace the extracted code with a call to new method

public void accept (Session newSession) {User id = newSession.getID ();if (_users.contains (id)) {

newSession.logOnFailed ();return;

}

_users.add (id);newSession.logOnSucceeded ();newSession.setLogOnTime (serverTimeStamp);newSession.notifyUser (serverState);

int latency = maxLatency (newSession);...

}

Page 161: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor161

Extract Method – Step 6

Compile and test

Page 162: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor162

Extract Method Thoughts

What if the code we wanted to extract modified two variables?

Does the name of the method tell us all that it does?

How many jobs does maxLatency have?

Page 163: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor163

Code Smell : Feature Envy

“If your neighbor’s kids spend all their time at your house, they might as well live there”

Look at the objects used in a method

Does the method use one object more than it uses pieces of itself?

[Refactoring] p.80

Page 164: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor164

Code Smell : Feature Envy (cont)

Server.accept (Session)

or Session.logOn (Server)?

public void accept (Session newSession) {User id = newSession.getID ();if (_users.contains (id)) {

newSession.logOnFailed ();return;

}

_users.add (id);newSession.logOnSucceeded ();newSession.setLogOnTime (serverTimeStamp);newSession.notifyUser (serverState);

}

Page 165: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor165

Refactoring : Move Method

Create a new method with a similar body in the class that it uses most.

Either turn the old method into a simple delegation, or remove it all together

aMethod ()

Class 1

Class 2

Class 1

aMethod ()

Class 2

Page 166: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor166

Move Method – Steps

Declare method in target classCopy and fit codeSet up a reference from the source object to the targetTurn the original method into a delegating method

accept (Session session){return session.logOn (this);}Check for overriding methods

Compile and testFind all users of the method

Adjust them to call method on target

Remove original methodCompile and test

Page 167: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor167

Move Method – Steps 1 & 2

Declare method in target and copy in code

public class Session {

public void logOn () {User id = newSession.getID ();if (_users.contains (id)) {

newSession.logOnFailed ();return;

}

_users.add (id);newSession.logOnSucceeded ();newSession.setLogOnTime (serverTimeStamp);newSession.notifyUser (serverState);

}

}

Page 168: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor168

Move Method – Steps 3 & 4

Set up reference, retarget methodsYou may have to create accessors

public class Session {

public void logOn (Server server) {User id = getID ();if (server.users ().contains (id)) {

logOnFailed ();return;

}server.users ().add (id);logOnSucceeded ();setLogOnTime (serverTimeStamp);notifyUser (serverState);

}

}

Page 169: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor169

Move Method – Remaining Steps

Delegate

Compile and testLook for callers of accept

Can they call logOn directly?

public void accept (Session newSession) {newSession.logOn (this);

}

Page 170: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor170

Move Method

When a method is more appropriate on one class than another

Can be used to:enrich “data” classes

balance out the responsibility load among peer classes

Objects end up communicating at a higher levelExample: Server doesn’t have to know much about Sessions at all now

Page 171: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor171

Back to the Server

Was it good to let people “get” the Server.users collection?

Not really

What about notifySuperusers ()?Probably

Higher level behavior is better“Get/Set” is very dumb behavior

Page 172: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor172

Exercise – Move Method

Can you make Point of Sale classes better using Move Method? [Refactoring] p.142

Try it

Don’t forget your tests

Page 173: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor173

Code Smell : Duplicated Code

Why is it a problem?Code riddled with duplication is harder to understand

Changing something in one place doesn’t have the intended effect

Duplication is often partialRefactor to true duplication and remove

[Refactoring] p.76

Page 174: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor174

Removing Duplication

Extract Method can be used to remove duplication

Additional RefactoringsExtract Superclass

Pull Up Method

Extract Class

Form Template Method

Page 175: Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0

Copyright (c) 2004 Object Mentor175

Duplication Exercise

Remove duplication from the command code in the exercise