Upload
amice-sparks
View
215
Download
0
Tags:
Embed Size (px)
Citation preview
Working Effectively with Legacy Code
Object Mentor, Inc.
Copyright 2003-2004 by Object Mentor, IncAll Rights Reserved
V1.0
Copyright (c) 2004 Object Mentor2
Introduction
What is Legacy Code?Change and Risks
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(!)
Copyright (c) 2004 Object Mentor4
Challenges in Software Change
Seen one way, changing software is easy
Copyright (c) 2004 Object Mentor5
Change and Risks
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
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!
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?
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?
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
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!”
Copyright (c) 2004 Object Mentor12
Safe Changes (the easy cases)Sprout Method
Sprout ClassWrap Method
Wrap Class
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
*
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
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
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
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
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
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
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
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)
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
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
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.
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
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
Copyright (c) 2004 Object Mentor27
Testing as a Programmer’s Tool
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
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
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
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
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
*
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.
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”
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?
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
Copyright (c) 2004 Object Mentor37
Getting a Class Under Test
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
Copyright (c) 2004 Object Mentor39
Testing Challenges
Unbuildable Class
Uninstantiable Class
Uncallable Method
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
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
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
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
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
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;}
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
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;}
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.
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
Copyright (c) 2004 Object Mentor50
Seam Types
Polymorphic CallsText Substitution
Linkage
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
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();
}}
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
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
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
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;
}
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
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.
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
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
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
Copyright (c) 2004 Object Mentor62
Seam Exploitation
Sensing
Separation
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
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
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
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
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
Copyright (c) 2004 Object Mentor68
Dependency Breaking Techniques
Manifest Dependencies
Hidden Dependencies
Practices
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
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
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
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
Copyright (c) 2004 Object Mentor73
Manifest and Hidden Dependencies
Manifest dependencies are the easiest to deal with
Most common technique: Extract Interface
Copyright (c) 2004 Object Mentor74
Practices
Signature Preservation
Leaning on the Compiler
Pair Programming
Single Goal Editing
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
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
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
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
Copyright (c) 2004 Object Mentor79
Writing Tests
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
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
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); }}
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);
Copyright (c) 2004 Object Mentor84
Extract Interface
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
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
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
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);};
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
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
Copyright (c) 2004 Object Mentor91
Breaking Dependencies 1
Parameterize Method
Extract and Override Getter
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);}
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
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
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
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
Copyright (c) 2004 Object Mentor97
Breaking Dependencies 2
Expose Static Method
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;
}…
}}
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
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
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.
Copyright (c) 2004 Object Mentor102
Scheduler Exercise #2
Feature – Remove HTML paragraph markup (<p></p>) in Meeting descriptions
Copyright (c) 2004 Object Mentor103
Breaking Dependencies 3
Introduce Instance Delegator
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) {
…}
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);}
}
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.
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)
Copyright (c) 2004 Object Mentor108
Dependency Breaking 6
Encapsulate Global References
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
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.
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.
Copyright (c) 2004 Object Mentor112
Dependency Breaking 7
Break Out Method Object
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
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.
Copyright (c) 2004 Object Mentor115
Scheduler Exercise #7
Change the Scheduler so that Flextime isn’t allowed on any day that has meetings
Copyright (c) 2004 Object Mentor116
Naming Issues
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 ;-)
Copyright (c) 2004 Object Mentor118
Interface Naming
Names are very important
Code is meant to be understood by humans
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
Copyright (c) 2004 Object Mentor120
Additional Dependency Breaking Techniques
Shift Up Feature
Introduce Static Setter
Parameterize Constructor
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
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?
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
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;
}
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
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
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){}
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
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
Copyright (c) 2004 Object Mentor130
Dealing with Long Methods
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?
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
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
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
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
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
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
Copyright (c) 2004 Object Mentor138
Refactoring: Improving Design
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
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
…
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
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”
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
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.
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
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
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…
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
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
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
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
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 ());
}
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;
}
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
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 () {
}
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 ());}
}
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 ());
}}
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;
}
Copyright (c) 2004 Object Mentor159
Extract Method – Step 4
Compile
(grind, grind, grind)
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);...
}
Copyright (c) 2004 Object Mentor161
Extract Method – Step 6
Compile and test
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?
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
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);
}
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
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
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);
}
}
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);
}
}
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);
}
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
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
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
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
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
Copyright (c) 2004 Object Mentor175
Duplication Exercise
Remove duplication from the command code in the exercise