Many developers would love to work on brand-new, cutting-edge, greenfield projects, never dealing with the mess of unintelligible code someone else left behind. But most of us spend most of our time maintaining existing code, and it is often spaghetti code with no unit tests, no documentation, and, if we are lucky, a comment that says, “Not sure how this works, but it does so don’t touch it.” We need to make changes, but we can’t even figure out what the code is supposed to do. You know your changes will pile on and make it worse. You can’t change the code safely without adding tests, but you can’t add tests without making changes. So how do you tackle this chicken-and-egg problem? You do it slowly and methodically, building a safety net along the way.
Join Gene as he talks about helping to maintain and improve code on an infamous software project- it was so bad it made the national news. He’ll explain his approach to breaking the code into manageable, maintainable chunks. He’ll talk about adding unit tests that actually test the code using mutation testing- one of his favorite subjects. If you have inherited a pile of code and want to clean it up into something you aren’t afraid to touch, this talk is for you. You’ll hear about some tools and approaches to help you turn legacy code into code you don’t hate.
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
How I Learned to Stop Worrying and Love Legacy Code
1. How I Learned to
Stop Worrying and
Love Legacy Code
@OtherDevOpsGene #KCDC2023
1
Gene Gotimer
DevOps Engineer at
Praeses, LLC
2. Define and Design the Optimal Survey Experience
What is
legacy code?
Legacy code is simply code without tests.
Code without tests is bad code.
Michael C. Feathers
Working Effectively with Legacy Code
@OtherDevOpsGene #KCDC2023
2
3. Define and Design the Optimal Survey Experience
LEGACY CODE
Rewrite
or not?
You’ll regret rewriting it from scratch.
Gene Gotimer
Even as I tell myself it will be different this time
@OtherDevOpsGene #KCDC2023
3
4. Define and Design the Optimal Survey Experience
LEGACY CODE
2,000-line
private static
void method
• Nothing but side effects
• Modifies input parameters- uses them for
output
• Calls services outside the class
• God method- does anything and everything
• “Untestable”
@OtherDevOpsGene #KCDC2023
4
6. Define and Design the Optimal Survey Experience
REFACTORING
What is
refactoring?
@OtherDevOpsGene #KCDC2023
6
Refactoring is a disciplined technique
for restructuring an existing body of code,
altering its internal structure
without changing its external behavior.
Martin Fowler
https://refactoring.com
7. Define and Design the Optimal Survey Experience
REFACTORING
What is
technical debt?
@OtherDevOpsGene #KCDC2023
7
What I should do:
1. Put the dirty dishes in the dishwasher
2. Run the dishwasher
3. Put the clean dishes away
What I do:
1. Put the dirty dishes in the dishwasher
2. Run the dishwasher
Technical debt
8. Define and Design the Optimal Survey Experience
REFACTORING
Example
@OtherDevOpsGene #KCDC2023
8
public boolean isUserAllowed(User someUser) {
boolean isAllowed = false;
for (String role : someUser.userRoles()) {
if (this.someService.allowedRoles().contains(role)) {
isAllowed = true;
break;
}
}
return isAllowed;
}
public boolean isUserAllowed(User someUser) {
return this.someService.allowedRoles().stream()
.anyMatch(someUser.userRoles()::contains);
}
9. Define and Design the Optimal Survey Experience
REFACTORING
Book by
Martin Fowler
Change Function Declaration
Change Reference to Value
Change Value to Reference
Collapse Hierarchy
Combine Functions into Class
Combine Functions into Transform
Consolidate Conditional Expression
Decompose Conditional
Encapsulate Collection
Encapsulate Record
Encapsulate Variable
Extract Class
Extract Function
Extract Superclass
Extract Variable
Hide Delegate
Inline Class
Inline Function
Inline Variable
Introduce Assertion
Introduce Parameter Object
Introduce Special Case
Move Field
@OtherDevOpsGene #KCDC2023
9
https://refactoring.com/catalog/
10. Define and Design the Optimal Survey Experience
REFACTORING
IDE refactoring
@OtherDevOpsGene #KCDC2023
10
11. Define and Design the Optimal Survey Experience
REFACTORING
What should you
refactor?
@OtherDevOpsGene #KCDC2023
11
Do not refactor that which offends you.
Refactor that which impedes you.
Tim Ottinger
@tottinge
13. Define and Design the Optimal Survey Experience
UNIT TESTING
What are
unit tests?
@OtherDevOpsGene #KCDC2023
13
Unit testing is a way for developers
to document the behavior of code.
Unit tests
• must be automated
• should be independent
• have no external dependencies
• usually test individual methods or smaller
• become our safety net for introducing
changes
15. Define and Design the Optimal Survey Experience
UNIT TESTING
Code Coverage
@OtherDevOpsGene #KCDC2023
15
Code coverage tools measure
the code executed when tests run,
not the code tested.
Covered code might be tested.
Uncovered code is not tested.
17. Define and Design the Optimal Survey Experience
MUTATION TESTING
What is
mutation
testing?
@OtherDevOpsGene #KCDC2023
17
public int foo(int i) {
i++;
return i;
}
i--;
public String bar(String s) {
if (s == null) {
// do something
!=
19. Define and Design the Optimal Survey Experience
MOCKS
What are
mocks?
@OtherDevOpsGene #KCDC2023
19
Mock objects have
• predetermined responses to specific requests,
• without calling the actual service or code.
Replace external services or
a graph of objects that is tedious to create.
Use interfaces to replace a “real” object with a mock.
Dependency injection makes it easy
to test with mocks.
21. Define and Design the Optimal Survey Experience
MOCKS
Another
example
@OtherDevOpsGene #KCDC2023
21
public ZapSensor(ZapSensorConfiguration configuration, FileSystem fileSystem,
PathResolver pathResolver, Rules rules) {
this.rules = rules;
this.report = new XmlReportFile(configuration, fileSystem, pathResolver);
}
@Before
public void setUp() {
final ZapSensorConfiguration configuration = mock(ZapSensorConfiguration.class);
final FileSystem fileSystem = mock(FileSystem.class);
final PathResolver pathResolver = mock(PathResolver.class);
final Rules rules = mock(Rules.class);
this.zapSensor = new ZapSensor(configuration, fileSystem,
pathResolver, rules);
}
22. Define and Design the Optimal Survey Experience
MOCKS
PowerMock
@OtherDevOpsGene #KCDC2023
22
Extends Mockito
Uses reflection to mock
• constructors
• final methods
• static methods
• private methods
Use PowerMock to help refactor and
eliminate the need for PowerMock.
23. Define and Design the Optimal Survey Experience
MOCKS
PowerMock
@OtherDevOpsGene #KCDC2023
23
Extends Mockito
Uses reflection to mock
• constructors
• final methods
• static methods
• private methods
Use PowerMock to help refactor and
eliminate the need for PowerMock.
24. Define and Design the Optimal Survey Experience
MOCKS
PowerMock
example
@OtherDevOpsGene #KCDC2023
24
void addIssue(SensorContext context, AlertItem alert) {
Severity severity =
ZapUtils.riskCodeToSonarQubeSeverity(alert.getRiskcode());
mockStatic(ZapUtils.class);
when(ZapUtils.riskCodeToSonarQubeSeverity(3))
.thenReturn(Severity.CRITICAL);
32. Define and Design the Optimal Survey Experience
WRAP-UP
2,000-line
private static
void method
@OtherDevOpsGene #KCDC2023
32
1. Extracted smaller, more testable chunks.
2. Replaced private methods with package-
private methods, @VisibleForTesting.
3. Used mutation testing to make sure code
being changed was tested.
4. Unit tested with mocks to isolate units.
5. Used PowerMock sparingly when needed.
6. Refactored to eliminate the need for
PowerMock.
7. Let static analysis tools identify additional
refactoring opportunities.
8. Repeat.
33. Define and Design the Optimal Survey Experience
WRAP-UP
Key takeaways
@OtherDevOpsGene #KCDC2023
33
• Legacy code is code without tests. Add tests
to make it non-legacy code.
• Unit tests are the best safety net for
making code changes.
• Use mutation testing to make sure your unit
tests are actually testing what you need
tested.
• Use a combination of mocking tools and
mocks to isolate your units to test.
• Look for refactoring opportunities to
incrementally make the code
more testable.
35. Define and Design the Optimal Survey Experience
WRAP-UP
.NET tools
@OtherDevOpsGene #KCDC2023
35
NUnit https://nunit.org
OpenCover https://github.com/OpenCover/opencover
Stryker.NET https://stryker-mutator.io
Moq https://github.com/moq/moq4
Microsoft Fakes https://bit.ly/2S1rRV6
SonarQube https://www.sonarqube.org
PMD CPD https://pmd.github.io
36. Define and Design the Optimal Survey Experience
WRAP-UP
Reading list
@OtherDevOpsGene #KCDC2023
36
Refactoring: Improving the Design of Existing Code,
by Martin Fowler https://amzn.com/0134757599
Working Effectively with Legacy Code,
by Michael Feathers https://amzn.com/0131177052
Clean Code: A Handbook of Agile Software Craftsmanship,
by Robert C. Martin https://amzn.com/0132350882
The Mikado Method, by Ola Ellnestam and Daniel Brolund
https://amzn.com/1617291218
Pragmatic Unit Testing in Java 8 with JUnit,
by Jeff Langr https://amzn.com/1941222595