This document discusses techniques for working with legacy code, including sprout method, wrap method, and wrap class. Sprout method involves extracting part of an existing method into a new method. Wrap method surrounds an existing method with new code. Wrap class creates a new class that delegates to the original class, allowing new behavior to be added. The techniques allow new functionality to be added to legacy code in a way that does not disrupt existing behavior and allows the new code to be tested independently.
2. Four Reasons to Change Software Adding a feature Fixing a bug Improving the design Optimizing resource usage
3. A Legacy Code Scenario What changes do we have to make? How will we know that we have done them correctly How will we know that we haven’t broken anything Avoiding change is a bad thing, but what is our alternative? Try harder. Hire more people
8. Legacy Code Dilemma When 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
11. Fake objects Support Real Tests public 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()); } }
13. Object Seam class CAsyncSslRec { ... virtual void PostReceiveError(UINT type, UINT errorcode); ... }; class TestingAsyncSslRec : public CAsyncSslRec { virtual void PostReceiveError(UINT type, UINT errorcode) { } };
14. Seam Types Preprocessing Seams Link Seams : static linking in C/C++/Flex and dynamic linking in Java Object Seams
15. I Don't Have Much Time and I Have to Change It Sprout Method Sprout Class Wrap Method Wrap Class
16. Sprout Method public 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); } ... }
17. Sprout Method public 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); } ... }
18. Sprout Method public 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); } ... }
20. Wrap Method public 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); } }
21. Wrap Method public 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() { ... } }
22. Wrap Method Advantages A good way of getting new, tested functionality into an application when we can't easily write tests for the calling code It explicitly makes the new functionality independent of existing functionality Disadvantages Can lead to poor names
23. Wrap Class 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); } ... }
24. Wrap Class class LoggingEmployee extends Employee { public LoggingEmployee(Employee e) { employee = e; } public void pay() { logPayment(); employee.pay(); } private void logPayment() { ... } ... }
25. Wrap Class class LoggingPayDispatcher { private Employee e; public LoggingPayDispatcher(Employee e) { this.e = e; } public void pay() { employee.pay(); logPayment(); } private void logPayment() { ... } ... }
26. Wrap Class Able to add new behavior into a system without adding it to an existing class When there are many calls to the code you want to wrap, it often pays to move toward a decorator-ish wrapper If new behavior has to happen at couple of places, simple class wrapper is very useful
27. I Don't Have Much Time and I Have to Change It: Summary Use 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 before Use Wrap Class when the behavior that I want to add is completely independent
Notas del editor
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)
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
Working with care – leads to using tools and techniques inefficiently
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
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.
Servlet and DBConnection are two issuesPossible option – pass real db connection but how about servlet itself
Mock objects are advanced type of fakes.
A seam is a place where you can alter behavior in your program without editing in that place.
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.
Disadvantages: Giving up on the codeAdvantages: Separating new code from old code
Every time that we pay an employee, we have to update a file with the employee's name so that it can be sent off to some reporting softwareThe easiest way to do….is method
We create a method with the name of the original method and have it delegate to our old code
Use of decoratorToolController controller = new StepNotifyingController( new AlarmingController ( new ACMEController()), notifyees);