Working with legacy code

        Andrea Polci
Legacy code: definition



“source code inherited from someone else and source
 code inherited from an older version of the software”
                      (wikipedia)

                 “code without tests”
                  (Michael Feathers)
Legacy code: caracteristics
●   Poor architecture
●   Stratification of modifications
●   Non-uniform coding styles
●   Poor written documentation
●   “Oral” documentation lost
●   No tests
●   Extremely valuable!
    ●   Only successful code become legacy code
Why we have legacy code?
● More and more features
● Shortcuts and hacks


● Developer rotation


● Development team growth


    ●   less communication
Why we need to change legacy
              code?
● New functionality
● Bug


● Refactoring


● Optimization
Options
●   Start from scratch?
●   Look for a new Job?
●   May be we need
    Rambo?
●   Or Mc Gyver?
●   May be we need
    some “tools” to work
    effectively with
    legacy code
Test

●   They gives feedback       ●   What about legacy
    on changes                    code?
●   Different kind of tests       ●   To modify it we need
                                      tests
    ●   Unit
                                  ●   To write test we need
    ●   Integration                   to modify the code
    ●   Functional
An algorithm
1) Identify what to change
2) Identify what to test
3) Break dependencies
4) Write the tests
5) Modify and refactoring
What to change

1) Identify what to change
   ●   Do we have enough knowledge to choose where
       to make changes?
   ●   Sometimes we need to modify many different
       places just to make a simple change.
2) Identify what to test
3) Break dependencies
4) Write the tests
5) Modify and refactoring
I don't understand the code

●   Note/Sketching
●   Listing Markup
●   Scratch Refactoring
●   Write tests
●   Delete Unused Code
    ●   There is the repository for that
I don't understand the structure
●   Long lived applications tend to loose structure
    ●   It takes long time to understand the big picture
    ●   There is no big picture
    ●   The team is in emergency mode and lose sight of the
        big picture
●   It's important that every developer understand the
    big picture or the code will diverge from the
    architecture
    ●   Tell the story of the system
    ●   Naked CRC
    ●   Conversation Scrutiny
What to test
1) Identify what to change
2) Identify what to test
   •   Can be an hard work
   •   Effect analisys
   •   How much time do we have?
3) Break dependencies
4) Write the tests
5) Modify and refactoring
I don't have the time to test
●   Be careful!
●   Sometimes there is no other option
●   Don't make it worse!
    ●   Try to isolate new (tested) code from legacy code
    ●   Sprout method/class
    ●   Wrap method/class
Sprout method
public class ProductLablePrinter{
  …
  public void printLabel(int productId) {
    String barcode;
    …
    // compute barcode
    …
    // print barcode
  }
}
Sprout method
public class ProductLablePrinter{
  …
  public void printLabel(int productId) {
    String barcode;
    …
    // compute barcode
    …

        logPrinted(barcode);

        // print barcode
    }

    protected void logBarcode(String barcode) {
      // My code
    }


}
Break Dependencies
1) Identify what to change
2) Identify what to test
3) Break dependencies
   ●   How do I put a class in a test harness?
   ●   How I know I'm not breaking anything?
4) Write the tests
5) Modify and refactoring
Sensing & Separation
●   Sensing:
    we break dependencies to sense when we can't
    access values our code computes
●   Separation:
    we break dependencies to separate when we
    can't even get a piece of code into a test
    harness to run
Sensing & Separation: Example


public class ProductLablePrinter{
  …
  Public void printBarcode(int productId) {
    String barcode;
    …
    // compute barcode
    …
    // print barcode;
  }
}
Seam

●   Seam:
    a place where you can alter behavior of your
    program without editing in that place
●   Enabling Point:
    Every seam has an enabling point, a place
    where you can make the decision to use one
    behaviour or another
●   Looking for existings seams in legacy code
    allow us to break dependencies (for sensing
    or separation) without refactoring.
Different kind of Seams

●   Preprocessor seams

●   Link seam

●   Object seam
Object seam: Example
public class ProductLablePrinter{
  …
  public void printLabel(int productId) {
    String barcode;
    …
    // compute barcode
    …
    printBarcode(barcode);
  }

    protected void printBarcode(String barcode) {
      // access to printer
    }
}

Public void testPrintBarcode() {
  ProductLablePrinter plp = new ProductLabelPrinter(){
    String lastPrinted = null;
    protected void printBarcode(String barcode) {lastPrinted=barcode;}
  }

    // … test code
}
I can't get this class into a Test

●   Objects of the class can't be created easily
    ●   Parameters we have to pass to the constructor
    ●   Hidden dependencies
●   The test harness won't easily build with the
    class in it
●   The constructor we need to use has bad side
    effects
●   Significant work happens in the constructor
    and we need to sense it
Example (1)
public void testLabelPrinter() {
    new LabelPrinter();
}


The constructor LabelPrinter is undefined
Example (2)
public class LabelPrinter {
  public LabelPrinter(Printer prn, Warehose wh) {
    …
    this.printer = prn;
    if(!this.printer.isOnline()) {
      throw new …
    }
  }
}

public void testLabelPrinter() {
  Printer prn = new Printer(“stampante”);
  Warehouse wh = new Warehouse(“magazzino1”);
  LabelPrinter labPrint = new LabelPrinter(prn, wh);
}

public class Printer {
  boolean isOnline(){ … }
  void startJob() { … }
  void printLine(String line) { … }
  void endJob() { … }
}
Example (3)
public interface PrinterInterface {
  boolean isOnline();
  void startJob();
  void printLine(String line);
  void endJob();
}

public class Printer implements PrinterInterface {
  …
}

public class LabelPrinter {
  public LabelPrinter(PrinterInterface prn, Warehose wh) {
    ...
  }
}
I can't run this method in a test
●   Method not accessible to the test
●   It's hard to construct the parameters
●   Side effects (database, access to
    hardware, ecc.)
●   Need to sense through objects used by the
    method
Test
1) Identify what to change
2) Identify what to test
3) Break dependencies
4) Write the tests
5) Modify and refactoring
Modify and refactoring
1) Identify what to change
2) Identify what to test
3) Break dependencies
4) Write the tests
5) Modify and refactoring
   ●   TDD
   ●   Programming by difference
Making changes takes too much
                time
●   Understanding
    ●   How can we increase our understanding of the code?
●   Lag Time
    ●   It takes to much time to see the effect of changes and
        this slow down the development
    ●   Built time
    ●   Slow tests
    ●   No unit tests
    ●   Often to solve this we need to break dependecies
Tools
●   Authomatic refactoring tools
    ●   Can we trust them?
●   Mock Objects
●   Unit test harnesses
    ●   JUnit
●   Other test harnesses
    ●   Non-unit tests
Conclusions
●   No “silver bullet” here
●   It's an hard work but (usually) not
    impossible
●   At first it will seems overwhelming, but
    things will get better as the number of
    tests increase
●   Be pragmatic!
Questions?
References
●   Workking Effectively with Legacy Code,
    Michael C. Feathers
●   Joel on Software: Things you should never do,
    part I
      http://www.joelonsoftware.com/articles/fog0000000069.html

●   Michael Dubakov: Refactoring vs Rewrite
      http://www.targetprocess.com/blog/2009/11/refactoring-vs-rewrite.html

Working With Legacy Code

  • 1.
    Working with legacycode Andrea Polci
  • 2.
    Legacy code: definition “sourcecode inherited from someone else and source code inherited from an older version of the software” (wikipedia) “code without tests” (Michael Feathers)
  • 3.
    Legacy code: caracteristics ● Poor architecture ● Stratification of modifications ● Non-uniform coding styles ● Poor written documentation ● “Oral” documentation lost ● No tests ● Extremely valuable! ● Only successful code become legacy code
  • 4.
    Why we havelegacy code? ● More and more features ● Shortcuts and hacks ● Developer rotation ● Development team growth ● less communication
  • 5.
    Why we needto change legacy code? ● New functionality ● Bug ● Refactoring ● Optimization
  • 6.
    Options ● Start from scratch? ● Look for a new Job? ● May be we need Rambo? ● Or Mc Gyver? ● May be we need some “tools” to work effectively with legacy code
  • 7.
    Test ● They gives feedback ● What about legacy on changes code? ● Different kind of tests ● To modify it we need tests ● Unit ● To write test we need ● Integration to modify the code ● Functional
  • 8.
    An algorithm 1) Identifywhat to change 2) Identify what to test 3) Break dependencies 4) Write the tests 5) Modify and refactoring
  • 9.
    What to change 1)Identify what to change ● Do we have enough knowledge to choose where to make changes? ● Sometimes we need to modify many different places just to make a simple change. 2) Identify what to test 3) Break dependencies 4) Write the tests 5) Modify and refactoring
  • 10.
    I don't understandthe code ● Note/Sketching ● Listing Markup ● Scratch Refactoring ● Write tests ● Delete Unused Code ● There is the repository for that
  • 11.
    I don't understandthe structure ● Long lived applications tend to loose structure ● It takes long time to understand the big picture ● There is no big picture ● The team is in emergency mode and lose sight of the big picture ● It's important that every developer understand the big picture or the code will diverge from the architecture ● Tell the story of the system ● Naked CRC ● Conversation Scrutiny
  • 12.
    What to test 1)Identify what to change 2) Identify what to test • Can be an hard work • Effect analisys • How much time do we have? 3) Break dependencies 4) Write the tests 5) Modify and refactoring
  • 13.
    I don't havethe time to test ● Be careful! ● Sometimes there is no other option ● Don't make it worse! ● Try to isolate new (tested) code from legacy code ● Sprout method/class ● Wrap method/class
  • 14.
    Sprout method public classProductLablePrinter{ … public void printLabel(int productId) { String barcode; … // compute barcode … // print barcode } }
  • 15.
    Sprout method public classProductLablePrinter{ … public void printLabel(int productId) { String barcode; … // compute barcode … logPrinted(barcode); // print barcode } protected void logBarcode(String barcode) { // My code } }
  • 16.
    Break Dependencies 1) Identifywhat to change 2) Identify what to test 3) Break dependencies ● How do I put a class in a test harness? ● How I know I'm not breaking anything? 4) Write the tests 5) Modify and refactoring
  • 17.
    Sensing & Separation ● Sensing: we break dependencies to sense when we can't access values our code computes ● Separation: we break dependencies to separate when we can't even get a piece of code into a test harness to run
  • 18.
    Sensing & Separation:Example public class ProductLablePrinter{ … Public void printBarcode(int productId) { String barcode; … // compute barcode … // print barcode; } }
  • 19.
    Seam ● Seam: a place where you can alter behavior of your program without editing in that place ● Enabling Point: Every seam has an enabling point, a place where you can make the decision to use one behaviour or another ● Looking for existings seams in legacy code allow us to break dependencies (for sensing or separation) without refactoring.
  • 20.
    Different kind ofSeams ● Preprocessor seams ● Link seam ● Object seam
  • 21.
    Object seam: Example publicclass ProductLablePrinter{ … public void printLabel(int productId) { String barcode; … // compute barcode … printBarcode(barcode); } protected void printBarcode(String barcode) { // access to printer } } Public void testPrintBarcode() { ProductLablePrinter plp = new ProductLabelPrinter(){ String lastPrinted = null; protected void printBarcode(String barcode) {lastPrinted=barcode;} } // … test code }
  • 22.
    I can't getthis class into a Test ● Objects of the class can't be created easily ● Parameters we have to pass to the constructor ● Hidden dependencies ● The test harness won't easily build with the class in it ● The constructor we need to use has bad side effects ● Significant work happens in the constructor and we need to sense it
  • 23.
    Example (1) public voidtestLabelPrinter() { new LabelPrinter(); } The constructor LabelPrinter is undefined
  • 24.
    Example (2) public classLabelPrinter { public LabelPrinter(Printer prn, Warehose wh) { … this.printer = prn; if(!this.printer.isOnline()) { throw new … } } } public void testLabelPrinter() { Printer prn = new Printer(“stampante”); Warehouse wh = new Warehouse(“magazzino1”); LabelPrinter labPrint = new LabelPrinter(prn, wh); } public class Printer { boolean isOnline(){ … } void startJob() { … } void printLine(String line) { … } void endJob() { … } }
  • 25.
    Example (3) public interfacePrinterInterface { boolean isOnline(); void startJob(); void printLine(String line); void endJob(); } public class Printer implements PrinterInterface { … } public class LabelPrinter { public LabelPrinter(PrinterInterface prn, Warehose wh) { ... } }
  • 26.
    I can't runthis method in a test ● Method not accessible to the test ● It's hard to construct the parameters ● Side effects (database, access to hardware, ecc.) ● Need to sense through objects used by the method
  • 27.
    Test 1) Identify whatto change 2) Identify what to test 3) Break dependencies 4) Write the tests 5) Modify and refactoring
  • 28.
    Modify and refactoring 1)Identify what to change 2) Identify what to test 3) Break dependencies 4) Write the tests 5) Modify and refactoring ● TDD ● Programming by difference
  • 29.
    Making changes takestoo much time ● Understanding ● How can we increase our understanding of the code? ● Lag Time ● It takes to much time to see the effect of changes and this slow down the development ● Built time ● Slow tests ● No unit tests ● Often to solve this we need to break dependecies
  • 30.
    Tools ● Authomatic refactoring tools ● Can we trust them? ● Mock Objects ● Unit test harnesses ● JUnit ● Other test harnesses ● Non-unit tests
  • 31.
    Conclusions ● No “silver bullet” here ● It's an hard work but (usually) not impossible ● At first it will seems overwhelming, but things will get better as the number of tests increase ● Be pragmatic!
  • 32.
  • 33.
    References ● Workking Effectively with Legacy Code, Michael C. Feathers ● Joel on Software: Things you should never do, part I http://www.joelonsoftware.com/articles/fog0000000069.html ● Michael Dubakov: Refactoring vs Rewrite http://www.targetprocess.com/blog/2009/11/refactoring-vs-rewrite.html