Working Effectively with Legacy Code

  1. Changing Software
    • Four reasons to change software
      • adding a feature
      • fixing a bug
      • improving design
      • optimizing resource usage
    • Adding features and fixing bugs
      • some argue whether something is a bug fix vs feature
      • more important distinction is behavior i.e. adding or changing
    • Improving Design (Refactoring)
      • keep behavior intact
      • programmers avoid because easy to lose behavior or add bad behavior
      • should have tests to make sure existing behavior doesn’t change
      • make more maintainable and no functional changes
    • Optimization
      • no functional changes, but use less time and/or memory
    • Putting it all together
      • refactoring and optimization keep functionality invariant
      • in terms of existing functionality, held constant for features, refactoring and optimization
      • in all cases, generally want to preserve much more than we’re changing
      • not only making sure things we change are correct, but also that its preserving rest of behavior
      • often don’t know how much behavior at risk
    • Risky Change
      • can have considerable risk
      • what changes?
      • how will we know we’ve done correctly?
      • how will we know we haven’t broken anything
      • avoiding changes will catch up with you
      • in poorly structured code, won’t feel good about change
      • people get rusty if don’t make changes and live in fear
  2. Working with Feedback
    • Intro
      • two ways change can be made 1) edit and pray 2) cover and modify
      • without automated tests, feedback loop can be large
      • extensive unit tests allow quicker and more confident refactoring
    • What is unit testing?
      • concerned with most atomic behavioral units of a system
      • in oop, usually units are classes
      • large tests make error localization difficult and can take long
      • also may be hard to cover behavior of newly added code
      • unit tests should be fast and help localize problems
      • 1/10th a second for a test is slow
      • unit tests shouldn’t 1) talk to db, 2) network calls, 3) filesystem 4) special things to set up environment
    • high level testing
      • useful to pin down behavior for set of classes
    • test coverings
      • safer to have tests around changes
      • more likely to catch mistakes
      • important to be able to instantiate class
      • dependency is one of the most critical problems in software
      • much legacy code work involves breaking dependencies so that changes can be easier
      • some cases not practical or possible to test without changing code
      • e.g. InvoiceUpdateResponder(DBConnection, InvoiceUpdateServlet)
      • use interface for db and pass in collection of invoice Ids vs servlet
      • do these initial refactorings very conservatively
    • the legacy code change algorithm
      • want to make functional changes that deliver value and more test coverage
      • identify change points: sensitive to your architecture if don’t know design enough 1) review ch16 or ch 17
      • Find test points: could be hard to know where to add
      • break dependencies: manifest as difficulty instantiating objects in test harness and difficulty running methods
      • write tests
    • make changes and refactor
      • advocate TDD to add features in legacy code
      • techniques in book not for clean, ideal, pattern-enriched design
      • “baby-steps” make a bit more maintainable
      • simple things like breaking down a large class can make a significant difference
  3. Sensing and separation
    • Intro
      • sensing: see effect of our calls
      • separation: can’t run class separately
      • hard to test if class is closed box
      • logic we want to test may have nothing to do with everything required to instantiate class
    • Faking collaborators
      • fake objects can be used to put code in place of something else to sense what is happening
      • e.g. let’s say we have Sale class with scan(barcode: String)
      • if display code buried, can be hard to check that part
      • could break out display class with showLine(line: String)
      • sale class can now hold that class or FakeDisplay
      • sale can accept display interface w/ showLine method
      • with fake display, can see what would have been called
      • helps localize error and understand a small piece
    • two sides of a fake object
      • can add additional methods to help testing
      • e.g. “showLine” is part of interface and “getLastLine” can help with testing
    • fakes distilled
      • often implemented as simple class
    • mock objects
      • more advanced objects that can perform assertions internally
      • we can tell them what calls to expect
      • not available in all languages but simple fake objects suffice in most situations
  4. The Seam Model
    • Intro
      • code seems poorly suited to testing
      • usually don’t see good coverage unless write tests as you develop
      • try to think of code as a huge sheet of text
    • Seams
      • how to change method without changing the call?
      • seam is place where you can alter behavior of program without editing in that place
      • e.g. subclass and override
      • many different kinds
      • replacing behavior at seams allow selectively excluding dependencies
      • can also run other code where those dependencies were to sense conditions
    • Preprocessing seams
      • some languages like C, C++ have preprocessing step prior to compilation
      • not good to use a lot in production code, but can be useful in language because provides seams
      • e.g. provide another definition for a routine
      • source should be same in production and test
      • every seam has an enabling point
    • Link seams
      • compilation usually not last step
      • linkers combine those representations
      • in language e.g. Java, compiler does linking
      • regardless of scheme, can usually exploit to substitute
      • in Java, when you import, java uses class path env to determine where java system looks to find those classes
      • you can create classes with same name and put in different directory and alter class path
      • can be handy in testing and breaking dependencies, don’t use in prod
      • enabling point is the class path
      • in some language, linking is static
      • easiest way to use link seam is to create separate library for any classes or functions you want to replace
      • alter build scripts to link those
      • useful if code littered with 3rd party library calls
      • can “sense” by fake and record calls, but can become complicated
    • Object seams
      • method that is called can belong to different objects
      • not all method class are seams e.g. if object is instantiated prior to call
      • if object is passed into arg, that is enabling point
      • may have to subclass and override
      • let’s you get tests in place safely
      • usually object seam best for OO languages
      • tests that depend on preprocessing/linking may be hard to maintain
      • when used to seeing seams can use different methods to structure new code
  5. Tools
    • Intro
      • need editor and build system
    • Automated refactoring tools
      • refactor: easier to understand, cheaper to modify, don’t change behavior
      • careful with automated tools
      • write own tests and check it doesn’t introduce subtle bugs
    • mock objects
      • several mock libraries freely available
    • unit-testing harnesses
      • xUnit very effective
      • lets programmer test in their language
      • all tests run in isolation
      • tests grouped in suites that can be run and re-run
    • junit
      • subclass “TestCase”
      • testXXX, where XXX is method name
      • uses reflection to find all test methods
      • instantiates class with method for each
    • general testing harness
      • Framework for Integrated Tests (FIT): write documents about your system.
        • Embed tables that describe inputs and outputs
      • Fitnesse: FIT hosted on a wiki
  6. I don’t have much time and have to change it
    • Intro
      • additional work initially, but saves time and frustration in long-run
      • initial steps hard. ask group how to get started
      • try to instantiate class in test harness
    • Sprout method
      • create new method
      • don’t intertwine changes into existing code
      • can move to static method. treat as staging area
      • Disadvantage: kind of giving up on testing existing class
      • Advantage: clear separation
    • Sprout class
      • avoid invasive refactorings
      • adding new responsibilities not related to main functionality
      • can’t get class in test harness
      • disadvantage: adds conceptual complexity
    • wrap method
      • rename method
      • create new method with same name as old and call
      • does not increase size of existing methods
      • new functionality explicitly independent
      • may lead to poor names
    • wrap class
      • create new class that can take in class and be used in its place
      • decorator pattern
      • wraps class and has same interface
      • good to use sparingly
    • overview
      • sprout method when existing method communicates a clear algorithm
      • wrap when new feature is as important as work that was there before
      • wrap class A) behavior I want to add is completely independent and don’t want to pollute class B) class large and don’t want to make worse
      • when you start seeing new classes or methods that are well-tested, leads you to want to fix other existing trash
  7. It takes forever to make a change
    • understanding
      • in bad system, takes a long time to find where changes must happen and change is difficult
      • if you don’t understand
      • ch 16 and ch 17
    • lag time
      • if you have to wait for a while for feedback, your mind engages in planning process each time
      • quick feedback allows trying approaches quickly and concentration is more intense
      • should be able to compile every class and module in your system separately from others
    • braking dependencies
      • first step, try to instantiate class in test harness
      • ch 9 if difficult
      • ch 10 if using individual method difficult
    • build dependencies
      • extract interfaces for your class
      • move code to new project or library
      • overall cost to build system goes up but avg time for a build can decrease
      • extract implementer e.g. change ConsultantSchedulerDB to an interface and add ConsultantSchedulerDBImpl
      • Dependency Inversion: interface dependency is more minor and unobtrusive. usually requires less changes to code.
  8. How do I add a feature
    • Intro
      • In general, better to get code under test first
    • TDD
      • write failing test, get to compile, make it pass, remove duplication, repeat
      • removing duplication important or making maintenance burden
      • either writing code or refactoring, never doing both at once
    • Programming by difference
      • In OO, can use inheritance to introduce features without modifying class directly then figure out how to integrate
      • doesn’t mean we keep the inheritance
      • e.g. MessageForwarding class extracts “from” address from a message and passes along to mailing list
      • want to anonymize in some cases
      • don’t need to update MessageForwarder, but make AnonymousMessageForwarder and override “getFromAddress”
      • new class for simple method, but test passes quickly, which can be used to preserve new behavior
      • downside, if we keep doing this design can degrade
      • e.g. want to be able to send bcc, can subclass again but what if you want to do that and anonymous from?
      • problem with inheritance is can only have one feature at a time
      • option 1) stop and refactor before adding second functionality
      • option 2) for anonymous forwarding, could have made this a config option and make class accept collection of properties
      • add functionality to base class and comment out override method in AnonymousMessageForwarder then remove it when not doing anything
      • getFrom method messy, but can extract method to clean up
      • not SRP, but if simple maybe ok
      • if many properties and code in class gets littered with conditional statements start using class rather than property collection
      • can use MailingConfiguration class and give it “getFromAddress”
      • can move more methods e.g. “buildRecipientList”
      • maybe “MailingList” more appropriate since config usually passive
      • “Rename Class” powerful refactoring changes way people see code
      • “Programming by Difference” useful, lets make change quickly and use tests to move to a clearer design
    • some gotchas
      • Liskov substitution principle: objects of subclasses should be substitutable for objects of their superclasses
      • 1) avoid overriding concrete methods
      • if you do, see if you can call the method you are overriding
      • overriding concrete can be changing the meaning of code
      • someone may see parent class and not know its pointing to subclass and get unexpected results
      • could make MessageForwarder abstract so no overriding of concrete methods
      • “normalized hierarchy”
      • some concrete override ok as long as not making LSP violations
      • good to think about normalized forms from time to time
  9. I can’t get this class into a test harness
    • case of the irritating parameter
      • first just try to initialize without args, compiler will tell you what you need
      • if connection is passed in, can extract interface and pass in fake
      • in test code, ok breaking encapsulation by making variables public to test, but should still be clean and easy to understand
      • ok to pass in null in test only, can simplify test for classes not required to test certain functionality
      • only do if you know run-time will detect null pointer errors
      • try starting out by passing in nulls and see if it complains
      • consider using null objects in prod
      • can also subclass and override if trouble is coming from one method make sure not altering behavior
    • the case of the hidden dependency
      • constructor uses some resource we can’t nicely access in test harness
      • e.g. calling new in constructor
      • “Parameterize Constructor” to externalize dependency
      • allows you to pass in fake
      • to not break client code, extract body of new constructor with passed in dependency to new “initialize” method and call that from constructor and use initialize in test
      • preserves signatures so safe change
      • other techniques for dependencies hidden in constructors
        • extract and override getter
        • extract and override factory method
        • supercede instance variable
      • I like to use parameterize constructor as often as I can
      • easy if created object doesn’t have constructor dependencies itself
    • the case of the construction blob
      • sometimes parameterize constructor may result in long param list
      • if a lot of objects are created certain object embedded in a blob of object creation
      • “supercede instance variable” you write setter to swap instance after constructing object
      • extract interface and pass in fake
    • the case of the irritating global dependency
      • singleton design pattern
      • problem is opacity hard to reason about
      • can relax singleton property by adding new static method that allows you to replace static instance
      • some reasons people want to enforce singletons
        • modeling real world and there is only one e.g. databases
        • if two created serious problems
        • multiple can use too much resources
      • primary reason people use is because don’t want to pass variables around
      • if for latter reason, may not need to enforce singleton property
      • can also subclass and override methods
      • can also extract interface
      • often times global dependencies aren’t used globally
      • refactoring and separating out responsibilities can help dependencies become localized
    • the case of the horrible include dependencies
      • some c++ specific
      • include code from header files
    • the case of the onion parameter
      • sometimes need to create a chain of objects to create an object
      • objects inside of objects like an onion
      • must at least be one class that doesn’t require another object to create
      • If the passed in parameter is not actually needed, pass in null
      • can also extract interface or implementation
    • the case of the aliased parameter
      • sometimes extract interface/implementation not practical
      • e.g. a parameter may have issues due to inheritance hierarchy
      • might have to create chain of interfaces that correspond 1 to 1 with classes which is a lot of work
      • If issue is just one method, subclass and override method
  10. I can’t run this method in a test harness
    • Intro
      • reasons why method may be hard to test
        • private
        • hard to construct parameters
        • side effects
        • need to sense through objects
    • the case of the hidden method
      • if its private you could try to test through the public method
      • if we need to test it, should make it public
      • if that bothers us usually 1) method is just utility or 2) method could adversely affect results
      • usually making public in case 1 is not too severe
      • second case, can move method to new class and create internal instance of it
      • good design is testable and design that isn’t testable is bad
      • can be hard to test if class has too many responsibilities
      • if can’t afford to separate responsibilities, can make private to protected and subclass and get access to that method
      • violates encapsulation, but fair trade for tests
    • the case of the “helpful” language feature
      • might not be able to subclass if sealed or final
      • “Adapt parameter” subclass non sealed superclass and pass that in as arg
      • can also extract interface and write wrapper can be annoying but price of security
    • the case of undetectable side-effects
      • separate work that is independent of side effecting methods
      • “Extract method”
      • create instance variable and only use them in methods
      • easier to test
      • potentially break out into different classes
  11. I need to make a change, what methods should I test?
    • Intro
      • write tests for each method that we change
      • think about change, see what it will affect and what affected things will affect
    • reasoning about effects
      • every functional change has associated chain of effects
      • see which methods it affects
      • understand what things can be affected and what won’t be
    • reasoning forward
      • if members are public, need to be aware if they are being used
      • see which clients are using as well as sub and superclasses
    • effect propagation
      • return values that are used by a caller
      • modifications to objects passed in
      • changes to global/static data
    • heuristics to use for looking for effects
      • identify method that will change
      • if there is return value look at its callers
      • see if method modifies any values
      • make sure to look for superclass and subclasses
      • look at parameters and see if they or any objects they return are used by code you want to change
      • look for global variables and static data that is modified
    • tools for reasoning
      • most important tool is knowledge of programming language
    • learning from effect analysis
      • best code doesn’t have many gotchas
      • in bad code, people don’t know what the rules are
      • programming gets easier as we narrow effects in a program
    • simplify effect sketches
      • removing duplication can let you have smaller set of endpoints
      • sometimes techniques break encapsulation, but purpose of encapsulation is to help us reason about code
      • having explanatory tests can make it easier to reason about code
      • in legacy code, a change in one place can have effects in many
      • know where to test for effects
  12. I need to make many changes in one area. Do I have to break dependencies for all classes involved?
    • Intro
      • sometimes pays to find a place to write tests for several changes at once
      • higher level tests can be good first step in getting unit tests in place
      • first need to figure out where to write them
    • Interception points
      • place where you can detect the effects of a particular change
      • requires effect reasoning / tracing
      • good to pick interception point close to change
    • higher level interception points
      • public methods best place
      • sometimes ok to test group of classes
      • pinch point is where multiple changes can be detected
      • sometimes impossible to find
    • judging design with pinch points
      • is a natural encapsulation boundary
      • could be indicative of a hidden class
    • pinch point traps
      • can become mini-integration tests
      • they can help write unit tests and later delete
  13. I need to make a change, but I don’t know what tests to write
    • Intro
      • automated tests help your team write correct code consistently
      • tests that specify become tests that preserve
      • can’t fix all bugs in legacy code
    • characterization tests
      • tests for the actual behavior of system not necessarily what its “supposed” to do
      • write code and make fail and see what correct behavior is
      • putting in mechanism to catch bugs later
      • acts as documentation. write tests until satisfied we understand it
      • before using a method in legacy code, check to see if there are tests for them
    • characterizing classes
      • try to figure out what it does at high level
      • look for tangled pieces of logic and consider using sensing variable
      • as you discover responsibilities make a list of what can go wrong and formulate tests to trigger
      • think about inputs you supply in tests and extreme values
      • should any conditions be true at all times?
      • as you discover bugs, fix if haven’t been deployed and consider downstream users if it has
    • targeted testing
      • consider different potential paths of execution and test
    • heuristics for writing
      • write tests for area you will make your changes
      • look at specific things you are going to change and attempt to write those
      • if extracting or moving functionality, verify you are exercising the code and it is connected properly
  14. Dependencies on libraries are killing me
    • can become overly reliant
    • what if they raise royalties and code is tightly intertwined with your code?
    • sometimes best thing is to write a thin wrapper
  15. My application is all API calls
    • first, identify computational core of code
    •  separate responsibilities
    • separate core logic from API
    • skin and wrap API
      • API is small
      • want complete separation from dependency
      • don’t have tests and can’t write through API
    • responsibility-based extraction
      • API more complex
      • code might depend on higher level interactions that we might not be able to get under test
  16. I don’t understand the code well-enough to change it
    • notes / sketching
      • draw unofficial diagrams
    • listing markup
      • print code
      • use markers and different colors
      • line up blocks
      • circle code you’d like to extract method
      • put marks on variables that effect others to see how changes propagate
    • scratch refactoring
      • just do it to understand code better
      • throw away
      • risk: make gross mistake and think system is doing something it isn’t
      • may tie us to that particular way of having done it
    • delete unused code
      • version control to get old things back
  17. My application has no structure
    • Intro
      • applications can degrade
      • system is so complex takes long time to get big picture
      • so complex there is no big picture
      • team in reactive mode lose sight of big picture
      • bad if architect and programmers have different view of system
      • important everyone on team knows architecture
    • telling the story of a system
      • have two people
      • one explains system in terms anyone can understand
      • start with most important aspect and use simple stories
      • serves as indication of what an ideal system could look like
      • do this often
      • when adding new features and thinking about where to add, think about what makes the story less of a lie and still accurate
    • naked CRC
      • explain architecture with blank index cards
      • each card represents and instance
      • stack for collections
      • use movements and position to explain interactions
    • conversation scrutiny
      • listen to conversations about design
      • are concepts same as concepts in code?
      • e.g. instead of using an array use a LockingPolicy for tracking counts
      • mistake if people think designing part is over
      • if no one feels comfortable introducing new abstractions code will get worse
  18. My test code is in the way
    • Intro
      • end up with a lot of test code
      • good to establish conventions else swamped
    • class naming convention
      • assumes test and prod code in same directories
      • usually test class for each class can prefix or suffix with “Test”
      • for fake objects I like to prefix “Fake”
      • for testing subclasses, prefix “Testing”
      • not dogmatic, consider how things end up getting grouped
    • test location
      • may impact code size and deployment
      • may not matter if running on own servers, but may if its commercial product running on someone else’s
      • some languages spanning directories doesn’t matter but in many languages and environments it does
      • moving back and forth can be taxing
      • could have scripts that remove tests in deployment can work if good naming convention
      • if you separate test of prod code, make sure its for a good reason
  19. My project is not object orientated. how do i make safe changes
    • Intro
      • can be hard to introduce tests to procedural code
      • hard to break dependencies
    • a hard case
      • e.g. function writes own notification to third party system
      • one way is writing library with fake and linking to it
      • might be ok, if behavior is peripheral to main logic of system
      • if you want specific sensing or return values, might be tedious, because can only link one function for each executable
      • can use file inclusion to include test definition and tests
      • file inclusion and macro replacement can help in thorniest code
    • adding new behavior
      • TDD can help alter design in good ways
      • extracting core logic from execution
      • if language supports function pointers can pass a struct of pointers to function and pass in different versions in prod and test
    • taking advantage of object orientation
      • in OO, can take advantage of object seams
      • not all software can be easily migrated to OO
      • first step, usually encapsulate global references
      • help get out of bad dependency situation
      • can find function declarations and wrap in a class
      • declare global instance
      • wrap the function using it in another class that takes global instance class as parameter and be able to pass in global version and fake in tests
    • it’s all object orientated
      • procedural programs are object-orientated in that its like having one object
      • OO allows you to break up program for testing
      • besides breaking dependencies, move to better design by grouping related functions in classes and breaking apart tangled responsibilities
  20. This class is too big and I don’t want it to get any bigger
    • Intro
      • big classes confusing
      • can result in clashes if people working concurrently
      • pain to test
      • encapsulation good, nut if it hides too much inside rots
      • first, use sprout method/class so things don’t get worse
      • key remedy is refactoring
      • SRP: every class should have a single responsibility
      • “responsibility” is vague focus on main purpose
    • seeing responsibilities
      • can list all methods and think about purpose
      • group them into lists
    • heuristic #1: group methods
      • look for similar method names
      • don’t have to move to new class right away
      • wait until you have to modify and decide later whether you should extract a class
    • heuristic #2: look at hidden methods
      • if many private/protected, can be indicative that another class dying to get out
      • if you have urge to test private method, chances are it should be public and if that bothers you, probably should be new class
    • heuristic #3: Look for decisions that can change
      • good idea to extract methods that reflect what you need a at a high level
    • heuristic #4: Look for internal relationships
      • look for relationships between instance variables and methods
      • usually some lumping / clustering
      • can use a feature sketch
      • start with all variables in circles
      • then add a method draw arrow to variable or method it uses
      • clusters could potentially be its own class
      • figure out if cluster has good distinct responsibility
      • lines connecting clusters are pinch points
    • heuristic #5: look for the primary responsibility
      • try to describe class responsibility in single sentence
      • SRP can be violated at interface and implementation level
      • interface violation has many methods and seems to be responsible for a lot of things
      • care more about implementation violation
      • for interface level, consider if client should use a new class directly
      • interface segregation principle: clients usually use different groupings of methods. Can create interface for each grouping that class implements and each client can see large class through interface which helps hide information
      • can be hard change if clients have to change code
    • heuristic #6: scratch refactoring if all else fails
    • heuristic #7: focus on the current work
      • if you are providing a different way of doing something, might help identify a a responsibility that you should extract
    • other techniques
      • way to get better is read more
      • read books about design patterns
      • read other people’s code, open source projects
      • see how people do and name things
    • moving forward / strategy
      • rare to find time for big rewrite and risky
      • better approach is identify responsibilities make sure everyone on team understands them and break down on an as-needed basis
      • spread out risk of changes
    • tactics
      • usually start at implementation level
      • introducing SRP at interface level requires more work
      • important that you can see tests around the methods
      • may not be able to test individual methods, nut can test bigger scope methods to get coverage
    • breaking out a class
      • identify responsibility
      • find associated instance variables and move somewhere
      • for methods, extract bodies into new methods and prefix “MOVING”
      • for parts of methods, extract and similar prefix
      • do text search and check that no variables you’re moving ar used outside
      • move to new class and update method calls with instance of new class
      • remove “MOVING”
    • warning
      • can have subtle bugs related to inheritance
      • especially with overridden variables / methods
      • don’t move original methods, create new methods by extracting bodies of old ones
    • after extract class
      • danger to be overambitious
      • sometimes best thing is just formulate a view of how it can be refactored
      • can help lead to future better direction
  21. I’m changing the same code all over the place
    • Intro
      • can remove duplication in small chunks
      • first thing we need are a set of tests
    • first steps
      • first, step back and get full scope of duplication
      • helps you think about what kind of class you’ll end up with
      • removing small pieces of duplication can make it easier to see larger areas later
      • can always refactor into other groupings later
      • when two methods look similar, extract the differences into other methods
      • be consistent about naming convention using abbreviations can be confusing and lead to inconsistency
      • provides less knobs for modifying
      • localized behavior easier to add/replace to
      • removing duplication helps design emerge
      • open/closed: don’t have to change much code to add new features
  22. I need to change a monster method and I can’t write tests for it
    • bulleted methods
      • nearly no indentation
      • might be tricky if temporary variables declared in one section and used in the rest
    • snarled
      • dominated by single large indented section
      • usually issue is mix of the two
    • tacking monsters with automated refactoring support
      • when doing automated, use tool exclusively
      • try to communicate at higher level
    • manual refactoring
      • tests are strongest tool
      • may forget to pass variable
      • may override method in base class
      • issue with types
    • introducing sensing variables
      • help sense conditions
      • remove later
      • check certain paths have been executed
      • keep them in place during refactorings
    • extract what you know
      • extract small pieces at first and add tests to cover them
      • 2-3 (5 at most) lines
      • try to have small coupling count (number of vars going in and coming out), harder to make mistakes
      • 0 coupling count safest, usually commands to do something to state
      • small chunks builds up over time
      • often start to extract 0-count methods
    • gleaning dependencies
      • sometimes code that is secondary to method’s main purpose
      • not all behavior is equal in an application
      • maybe just write test covering functionality you care about
    • breaking out a method object
      • create class whose only responsibility is to do the work of your monster method
    • skeletonize methods
      • can extract condition and body separately
      • e.g. if (orderNeedsRecalculation) recalculateOrder(order)
    • fixed sequences
      • extract condition and body together
      • recalculateOrder(order)
    • strategy
      • can be conflicting
      • lean towards skeletonize when feel control structure will need to be refactored (snarled)
      • sequence when identifying sequence will make code cleaner (bulleted)
    • extract to the current class first
      • sometimes may identify behavior that should live in another class
      • don’t move initially
      • refactor with potentially awkward name and can always move later
      • less error prone
    • extract small pieces
      • safer and will be more likely to succeed
    • be prepared to redo extractions
      • there are many ways to break down a monster
      • can often times discover better ways
      • not wasted effort because that leads to insight for better design
  23. How do I know that I’m not breaking anything?
    • hyperaware editing
      • thinking about how each key stroke affects everything else
      • good to get constant feedback
    • single-goal editing
      • focus on one thing at a time
    • preserve signatures
      • be particularly conservative for refactorings done without tests
      • don’t do too many things at once
    • lean on compiler
      • can do type checking and variable usage
      • may not be practical if compile takes too long
      • inheritance may cause issues
      • good to know about limitations
    • pair programming
  24. We feel overwhelmed. It isn’t going to get any better
    • figure out what’s in it for you
    • connect to what’s fun about programming
    • not much can replace a workplace with people you respect and know how to have fun
    • connect with larger community
    • view legacy code as a challenge
    • if low morale, consider getting ugliest, most obnoxious set of classes under test
  25. Dependency-breaking techniques
    • adapt a parameter
      • extract interface often best choice, but some cases may be hard
      • can wrap parameter in new class/interface
      • create prod and test implementation
      • try to create narrow interfaces targeted towards what you need
    • breaking out method object
      • create class to house method code
      • in constructor pass some args as method and reference to original class if requires its methods or instance variables
      • create execution method e.g. “run”
      • instantiate and use it to delegate
      • consider extract interface to break dependency on original class
    • definition completion
      • in test file (where method definition in source not header) and define missing methods
      • not recommended, but if you use, try to replace soon
    • encapsulate global references
      • identify globals
      • create a class and copy globals into them
      • comment out initial globals
      • initialize class and replace previous globals with instance globals
    • expose static method
      • preserve signatures whenever possible
      • statics aren’t really part of class
      • extract method to static
    • extract and override call
      • use often
      • use unless many different calls against same global
      • identify call, create new method and give it signature
      • override method in testing subclass
    • extract and override factory method
      • identify an object in constructor
      • extract all work involved in creation into factory method
      • create testing subclass
    • extract and override getter
      • introduce a getter for the instance variable that you want to replace with a fake object
      • don’t use often
      • better when many problematic methods on same object
    • extract implementer
      • make copy of class declaration and give another name e.g. prefix “Production”
      • turn source class into an interface
      • make “Production” class implement interface
      • make sure all method signatures in interface are implemented
      • find places where instances of source class were created and replace with new prod class
      • can get tricky with inheritance hierarchies
    • extract interface
      • don’t need to extract all public methods
      • create a new interface, don’t add any methods
      • make class you are extracting implement interface
      • change place where you want to use object with interface
      • compile and introduce method declaration on interface that compiler throws errors for
    • link substitution
      • create a dummy library
      • could create list and track calls and make sure if called in right order
      • good in C functions that are mostly data sinks that don’t care about return type e.g. graphic libraries
      • in Java, create class with same name and methods and change your class path
    • parameterize constructor
      • externalize object creation in constructor
      • add parameter for object in constructor and set it
      • remove body in constructor and call new method passing in object instantiated
      • allows clients to not change, while in test can pass in arbitrary new object
      • risk: other code now can access that object and can increase dependency, generally small risk
      • I use this technique a lot
    • parameterize method
      • pass in object instead of instantiating it directly
      • can add new object dependency to interface
    • primitize parameter
      • develop free function that does the work using intermediate data structure
      • make method in class delegate to this
      • leaves code in bad state
      • consider adding new code to your class or sprout class
      • only use if you’re confident you will take time to bring the class under test later
    • pull up feature
      • if methods you want to test don’t use dependency, create superclass and pull them up
      • requires testing subclass (abstract superclass)
      • not ideal from design point of view
      • might be risky to delegate, can do later
    • push down dependency
      • identify problem dependency
      • create new subclass with name that communicates specific environment of those dependencies
      • copy instance variables and methods that contain those into new subclass
      • make methods protected and abstract in original class
      • make original class abstract
      • create testing subclass
    • replace function with function pointer
      • find functions you want to replace
      • create function pointers with same name
      • rename original function
      • initialize pointers to addresses of old functions
    • replace global reference with getter
      • identify global reference and use a getter instead
      • subclass in test
    • subclass and override method
      • identify dependencies you want to separate or sense
      • make each method overridable
      • create subclass and override methods
    • supercede instance variable
      • create method “supercedeXX” that sets new variables
      • in C++, can’t override call to virtual function in constructor
      • extract and override factory method usually better choice for language that allow virtual calls in constructor
    • template redefinition
      • identify feature you want to replace in the class
      • turn class into template, parameterize it by the variables that you need to replace and copying method bodies into the header
      • give the template another name e.g. suffix with “Impl”
      • Add a typedef statement after template definition and instantiate the template on new types that will replace the ones you need to replace to test
    • text redefinition
      • in interpreted languages methods can be redefined on the fly
      • one downside, class may be permanently modified for other tests
      • can do in C and C++ using preprocessor
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s