More Related Content Similar to Get to Green: How to Safely Refactor Legacy Code (20) More from Gene Gotimer (20) Get to Green: How to Safely Refactor Legacy Code1. 8/7/2019
1
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 1@CoverosGene #THATConference
Agility. Security. Delivered.Get to Green:
How to Safely Refactor Legacy Code
Gene Gotimer
@CoverosGene
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 2@CoverosGene #THATConference
Agility. Security. Delivered.
2. 8/7/2019
2
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 3@CoverosGene #THATConference
About Coveros
• Coveros helps companies accelerate the delivery of
secure, reliable software using agile methods
• Agile & DevOps Services
• DevOps Implementation
• DevSecOps Integration
• Agile Transformations & Coaching
• Agile Software Development
• Agile Testing & Automation
• Agile, DevOps, Testing, Security Training
• Open Source Products
• SecureCI – Secure DevOps toolchain
• Selenified – Agile test framework
Development Platforms
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 4@CoverosGene #THATConference
Selected Commercial Clients
3. 8/7/2019
3
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 5@CoverosGene #THATConference
Selected Federal Clients
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 6@CoverosGene #THATConference
Refactoring
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
4. 8/7/2019
4
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 7@CoverosGene #THATConference
Legacy Code
Legacy code is simply code without tests.
Code without tests is bad code.
Michael C. Feathers, Working Effectively with Legacy Code
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 8@CoverosGene #THATConference
Addressing Legacy Code
The temptation is to get rid of it and
start over from scratch.
But even if legacy code is bad code:
• it works (maybe, sort of)
• it is a known quantity
• there may be a reason the solution
isn’t as simple as you think it could
or should be
5. 8/7/2019
5
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 9@CoverosGene #THATConference
Why Refactor At All?
If it works, why change it?
Because bad code carries cost and risk.
• What if it needs to be updated?
• What if it has a security issue?
• What if it is incompatible with an
update that fixes a security problem?
• Be proactive, not reactive.
• Don't wait until it has to be changed right now.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 10@CoverosGene #THATConference
Example Refactored Code
public boolean isUserAllowed(User someUser) {
boolean isAllowed = false;
for (String role : someUser.userRoles()) {
if (this.currentService.allowedRoles().contains(role)) {
isAllowed = true;
break;
}
}
return isAllowed;
}
public boolean isUserAllowed(User someUser) {
return this.currentService.allowedRoles().stream()
.anyMatch(someUser.userRoles()::contains);
}
6. 8/7/2019
6
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 11@CoverosGene #THATConference
Refactoring
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 12@CoverosGene #THATConference
Catalog of Refactorings
• 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
• Move Function
• Move Statements into Function
• Move Statements to Callers
• Parameterize Function
• Preserve Whole Object
• Pull Up Constructor Body
• Pull Up Field
• Pull Up Method
• Push Down Field
• Push Down Method
• Remove Dead Code
• Remove Flag Argument
• Remove Middle Man
• Remove Setting Method
• Remove Subclass
• Rename Field
• Rename Variable
• Replace Command with Function
• Replace Conditional with Polymorphism
• Replace Constructor with Factory Function
• Replace Control Flag with Break
• Replace Derived Variable with Query
• Replace Error Code with Exception
• Replace Exception with Precheck
• Replace Function with Command
• Replace Inline Code with Function Call
• Replace Loop with Pipeline
• Replace Magic Literal
• Replace Nested Conditional with Guard Clauses
• Replace Parameter with Query
• Replace Primitive with Object
• Replace Query with Parameter
• Replace Subclass with Delegate
• Replace Superclass with Delegate
• Replace Temp with Query
• Replace Type Code with Subclasses
• Return Modified Value
• Separate Query from Modifier
• Slide Statements
• Split Loop
• Split Phase
• Split Variable
• Substitute Algorithm
https://refactoring.com/catalog/
7. 8/7/2019
7
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 13@CoverosGene #THATConference
What to Refactor
Do not refactor that which offends you.
Refactor that which impedes you.
Tim Ottinger, @tottinge
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 14@CoverosGene #THATConference
First Step
Whenever I do refactoring,
the first step is always the same.
I need to build a solid set of tests
for that section of code.
Martin Fowler, Refactoring: Improving the Design of Existing Code
8. 8/7/2019
8
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 15@CoverosGene #THATConference
Unit Testing
Unit testing is a way for developers
to document the behavior of code.
Unit tests
• are automated,
• are independent,
• usually test individual methods or smaller,
• have no external dependencies, and
• become our safety net for fearless refactoring.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 16@CoverosGene #THATConference
Unit Testing Enables Change
• The challenge of how to refactor legacy code becomes
a challenge of how to unit test legacy code.
• Once it has tests, it isn’t legacy code anymore.
• We have a safety net.
• We can fearlessly refactor.
• We can just follow the catalog of refactorings.
• And/or we can modify the behavior safely in the future.
9. 8/7/2019
9
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 17@CoverosGene #THATConference
Testing a Private Method
private double getPerimeter() {
double perimeter = 0.0d;
for (double length : lengths) {
perimeter += length;
}
return perimeter;
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
double getPerimeter() {
double perimeter = 0.0d;
for (double length : lengths) {
perimeter += length;
}
return perimeter;
}
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 18@CoverosGene #THATConference
Key Takeaways
• Safety is the goal. Strive for fearless refactoring.
• Don't over-complicate the solution.
• Unit tests will almost always be the safety net,
but it doesn't have to be unit tests.
Simple code reviews can provide a safety net.
10. 8/7/2019
10
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 19@CoverosGene #THATConference
Code Coverage
• Measures code executed when unit tests run
• NOT amount of code tested
• Good tool to find untested code
• Not covered == not tested
• Covered == possibly tested
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 20@CoverosGene #THATConference
Mutation Testing
Mutation testing
public int foo(int i) {
i--;
return i;
}
public int foo(int i) {
i++;
return i;
}
public String bar(String s) {
if (s == null) {
// do something
public String bar(String s) {
if (s != null) {
// do something
11. 8/7/2019
11
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 21@CoverosGene #THATConference
PIT Report Example
Light green shows line coverage, dark green shows mutation coverage.
Light pink show lack of line coverage, dark pink shows lack of mutation coverage.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 22@CoverosGene #THATConference
Key Takeaways
• Code coverage tells you what code is executed during tests.
• Mutation testing tells you what code is tested.
• You don't need 100% tested,
but at least the pieces that you
want to change need a safety net.
12. 8/7/2019
12
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 23@CoverosGene #THATConference
Testing a God Method
• God method = “The Method That Does Everything”
• For example: 2,000-line Java method, private static void
• Nothing but side effects
• Modifies parameters
• Calls external services
• Implements time travel, I assume
• Small, incremental changes
• Need a safety net
• Unit testing will be necessary, but difficult
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 24@CoverosGene #THATConference
“Untestable” Code
We want to add tests, but the code isn’t making that easy.
• Important methods are void, rely on side effects, etc.
• Long methods do too much to comprehend, or require complicated
and specific setup before the test can be executed.
• Other code or services are invoked from within, so the unit of code
can’t be easily isolated.
13. 8/7/2019
13
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 25@CoverosGene #THATConference
Mock Frameworks
• Allow you to create objects that have predetermined responses to
specific requests.
• Rely on interfaces to replace “real” instances.
• Replacement for external services in unit tests,
• or sometimes when it is just tedious to create a graph of objects.
• Dependency injection makes testing with mocks easy.
when(mockService.get(anyInt()).thenReturn("foo");
when(mockService.get(-1).thenThrow(new IllegalArgumentException());
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 26@CoverosGene #THATConference
Using mocks
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);
}
14. 8/7/2019
14
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 27@CoverosGene #THATConference
PowerMock
• Useful when you don't have interfaces or dependency injection
• Extends Mockito and EasyMock
• Uses reflection to mock
• constructors
• final methods
• static methods
• private methods
• Make PowerMock use temporary.
The goal is to use it to refactor so that you do not need it.
“Please note that PowerMock is mainly intended for people with expert
knowledge in unit testing. Putting it in the hands of junior developers
may cause more harm than good.”
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 28@CoverosGene #THATConference
Using PowerMock
…
void addIssue(SensorContext context, AlertItem alert) {
Severity severity =
ZapUtils.riskCodeToSonarQubeSeverity(alert.getRiskcode());
…
mockStatic(ZapUtils.class);
…
when(ZapUtils.riskCodeToSonarQubeSeverity(3))
.thenReturn(Severity.CRITICAL);
15. 8/7/2019
15
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 29@CoverosGene #THATConference
Removing PowerMock
…
void addIssue(SensorContext context, AlertItem alert) {
Severity severity =
ZapUtils.riskCodeToSonarQubeSeverity(alert.getRiskcode());
…
void addIssue(SensorContext context, Severity severity) {
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 30@CoverosGene #THATConference
Removing PowerMock
public static Severity riskCodeToSonarQubeSeverity(int riskcode) {
if (riskcode == 3) {
return Severity.CRITICAL;
} else if (riskcode == 2) {
return Severity.MAJOR;
} else if (riskcode == 1) {
return Severity.MINOR;
} else {
return Severity.INFO;
}
}
16. 8/7/2019
16
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 31@CoverosGene #THATConference
Removing PowerMock
@Deprecated
public static Severity riskCodeToSonarQubeSeverity(int riskcode) {
return new ZapUtils().riskCodeToSonarQubeSeverity(riskcode);
}
public Severity riskCodeToSonarQubeSeverity(int riskcode) {
if (riskcode == 3) {
return Severity.CRITICAL;
} else if (riskcode == 2) {
return Severity.MAJOR;
} else if (riskcode == 1) {
return Severity.MINOR;
} else {
return Severity.INFO;
}
}
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 32@CoverosGene #THATConference
Key Takeaways
• Use mocking frameworks to isolate units from
external dependencies.
• Mocks can also simplify tests by making it easier
to set up objects that you need but aren't
directly involved in the test.
• Frameworks like PowerMock can be
used as temporary solutions. The goal is to
use PowerMock to safely refactor until you
don't need PowerMock anymore.
17. 8/7/2019
17
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 33@CoverosGene #THATConference
Static Code Analysis
• Inspects source code for
• code coverage
• duplicated code
• complex code
• tightly coupled code
• security issues
• Helps identify riskiest code
• Refactoring opportunities
• Also shows
• unused variables
• confusing code
• best practices
• coding standards
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 34@CoverosGene #THATConference
18. 8/7/2019
18
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 35@CoverosGene #THATConference
IDE Code Inspections
• IDEs have some static analysis built-in (red, squiggly lines)
• Other static analysis tools can be integrated
• e.g., SonarLint
• Quicker feedback loop, could show some low-hanging fruit
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 36@CoverosGene #THATConference
IDE Refactoring
• IDEs have built-in refactoring support
• You still need unit tests as the safety net
19. 8/7/2019
19
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 37@CoverosGene #THATConference
Key Takeaways
• Static code analysis tools can identify riskiest code,
which are prime candidates for refactoring.
• IDEs often have static code analysis built in.
• IDEs often have refactoring tools built in.
You still need unit tests.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 38@CoverosGene #THATConference
General Plan of Attack
For example, for our 2,000-line Java method, private static void
1. Unit test with mocks to isolate units.
2. Use PowerMock when necessary.
3. Use mutation testing to make sure code being touched is tested.
4. Replace chunks with package-private methods, @VisibleForTesting.
5. Refactor to eliminate the need for PowerMock.
6. Let static analysis tools identify refactoring opportunities.
7. Use IDE tools to perform individual refactorings.
One change at a time. Refactor – Test – Repeat.
20. 8/7/2019
20
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 39@CoverosGene #THATConference
#Coveros5
• Safety is the goal. Strive for fearless refactoring.
• Unit tests are the best safety net for making code changes.
• Use mutation testing to make sure
your unit tests are actually covering
what you need covered.
• Use a combination of mocking tools and
mocks to isolate your units to test.
• Make small, incremental, safe changes.
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 40@CoverosGene #THATConference
Tools Required
• Unit testing framework
• Code coverage tool
• Mutation testing tool
• Mock frameworks
• Static analysis tools
• IDE refactoring tools
• Lots of patience
21. 8/7/2019
21
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 41@CoverosGene #THATConference
Java Tools
• JUnit https://junit.org
• JaCoCo https://www.eclemma.org/jacoco
• PIT http://pitest.org
• Mockito https://github.com/mockito/mockito
• EasyMock http://easymock.org
• PowerMock https://github.com/powermock/powermock
• PMD CPD https://pmd.github.io
• SonarQube https://www.sonarqube.org
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 42@CoverosGene #THATConference
C#/.NET Tools
• NUnit https://nunit.org
• NCover (commercial) https://www.ncover.com
• OpenCover https://github.com/OpenCover/opencover
• Ninja Turtles http://www.mutation-testing.net
• Moq https://github.com/moq/moq4
• Microsoft Fakes https://docs.microsoft.com/en-
us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes
• PMD CPD https://pmd.github.io
• SonarQube https://www.sonarqube.org
22. 8/7/2019
22
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 43@CoverosGene #THATConference
Reading List
• 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
• Pragmatic Unit Testing in Java 8 with JUnit,
by Jeff Langr https://amzn.com/1941222595
© COPYRIGHT 2019 COVEROS, INC. ALL RIGHTS RESERVED. 44@CoverosGene #THATConference
Questions?
gene.gotimer@coveros.com
@CoverosGene
https://hub.techwell.com/
Live refactoring office hours: Tuesday, August 13, noon-1PM Central Time
I'll post a link on Slack.