Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


Presentation on theme: "Working Effectively with Legacy Code Object Mentor, Inc. Copyright  2003-2004 by Object Mentor, Inc All Rights Reserved V1.0."— Presentation transcript:

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

2 Copyright (c) 2004 Object Mentor 2 Introduction What is Legacy Code? Change and Risks

3 Copyright (c) 2004 Object Mentor 3 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(!)

4 Copyright (c) 2004 Object Mentor 4 Challenges in Software Change Seen one way, changing software is easy

5 Copyright (c) 2004 Object Mentor 5 Change and Risks

6 Copyright (c) 2004 Object Mentor 6 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

7 Copyright (c) 2004 Object Mentor 7 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!

8 Copyright (c) 2004 Object Mentor 8 Changing Code Changing code can introduce bugs, but not changing code causes more bugs (eventually) Imagine a system that is never refactored It’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?

9 Copyright (c) 2004 Object Mentor 9 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?

10 Copyright (c) 2004 Object Mentor 10 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 Effects New 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

11 Copyright (c) 2004 Object Mentor 11 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!”

12 Copyright (c) 2004 Object Mentor 12 Safe Changes (the easy cases) Sprout Method Sprout Class Wrap Method Wrap Class

13 Copyright (c) 2004 Object Mentor 13 A Simple System, a Simple Change Simple system to generate a page of HTML Feature Request We need to add in a table header

14 Copyright (c) 2004 Object Mentor 14 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-first 6.Remove the comment in the source method to enable the call

15 Copyright (c) 2004 Object Mentor 15 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

16 Copyright (c) 2004 Object Mentor 16 Sprout Method Advantages You can write tests directly against the new method The 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

17 Copyright (c) 2004 Object Mentor 17 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

18 Copyright (c) 2004 Object Mentor 18 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

19 Copyright (c) 2004 Object Mentor 19 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

20 Copyright (c) 2004 Object Mentor 20 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 headers Unlike Sprout Method you don’t have to modify the header You introduce new headers and dependencies can be allocated more finely in the system

21 Copyright (c) 2004 Object Mentor 21 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)

22 Copyright (c) 2004 Object Mentor 22 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

23 Copyright (c) 2004 Object Mentor 23 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

24 Copyright (c) 2004 Object Mentor 24 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.

25 Copyright (c) 2004 Object Mentor 25 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

26 Copyright (c) 2004 Object Mentor 26 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

27 Copyright (c) 2004 Object Mentor 27 Testing as a Programmer’s Tool

28 Copyright (c) 2004 Object Mentor 28 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

29 Copyright (c) 2004 Object Mentor 29 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

30 Copyright (c) 2004 Object Mentor 30 Qualities of a Good Harness Easy to understand Easy to use Creating a new test is a one-step process

31 Copyright (c) 2004 Object Mentor 31 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

32 Copyright (c) 2004 Object Mentor 32 Basic xUnit Architecture

33 Copyright (c) 2004 Object Mentor 33 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.

34 Copyright (c) 2004 Object Mentor 34 Sprout Side-Effects Remember the things that can go wrong: 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 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”

35 Copyright (c) 2004 Object Mentor 35 Deciding How Far to Go Risk Does this piece of code have history which warrants extra care Do I understand this code, is this code understandable? What are the ramifications of a bug? Reward What 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? Fear Are you just tired of being scared of this piece of code?

36 Copyright (c) 2004 Object Mentor 36 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

37 Copyright (c) 2004 Object Mentor 37 Getting a Class Under Test

38 Copyright (c) 2004 Object Mentor 38 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

39 Copyright (c) 2004 Object Mentor 39 Testing Challenges Unbuildable Class Uninstantiable Class Uncallable Method

40 Copyright (c) 2004 Object Mentor 40 Unbuildable Class The changing class can’t be compiled or linked into a test harness Causes Too many includes Deep transitive dependencies

41 Copyright (c) 2004 Object Mentor 41 Uninstantiable Class Class compiles in test harness but can be instantiated Causes Transitive construction dependencies Constructor call takes too long Side effects of constructor are inappropriate

42 Copyright (c) 2004 Object Mentor 42 Uncallable Method Able to instantiate class, but unable to call methods under test Causes Trouble creating method arguments Call takes too long Bad side effects

43 Copyright (c) 2004 Object Mentor 43 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

44 Copyright (c) 2004 Object Mentor 44 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

45 Copyright (c) 2004 Object Mentor 45 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 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; }

46 Copyright (c) 2004 Object Mentor 46 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 used The distance function depends on point Consider an alternative

47 Copyright (c) 2004 Object Mentor 47 Seam Example How is this code different? double perimeter(vector 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; }

48 Copyright (c) 2004 Object Mentor 48 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.

49 Copyright (c) 2004 Object Mentor 49 Seam Example How else could we put in a less expensive distance function? Think about the build stages We 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

50 Copyright (c) 2004 Object Mentor 50 Seam Types Polymorphic Calls Text Substitution Linkage

51 Copyright (c) 2004 Object Mentor 51 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

52 Copyright (c) 2004 Object Mentor 52 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(); }

53 Copyright (c) 2004 Object Mentor 53 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

54 Copyright (c) 2004 Object Mentor 54 Polymorphic Call Seam Advantages Leverages features of the language to do the work Making a method virtual opens up other possibilities in your design Disadvantages You 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 use Preferred method of breaking dependencies. Use when the recompilation isn’t too costly

55 Copyright (c) 2004 Object Mentor 55 Subclass To Override A valid use of inheritance Subclass and override the things that get in the way in a test Test 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

56 Copyright (c) 2004 Object Mentor 56 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; }

57 Copyright (c) 2004 Object Mentor 57 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

58 Copyright (c) 2004 Object Mentor 58 Text Substitution Seam To use 1.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.

59 Copyright (c) 2004 Object Mentor 59 Text Substitution Seam Advantages Useful when you have many dependencies around a class and you need to get that class under test Disadvantages May require the creation of many fake implementations Doesn’t really break dependencies in the production code When to Use Use when you have a very large class and you need to get it into a separate harness to do critical work

60 Copyright (c) 2004 Object Mentor 60 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

61 Copyright (c) 2004 Object Mentor 61 Linkage Seam Advantages Can separate dependencies over a large set of calls without altering any source Disadvantages Have to manage the fake libraries, make sure you can’t release them into production Makes more sense for extern “C” functions. The work to duplicate a full class interface exactly is hard When to use When dealing with existing libraries that are used extensively throughout an application

62 Copyright (c) 2004 Object Mentor 62 Seam Exploitation Sensing Separation

63 Copyright (c) 2004 Object Mentor 63 Sensing and Separation There are two reasons to break dependencies in a design Sensing We need to understand what the values are at a particular point in the program flow Separation We need to break a dependency to make testing easier

64 Copyright (c) 2004 Object Mentor 64 Sensing Programs are usually opaque We 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

65 Copyright (c) 2004 Object Mentor 65 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

66 Copyright (c) 2004 Object Mentor 66 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

67 Copyright (c) 2004 Object Mentor 67 Sensing and Separation You don’t always have to use a seam to sense One 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 again Can you write tests against the logging values Probes Invasive, you have to modify the code (possibly obscuring it) Tests will forever be dependent on log calls

68 Copyright (c) 2004 Object Mentor 68 Dependency Breaking Techniques Manifest Dependencies Hidden Dependencies Practices

69 Copyright (c) 2004 Object Mentor 69 Dependencies They cause the trouble you have when you try to Link 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

70 Copyright (c) 2004 Object Mentor 70 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

71 Copyright (c) 2004 Object Mentor 71 Manifest Dependencies Pretty easy to separate Try 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

72 Copyright (c) 2004 Object Mentor 72 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

73 Copyright (c) 2004 Object Mentor 73 Manifest and Hidden Dependencies Manifest dependencies are the easiest to deal with Most common technique: Extract Interface

74 Copyright (c) 2004 Object Mentor 74 Practices Signature Preservation Leaning on the Compiler Pair Programming Single Goal Editing

75 Copyright (c) 2004 Object Mentor 75 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

76 Copyright (c) 2004 Object Mentor 76 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

77 Copyright (c) 2004 Object Mentor 77 Pair Programming Vital when working to get tests in place An extra set of eyes helps Breaking apart code for test is like surgery Doctors never operate alone

78 Copyright (c) 2004 Object Mentor 78 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

79 Copyright (c) 2004 Object Mentor 79 Writing Tests

80 Copyright (c) 2004 Object Mentor 80 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

81 Copyright (c) 2004 Object Mentor 81 Characterization Testing What kind of tests do you need? It’s great to write as many as you need to Feel 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

82 Copyright (c) 2004 Object Mentor 82 Characterization Testing Example What kind of tests do we need if we are going to move part of this method to BillingPlan? class ResidentialAccount void 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); }

83 Copyright (c) 2004 Object Mentor 83 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);

84 Copyright (c) 2004 Object Mentor 84 Extract Interface

85 Copyright (c) 2004 Object Mentor 85 Extract Interface A key refactoring for legacy code Safe With a few rules in mind you can do this without a chance of breaking your software Can take a little time

86 Copyright (c) 2004 Object Mentor 86 Extract Interface Interface A class which contains only pure virtual member- functions Can safely inherit more than one them to present different faces to users of your class

87 Copyright (c) 2004 Object Mentor 87 Extract Interface Steps 1.Find the member-functions that your class uses from the target class 2.Think of an interface name for the responsibility of those methods 3.Verify that no subclass of target class defines those functions non- virtually 4.Create an empty interface class with that name 5.Make the target class inherit from the interface class 6.Replace all target class references in the client class with the name of the interface 7.Lean on the Compiler to find the methods the interface needs 8.Copy function signatures for all unfound functions to the new interface. Make them pure virtual

88 Copyright (c) 2004 Object Mentor 88 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); };

89 Copyright (c) 2004 Object Mentor 89 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

90 Copyright (c) 2004 Object Mentor 90 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

91 Copyright (c) 2004 Object Mentor 91 Breaking Dependencies 1 Parameterize Method Extract and Override Getter

92 Copyright (c) 2004 Object Mentor 92 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); }

93 Copyright (c) 2004 Object Mentor 93 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

94 Copyright (c) 2004 Object Mentor 94 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

95 Copyright (c) 2004 Object Mentor 95 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

96 Copyright (c) 2004 Object Mentor 96 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

97 Copyright (c) 2004 Object Mentor 97 Breaking Dependencies 2 Expose Static Method

98 Copyright (c) 2004 Object Mentor 98 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; } … }

99 Copyright (c) 2004 Object Mentor 99 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

100 Copyright (c) 2004 Object Mentor 100 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

101 Copyright (c) 2004 Object Mentor 101 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 -> validatePacket Increase 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.

102 Copyright (c) 2004 Object Mentor 102 Scheduler Exercise #2 Feature – Remove HTML paragraph markup ( ) in Meeting descriptions

103 Copyright (c) 2004 Object Mentor 103 Breaking Dependencies 3 Introduce Instance Delegator

104 Copyright (c) 2004 Object Mentor 104 Introduce Instance Delegator Static methods are hard to fake because you don’t have a polymorphic call seam You can make one static void BankingServices::updateAccountBalance( int userID, Money amount) { … }

105 Copyright (c) 2004 Object Mentor 105 Introduce Instance Delegator After introduction.. class BankingServices public static void updateAccountBalance( int userID, Money amount) { … } public void updateBalance( int userID, Money amount) { updateAccountBalance(userID, amount); }

106 Copyright (c) 2004 Object Mentor 106 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.

107 Copyright (c) 2004 Object Mentor 107 Scheduler Exercise #3 Change Scheduler.getMeeting so that it returns meetings only if it is a workday (using the TimeServices.IsWorkday method)

108 Copyright (c) 2004 Object Mentor 108 Dependency Breaking 6 Encapsulate Global References

109 Copyright (c) 2004 Object Mentor 109 Encapsulate Global References Free functions and variables can be faked if they are references from a class The reference provides a seam

110 Copyright (c) 2004 Object Mentor 110 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.

111 Copyright (c) 2004 Object Mentor 111 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.

112 Copyright (c) 2004 Object Mentor 112 Dependency Breaking 7 Break Out Method Object

113 Copyright (c) 2004 Object Mentor 113 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

114 Copyright (c) 2004 Object Mentor 114 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.

115 Copyright (c) 2004 Object Mentor 115 Scheduler Exercise #7 Change the Scheduler so that Flextime isn’t allowed on any day that has meetings

116 Copyright (c) 2004 Object Mentor 116 Naming Issues

117 Copyright (c) 2004 Object Mentor 117 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 ;-)

118 Copyright (c) 2004 Object Mentor 118 Interface Naming Names are very important Code is meant to be understood by humans

119 Copyright (c) 2004 Object Mentor 119 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

120 Copyright (c) 2004 Object Mentor 120 Additional Dependency Breaking Techniques Shift Up Feature Introduce Static Setter Parameterize Constructor

121 Copyright (c) 2004 Object Mentor 121 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 test All of the code that you need is placed in NewAbstractSuperclass. NewSuperclass is just a concrete class for testing

122 Copyright (c) 2004 Object Mentor 122 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?

123 Copyright (c) 2004 Object Mentor 123 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

124 Copyright (c) 2004 Object Mentor 124 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; }

125 Copyright (c) 2004 Object Mentor 125 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

126 Copyright (c) 2004 Object Mentor 126 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

127 Copyright (c) 2004 Object Mentor 127 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) { }

128 Copyright (c) 2004 Object Mentor 128 Parameterize Constructor Advantages Very easy to do Don’t break existing clients Disadvantages If the parameterized constructor is used in production code, it can make clients dependent on those classes

129 Copyright (c) 2004 Object Mentor 129 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

130 Copyright (c) 2004 Object Mentor 130 Dealing with Long Methods

131 Copyright (c) 2004 Object Mentor 131 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?

132 Copyright (c) 2004 Object Mentor 132 Sensing Variables What can a long method affect? Parameters, return value, instance variables We can introduce instance variables to sense conditions

133 Copyright (c) 2004 Object Mentor 133 Extract What You Know Extract Small Pieces Small = 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

134 Copyright (c) 2004 Object Mentor 134 Extract What You Know Possible Errors for Extract Method Type Conversion Unassigned Return Value Dropped Parameters Statement inversion to set up the extraction

135 Copyright (c) 2004 Object Mentor 135 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

136 Copyright (c) 2004 Object Mentor 136 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

137 Copyright (c) 2004 Object Mentor 137 Big Picture: Long Method Strategies Skeletonize Extract bodies of conditionals and loops into separate methods Create Sequences Extract conditions and loops into separate methods Scratch Refactoring Agree with your partner to throw the first refactoring session away Do it to discover what direction you want to move towards

138 Copyright (c) 2004 Object Mentor 138 Refactoring: Improving Design

139 Copyright (c) 2004 Object Mentor 139 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 tool John Brant/Don Roberts Refactoring: Improving the Design of Existing Code Martin Fowler

140 Copyright (c) 2004 Object Mentor 140 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 …

141 Copyright (c) 2004 Object Mentor 141 How Does Refactoring Help? Improves the design of software Bad/decaying design makes new functionality harder to add We don’t have to be right the first time… Makes software easier to understand Code communicates its intent as clearly as possible Helps us find defects Clarifying code/structure gives deeper understanding Allows us to spot bugs more efficiently

142 Copyright (c) 2004 Object Mentor 142 Is Refactoring a Silver Bullet? No! A valuable tool “A pair of silver pliers that help you keep a good grip on your code”

143 Copyright (c) 2004 Object Mentor 143 What is Refactoring? Move from one valid state to another Tests 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

144 Copyright (c) 2004 Object Mentor 144 Definitions of Refactoring Loose Usage Reorganize 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.

145 Copyright (c) 2004 Object Mentor 145 When Should You Refactor? To add new functionality Existing structure may not allow it “Shoehorning in” a feature makes other features harder to add To fix a defect Code was probably not clear enough if the bug managed to slip through When doing code reviews Always As part of a write-test, write-code, refactor cycle

146 Copyright (c) 2004 Object Mentor 146 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

147 Copyright (c) 2004 Object Mentor 147 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…

148 Copyright (c) 2004 Object Mentor 148 Code Smells Many things make a design hard to maintain Develop a “nose” for bad structurings Long Method Large Class Feature Envy Data class Duplicated Code … [Refactoring] p.75

149 Copyright (c) 2004 Object Mentor 149 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 logic Adding new behavior should not require changing existing code Keep design simple Minimize the story you would tell to explain the system Watch out for complex conditional logic YAGNI

150 Copyright (c) 2004 Object Mentor 150 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

151 Copyright (c) 2004 Object Mentor 151 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 system How do you make a method communicate at a higher level? Name its parts Extract Method

152 Copyright (c) 2004 Object Mentor 152 Refactoring: Extract Method You have a code fragment that can be grouped together Turn 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 details System.out.println (“name “ + _name); System.out.println (“amount “ + getOutstanding ()); }

153 Copyright (c) 2004 Object Mentor 153 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; }

154 Copyright (c) 2004 Object Mentor 154 Steps for Extract Method Create method named after intention of code Copy extracted code Look for local variables and parameters turn into parameter turn into return value declare within method Compile Replace code fragment with call to new method Compile and test

155 Copyright (c) 2004 Object Mentor 155 Extract Method – Step 1 Create a method named after the intention of the code Private, accepts void, returns void private void maxLatency () { }

156 Copyright (c) 2004 Object Mentor 156 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 ()); }

157 Copyright (c) 2004 Object Mentor 157 Extract Method – Step 3 Look for local variables and parameters pass 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 ()); }

158 Copyright (c) 2004 Object Mentor 158 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; }

159 Copyright (c) 2004 Object Mentor 159 Extract Method – Step 4 Compile (grind, grind, grind)

160 Copyright (c) 2004 Object Mentor 160 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);... }

161 Copyright (c) 2004 Object Mentor 161 Extract Method – Step 6 Compile and test

162 Copyright (c) 2004 Object Mentor 162 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?

163 Copyright (c) 2004 Object Mentor 163 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

164 Copyright (c) 2004 Object Mentor 164 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); }

165 Copyright (c) 2004 Object Mentor 165 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

166 Copyright (c) 2004 Object Mentor 166 Move Method – Steps Declare method in target class Copy and fit code Set up a reference from the source object to the target Turn the original method into a delegating method accept (Session session){return session.logOn (this);} Check for overriding methods Compile and test Find all users of the method Adjust them to call method on target Remove original method Compile and test

167 Copyright (c) 2004 Object Mentor 167 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); }

168 Copyright (c) 2004 Object Mentor 168 Move Method – Steps 3 & 4 Set up reference, retarget methods You 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); }

169 Copyright (c) 2004 Object Mentor 169 Move Method – Remaining Steps Delegate Compile and test Look for callers of accept Can they call logOn directly? public void accept (Session newSession) { newSession.logOn (this); }

170 Copyright (c) 2004 Object Mentor 170 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 level Example: Server doesn’t have to know much about Sessions at all now

171 Copyright (c) 2004 Object Mentor 171 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

172 Copyright (c) 2004 Object Mentor 172 Exercise – Move Method Can you make Point of Sale classes better using Move Method? [Refactoring] p.142 Try it Don’t forget your tests

173 Copyright (c) 2004 Object Mentor 173 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 partial Refactor to true duplication and remove [Refactoring] p.76

174 Copyright (c) 2004 Object Mentor 174 Removing Duplication Extract Method can be used to remove duplication Additional Refactorings Extract Superclass Pull Up Method Extract Class Form Template Method

175 Copyright (c) 2004 Object Mentor 175 Duplication Exercise Remove duplication from the command code in the exercise


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

Similar presentations


Ads by Google