The document describes a meetup organized by Cork Software Crafters on breaking dependencies in legacy code. The meetup agenda includes a welcome from 6:15-6:30pm, an introduction from 6:30-6:45pm, hands-on time from 6:45-8:00pm, and a retrospective from 8:00pm to discuss solutions. The facilitator is Paulo Clavijo and participants are encouraged to suggest new topics and hands-on sessions for future meetups.
1. Cork Software Crafters
Follow us on
Twitter: @CorkSwCraft
Meetup: www.meetup.com/Cork-Software-Craftsmanship-Meetup
Slack: softwarecrafters.slack.com/messages/cork
Breaking dependencies in legacy code
24th September 2019
facilitated by Paulo Clavijo
Thanks
2. We are always looking for new ideas, new talks and
mostly new hands-on sessions for our meetups.
Contact us if you want to collaborate!
8. Legacy Code
“Code without tests is bad code. It doesn't matter how well
written it is; it doesn't matter how pretty or object-oriented or
well-encapsulated it is.” - Michael Feathers
Paulo Clavijo @pclavijo
9. Legacy Code
“Legacy code is valuable code that we’re
afraid to change.” - J.B. Rainsberger
Paulo Clavijo @pclavijo
11. The Legacy Code Dilemma
“When we have to change code, we should
have tests in place. To put tests in place, we
often have to change code.” - Michael Feathers
12. Steps to change Legacy Code
1. Identify change points
2. Find testing points
3. Break dependencies if necessary
4. Write tests to cover existing behavior
5. Make the change using TDD
what module /component / interface to change
14. The Seam Model
A seam is a place where you can alter behavior in your program without editing in that
place.
Every seam has an enabling point, a place where you can make the decision to use one
behavior or another.
In most cases, a seam is a method call where the callee can be changed
without changing the caller.
Source: Working Effectively With Legacy Code, Michael Feathers
15. The Seam Model
In object-oriented languages, not all method calls are seams. Is the call to Recalculate an object seam?
public class CustomSpreadsheet extends Spreadsheet {
public Spreadsheet buildMartSheet() {
...
Cell cell = new FormulaCell(this, "A1", "=A2+A3");
...
cell.Recalculate();
...
}
...
}
Source: Working Effectively With Legacy Code, Michael Feathers
No. In this code, we’re creating a cell and then using it in the same method. There is no enabling point. We can’t
change which Recalculate method is called because the choice depends on the class of the cell. The class of the cell
is decided when the object is created, and we can’t change it without modifying the method.
16. The Seam Model
Is the call to Recalculate a seam now?
public class CustomSpreadsheet extends Spreadsheet {
public Spreadsheet buildMartSheet(Cell cell) {
...
cell.Recalculate();
...
}
...
}
Source: Working Effectively With Legacy Code, Michael Feathers
Yes. We can create a CustomSpreadsheet in a test and call buildMartSheet with whatever kind of Cell we want to use.
We’ll have ended up varying what the call to cell.Recalculate does without changing the method that calls it.
The enabling point is the argument list of buildMartSheet. We can decide what kind of an object to pass and change
the behavior of Recalculate any way that we want to for testing.
17. Creating Seams
If your code lacks a seam where you need one, the usual solution is to extract a method
containing the code you want to alter. Then you can either extend the class and override
the method, or move the method to a separate class or interface, and provide the caller
with the class or interface.
Depending on your programming language, there might be other options such as passing
a function or patching the extracted method at runtime.
19. Dependency-Breaking Techniques
● Techniques to decouple classes well enough to get them under test.
● These refactorings are intended to be done without tests, to get tests in place.
● These techniques do not immediately make your design better.
● Help geetting code under test hence your system will be more maintainable.
20. Dependency-Breaking Techniques
● Parameterise Constructor
● Subclass And Override Method
○ Extract And Override Call
○ Extract And Override Factory Method
○ Replace Global Reference With Getter
● Encapsulate Global Reference
● Break Out Method Object
● Extract Pure Functions
● Adapt Parameter
...
21. Parameterise Constructor
public class AccountService {
private final TransactionLogger logger;
public AccountService() {
logger = new TransactionLogger();
}
public void deposit(Money amount) {
…
logger.log(transaction)
…
}
}
public class AccountService {
private final TransactionLogger logger;
public AccountService() {
this(new TransactionLogger());
}
public AccountService(TransactionLogger logger) {
this.logger = logger;
}
public void deposit(Money amount) {
…
logger.log(transaction)
…
}
}
22. Subclass and Override Method
private a() {
...
}
Class
Testing Subclass
protected a() {
...
}
Class
@Override
protected a() {
...
}
A core technique for breaking dependencies in OO.
23. Extract And Override Call
A way to break dependencies on global variables and static methods.
public a() {
...
logic
...
}
Class
public a() {
...
b()
...
}
Class
protected b() {
logic
}
Testing Subclass
@Override
protected b() {
test logic
}
25. Extract And Override Factory Method
public A() {
b = new B()
}
Class Class
protected makeB() {
Return new B()
}
Testing Subclass
@Override
protected makeB() {
...
}
public A() {
b = makeB()
}
26. Replace Global Reference With Getter
public a() {
...
GLOBAL_X
...
}
Class
public a() {
...
getX()
...
}
Class
protected x() {
return GLOBAL_X
}
Testing Subclass
@Override
protected x() {
...
}
27. Break Out Method Object
Class
New Class
longMethod() {
...
...
}
longMethod(...) {
...
...
}
b() { … }
c() { … }
...
28. Extract Pure Functions
Extract Pure Functions to split large methods and classes into smaller ones that we can
understand and test.
“A pure function is a function that has the following properties: Its return value is the
same for the same arguments. Its evaluation has no side effects.” - Wikipedia
Using static methods can be a good option in Java
30. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
http://bit.ly/2md90q9
We have some legacy code. We need to make changes. To make changes we need
to introduce tests first. We might have to change some code to enable
testing. We need to introduce so-called Seams (see Michael Feathers'
Working Effectively with Legacy Code).
Changing code without test is risky, so we want to
● Only change as little code as possible.
● Rely on automated Refactoring tools as much as possible.
● You must not change the public API of the class.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
31. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
Assignment A
Problem Category
The system under test depends on a collaborator with non deterministic
behaviour. The collaborator is initialised in the constructor.
Task
The given code calculates the discount for a purchase in our online shop. The
main logic is in Discount.
● Bring Discount under test. Make sure to cover all paths in the core logic.
● There is an existing DiscountTest with a first test case which might or
might not work.
● You cannot change MarketingCampaign because it is used by other teams as
well.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
32. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
Assignment B
Problem Category
The system under test contains non deterministic behaviour, which is located in
a few methods.
Task
The given MarketingCampaign controls the marketing actions which run on our
online shop. During campaigns we e.g. offer discounts.
● Bring MarketingCampaign under test. Make sure to cover all paths in the
core logic.
● There is an existing MarketingCampaignTest with a first test case which
might or might not work.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
33. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
Assignment C
Problem Category
The system under test depends on a collaborator with database access. The
database is not available in our test environment. The collaborator is a static
call.
Task
The given code creates the receipt with the calculated tax for a purchase in
our online shop. The main logic is in Checkout.
● Bring Checkout under test. Make sure to cover all paths in the core logic.
● There is an existing CheckoutTest with a first test case which might or might
not work.
● You cannot change ReceiptRepository because it is used by other teams as well.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
34. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
Assignment D
Problem Category
The system under test depends on a collaborator with slow behaviour due to a
HTTP call. It is not guaranteed that the REST server is available. The
collaborator is a Singleton and called multiple times.
Task
The given code calculates the shipping cost for a purchase depending on the
destination in our online shop. The main logic is in ShippingCost.
● Bring ShippingCost under test. Make sure to cover all paths in the core logic.
● There is an existing ShippingCostTest with a first test case which might or
might not work.
● You cannot change RestCountriesAPI because it is used by other teams as well.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
35. Dependency Breaking Katas
Cork Software Crafters Paulo Clavijo (@pclavijo)
Assignment E
Problem Category
The system under test depends on a collaborator with user interaction. The
manual step is not suitable for our test environment. The collaborator is
created inside the class.
Task
The given code collects the necessary user confirmations during a purchase in
our online shop. The main logic is in Checkout.
● Bring Checkout under test. Make sure to cover all paths in the core logic.
● There is an existing CheckoutTest with a first test case which might or
might not work.
by Peter Kofler
https://github.com/emilybache/GildedRose-Refactoring-Kata
36. Retrospective
● What went well?
● What problems did you encounter?
● What have you learned?
● What surprised you?
Paulo Clavijo (@pclavijo)Cork Software Crafters