SlideShare a Scribd company logo
How to write
MAINTAINABLE CODE
without tests
๙
– That’s me.
“How can we keep moving - like musicians who
keep performing during a concert after making
mistakes?”
JUTI NOPPORNPITAKSPEAKER SENIOR SOFTWARE DEVELOPER AT STATFLO
Awesome, the test automation is.
It’s just like rehearsal. Practice makes you confident.
Sadly, not everyone has
the luxury of time or the resources to
achieve test automation.
Especially for those in the start-up community,
where every second counts.
This topic is not about
ignoring test automation.
It is about how to design
software before thinking
about testing.
Also, it is about what you
should expect if writing
tests is not an option.
ใ(ー 。ーใ)
MAINTAINABILITY
The maintainability of software
• isolate defects or causes,
• correct defects or causes,
• repair or replace defective
components without having to modify
still working ones,
• make future maintenance easier, or
• resist external changes.
Maintainability is a measure
of the ease with which a
piece of software can be
maintained in order to:
SANS TESTS
Maintainability
without tests
In this case, all points become more
difficult to achieve but it is still within
the realm of possibility.
(ー x ー)ง
Keep things lean and simple!
When writing tests is not an option,
then speed is all that matters.
In this case it is all about how fast any
developers can understand the source
code in a short amount of time so that they
start either understanding or resolving the
defects as soon as possible.
Keep things lean and simple!
When writing tests is not an option,
then speed is all that matters.
No unnecessary defensive code
Defensive code usually guards against
improper use of code components.
However, many of us are working with the
internal code. As we have full access to
the source code, misuse is less likely.
Keep things lean and simple!
When writing tests is not an option,
then speed is all that matters.
No unnecessary defensive code
Only catch the exceptions you need.
Keep things lean and simple!
When writing tests is not an option,
then speed is all that matters.
No unnecessary defensive code
Only catch the exceptions you need.
Keep the cyclomatic complexity low.
The low cyclomatic complexity of any
given subroutine indicates that the
subroutine is not too complicated as the
number of linearly independent execution
paths is very small.
Keep things lean and simple!
When writing tests is not an option,
then speed is all that matters.
No unnecessary defensive code
Only catch the exceptions you need.
Hence, if you did it right…
Simple Implementation
Low Cyclomatic Complexity
Easily Understandable
Low Test Complexity
Keep the cyclomatic complexity low.
The cyclomatic complexity directly
affects the test complexity.
When we say we have test coverage, the "coverage" is not
about how many lines have been executed. It’s about how many
independent execution paths are covered by test automation.
Practically, we should aim to achieve the minimum test
coverage where there exists enough test cases to test all
independent execution paths.
FUN FACT
Separation of Concerns
It is about separating a computer program into
distinct sections such that each section addresses a
separate concern. (Source: Wikipedia)
You can think of concerns as sub problems and sections
as solutions to a corresponding sub problem.
Separation of Concerns
The benefits are (A) you can implement test cases for a
particular section without testing the whole program or
subroutine and (B) the program can be easily refactored.
In term of maintainability, the separation of concerns
helps us isolate the program by concern.
Suppose we have the code to parse CLI arguments
and configuration files.
import argparse, json
def main():
parser = argparse.ArgumentParser()
parser.define('config_path')
args = parser.parse_args()
with open(args.config_path, 'r') as f:
config = json.load(f)
# ...
if __name__ == '__main__': main()
Let's separate them.
import argparse, json
def define_parser():
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def parse_json_config(config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
def main():
parser = define_parser()
args = parser.parse_args()
config = parse_json_config(args.config_path)
# ...
if __name__ == '__main__': main()
Define the CLI arguments
Load and parse the JSON file
The main script
Single Responsibility
– Robert C Martin
“Module or class should have responsibility on a single
part of the functionality provided by the software, and
responsibility should be entirely encapsulated by the
class. All of its services should be narrowly aligned with
that responsibility.”
Basically, think of responsibility like
DUTYto solve one particular problem.
く(ー_ー)
Single responsibility isolates the program by functionality.
With proper design and implementation, any
components of a computer program can be easily
maintained with minimal or no impact on other
components.
You can use contract-based design (or Design by
Contract) to define expected functionalities.
From the the previous example where each concern
is clearly separated…
import argparse, json
def define_parser():
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def parse_json_config(config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
def main():
parser = define_parser()
args = parser.parse_args()
config = parse_json_config(args.config_path)
# ...
if __name__ == '__main__': main()
Define the CLI arguments
Load and parse the JSON file
The main script
After separating responsibilities...
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(args.config_path)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
To handle user input
from the terminal
To be the main
application script
To handle configuration
loading
Dependency Injection
(DI)
Let’s say… We want to decouple our classes
from their dependencies so that these
dependencies can be replaced or updated
with minimal or no changes to the classes.
Additionally, if the time permits, we want
to test our classes in isolation, without
using dependencies, e.g., unit tests.
Lastly, we do not want our classes
to be responsible for locating and
managing dependency construction
and resolution.
Service The object you want to use
Client
The object that depends on other
services
Injector
Responsible for constructing
services and injecting them into
the clients
General terms on roles in
Dependency Injection
Previously in Single Responsibility
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(
args.config_path
)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(
args.config_path
)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
This module is acting
as the injector.
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(
args.config_path
)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
Define loader as a service
without dependencies
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(
args.config_path
)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
Define console as a
service with loader as
the only dependency.
# app/cli.py
import argparse
class Console(object):
def __init__(self, loader):
self.loader = loader
def define_parser(self):
parser = argparse.ArgumentParser()
parser.define('config_path')
return parser
def run(self):
parser = define_parser()
args = parser.parse_args()
config = self.loader.load(
args.config_path
)
# ...
# app/loader.py
import json
class ConfigLoader(object):
def load(self, config_path):
with open(config_path, 'r') as f:
config = json.load(f)
return config
# main.py
from app.cli import Console
from app.loader import ConfigLoader
def main():
loader = ConfigLoader()
console = Console(loader)
console.run()
if __name__ == '__main__': main()
As you can see, it is possible to
replace loader with anything by
injecting any services that satisfy the
contract required by console, e.g.,
the replacement must have the
method load whose the first
argument is a string and the returning
value is a dictionary.
Sadly, there are very few dependency-injection
frameworks available for Python like:
• pinject (github.com/google/pinject)
• imagination (github.com/shiroyuki/imagination).
These are a few foundations for
achieving code maintainability
even if you do not have complete
or usable test automation.
There are also many interesting
patterns that can help you.
For example, SOLID Design Principles, Adapter Pattern, Factory Pattern, Repository & Unit of Work Pattern etc.
But always watch out for…
Code Readability
When time is not on our side, as humans are not capable
of remembering something forever, we have to make
sure that the source code is implemented clearly, in a
way that’s readable and understandable in short time.
Messy source code may psychologically make software
developers feel like it is complicated even when it isn’t.
Messy Python code can make you feel like you’re
reading machine code.
def prep(u_list):
return [
{
key: getattr(u, k) if k != ‘stores’ else [
{
‘id’: s.id,
‘name’: s.name,
}
for s in s_list
]
for k in (‘id’, ’fullname’, ‘email’, ‘stores’)
}
for u in u_list
]
Over-engineering
For example, early/premature/excessive
abstraction, attempts to solve universal-yet-
unknown problems, etc.
Circular Dependencies
Suppose two services depend on each other.
This tight coupling between objects creates the
"chicken & egg" situation.
The very first few problems are:
• No one know what should be constructed first.
• Why are they separated in the first place?
Contact Information
• Homepage: http://www.shiroyuki.com
• GitHub: @shiroyuki
• Twitter: @shiroyuki
• 500px: shiroyuki
• Medium: @shiroyuki
• LinkedIn: jnopporn
This link to this slide should be available soon at
https://www.shiroyuki.com/talks/201611-pycon-ca/
Statflo is actively hiring developers who are passionate
about building and owning something great. 



If you'd like to hear more, we'd love to talk.
Send an email to careers@statflo.com with “PyCon
Application” in the subject line, and we'll reach out.
Looking for an opportunity to put these 

principles to work?

More Related Content

What's hot

Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step Training
Ram Awadh Prasad, PMP
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
Michelangelo van Dam
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
Jeremy Cook
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best Practices
Edorian
 
PHPUnit
PHPUnitPHPUnit
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
Rowan Merewood
 
jasmine
jasminejasmine
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
Mindfire Solutions
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
Thanh Robi
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
scidept
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
Sebastian Marek
 
Php string function
Php string function Php string function
Php string function
Ravi Bhadauria
 
Testing Legacy Rails Apps
Testing Legacy Rails AppsTesting Legacy Rails Apps
Testing Legacy Rails Apps
Rabble .
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS Applications
FITC
 
Clean Code
Clean CodeClean Code
Clean Code
Nascenia IT
 
Working Effectively With Legacy Perl Code
Working Effectively With Legacy Perl CodeWorking Effectively With Legacy Perl Code
Working Effectively With Legacy Perl Code
erikmsp
 
Perl Tidy Perl Critic
Perl Tidy Perl CriticPerl Tidy Perl Critic
Perl Tidy Perl Critic
olegmmiller
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
Enterprise PHP Center
 
Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017
Colin Oakley
 
Describe's Full of It's
Describe's Full of It'sDescribe's Full of It's
Describe's Full of It's
Jim Lynch
 

What's hot (20)

Unit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step TrainingUnit Testng with PHP Unit - A Step by Step Training
Unit Testng with PHP Unit - A Step by Step Training
 
Unit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnitUnit testing PHP apps with PHPUnit
Unit testing PHP apps with PHPUnit
 
PHPUnit: from zero to hero
PHPUnit: from zero to heroPHPUnit: from zero to hero
PHPUnit: from zero to hero
 
PhpUnit Best Practices
PhpUnit Best PracticesPhpUnit Best Practices
PhpUnit Best Practices
 
PHPUnit
PHPUnitPHPUnit
PHPUnit
 
Living With Legacy Code
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
 
jasmine
jasminejasmine
jasmine
 
Test Driven Development with PHPUnit
Test Driven Development with PHPUnitTest Driven Development with PHPUnit
Test Driven Development with PHPUnit
 
PHPUnit best practices presentation
PHPUnit best practices presentationPHPUnit best practices presentation
PHPUnit best practices presentation
 
Working Effectively With Legacy Code
Working Effectively With Legacy CodeWorking Effectively With Legacy Code
Working Effectively With Legacy Code
 
Test your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practiceTest your code like a pro - PHPUnit in practice
Test your code like a pro - PHPUnit in practice
 
Php string function
Php string function Php string function
Php string function
 
Testing Legacy Rails Apps
Testing Legacy Rails AppsTesting Legacy Rails Apps
Testing Legacy Rails Apps
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS Applications
 
Clean Code
Clean CodeClean Code
Clean Code
 
Working Effectively With Legacy Perl Code
Working Effectively With Legacy Perl CodeWorking Effectively With Legacy Perl Code
Working Effectively With Legacy Perl Code
 
Perl Tidy Perl Critic
Perl Tidy Perl CriticPerl Tidy Perl Critic
Perl Tidy Perl Critic
 
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur PurnamaEPHPC Webinar Slides: Unit Testing by Arthur Purnama
EPHPC Webinar Slides: Unit Testing by Arthur Purnama
 
Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017Testing most things in JavaScript - LeedsJS 31/05/2017
Testing most things in JavaScript - LeedsJS 31/05/2017
 
Describe's Full of It's
Describe's Full of It'sDescribe's Full of It's
Describe's Full of It's
 

Similar to How to write maintainable code without tests

AngularJS application architecture
AngularJS application architectureAngularJS application architecture
AngularJS application architecture
Gabriele Falace
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdf
Hans Jones
 
Reliable Javascript
Reliable Javascript Reliable Javascript
Reliable Javascript
Glenn Stovall
 
Javascript
JavascriptJavascript
Javascript
Sheldon Abraham
 
C Tutorials
C TutorialsC Tutorials
C Tutorials
Sudharsan S
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
ciklum_ods
 
Basic structure of c programming
Basic structure of c programmingBasic structure of c programming
Basic structure of c programming
TejaswiB4
 
Basic structure of c programming
Basic structure of c programmingBasic structure of c programming
Basic structure of c programming
TejaswiB4
 
C++ How to program
C++ How to programC++ How to program
C++ How to program
Mohammad Golyani
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
pleeps
 
Programming Fundamentals With OOPs Concepts (Java Examples Based)
Programming Fundamentals With OOPs Concepts (Java Examples Based)Programming Fundamentals With OOPs Concepts (Java Examples Based)
Programming Fundamentals With OOPs Concepts (Java Examples Based)
indiangarg
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With Python
Siddhi
 
Practical Celery
Practical CeleryPractical Celery
Practical Celery
Cameron Maske
 
Preprocessor
PreprocessorPreprocessor
Preprocessor
lalithambiga kamaraj
 
Write codeforhumans
Write codeforhumansWrite codeforhumans
Write codeforhumans
Narendran R
 
(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net
Nico Ludwig
 
The GO Language : From Beginners to Gophers
The GO Language : From Beginners to GophersThe GO Language : From Beginners to Gophers
The GO Language : From Beginners to Gophers
Alessandro Sanino
 
Python_UNIT-I.pptx
Python_UNIT-I.pptxPython_UNIT-I.pptx
Python_UNIT-I.pptx
mustafatahertotanawa1
 
Patterns in Python
Patterns in PythonPatterns in Python
Patterns in Python
dn
 
Java history, versions, types of errors and exception, quiz
Java history, versions, types of errors and exception, quiz Java history, versions, types of errors and exception, quiz
Java history, versions, types of errors and exception, quiz
SAurabh PRajapati
 

Similar to How to write maintainable code without tests (20)

AngularJS application architecture
AngularJS application architectureAngularJS application architecture
AngularJS application architecture
 
MT_01_unittest_python.pdf
MT_01_unittest_python.pdfMT_01_unittest_python.pdf
MT_01_unittest_python.pdf
 
Reliable Javascript
Reliable Javascript Reliable Javascript
Reliable Javascript
 
Javascript
JavascriptJavascript
Javascript
 
C Tutorials
C TutorialsC Tutorials
C Tutorials
 
Bring the fun back to java
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
 
Basic structure of c programming
Basic structure of c programmingBasic structure of c programming
Basic structure of c programming
 
Basic structure of c programming
Basic structure of c programmingBasic structure of c programming
Basic structure of c programming
 
C++ How to program
C++ How to programC++ How to program
C++ How to program
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Programming Fundamentals With OOPs Concepts (Java Examples Based)
Programming Fundamentals With OOPs Concepts (Java Examples Based)Programming Fundamentals With OOPs Concepts (Java Examples Based)
Programming Fundamentals With OOPs Concepts (Java Examples Based)
 
Test Driven Development With Python
Test Driven Development With PythonTest Driven Development With Python
Test Driven Development With Python
 
Practical Celery
Practical CeleryPractical Celery
Practical Celery
 
Preprocessor
PreprocessorPreprocessor
Preprocessor
 
Write codeforhumans
Write codeforhumansWrite codeforhumans
Write codeforhumans
 
(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net(1) c sharp introduction_basics_dot_net
(1) c sharp introduction_basics_dot_net
 
The GO Language : From Beginners to Gophers
The GO Language : From Beginners to GophersThe GO Language : From Beginners to Gophers
The GO Language : From Beginners to Gophers
 
Python_UNIT-I.pptx
Python_UNIT-I.pptxPython_UNIT-I.pptx
Python_UNIT-I.pptx
 
Patterns in Python
Patterns in PythonPatterns in Python
Patterns in Python
 
Java history, versions, types of errors and exception, quiz
Java history, versions, types of errors and exception, quiz Java history, versions, types of errors and exception, quiz
Java history, versions, types of errors and exception, quiz
 

Recently uploaded

UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling ExtensionsUI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
Peter Muessig
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 
What’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete RoadmapWhat’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete Roadmap
Envertis Software Solutions
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
VALiNTRY360
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
Patrick Weigel
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
dakas1
 
UI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design SystemUI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design System
Peter Muessig
 
8 Best Automated Android App Testing Tool and Framework in 2024.pdf
8 Best Automated Android App Testing Tool and Framework in 2024.pdf8 Best Automated Android App Testing Tool and Framework in 2024.pdf
8 Best Automated Android App Testing Tool and Framework in 2024.pdf
kalichargn70th171
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
Alberto Brandolini
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
gapen1
 
Using Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query PerformanceUsing Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query Performance
Grant Fritchey
 
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom KittEnhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Peter Caitens
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
Green Software Development
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
sjcobrien
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
Marcin Chrost
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
Green Software Development
 
Preparing Non - Technical Founders for Engaging a Tech Agency
Preparing Non - Technical Founders for Engaging  a  Tech AgencyPreparing Non - Technical Founders for Engaging  a  Tech Agency
Preparing Non - Technical Founders for Engaging a Tech Agency
ISH Technologies
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
Sven Peters
 
Project Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdfProject Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdf
Karya Keeper
 

Recently uploaded (20)

UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling ExtensionsUI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
UI5con 2024 - Boost Your Development Experience with UI5 Tooling Extensions
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 
What’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete RoadmapWhat’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete Roadmap
 
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdfTop Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
Top Benefits of Using Salesforce Healthcare CRM for Patient Management.pdf
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
 
UI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design SystemUI5con 2024 - Bring Your Own Design System
UI5con 2024 - Bring Your Own Design System
 
8 Best Automated Android App Testing Tool and Framework in 2024.pdf
8 Best Automated Android App Testing Tool and Framework in 2024.pdf8 Best Automated Android App Testing Tool and Framework in 2024.pdf
8 Best Automated Android App Testing Tool and Framework in 2024.pdf
 
Modelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - AmsterdamModelling Up - DDDEurope 2024 - Amsterdam
Modelling Up - DDDEurope 2024 - Amsterdam
 
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
如何办理(hull学位证书)英国赫尔大学毕业证硕士文凭原版一模一样
 
Using Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query PerformanceUsing Query Store in Azure PostgreSQL to Understand Query Performance
Using Query Store in Azure PostgreSQL to Understand Query Performance
 
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom KittEnhanced Screen Flows UI/UX using SLDS with Tom Kitt
Enhanced Screen Flows UI/UX using SLDS with Tom Kitt
 
Energy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina JonuziEnergy consumption of Database Management - Florina Jonuzi
Energy consumption of Database Management - Florina Jonuzi
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
 
14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
GreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-JurisicGreenCode-A-VSCode-Plugin--Dario-Jurisic
GreenCode-A-VSCode-Plugin--Dario-Jurisic
 
Preparing Non - Technical Founders for Engaging a Tech Agency
Preparing Non - Technical Founders for Engaging  a  Tech AgencyPreparing Non - Technical Founders for Engaging  a  Tech Agency
Preparing Non - Technical Founders for Engaging a Tech Agency
 
Microservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we workMicroservice Teams - How the cloud changes the way we work
Microservice Teams - How the cloud changes the way we work
 
Project Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdfProject Management: The Role of Project Dashboards.pdf
Project Management: The Role of Project Dashboards.pdf
 

How to write maintainable code without tests

  • 1. How to write MAINTAINABLE CODE without tests ๙
  • 2. – That’s me. “How can we keep moving - like musicians who keep performing during a concert after making mistakes?”
  • 3. JUTI NOPPORNPITAKSPEAKER SENIOR SOFTWARE DEVELOPER AT STATFLO
  • 4. Awesome, the test automation is. It’s just like rehearsal. Practice makes you confident.
  • 5. Sadly, not everyone has the luxury of time or the resources to achieve test automation. Especially for those in the start-up community, where every second counts.
  • 6. This topic is not about ignoring test automation.
  • 7. It is about how to design software before thinking about testing.
  • 8. Also, it is about what you should expect if writing tests is not an option.
  • 11. The maintainability of software • isolate defects or causes, • correct defects or causes, • repair or replace defective components without having to modify still working ones, • make future maintenance easier, or • resist external changes. Maintainability is a measure of the ease with which a piece of software can be maintained in order to:
  • 13. Maintainability without tests In this case, all points become more difficult to achieve but it is still within the realm of possibility.
  • 15. Keep things lean and simple! When writing tests is not an option, then speed is all that matters. In this case it is all about how fast any developers can understand the source code in a short amount of time so that they start either understanding or resolving the defects as soon as possible.
  • 16. Keep things lean and simple! When writing tests is not an option, then speed is all that matters. No unnecessary defensive code Defensive code usually guards against improper use of code components. However, many of us are working with the internal code. As we have full access to the source code, misuse is less likely.
  • 17. Keep things lean and simple! When writing tests is not an option, then speed is all that matters. No unnecessary defensive code Only catch the exceptions you need.
  • 18. Keep things lean and simple! When writing tests is not an option, then speed is all that matters. No unnecessary defensive code Only catch the exceptions you need. Keep the cyclomatic complexity low. The low cyclomatic complexity of any given subroutine indicates that the subroutine is not too complicated as the number of linearly independent execution paths is very small.
  • 19. Keep things lean and simple! When writing tests is not an option, then speed is all that matters. No unnecessary defensive code Only catch the exceptions you need. Hence, if you did it right… Simple Implementation Low Cyclomatic Complexity Easily Understandable Low Test Complexity Keep the cyclomatic complexity low.
  • 20. The cyclomatic complexity directly affects the test complexity. When we say we have test coverage, the "coverage" is not about how many lines have been executed. It’s about how many independent execution paths are covered by test automation. Practically, we should aim to achieve the minimum test coverage where there exists enough test cases to test all independent execution paths. FUN FACT
  • 21. Separation of Concerns It is about separating a computer program into distinct sections such that each section addresses a separate concern. (Source: Wikipedia) You can think of concerns as sub problems and sections as solutions to a corresponding sub problem.
  • 22. Separation of Concerns The benefits are (A) you can implement test cases for a particular section without testing the whole program or subroutine and (B) the program can be easily refactored. In term of maintainability, the separation of concerns helps us isolate the program by concern.
  • 23. Suppose we have the code to parse CLI arguments and configuration files. import argparse, json def main(): parser = argparse.ArgumentParser() parser.define('config_path') args = parser.parse_args() with open(args.config_path, 'r') as f: config = json.load(f) # ... if __name__ == '__main__': main()
  • 24. Let's separate them. import argparse, json def define_parser(): parser = argparse.ArgumentParser() parser.define('config_path') return parser def parse_json_config(config_path): with open(config_path, 'r') as f: config = json.load(f) return config def main(): parser = define_parser() args = parser.parse_args() config = parse_json_config(args.config_path) # ... if __name__ == '__main__': main() Define the CLI arguments Load and parse the JSON file The main script
  • 26. – Robert C Martin “Module or class should have responsibility on a single part of the functionality provided by the software, and responsibility should be entirely encapsulated by the class. All of its services should be narrowly aligned with that responsibility.”
  • 27. Basically, think of responsibility like DUTYto solve one particular problem. く(ー_ー)
  • 28. Single responsibility isolates the program by functionality.
  • 29. With proper design and implementation, any components of a computer program can be easily maintained with minimal or no impact on other components.
  • 30. You can use contract-based design (or Design by Contract) to define expected functionalities.
  • 31. From the the previous example where each concern is clearly separated… import argparse, json def define_parser(): parser = argparse.ArgumentParser() parser.define('config_path') return parser def parse_json_config(config_path): with open(config_path, 'r') as f: config = json.load(f) return config def main(): parser = define_parser() args = parser.parse_args() config = parse_json_config(args.config_path) # ... if __name__ == '__main__': main() Define the CLI arguments Load and parse the JSON file The main script
  • 32. After separating responsibilities... # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load(args.config_path) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main() To handle user input from the terminal To be the main application script To handle configuration loading
  • 34. Let’s say… We want to decouple our classes from their dependencies so that these dependencies can be replaced or updated with minimal or no changes to the classes.
  • 35. Additionally, if the time permits, we want to test our classes in isolation, without using dependencies, e.g., unit tests.
  • 36. Lastly, we do not want our classes to be responsible for locating and managing dependency construction and resolution.
  • 37. Service The object you want to use Client The object that depends on other services Injector Responsible for constructing services and injecting them into the clients General terms on roles in Dependency Injection
  • 38. Previously in Single Responsibility # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load( args.config_path ) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main()
  • 39. # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load( args.config_path ) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main() This module is acting as the injector.
  • 40. # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load( args.config_path ) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main() Define loader as a service without dependencies
  • 41. # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load( args.config_path ) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main() Define console as a service with loader as the only dependency.
  • 42. # app/cli.py import argparse class Console(object): def __init__(self, loader): self.loader = loader def define_parser(self): parser = argparse.ArgumentParser() parser.define('config_path') return parser def run(self): parser = define_parser() args = parser.parse_args() config = self.loader.load( args.config_path ) # ... # app/loader.py import json class ConfigLoader(object): def load(self, config_path): with open(config_path, 'r') as f: config = json.load(f) return config # main.py from app.cli import Console from app.loader import ConfigLoader def main(): loader = ConfigLoader() console = Console(loader) console.run() if __name__ == '__main__': main() As you can see, it is possible to replace loader with anything by injecting any services that satisfy the contract required by console, e.g., the replacement must have the method load whose the first argument is a string and the returning value is a dictionary.
  • 43. Sadly, there are very few dependency-injection frameworks available for Python like: • pinject (github.com/google/pinject) • imagination (github.com/shiroyuki/imagination).
  • 44. These are a few foundations for achieving code maintainability even if you do not have complete or usable test automation.
  • 45. There are also many interesting patterns that can help you. For example, SOLID Design Principles, Adapter Pattern, Factory Pattern, Repository & Unit of Work Pattern etc.
  • 46. But always watch out for…
  • 47. Code Readability When time is not on our side, as humans are not capable of remembering something forever, we have to make sure that the source code is implemented clearly, in a way that’s readable and understandable in short time. Messy source code may psychologically make software developers feel like it is complicated even when it isn’t. Messy Python code can make you feel like you’re reading machine code.
  • 48. def prep(u_list): return [ { key: getattr(u, k) if k != ‘stores’ else [ { ‘id’: s.id, ‘name’: s.name, } for s in s_list ] for k in (‘id’, ’fullname’, ‘email’, ‘stores’) } for u in u_list ]
  • 49. Over-engineering For example, early/premature/excessive abstraction, attempts to solve universal-yet- unknown problems, etc.
  • 50. Circular Dependencies Suppose two services depend on each other. This tight coupling between objects creates the "chicken & egg" situation. The very first few problems are: • No one know what should be constructed first. • Why are they separated in the first place?
  • 51. Contact Information • Homepage: http://www.shiroyuki.com • GitHub: @shiroyuki • Twitter: @shiroyuki • 500px: shiroyuki • Medium: @shiroyuki • LinkedIn: jnopporn
  • 52. This link to this slide should be available soon at https://www.shiroyuki.com/talks/201611-pycon-ca/
  • 53. Statflo is actively hiring developers who are passionate about building and owning something great. 
 
 If you'd like to hear more, we'd love to talk. Send an email to careers@statflo.com with “PyCon Application” in the subject line, and we'll reach out. Looking for an opportunity to put these 
 principles to work?