https://github.com/thoughtworksinc/workingeffectivelywithlegacycode
https://github.com/thoughtworksinc/workingeffectivelywithlegacycode
Last synced: about 1 month ago
JSON representation
- Host: GitHub
- URL: https://github.com/thoughtworksinc/workingeffectivelywithlegacycode
- Owner: ThoughtWorksInc
- Created: 2017-08-22T15:18:27.000Z (almost 8 years ago)
- Default Branch: master
- Last Pushed: 2017-08-22T16:09:22.000Z (almost 8 years ago)
- Last Synced: 2025-04-05T21:51:14.058Z (2 months ago)
- Language: Java
- Size: 105 KB
- Stars: 15
- Watchers: 8
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
WorkingEffectivelyWithLegacyCode
================================## Why do we change software?
1. Add a feature - Add some behavior, hold existing behavior constant
1. Fix a bug - Change some behavior, hold other behavior constant
1. Improve design - Change code structure, hold existing behavior constant
1. Optimize resource usage - Change code to improve resource usage, hold existing behavior constantDetecting changes in existing behavior is important!
# Legacy Code
Legacy code is code without tests. -- *Michael Feathers*## The Legacy Code Dilemma
When we change code, we should have tests in place. To put tests in place, we often have to change code.## The Legacy Code Change Algorithm
1. Identify Change Points
1. Find Test Points
1. Break Dependencies
1. Write Tests
1. Make Changes and Refactor### How do I know that I'm not breaking anything?
#### Rules
1. Hyper-aware Editing
1. Single Goal Editing
1. Preserve Signatures
1. Lean on the Compiler
1. Pair Programming#### Seams
A seam is a place where you can alter behavior in your program without editing in that place.##### Characterization Tests
* *Characterizes* the actual behavior of the code.
* Use white box testing to identify useful input values
* Assert on the current actual results##### Interception Points
* An interception point is simply a point in your program where you can detect the effects of a particular change. Make this as close to your change points as you can.#### Process
* Automated Refactoring to introduce basic seams and break dependencies
* Cover with Characterization Tests and Regular Unit Tests
* Introduce seams at the change and interception points using less safe refactorings (if needed)
* TDD change## Breaking Dependencies
### Sensing & SeparationWe break dependencies:
* so we can *sense* when we can't access values our code computes
* to *separate* when we can't even get a piece of code into a test harness to run.#### Sensing
* *verify*
* getters and non-private fields#### Separation
* *when*
* avoid using real resources
* helps write maintainable tests## Principles
#### In order to make code better, we sometimes need to make some aspect of it worse.
Testable & Clear > **Testable & Muddy** > **Untestable & Clear** > Untestable & Muddy_When you break dependencies in legacy code, you often have to suspend your sense of aesthetics a bit. Some
dependencies break cleanly; others end up looking less than ideal from a design point of view. They are like the
incision point in surgery: There might be a scar left in your code after your work, but everything beneath it can get better.__If later you can cover the code around the point where you broke the dependencies, you can heal the scar too._
#### We have to carefully balance these priorities
* New features
* Design Improvements
* Tests## Practices
### Techniques for Breaking Dependencies
#### Parameterize Constructor
* Inject a dependency instead of leaving it internal to a class#### Parameterize Method
* Inject a dependency instead of leaving it internal to a method#### Spawn Method
* Introduce a method and TDD that#### Spawn Class
* Introduce a class and TDD that#### Break out Method Object
* Extract method you want to change into a new class and test that#### Subclass and Override Method
* Test a subclass of your real class and override methods with dependencies#### Extract and Override Call
* Extract tough dependency and override it then test child class#### Extract and Override Factory Method (No example yet)
* Move constructor dependency to a method and override it#### Pull up Feature
* Pull the parts of a class you want to test into a new abstract base class then test a child of that#### Push down Dependencies
* Make current class abstract and push your dependencies into a child class. Test through a test child class.#### Expose Static Method
* Change existing method to be static (if it can be). You can test without an instance#### Wrap Method
* Introduce a method that contains an existing method and a call to you new method#### Wrap Class aka Decorator
* Wrap your hard to test class with a Decorator and TDD the decorator#### Adapt Parameter
* Use adapter pattern on tough dependency#### Encapsulate Global Reference
* Introduce new class that holds your global which it exposes with a getter#### Introduce Instance Delegator (No example yet)
* Introduce a new class that contains related global methods#### Primitivize Parameter
* Pass the values from an object instead of the object#### Introduce Static Setter (No example yet)
* Add a setInstance to your existing Singleton (**Danger!**)### Supporting Concepts
#### Scratch Refactoring
* Refactor the code to understand it better, *then throw it away*.
* Use only automated refactorings, *then check it in*. <- Bill's version#### Removing Duplication
* Use automated refactorings to make different code blocks identical
* Extract method or variable (IDE does the rest)
* Do example#### Monster Methods
* Bulleted Method - indentation is not the most obvious problem
* Snarled Method - indentation makes you dizzy#### Command/Query Separation
* Modify state or report state. Getters should be idempotent