SlideShare uma empresa Scribd logo
1 de 38
Baixar para ler offline
How I Learned to
Stop Worrying and
Love Legacy Code
@OtherDevOpsGene #KCDC2023
1
Gene Gotimer
DevOps Engineer at
Praeses, LLC
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
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
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
Refactoring
Unit Testing
Mutation Testing
Mocks
Static Analysis
@OtherDevOpsGene #KCDC2023
5
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
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
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);
}
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/
Define and Design the Optimal Survey Experience​
REFACTORING
IDE refactoring
@OtherDevOpsGene #KCDC2023
10
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
Refactoring
Unit Testing
Mutation Testing
Mocks
Static Analysis
@OtherDevOpsGene #KCDC2023
12
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
Define and Design the Optimal Survey Experience​
REFACTORING
Testing a
private method
@OtherDevOpsGene #KCDC2023
14
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;
}
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.
Refactoring
Unit Testing
Mutation Testing
Mocks
Static Analysis
@OtherDevOpsGene #KCDC2023
16
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
!=
Refactoring
Unit Testing
Mutation Testing
Mocks
Static Analysis
@OtherDevOpsGene #KCDC2023
18
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.
Define and Design the Optimal Survey Experience​
MOCKS
Examples
@OtherDevOpsGene #KCDC2023
20
SomeService mockService = mock(SomeService.class);
when(mockService.get(1234L).thenReturn("foo");
…
// code that calls SomeService
verify(mockService, times(1)).get(1234L);
when(mockService.get(-1L)
.thenThrow(new IllegalArgumentException());
when(mockService.put(someNewObj)
.thenThrow(new FileLockInterruptionException());
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);
}
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.
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.
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);
Define and Design the Optimal Survey Experience​
MOCKS
Removing
PowerMock
@OtherDevOpsGene #KCDC2023
25
void addIssue(SensorContext context, AlertItem alert) {
Severity severity =
ZapUtils.riskCodeToSonarQubeSeverity(alert.getRiskcode());
void addIssue(SensorContext context, Severity severity) {
Define and Design the Optimal Survey Experience​
MOCKS
Removing
PowerMock
@OtherDevOpsGene #KCDC2023
26
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;
}
}
Define and Design the Optimal Survey Experience​
MOCKS
Removing
PowerMock
@OtherDevOpsGene #KCDC2023
27
@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;
}
}
Refactoring
Unit Testing
Mutation Testing
Mocks
Static Analysis
@OtherDevOpsGene #KCDC2023
28
Define and Design the Optimal Survey Experience​
STATIC ANALYSIS
What is
static analysis?
@OtherDevOpsGene #KCDC2023
29
Inspects source code for
• bad code practices
• refactoring opportunities
Looks for
• duplicated code
• unused variables
• complex code
• confusing code
• risky code
• recommended practices
• security issues
Define and Design the Optimal Survey Experience​
STATIC ANALYSIS
Static analysis
in IDEs
@OtherDevOpsGene #KCDC2023
30
IDEs have some static analysis built-in
• red, squiggly lines
Other static analysis tools can be integrated
• SonarLint
• PMD
Quick feedback loop -> proactive changes
Wrap-up
@OtherDevOpsGene #KCDC2023
31
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.
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.
Define and Design the Optimal Survey Experience​
WRAP-UP
Java tools
@OtherDevOpsGene #KCDC2023
34
JUnit https://junit.org
JaCoCo https://www.eclemma.org/jacoco
PIT https://pitest.org
Mockito https://github.com/mockito/mockito
PowerMock https://github.com/powermock/powermock
SonarQube https://www.sonarqube.org
PMD CPD https://pmd.github.io
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
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
37
Questions?
@OtherDevOpsGene #KCDC2023
38
Gene Gotimer
DevOps Engineer at
Praeses, LLC

Mais conteúdo relacionado

Semelhante a How I Learned to Stop Worrying and Love Legacy Code

Testing, Learning and Professionalism — 20171214
Testing, Learning and Professionalism — 20171214Testing, Learning and Professionalism — 20171214
Testing, Learning and Professionalism — 20171214David Rodenas
 
Code quality
Code qualityCode quality
Code qualityProvectus
 
Automating good coding practices
Automating good coding practicesAutomating good coding practices
Automating good coding practicesKevin Peterson
 
Binary Studio Academy: .NET Code Testing
Binary Studio Academy: .NET Code TestingBinary Studio Academy: .NET Code Testing
Binary Studio Academy: .NET Code TestingBinary Studio
 
JavaScript Unit Testing
JavaScript Unit TestingJavaScript Unit Testing
JavaScript Unit TestingKeir Bowden
 
Improving the Quality of Existing Software - DevIntersection April 2016
Improving the Quality of Existing Software - DevIntersection April 2016Improving the Quality of Existing Software - DevIntersection April 2016
Improving the Quality of Existing Software - DevIntersection April 2016Steven Smith
 
Test-Driven Design Insights@DevoxxBE 2023.pptx
Test-Driven Design Insights@DevoxxBE 2023.pptxTest-Driven Design Insights@DevoxxBE 2023.pptx
Test-Driven Design Insights@DevoxxBE 2023.pptxVictor Rentea
 
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...Antonio de la Torre Fernández
 
Open Source Power Tools - Opensouthcode 2018-06-02
Open Source Power Tools - Opensouthcode 2018-06-02Open Source Power Tools - Opensouthcode 2018-06-02
Open Source Power Tools - Opensouthcode 2018-06-02Jorge Hidalgo
 
Mapping Detection Coverage
Mapping Detection CoverageMapping Detection Coverage
Mapping Detection CoverageJared Atkinson
 
Improving the Quality of Existing Software
Improving the Quality of Existing SoftwareImproving the Quality of Existing Software
Improving the Quality of Existing SoftwareSteven Smith
 
The Art of Unit Testing - Towards a Testable Design
The Art of Unit Testing - Towards a Testable DesignThe Art of Unit Testing - Towards a Testable Design
The Art of Unit Testing - Towards a Testable DesignVictor Rentea
 
Automock: Interaction-Based Mock Code Generation
Automock: Interaction-Based Mock Code GenerationAutomock: Interaction-Based Mock Code Generation
Automock: Interaction-Based Mock Code GenerationSabrina Souto
 
Testing: ¿what, how, why?
Testing: ¿what, how, why?Testing: ¿what, how, why?
Testing: ¿what, how, why?David Rodenas
 

Semelhante a How I Learned to Stop Worrying and Love Legacy Code (20)

Testing, Learning and Professionalism — 20171214
Testing, Learning and Professionalism — 20171214Testing, Learning and Professionalism — 20171214
Testing, Learning and Professionalism — 20171214
 
Design for Testability
Design for TestabilityDesign for Testability
Design for Testability
 
Code quality
Code qualityCode quality
Code quality
 
Automating good coding practices
Automating good coding practicesAutomating good coding practices
Automating good coding practices
 
Binary Studio Academy: .NET Code Testing
Binary Studio Academy: .NET Code TestingBinary Studio Academy: .NET Code Testing
Binary Studio Academy: .NET Code Testing
 
JavaScript Unit Testing
JavaScript Unit TestingJavaScript Unit Testing
JavaScript Unit Testing
 
Tdd,Ioc
Tdd,IocTdd,Ioc
Tdd,Ioc
 
NET Code Testing
NET Code TestingNET Code Testing
NET Code Testing
 
Improving the Quality of Existing Software - DevIntersection April 2016
Improving the Quality of Existing Software - DevIntersection April 2016Improving the Quality of Existing Software - DevIntersection April 2016
Improving the Quality of Existing Software - DevIntersection April 2016
 
Test-Driven Design Insights@DevoxxBE 2023.pptx
Test-Driven Design Insights@DevoxxBE 2023.pptxTest-Driven Design Insights@DevoxxBE 2023.pptx
Test-Driven Design Insights@DevoxxBE 2023.pptx
 
Coding Naked 2023
Coding Naked 2023Coding Naked 2023
Coding Naked 2023
 
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...
20191116 DevFest 2019 The Legacy Code came to stay (El legacy vino para queda...
 
Open Source Power Tools - Opensouthcode 2018-06-02
Open Source Power Tools - Opensouthcode 2018-06-02Open Source Power Tools - Opensouthcode 2018-06-02
Open Source Power Tools - Opensouthcode 2018-06-02
 
Mapping Detection Coverage
Mapping Detection CoverageMapping Detection Coverage
Mapping Detection Coverage
 
Improving the Quality of Existing Software
Improving the Quality of Existing SoftwareImproving the Quality of Existing Software
Improving the Quality of Existing Software
 
The Art of Unit Testing - Towards a Testable Design
The Art of Unit Testing - Towards a Testable DesignThe Art of Unit Testing - Towards a Testable Design
The Art of Unit Testing - Towards a Testable Design
 
Automock: Interaction-Based Mock Code Generation
Automock: Interaction-Based Mock Code GenerationAutomock: Interaction-Based Mock Code Generation
Automock: Interaction-Based Mock Code Generation
 
AAA Automated Testing
AAA Automated TestingAAA Automated Testing
AAA Automated Testing
 
Testing: ¿what, how, why?
Testing: ¿what, how, why?Testing: ¿what, how, why?
Testing: ¿what, how, why?
 
Unit Testing
Unit TestingUnit Testing
Unit Testing
 

Mais de Gene Gotimer

A Developer’s Guide to Kubernetes Security
A Developer’s Guide to Kubernetes SecurityA Developer’s Guide to Kubernetes Security
A Developer’s Guide to Kubernetes SecurityGene Gotimer
 
Ten Ways To Doom Your DevOps
Ten Ways To Doom Your DevOpsTen Ways To Doom Your DevOps
Ten Ways To Doom Your DevOpsGene Gotimer
 
Keeping Your Kubernetes Cluster Secure
Keeping Your Kubernetes Cluster SecureKeeping Your Kubernetes Cluster Secure
Keeping Your Kubernetes Cluster SecureGene Gotimer
 
Keeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureKeeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureGene Gotimer
 
Explain DevOps To Me Like I’m Five: DevOps for Managers
Explain DevOps To Me Like I’m Five: DevOps for ManagersExplain DevOps To Me Like I’m Five: DevOps for Managers
Explain DevOps To Me Like I’m Five: DevOps for ManagersGene Gotimer
 
Keeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureKeeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureGene Gotimer
 
Creative Solutions to Already Solved Problems II
Creative Solutions to Already Solved Problems IICreative Solutions to Already Solved Problems II
Creative Solutions to Already Solved Problems IIGene Gotimer
 
Creative Solutions to Already Solved Problems
Creative Solutions to Already Solved ProblemsCreative Solutions to Already Solved Problems
Creative Solutions to Already Solved ProblemsGene Gotimer
 
Get to Green: How to Safely Refactor Legacy Code
Get to Green: How to Safely Refactor Legacy CodeGet to Green: How to Safely Refactor Legacy Code
Get to Green: How to Safely Refactor Legacy CodeGene Gotimer
 
DevOps for Leadership
DevOps for LeadershipDevOps for Leadership
DevOps for LeadershipGene Gotimer
 
Pyramid Discussion: DevOps Adoption in Large, Slow Organizations
Pyramid Discussion: DevOps Adoption in Large, Slow OrganizationsPyramid Discussion: DevOps Adoption in Large, Slow Organizations
Pyramid Discussion: DevOps Adoption in Large, Slow OrganizationsGene Gotimer
 
A better faster pipeline for software delivery, even in the government
A better faster pipeline for software delivery, even in the governmentA better faster pipeline for software delivery, even in the government
A better faster pipeline for software delivery, even in the governmentGene Gotimer
 
Building the Pipeline of My Dreams
Building the Pipeline of My DreamsBuilding the Pipeline of My Dreams
Building the Pipeline of My DreamsGene Gotimer
 
Tests Your Pipeline Might Be Missing
Tests Your Pipeline Might Be MissingTests Your Pipeline Might Be Missing
Tests Your Pipeline Might Be MissingGene Gotimer
 
A Definition of Done for DevSecOps
A Definition of Done for DevSecOpsA Definition of Done for DevSecOps
A Definition of Done for DevSecOpsGene Gotimer
 
A Better, Faster Pipeline for Software Delivery
A Better, Faster Pipeline for Software DeliveryA Better, Faster Pipeline for Software Delivery
A Better, Faster Pipeline for Software DeliveryGene Gotimer
 
Open Source Security Tools for the Pipeline
Open Source Security Tools for the PipelineOpen Source Security Tools for the Pipeline
Open Source Security Tools for the PipelineGene Gotimer
 
Which Development Metrics Should I Watch?
Which Development Metrics Should I Watch?Which Development Metrics Should I Watch?
Which Development Metrics Should I Watch?Gene Gotimer
 
Add Security Testing Tools to Your Delivery Pipeline
Add Security Testing Tools to Your Delivery PipelineAdd Security Testing Tools to Your Delivery Pipeline
Add Security Testing Tools to Your Delivery PipelineGene Gotimer
 
Testing in a Continuous Delivery Pipeline - Better, Faster, Cheaper
Testing in a Continuous Delivery Pipeline - Better, Faster, CheaperTesting in a Continuous Delivery Pipeline - Better, Faster, Cheaper
Testing in a Continuous Delivery Pipeline - Better, Faster, CheaperGene Gotimer
 

Mais de Gene Gotimer (20)

A Developer’s Guide to Kubernetes Security
A Developer’s Guide to Kubernetes SecurityA Developer’s Guide to Kubernetes Security
A Developer’s Guide to Kubernetes Security
 
Ten Ways To Doom Your DevOps
Ten Ways To Doom Your DevOpsTen Ways To Doom Your DevOps
Ten Ways To Doom Your DevOps
 
Keeping Your Kubernetes Cluster Secure
Keeping Your Kubernetes Cluster SecureKeeping Your Kubernetes Cluster Secure
Keeping Your Kubernetes Cluster Secure
 
Keeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureKeeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster Secure
 
Explain DevOps To Me Like I’m Five: DevOps for Managers
Explain DevOps To Me Like I’m Five: DevOps for ManagersExplain DevOps To Me Like I’m Five: DevOps for Managers
Explain DevOps To Me Like I’m Five: DevOps for Managers
 
Keeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster SecureKeeping your Kubernetes Cluster Secure
Keeping your Kubernetes Cluster Secure
 
Creative Solutions to Already Solved Problems II
Creative Solutions to Already Solved Problems IICreative Solutions to Already Solved Problems II
Creative Solutions to Already Solved Problems II
 
Creative Solutions to Already Solved Problems
Creative Solutions to Already Solved ProblemsCreative Solutions to Already Solved Problems
Creative Solutions to Already Solved Problems
 
Get to Green: How to Safely Refactor Legacy Code
Get to Green: How to Safely Refactor Legacy CodeGet to Green: How to Safely Refactor Legacy Code
Get to Green: How to Safely Refactor Legacy Code
 
DevOps for Leadership
DevOps for LeadershipDevOps for Leadership
DevOps for Leadership
 
Pyramid Discussion: DevOps Adoption in Large, Slow Organizations
Pyramid Discussion: DevOps Adoption in Large, Slow OrganizationsPyramid Discussion: DevOps Adoption in Large, Slow Organizations
Pyramid Discussion: DevOps Adoption in Large, Slow Organizations
 
A better faster pipeline for software delivery, even in the government
A better faster pipeline for software delivery, even in the governmentA better faster pipeline for software delivery, even in the government
A better faster pipeline for software delivery, even in the government
 
Building the Pipeline of My Dreams
Building the Pipeline of My DreamsBuilding the Pipeline of My Dreams
Building the Pipeline of My Dreams
 
Tests Your Pipeline Might Be Missing
Tests Your Pipeline Might Be MissingTests Your Pipeline Might Be Missing
Tests Your Pipeline Might Be Missing
 
A Definition of Done for DevSecOps
A Definition of Done for DevSecOpsA Definition of Done for DevSecOps
A Definition of Done for DevSecOps
 
A Better, Faster Pipeline for Software Delivery
A Better, Faster Pipeline for Software DeliveryA Better, Faster Pipeline for Software Delivery
A Better, Faster Pipeline for Software Delivery
 
Open Source Security Tools for the Pipeline
Open Source Security Tools for the PipelineOpen Source Security Tools for the Pipeline
Open Source Security Tools for the Pipeline
 
Which Development Metrics Should I Watch?
Which Development Metrics Should I Watch?Which Development Metrics Should I Watch?
Which Development Metrics Should I Watch?
 
Add Security Testing Tools to Your Delivery Pipeline
Add Security Testing Tools to Your Delivery PipelineAdd Security Testing Tools to Your Delivery Pipeline
Add Security Testing Tools to Your Delivery Pipeline
 
Testing in a Continuous Delivery Pipeline - Better, Faster, Cheaper
Testing in a Continuous Delivery Pipeline - Better, Faster, CheaperTesting in a Continuous Delivery Pipeline - Better, Faster, Cheaper
Testing in a Continuous Delivery Pipeline - Better, Faster, Cheaper
 

Último

The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfkalichargn70th171
 
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...Marko Lohert
 
SQL Injection Introduction and Prevention
SQL Injection Introduction and PreventionSQL Injection Introduction and Prevention
SQL Injection Introduction and PreventionMohammed Fazuluddin
 
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdfMicrosoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdfQ-Advise
 
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfImplementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfVictor Lopez
 
Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024Andreas Granig
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdftimtebeek1
 
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)Gáspár Nagy
 
IT Software Development Resume, Vaibhav jha 2024
IT Software Development Resume, Vaibhav jha 2024IT Software Development Resume, Vaibhav jha 2024
IT Software Development Resume, Vaibhav jha 2024vaibhav130304
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationElement34
 
What need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java DevelopersWhat need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java DevelopersEmilyJiang23
 
Community is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletCommunity is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletAndrea Goulet
 
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...naitiksharma1124
 
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024Primacy Infotech
 
how-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdfhow-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdfMehmet Akar
 
The Impact of PLM Software on Fashion Production
The Impact of PLM Software on Fashion ProductionThe Impact of PLM Software on Fashion Production
The Impact of PLM Software on Fashion ProductionWave PLM
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024Shane Coughlan
 
Lessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdfLessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdfSrushith Repakula
 
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
KLARNA -  Language Models and Knowledge Graphs: A Systems ApproachKLARNA -  Language Models and Knowledge Graphs: A Systems Approach
KLARNA - Language Models and Knowledge Graphs: A Systems ApproachNeo4j
 
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024SimonedeGijt
 

Último (20)

The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
 
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...
Reinforcement Learning – a Rewards Based Approach to Machine Learning - Marko...
 
SQL Injection Introduction and Prevention
SQL Injection Introduction and PreventionSQL Injection Introduction and Prevention
SQL Injection Introduction and Prevention
 
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdfMicrosoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
 
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdfImplementing KPIs and Right Metrics for Agile Delivery Teams.pdf
Implementing KPIs and Right Metrics for Agile Delivery Teams.pdf
 
Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024Automate your OpenSIPS config tests - OpenSIPS Summit 2024
Automate your OpenSIPS config tests - OpenSIPS Summit 2024
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
Tree in the Forest - Managing Details in BDD Scenarios (live2test 2024)
 
IT Software Development Resume, Vaibhav jha 2024
IT Software Development Resume, Vaibhav jha 2024IT Software Development Resume, Vaibhav jha 2024
IT Software Development Resume, Vaibhav jha 2024
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test Automation
 
What need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java DevelopersWhat need to be mastered as AI-Powered Java Developers
What need to be mastered as AI-Powered Java Developers
 
Community is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletCommunity is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea Goulet
 
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...
COMPUTER AND ITS COMPONENTS PPT.by naitik sharma Class 9th A mittal internati...
 
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024
Odoo vs Shopify: Why Odoo is Best for Ecommerce Website Builder in 2024
 
how-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdfhow-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdf
 
The Impact of PLM Software on Fashion Production
The Impact of PLM Software on Fashion ProductionThe Impact of PLM Software on Fashion Production
The Impact of PLM Software on Fashion Production
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024
 
Lessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdfLessons Learned from Building a Serverless Notifications System.pdf
Lessons Learned from Building a Serverless Notifications System.pdf
 
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
KLARNA -  Language Models and Knowledge Graphs: A Systems ApproachKLARNA -  Language Models and Knowledge Graphs: A Systems Approach
KLARNA - Language Models and Knowledge Graphs: A Systems Approach
 
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
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
  • 5. Refactoring Unit Testing Mutation Testing Mocks Static Analysis @OtherDevOpsGene #KCDC2023 5
  • 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
  • 12. Refactoring Unit Testing Mutation Testing Mocks Static Analysis @OtherDevOpsGene #KCDC2023 12
  • 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
  • 14. Define and Design the Optimal Survey Experience​ REFACTORING Testing a private method @OtherDevOpsGene #KCDC2023 14 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; }
  • 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.
  • 16. Refactoring Unit Testing Mutation Testing Mocks Static Analysis @OtherDevOpsGene #KCDC2023 16
  • 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 !=
  • 18. Refactoring Unit Testing Mutation Testing Mocks Static Analysis @OtherDevOpsGene #KCDC2023 18
  • 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.
  • 20. Define and Design the Optimal Survey Experience​ MOCKS Examples @OtherDevOpsGene #KCDC2023 20 SomeService mockService = mock(SomeService.class); when(mockService.get(1234L).thenReturn("foo"); … // code that calls SomeService verify(mockService, times(1)).get(1234L); when(mockService.get(-1L) .thenThrow(new IllegalArgumentException()); when(mockService.put(someNewObj) .thenThrow(new FileLockInterruptionException());
  • 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);
  • 25. Define and Design the Optimal Survey Experience​ MOCKS Removing PowerMock @OtherDevOpsGene #KCDC2023 25 void addIssue(SensorContext context, AlertItem alert) { Severity severity = ZapUtils.riskCodeToSonarQubeSeverity(alert.getRiskcode()); void addIssue(SensorContext context, Severity severity) {
  • 26. Define and Design the Optimal Survey Experience​ MOCKS Removing PowerMock @OtherDevOpsGene #KCDC2023 26 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; } }
  • 27. Define and Design the Optimal Survey Experience​ MOCKS Removing PowerMock @OtherDevOpsGene #KCDC2023 27 @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; } }
  • 28. Refactoring Unit Testing Mutation Testing Mocks Static Analysis @OtherDevOpsGene #KCDC2023 28
  • 29. Define and Design the Optimal Survey Experience​ STATIC ANALYSIS What is static analysis? @OtherDevOpsGene #KCDC2023 29 Inspects source code for • bad code practices • refactoring opportunities Looks for • duplicated code • unused variables • complex code • confusing code • risky code • recommended practices • security issues
  • 30. Define and Design the Optimal Survey Experience​ STATIC ANALYSIS Static analysis in IDEs @OtherDevOpsGene #KCDC2023 30 IDEs have some static analysis built-in • red, squiggly lines Other static analysis tools can be integrated • SonarLint • PMD Quick feedback loop -> proactive changes
  • 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.
  • 34. Define and Design the Optimal Survey Experience​ WRAP-UP Java tools @OtherDevOpsGene #KCDC2023 34 JUnit https://junit.org JaCoCo https://www.eclemma.org/jacoco PIT https://pitest.org Mockito https://github.com/mockito/mockito PowerMock https://github.com/powermock/powermock SonarQube https://www.sonarqube.org PMD CPD https://pmd.github.io
  • 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
  • 37. 37