Working effectively with legacy code

3,738 views
3,592 views

Published on

Published in: Technology
0 Comments
2 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,738
On SlideShare
0
From Embeds
0
Number of Embeds
10
Actions
Shares
0
Downloads
11
Comments
0
Likes
2
Embeds 0
No embeds

No notes for slide
  • 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
  • "<tr><td>Department</td><td>Manager</td><td>Profit</td><td>Expenses</td></tr>"
  • 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);
  • Without using decorator
  • Working effectively with legacy code

    1. 1. Working Effectively with Legacy Code – Part I<br />-ShriKantVashishtha<br />Aug 19, 2009<br />
    2. 2. Four Reasons to Change Software<br />Adding a feature<br />Fixing a bug<br />Improving the design<br />Optimizing resource usage<br />
    3. 3. A Legacy Code Scenario<br />What changes do we have to make?<br />How will we know that we have done them correctly<br />How will we know that we haven’t broken anything<br />Avoiding change is a bad thing, but what is our alternative?<br />Try harder. Hire more people<br />
    4. 4. Way of doing changes - Edit and Pray<br />
    5. 5. Way of doing changes - Cover and Modify<br />
    6. 6. Why Unit Tests Are Important?<br />Error localization<br />Execution time<br />Coverage<br />
    7. 7. A Typical Legacy Code Sample<br />
    8. 8. Legacy Code Dilemma<br />When we change code, we should have tests in place. To put tests in place, we often have to change code.<br />Use Primitivize Parameter and Extract Interface to remove these issues<br />
    9. 9. Fake Objects<br />
    10. 10. Fake Objects<br />
    11. 11. Fake objects Support Real Tests<br />public class FakeDisplay implements Display<br />{<br /> private String lastLine = "";<br /> public void showLine(String line) {<br />lastLine = line;<br /> }<br /> public String getLastLine() {<br /> return lastLine;<br /> }<br />}<br />import junit.framework.*;<br />public class SaleTest extends TestCase<br />{<br /> public void testDisplayAnItem() {<br />FakeDisplay display = new FakeDisplay();<br /> Sale sale = new Sale(display);<br />sale.scan("1");<br />assertEquals("Milk $3.99", display.getLastLine());<br /> }<br />}<br />
    12. 12. Seams<br />boolCAsyncSslRec::Init()<br />{<br /> …<br /> if (!m_bFailureSent) {<br />m_bFailureSent=TRUE;<br />PostReceiveError(SOCKETCALLBACK, SSL_FAILURE);<br /> }<br />CreateLibrary(m_hSslDll1,"syncesel1.dll");<br />CreateLibrary(m_hSslDll2,"syncesel2.dll");<br /> ….<br /> return true;<br />}<br />
    13. 13. Object Seam<br />class CAsyncSslRec<br />{<br /> ...<br /> virtual void PostReceiveError(UINT type, UINT errorcode);<br /> ...<br />};<br />class TestingAsyncSslRec : public CAsyncSslRec<br />{<br /> virtual void PostReceiveError(UINT type, UINT errorcode)<br /> {<br /> }<br />};<br />
    14. 14. Seam Types<br />Preprocessing Seams<br />Link Seams : static linking in C/C++/Flex and dynamic linking in Java<br />Object Seams<br />
    15. 15. I Don't Have Much Time and I Have to Change It<br />Sprout Method<br />Sprout Class<br />Wrap Method<br />Wrap Class<br />
    16. 16. Sprout Method<br />public class TransactionGate<br />{<br /> public void postEntries(List entries) {<br /> for (Iterator it = entries.iterator(); it.hasNext(); ) {<br /> Entry entry = (Entry)it.next();<br />entry.postDate();<br /> }<br />transactionBundle.getListManager().add(entries);<br /> }<br /> ...<br />}<br />
    17. 17. Sprout Method<br />public class TransactionGate<br />{<br /> public void postEntries(List entries) {<br /> List entriesToAdd = new LinkedList();<br /> for (Iterator it = entries.iterator(); it.hasNext(); ) {<br /> Entry entry = (Entry)it.next();<br /> if (!transactionBundle.getListManager().hasEntry(entry) {<br />entry.postDate();<br />entriesToAdd.add(entry);<br /> }<br /> }<br />transactionBundle.getListManager().add(entriesToAdd);<br /> }<br /> ...<br />}<br />
    18. 18. Sprout Method<br />public class TransactionGate<br />{<br /> ...<br /> List uniqueEntries(List entries) {<br /> List result = new ArrayList();<br /> for (Iterator it = entries.iterator(); it.hasNext(); ) {<br /> Entry entry = (Entry)it.next();<br /> if (!transactionBundle.getListManager().hasEntry(entry) {<br />result.add(entry);<br /> }<br /> }<br /> return result;<br /> }<br /> ...<br />}<br />public class TransactionGate<br />{<br /> ...<br /> public void postEntries(List entries) {<br /> List entriesToAdd = uniqueEntries(entries);<br /> for (Iterator it = entriesToAdd.iterator(); it.hasNext(); ) {<br /> Entry entry = (Entry)it.next();<br />entry.postDate();<br /> }<br />transactionBundle.getListManager().add(entriesToAdd);<br /> }<br /> ...<br />}<br />
    19. 19. Sprout Class<br />std::string QuarterlyReportGenerator::generate()<br />{<br /> std::vector<Result> results = database.queryResults(<br />beginDate, endDate);<br /> std::string pageText;<br />pageText += "<html><head><title>"<br /> "Quarterly Report"<br /> "</title></head><body><table>";<br /> if (results.size() != 0) {<br /> for (std::vector<Result>::iterator it = results.begin();<br /> it != results.end();<br /> ++it) {<br />pageText += "<tr>";<br />pageText += "<td>" + it->department + "</td>";<br />pageText += "<td>" + it->manager + "</td>";<br /> char buffer [128];<br />sprintf(buffer, "<td>$%d</td>", it->netProfit / 100);<br />pageText += std::string(buffer);<br />sprintf(buffer, "<td>$%d</td>", it->operatingExpense / 100);<br />pageText += std::string(buffer);<br />pageText += "</tr>";<br /> }<br /> } else {<br />pageText += "No results for this period";<br /> }<br />pageText += "</table>";<br />pageText += "</body>";<br />pageText += "</html>";<br /> return pageText;<br />}<br />
    20. 20. Wrap Method<br />public class Employee<br />{<br /> ...<br /> public void pay() {<br /> Money amount = new Money();<br /> for (Iterator it = timecards.iterator(); it.hasNext(); ) {<br /> Timecard card = (Timecard)it.next();<br /> if (payPeriod.contains(date)) {<br />amount.add(card.getHours() * payRate);<br /> }<br /> }<br />payDispatcher.pay(this, date, amount);<br /> }<br />}<br />
    21. 21. Wrap Method<br />public class Employee<br />{<br /> private void dispatchPayment() {<br /> Money amount = new Money();<br /> for (Iterator it = timecards.iterator(); it.hasNext(); ) {<br /> Timecard card = (Timecard)it.next();<br /> if (payPeriod.contains(date)) {<br />amount.add(card.getHours() * payRate);<br /> }<br /> }<br />payDispatcher.pay(this, date, amount);<br /> }<br /> public void pay() {<br />logPayment();<br />dispatchPayment();<br /> }<br /> private void logPayment() {<br /> ...<br /> }<br />}<br />
    22. 22. Wrap Method<br />Advantages<br />A good way of getting new, tested functionality into an application when we can't easily write tests for the calling code<br />It explicitly makes the new functionality independent of existing functionality<br />Disadvantages<br />Can lead to poor names<br />
    23. 23. Wrap Class<br />class Employee<br />{<br /> public void pay() {<br /> Money amount = new Money();<br /> for (Iterator it = timecards.iterator(); it.hasNext(); ) {<br /> Timecard card = (Timecard)it.next();<br /> if (payPeriod.contains(date)) {<br />amount.add(card.getHours() * payRate);<br /> }<br /> }<br />payDispatcher.pay(this, date, amount);<br /> }<br /> ...<br />}<br />
    24. 24. Wrap Class<br />class LoggingEmployee extends Employee<br />{<br /> public LoggingEmployee(Employee e) {<br /> employee = e;<br /> }<br /> public void pay() {<br />logPayment();<br />employee.pay();<br /> }<br /> private void logPayment() {<br /> ...<br /> }<br /> ...<br />}<br />
    25. 25. Wrap Class<br />class LoggingPayDispatcher<br />{<br /> private Employee e;<br /> public LoggingPayDispatcher(Employee e) {<br />this.e = e;<br /> }<br /> public void pay() {<br />employee.pay();<br />logPayment();<br /> }<br /> private void logPayment() {<br /> ...<br /> }<br /> ...<br />}<br />
    26. 26. Wrap Class<br />Able to add new behavior into a system without adding it to an existing class<br />When there are many calls to the code you want to wrap, it often pays to move toward a decorator-ish wrapper<br />If new behavior has to happen at couple of places, simple class wrapper is very useful<br />
    27. 27. I Don't Have Much Time and I Have to Change It: Summary<br />Use Sprout Method when the code you have in the existing method communicates a clear algorithm to the reader. <br />Use Wrap Method when you think that the new feature you’re adding is as important as the work that was there before<br />Use Wrap Class when the behavior that I want to add is completely independent<br />

    ×