Working Effectively with Legacy Code – Part I-ShriKantVashishthaAug 19, 2009
Four Reasons to Change SoftwareAdding a featureFixing a bugImproving the designOptimizing resource usage
A Legacy Code ScenarioWhat changes do we have to make?How will we know that we have done them correctlyHow will we know that we haven’t broken anythingAvoiding change is a bad thing, but what is our alternative?Try harder. Hire more people
Way of doing changes - Edit and Pray
Way of doing changes - Cover and Modify
Why Unit Tests Are Important?Error localizationExecution timeCoverage
A Typical Legacy Code Sample
Legacy Code DilemmaWhen we change code, we should have tests in place. To put tests in place, we often have to change code.Use Primitivize Parameter and Extract Interface to remove these issues
Fake Objects
Fake Objects
Fake objects Support Real Testspublic class FakeDisplay implements Display{    private String lastLine = "";    public void showLine(String line) {lastLine = line;    }    public String getLastLine() {        return lastLine;    }}import junit.framework.*;public class SaleTest extends TestCase{    public void testDisplayAnItem() {FakeDisplay display = new FakeDisplay();        Sale sale = new Sale(display);sale.scan("1");assertEquals("Milk $3.99", display.getLastLine());    }}
SeamsboolCAsyncSslRec::Init(){    …    if (!m_bFailureSent) {m_bFailureSent=TRUE;PostReceiveError(SOCKETCALLBACK, SSL_FAILURE);    }CreateLibrary(m_hSslDll1,"syncesel1.dll");CreateLibrary(m_hSslDll2,"syncesel2.dll");    ….    return true;}
Object Seamclass CAsyncSslRec{    ...    virtual void PostReceiveError(UINT type, UINT errorcode);    ...};class TestingAsyncSslRec : public CAsyncSslRec{    virtual void PostReceiveError(UINT type, UINT errorcode)    {    }};
Seam TypesPreprocessing SeamsLink Seams : static linking in C/C++/Flex and dynamic linking in JavaObject Seams
I Don't Have Much Time and I Have to Change ItSprout MethodSprout ClassWrap MethodWrap Class
Sprout Methodpublic class TransactionGate{    public void postEntries(List entries) {        for (Iterator it = entries.iterator(); it.hasNext(); ) {            Entry entry = (Entry)it.next();entry.postDate();        }transactionBundle.getListManager().add(entries);    }    ...}
Sprout Methodpublic class TransactionGate{    public void postEntries(List entries) {        List entriesToAdd = new LinkedList();        for (Iterator it = entries.iterator(); it.hasNext(); ) {            Entry entry = (Entry)it.next();   if (!transactionBundle.getListManager().hasEntry(entry) {entry.postDate();entriesToAdd.add(entry);            }        }transactionBundle.getListManager().add(entriesToAdd);    }    ...}
Sprout Methodpublic class TransactionGate{    ...    List uniqueEntries(List entries) {        List result = new ArrayList();        for (Iterator it = entries.iterator(); it.hasNext(); ) {            Entry entry = (Entry)it.next();            if (!transactionBundle.getListManager().hasEntry(entry) {result.add(entry);            }        }        return result;    }    ...}public class TransactionGate{    ...    public void postEntries(List entries) {        List entriesToAdd = uniqueEntries(entries);        for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) {            Entry entry = (Entry)it.next();entry.postDate();        }transactionBundle.getListManager().add(entriesToAdd);    }    ...}
Sprout Classstd::string QuarterlyReportGenerator::generate(){    std::vector<Result> results = database.queryResults(beginDate, endDate);    std::string pageText;pageText += "<html><head><title>"            "Quarterly Report"            "</title></head><body><table>";    if (results.size() != 0) {        for (std::vector<Result>::iterator it = results.begin();                it != results.end();                ++it) {pageText += "<tr>";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 += std::string(buffer);pageText += "</tr>";        }    } else {pageText += "No results for this period";    }pageText += "</table>";pageText += "</body>";pageText += "</html>";    return pageText;}
Wrap Methodpublic class Employee{    ...    public void pay() {        Money amount = new Money();        for (Iterator it = timecards.iterator(); it.hasNext(); ) {            Timecard card = (Timecard)it.next();            if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate);            }        }payDispatcher.pay(this, date, amount);    }}
Wrap Methodpublic class Employee{    private void dispatchPayment() {        Money amount = new Money();        for (Iterator it = timecards.iterator(); it.hasNext(); ) {            Timecard card = (Timecard)it.next();            if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate);            }        }payDispatcher.pay(this, date, amount);    }    public void pay() {logPayment();dispatchPayment();    }    private void logPayment() {    ...    }}
Wrap MethodAdvantagesA good way of getting new, tested functionality into an application when we can't easily write tests for the calling codeIt explicitly makes the new functionality independent of existing functionalityDisadvantagesCan lead to poor names
Wrap Classclass Employee{    public void pay() {        Money amount = new Money();        for (Iterator it = timecards.iterator(); it.hasNext(); ) {            Timecard card = (Timecard)it.next();            if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate);            }        }payDispatcher.pay(this, date, amount);    }    ...}
Wrap Classclass LoggingEmployee extends Employee{    public LoggingEmployee(Employee e) {        employee = e;    }    public void pay() {logPayment();employee.pay();    }    private void logPayment() {        ...    }    ...}
Wrap Classclass LoggingPayDispatcher{    private Employee e;    public LoggingPayDispatcher(Employee e) {this.e  = e;    }    public void pay() {employee.pay();logPayment();    }    private void logPayment() {        ...    }    ...}
Wrap ClassAble to add new behavior into a system without adding it to an existing classWhen there are many calls to the code you want to wrap, it often pays to move toward a decorator-ish wrapperIf new behavior has to happen at couple of places, simple class wrapper is very useful
I Don't Have Much Time and I Have to Change It: SummaryUse Sprout Method when the code you have in the existing method communicates a clear algorithm to the reader. Use Wrap Method when you think that the new feature you’re adding is as important as the work that was there beforeUse Wrap Class when the behavior that I want to add is completely independent

Working effectively with legacy code

  • 1.
    Working Effectively withLegacy Code – Part I-ShriKantVashishthaAug 19, 2009
  • 2.
    Four Reasons toChange SoftwareAdding a featureFixing a bugImproving the designOptimizing resource usage
  • 3.
    A Legacy CodeScenarioWhat changes do we have to make?How will we know that we have done them correctlyHow will we know that we haven’t broken anythingAvoiding change is a bad thing, but what is our alternative?Try harder. Hire more people
  • 4.
    Way of doingchanges - Edit and Pray
  • 5.
    Way of doingchanges - Cover and Modify
  • 6.
    Why Unit TestsAre Important?Error localizationExecution timeCoverage
  • 7.
    A Typical LegacyCode Sample
  • 8.
    Legacy Code DilemmaWhenwe change code, we should have tests in place. To put tests in place, we often have to change code.Use Primitivize Parameter and Extract Interface to remove these issues
  • 9.
  • 10.
  • 11.
    Fake objects SupportReal Testspublic class FakeDisplay implements Display{ private String lastLine = ""; public void showLine(String line) {lastLine = line; } public String getLastLine() { return lastLine; }}import junit.framework.*;public class SaleTest extends TestCase{ public void testDisplayAnItem() {FakeDisplay display = new FakeDisplay(); Sale sale = new Sale(display);sale.scan("1");assertEquals("Milk $3.99", display.getLastLine()); }}
  • 12.
    SeamsboolCAsyncSslRec::Init(){ … if (!m_bFailureSent) {m_bFailureSent=TRUE;PostReceiveError(SOCKETCALLBACK, SSL_FAILURE); }CreateLibrary(m_hSslDll1,"syncesel1.dll");CreateLibrary(m_hSslDll2,"syncesel2.dll"); …. return true;}
  • 13.
    Object Seamclass CAsyncSslRec{ ... virtual void PostReceiveError(UINT type, UINT errorcode); ...};class TestingAsyncSslRec : public CAsyncSslRec{ virtual void PostReceiveError(UINT type, UINT errorcode) { }};
  • 14.
    Seam TypesPreprocessing SeamsLinkSeams : static linking in C/C++/Flex and dynamic linking in JavaObject Seams
  • 15.
    I Don't HaveMuch Time and I Have to Change ItSprout MethodSprout ClassWrap MethodWrap Class
  • 16.
    Sprout Methodpublic classTransactionGate{ public void postEntries(List entries) { for (Iterator it = entries.iterator(); it.hasNext(); ) { Entry entry = (Entry)it.next();entry.postDate(); }transactionBundle.getListManager().add(entries); } ...}
  • 17.
    Sprout Methodpublic classTransactionGate{ public void postEntries(List entries) { List entriesToAdd = new LinkedList(); for (Iterator it = entries.iterator(); it.hasNext(); ) { Entry entry = (Entry)it.next(); if (!transactionBundle.getListManager().hasEntry(entry) {entry.postDate();entriesToAdd.add(entry); } }transactionBundle.getListManager().add(entriesToAdd); } ...}
  • 18.
    Sprout Methodpublic classTransactionGate{ ... List uniqueEntries(List entries) { List result = new ArrayList(); for (Iterator it = entries.iterator(); it.hasNext(); ) { Entry entry = (Entry)it.next(); if (!transactionBundle.getListManager().hasEntry(entry) {result.add(entry); } } return result; } ...}public class TransactionGate{ ... public void postEntries(List entries) { List entriesToAdd = uniqueEntries(entries); for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) { Entry entry = (Entry)it.next();entry.postDate(); }transactionBundle.getListManager().add(entriesToAdd); } ...}
  • 19.
    Sprout Classstd::string QuarterlyReportGenerator::generate(){ std::vector<Result> results = database.queryResults(beginDate, endDate); std::string pageText;pageText += "<html><head><title>" "Quarterly Report" "</title></head><body><table>"; if (results.size() != 0) { for (std::vector<Result>::iterator it = results.begin(); it != results.end(); ++it) {pageText += "<tr>";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 += std::string(buffer);pageText += "</tr>"; } } else {pageText += "No results for this period"; }pageText += "</table>";pageText += "</body>";pageText += "</html>"; return pageText;}
  • 20.
    Wrap Methodpublic classEmployee{ ... public void pay() { Money amount = new Money(); for (Iterator it = timecards.iterator(); it.hasNext(); ) { Timecard card = (Timecard)it.next(); if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate); } }payDispatcher.pay(this, date, amount); }}
  • 21.
    Wrap Methodpublic classEmployee{ private void dispatchPayment() { Money amount = new Money(); for (Iterator it = timecards.iterator(); it.hasNext(); ) { Timecard card = (Timecard)it.next(); if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate); } }payDispatcher.pay(this, date, amount); } public void pay() {logPayment();dispatchPayment(); } private void logPayment() { ... }}
  • 22.
    Wrap MethodAdvantagesA goodway of getting new, tested functionality into an application when we can't easily write tests for the calling codeIt explicitly makes the new functionality independent of existing functionalityDisadvantagesCan lead to poor names
  • 23.
    Wrap Classclass Employee{ public void pay() { Money amount = new Money(); for (Iterator it = timecards.iterator(); it.hasNext(); ) { Timecard card = (Timecard)it.next(); if (payPeriod.contains(date)) {amount.add(card.getHours() * payRate); } }payDispatcher.pay(this, date, amount); } ...}
  • 24.
    Wrap Classclass LoggingEmployeeextends Employee{ public LoggingEmployee(Employee e) { employee = e; } public void pay() {logPayment();employee.pay(); } private void logPayment() { ... } ...}
  • 25.
    Wrap Classclass LoggingPayDispatcher{ private Employee e; public LoggingPayDispatcher(Employee e) {this.e = e; } public void pay() {employee.pay();logPayment(); } private void logPayment() { ... } ...}
  • 26.
    Wrap ClassAble toadd new behavior into a system without adding it to an existing classWhen there are many calls to the code you want to wrap, it often pays to move toward a decorator-ish wrapperIf new behavior has to happen at couple of places, simple class wrapper is very useful
  • 27.
    I Don't HaveMuch Time and I Have to Change It: SummaryUse Sprout Method when the code you have in the existing method communicates a clear algorithm to the reader. Use Wrap Method when you think that the new feature you’re adding is as important as the work that was there beforeUse Wrap Class when the behavior that I want to add is completely independent

Editor's Notes

  • #3 It seems nearly impossible to add behavior without changing it to some degree.Improving design – a series of small structural modifications, supported by tests to make the code easier to change.Optimization – functionality exactly the same but changing something else (usually time or memory)
  • #4 Talk about a team policy where – if it’s not broke, don’t fix it. – Talk about problems it createsDifference between good and bad systems
  • #5 Working with care – leads to using tools and techniques inefficiently
  • #6 Use of safety netCovering software means covering with testsTesting to attempt to show correctnessVsTesting to detect change – tests as software vise – keep most of the behavior fixed and change only what you intend toRegression tests with or without writing tests – mention a story about doing the testing at the end
  • #7 Other kinds of tests often masquerade as unit tests. A test is not a unit test if:It talks to a database.It communicates across a network.It touches the file system.You have to do special things to your environment (such as editing configuration files) to run it.
  • #8 Servlet and DBConnection are two issuesPossible option – pass real db connection but how about servlet itself
  • #12 Mock objects are advanced type of fakes.
  • #13 A seam is a place where you can alter behavior in your program without editing in that place.
  • #17 We need to add code to verify that none of the new entries are already in transactionBundle before we post their dates and add them.
  • #19 Disadvantages: Giving up on the codeAdvantages: Separating new code from old code
  • #20 &quot;&lt;tr&gt;&lt;td&gt;Department&lt;/td&gt;&lt;td&gt;Manager&lt;/td&gt;&lt;td&gt;Profit&lt;/td&gt;&lt;td&gt;Expenses&lt;/td&gt;&lt;/tr&gt;&quot;
  • #21 Every time that we pay an employee, we have to update a file with the employee&apos;s name so that it can be sent off to some reporting softwareThe easiest way to do….is method
  • #22 We create a method with the name of the original method and have it delegate to our old code
  • #25 Use of decoratorToolController controller = new StepNotifyingController( new AlarmingController ( new ACMEController()), notifyees);
  • #26 Without using decorator