Working Effectively with Legacy Code

Slides:



Advertisements
Similar presentations
Written by: Dr. JJ Shepherd
Advertisements

Georgia Institute of Technology Workshop for CS-AP Teachers Chapter 3 Advanced Object-Oriented Concepts.
ITEC200 – Week03 Inheritance and Class Hierarchies.
1 Software Maintenance and Evolution CSSE 575: Session 6, Part 4 Breaking Dependencies Steve Chenoweth Office Phone: (812) Cell: (937)
Programming with Java © 2002 The McGraw-Hill Companies, Inc. All rights reserved. 1 Chapter 12 More OOP, Interfaces, and Inner Classes.
Static and Dynamic Behavior Fall 2005 OOPD John Anthony.
(c) University of Washington03-1 CSC 143 Java Inheritance Reading: Ch. 10.
CSE 332: C++ templates and generic programming I Motivation for Generic Programming in C++ We’ve looked at procedural programming –Reuse of code by packaging.
Inheritance. Types of Inheritance Implementation inheritance means that a type derives from a base type, taking all the base type’s member fields and.
1 Using Classes Object-Oriented Programming Using C++ Second Edition 5.
Using Classes Object-Oriented Programming Using C++ Second Edition 5.
CSM-Java Programming-I Spring,2005 Introduction to Objects and Classes Lesson - 1.
REFACTORING Lecture 4. Definition Refactoring is a process of changing the internal structure of the program, not affecting its external behavior and.
Dr. Ahmad R. Hadaegh A.R. Hadaegh California State University San Marcos (CSUSM) Page 1 Virtual Functions Polymorphism Abstract base classes.
“is a”  Define a new class DerivedClass which extends BaseClass class BaseClass { // class contents } class DerivedClass : BaseClass { // class.
CSE 332: C++ templates This Week C++ Templates –Another form of polymorphism (interface based) –Let you plug different types into reusable code Assigned.
1 CSSE 375 – Software Construction & Evolution Problems with Changing Software - 2 Steve Chenoweth, RHIT Below – How do you know if your unit test really.
Chapter 9 Defining New Types. Objectives Explore the use of member functions when creating a struct. Introduce some of the concepts behind object-oriented.
A First Book of C++: From Here To There, Third Edition2 Objectives You should be able to describe: Function and Parameter Declarations Returning a Single.
The Java Programming Language
Method Overriding Remember inheritance: when a child class inherits methods, variables, etc from a parent class. Example: public class Dictionary extends.
Templates An introduction. Simple Template Functions template T max(T x, T y) { if (x > y) { return x; } else { return y; } } int main(void) { int x =
Testing. 2 Overview Testing and debugging are important activities in software development. Techniques and tools are introduced. Material borrowed here.
Working Effectively with Legacy Code Object Mentor, Inc. Copyright  by Object Mentor, Inc All Rights Reserved V1.0.
1 Software Maintenance and Evolution CSSE 575: Session 6, Part 3 Problems with Changing Software - 2 Steve Chenoweth Office Phone: (812) Cell:
Refactoring1 Improving the structure of existing code.
Refactoring Deciding what to make a superclass or interface is difficult. Some of these refactorings are helpful. Some research items include Inheritance.
Programming in Java CSCI-2220 Object Oriented Programming.
Object Oriented Software Development
Refactoring. Refactoring Overview  What is refactoring?  What are four good reasons to refactor?  When should you refactor?  What is a bad smell (relative.
REFACTORINGREFACTORING. Realities Code evolves substantially during development Requirements changes 1%-4% per month on a project Current methodologies.
Programming with Java © 2002 The McGraw-Hill Companies, Inc. All rights reserved. 1 McGraw-Hill/Irwin Chapter 5 Creating Classes.
Reusing Classes composition The trick is to use the classes without soiling the existing code. In this chapter you’ll see two ways to accomplish.
Chapter 5 Introduction to Defining Classes
M1G Introduction to Programming 2 5. Completing the program.
CPS Inheritance and the Yahtzee program l In version of Yahtzee given previously, scorecard.h held information about every score-card entry, e.g.,
Design Patterns Software Engineering CS 561. Last Time Introduced design patterns Abstraction-Occurrence General Hierarchy Player-Role.
Introduction to Object-Oriented Programming Lesson 2.
Method Overriding Remember inheritance: when a child class inherits methods, variables, etc from a parent class. Example: public class Dictionary extends.
Interfaces F What is an Interface? F Creating an Interface F Implementing an Interface F What is Marker Interface?
Creating a GUI Class An example of class design using inheritance and interfaces.
Refactoring1 Improving the structure of existing code.
Inheritance and Class Hierarchies Chapter 3. Chapter 3: Inheritance and Class Hierarchies2 Chapter Objectives To understand inheritance and how it facilitates.
CS2102: Lecture on Abstract Classes and Inheritance Kathi Fisler.
Quick Review of OOP Constructs Classes:  Data types for structured data and behavior  fields and methods Objects:  Variables whose data type is a class.
Written by: Dr. JJ Shepherd
Session 7 Introduction to Inheritance. Accumulator Example a simple calculator app classes needed: –AdderApp - contains main –AddingFrame - GUI –CloseableFrame.
Overview of C++ Polymorphism
1 C# - Inheritance and Polymorphism. 2 1.Inheritance 2.Implementing Inheritance in C# 3.Constructor calls in Inheritance 4.Protected Access Modifier 5.The.
Refactoring. DCS – SWC 2 Refactoring ”A change made to the internal structure of software to make it easier to understand and cheaper to modify without.
(c) University of Washington10-1 CSC 143 Java Errors and Exceptions Reading: Ch. 15.
Author: DoanNX Time: 45’.  OOP concepts  OOP in Java.
Module 9. Dealing with Generalization Course: Refactoring.
Structure A Data structure is a collection of variable which can be same or different types. You can refer to a structure as a single variable, and to.
CSCE 240 – Intro to Software Engineering Lecture 3.
9.1 CLASS (STATIC) VARIABLES AND METHODS Defining classes is only one aspect of object-oriented programming. The real power of object-oriented programming.
Chapter 5 Introduction to Defining Classes Fundamentals of Java.
Catalog of Refactoring (6) Making Method Calls Simpler.
Motivation for Generic Programming in C++
Copyright © Jim Fawcett Spring 2017
Catalog of Refactoring
Inheritance and Polymorphism
Behavioral Design Patterns
Section 11.1 Class Variables and Methods
Week 6 Object-Oriented Programming (2): Polymorphism
Improving the structure of existing code
Overview of C++ Polymorphism
Review: libraries and packages
Refactoring.
Presentation transcript:

Working Effectively with Legacy Code Module 1: Administration November, 1999 Working Effectively with Legacy Code Michael Feathers mfeathers@objectmentor.com Notes 2/5/2004 Michael Feathers Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Module 1: Administration November, 1999 Background Want to do Extreme Programming …but transitioning from a dirty code base if (m_nCriticalError) return; m_nCriticalError=nErrorCode; switch (nEvent) { case FD_READ: case FD_FORCEREAD: if (GetLayerState()==connecting && !nErrorCode) Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

First Reaction Pretend the code isn’t there Use TDD to create new classes, make direct changes to the old ones, hope that they are correct We’d actually like to be even more conservative. We’d like to avoid having to go back to that code ever again. Is this viable?

Let’s Try It

PageGenerator::generate() std::string PageGenerator::generate() { std::vector<Result> results = database.queryResults(beginDate, endDate); std::string pageText; pageText += "<html>"; pageText += "<head>"; pageText += "<title>"; pageText += "Quarterly Report"; pageText += "</title>"; pageText += "</head>"; pageText += "<body>"; pageText += "<h1>"; pageText += "</h1><table>"; …

Continued.. … if (results.size() != 0) { pageText += "<table border=\"1\">"; for (std::vector<Result>::iterator it = results.begin(); it != results.end(); ++it) { pageText += "<tr border=\"1\">"; pageText += "<td>" + it->department + "</td>"; pageText += "<td>" + it->manager + "</td>"; char buffer [128]; sprintf(buffer, "<td>$%d</td>", it->netProfit / 100); pageText += std::string(buffer); sprintf(buffer, "<td>$%d</td>", it->operatingExpense / 100); pageText += "</tr>"; } pageText += "</table>"; } else {

Continued.. … pageText += "<p>No results for this period</p>"; } pageText += "</body>"; pageText += "</html>"; return pageText; // Argh!!

Changes Making changes inline makes methods longer Changes are untested, the code never has a chance to get better We could add code to new methods, and delegate from here Upside: This method won’t get longer We can make progress Downsides: this method won’t be tested Sometimes, it doesn’t work

Chia Pet Pattern When you have legacy code, try to not to add inline code to it. Instead, write tested code in new classes and methods, and delegate to them. The techniques for doing this are called Sprout Method and Sprout Class

Why are we so Conservative? When we don’t have tests it is easy to make unintentional errors when we change code Most of development is about preserving behavior. If it wasn’t we would be able to go much faster

The Refactoring Dilemma When we refactor, we should have tests. To put tests in place, we often have to refactor

Most applications are glued together We don’t know this until we try to test pieces in isolation

Kinds of Glue Singletons (a.k.a. global variables) If there can only be one of an object you’d better hope that it is good for your tests too Internal instantiation When a class creates an instance of a hard coded class, you’d better hope that class runs well in tests Concrete Dependency When a class uses a concrete class, you’d better hope that class lets you know what is happening to it

Breaking Dependencies The way that we break dependencies depends upon the tools available to us. If we have safe extract method and safe extract interface in an IDE, We can do a lot If we don’t, we have to refactor manually, and very conservatively. We have to break dependencies as directly and simply as possible. Sometimes breaking dependencies is ugly

‘The Undetectable Side-Effect’ An ugly Java class (it’s uglier inside, trust me!)

(told you) public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); … private AccountDetailFrame(…) { … } public void actionPerformed(ActionEvent event) { String source = (String)event.getActionCommand(); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; display.setText(accountDescription); }

Separate a dependency public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); … private AccountDetailFrame(…) { … } public void actionPerformed(ActionEvent event) { String source = (String)event.getActionCommand(); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; display.setText(accountDescription); }

After a method extraction.. public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { … public void actionPerformed(ActionEvent event) { performCommand((String)event.getActionCommand()); } void performCommand(String source); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; display.setText(accountDescription);

More offensive dependencies.. public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { … void performCommand(String source); if (source.equals(“project activity”)) { DetailFrame detailDisplay = new DetailFrame(); detailDisplay.setDescription( getDetailText() + “ “ + getProjectText()); detailDisplay.show(); String accountDescription = detailDisplay.getAccountSymbol(); accountDescription += “: “; display.setText(accountDescription); }

More refactoring.. public class AccountDetailFrame extends Frame implements ActionListener, WindowListener { private TextField display = new TextField(10); private DetailFrame detailDisplay; … void performCommand(String source); if (source.equals(“project activity”)) { setDescription(getDetailText() + “” + getProjectText()); String accountDescripton = getAccountSymbol(); accountDescription += “: “; setDisplayText(accountDescription); }

The aftermath.. We can subclass and override getAccountSymbol, setDisplayText, and setDescription to sense our work

A Test.. public void testPerformCommand() { TestingAccountDetailFrame frame = new TestingAccountDetailFrame(); frame.accountSymbol = “SYM”; frame.performCommand(“project activity”); assertEquals(“Magma!”, frame.getDisplayText()); }

Classes are like cells.. Most designs need some mitosis

Making it Better We can make the design better by extracting classes for those separate responsibilities

Better vs. Best “Best is the enemy of good” – Voltaire (paraphrased)

Automated Tool Moves If you have a safe refactoring tool and you are going to use it to break dependencies without tests, do a refactoring series: a series of automated refactorings with no other edits. When tool operations are safe, all that does is move the risk to your edits. Minimize them by leaning on the tool

Manual Moves Lean on the Compiler Preserve Signatures Single Goal Editing

Leaning on the Compiler Module 1: Administration November, 1999 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 Self explanatory… new name for an old technique. Point out that there is no harm triggering a build while you and your partner think about other things. It helps identify problems. In any strongly typed language there are declarations and uses. You can find uses by changing declarations. This can happen locally by changing the name of a variable or making it const. It can also happen globally by extracting an interface and the hiding the name of the class so that all uses can be found. Why not just search for what you need? You can, but searches can be tricked. Not all instances of “foo” in a file may be the name of the same variable. You can’t trick the type checker, or rather, if you can, the language is broken. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Signature Preservation Module 1: Administration November, 1999 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 Show on the board how a formal argument list can be: The signature of a member function declaration The signature of a member function definition A call (once you drop the type information) A series of declarations (if you replace commas with semicolons) Any set of refactorings that you can do where you preserve signatures is safer because there is no chance of a type conversion error. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Single Goal Editing Do one thing at a time

The Legacy Code Change Algorithm Identify Change Points Identify Test Points Break Dependencies Write Tests Refactor or Change

Development Speed Mars Spirit Rover “What can you do in 14 minutes?”

Breaking Dependencies 0 Module 1: Administration November, 1999 Breaking Dependencies 0 Extract Interface Extract Implementor Mention that this is the key technique for breaking dependencies in C++. No other technique gives you as much bang for the buck. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Module 1: Administration November, 1999 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 Self-explanatory Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Module 1: Administration November, 1999 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 Point out that this is a separate language construct in many modern languages. Interfaces are maximally flexible because they don’t dictate implementation at all. Also explain the Dependency Inversion Principle again to show them how interfaces can be used to break dependencies and make builds faster. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Extract Interface Steps Module 1: Administration November, 1999 Extract Interface Steps Find the member-functions that your class uses from the target class Think of an interface name for the responsibility of those methods Verify that no subclass of target class defines those functions non-virtually Create an empty interface class with that name Make the target class inherit from the interface class Replace all target class references in the client class with the name of the interface Lean on the Compiler to find the methods the interface needs Copy function signatures for all unfound functions to the new interface. Make them pure virtual Would be good to show a demo of this. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

The Non-Virtual Override Problem Module 1: Administration November, 1999 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 Draw uses of these classes on the board: EventProcessor *processor = new EventProcessor; Processor->handle(event); Remember that when you have a non-virtual function, the type of the reference, not the type of the object, determines which method will be called. EventProcessor *processor = new MultiplexEventProcessor; Processor->handle(event); // same function is called “The moral is: don’t override non-virtual methods. It is confusing, and if you do, you have some checking to do before you Extract Interface.” Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

The Non-Virtual Override Problem Module 1: Administration November, 1999 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 Show on the board the silent change that happens when you make a base method virtual. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

The Non-Virtual Override Problem Module 1: Administration November, 1999 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 Show ‘replacing calls’ on the board. In the case of processor->handle(event), we can introduce a new function: handleEvent. This one will be virtual and we can have it delegate to handle() for the derived class. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Extract Implementor Same as Extract Interface only we push down instead of pull up.

Breaking Dependencies 1 Module 1: Administration November, 1999 Breaking Dependencies 1 Parameterize Method Extract and Override Call Mention that we are going to go through a series of techniques and exercises now. Each of them will be targeted towards some dependency problem. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Module 1: Administration November, 1999 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); Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Steps – Parameterize Method Module 1: Administration November, 1999 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 Mention that the downside to this technique is that it breaks encapsulation a little. If you are trying to reason about your code, you have a new case where an object could be created externally to the class. But that is fair trade to get it under test. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Extract and Override Call When we have a bad dependency in a method and it is represented as a call. We can extract the call to a method and then override it in a testing subclass.

Steps – Extract and Override Call Module 1: Administration November, 1999 Steps – Extract and Override Call Extract a method for the call (remember to preserve signatures) Make the method virtual Create a testing subclass that overrides that method Add discussion of constructor mocking options for C++.. Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Breaking Dependencies 2 Link-Time Polymorphism

Link-Time Polymorphism void account_deposit(int amount) { struct Call *call = (struct Call *) calloc(1, sizeof (struct Call)); call->type = ACC_DEPOSIT; call->arg0 = amount; append(g_calls, call); }

Steps – Link-Time Polymorphism Identify the functions or classes you want to fake Produce alternative definitions for them Adjust your build so that the alternative definitions are included rather than the production versions.

Breaking Dependencies 3 Expose Static Method

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; } …

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

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

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.

Breaking Dependencies 4 Introduce Instance Delegator

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) { … }

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

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.

Template Redefinition Dependency Breaking 5 Template Redefinition

Template Redefinition C++ gives you the ability to break dependencies using templates class AsyncReceptionPort { private: CSocket m_socket; // bad dependency Packet m_packet; int m_segmentSize; … public: AsyncReceptionPort(); void Run(); };

Template Redefinition template<typename SOCKET> class AsyncReceptionPortImpl { private: SOCKET m_socket; Packet m_packet; int m_segmentSize; … public: AsyncReceptionPortImpl(); void Run(); }; typedef AsyncReceptionPortImpl<CSocket> AsyncReceptionPort;

Steps – Template Redefinition Module 1: Administration November, 1999 Steps – Template Redefinition Identify the features you want to replace in the class you need to test. Turn the class into a template, parameterizing it by the variables you need to replace and copying the method bodies up into the header. Give the template another name. One common practice is to use the word “Impl” as a suffix for the new template Add a typedef statement after the template definition, defining the template with its original arguments using the original class name In the test file include the template definition and instantiate the template on new types which will replace the ones you need to replace for test. Disadvantages: template bloat, dependencies because we move into the headers, some debuggers don’t support tracing into templates Object-Oriented Programming in C#, @1997-2000 Object Mentor, Inc.

Writing Tests

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

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

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); }

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);

Questions & Concerns Why is my code uglier? What can I do to alleviate this. Does this testing help? What about access protection? Can I use reflection for these things?

WELC Understanding, Isolating, Testing, and Changing ..Untested Code www.objectmentor.com www.michaelfeathers.com