SlideShare a Scribd company logo
Refactor Legacy Code Through Pure Functions
Alex Bolboaca, CTO, Trainer and Coach at Mozaic Works
 alexboly  https://mozaicworks.com  alex.bolboaca@mozaicworks.com
. . . . . . . . . . . . . . . . . . . .
The Problem Of Legacy Code
Existing Methods
Disadvantages of existing methods
Step 1: To pure functions
Step 2: Test pure functions
Step 3: Pure functions to classes
Final Thoughts
The Problem Of Legacy Code
Legacy Code is everywhere
Depiction of legacy code
Legacy Code erodes the will of programmers
What is Legacy Code?
Michael Feathers: “Any piece of code that doesn’t have automated tests”
Generalized Definition
Alex Bolboaca: “Any piece of code that you are afraid to change”
Legacy Code leads to the Dark Side
Yoda: “Fear leads to anger. Anger leads to hate. Hate leads to suffering.”
Existing Methods
“Change and pray” method
If it works for you, great. Is it repeatable and transferrable though?
“No more changes” method
Freeze code, nobody touches it. Works with strangler pattern
Strangler Pattern
Strangle the existing code with a new implementation until the new implementation works fine.
Michael Feathers’ Refactoring method
Identify seams -> small, safe refactorings -> write characterization tests -> refactor code to desired design
Mikado method
Disadvantages of existing methods
Legacy Code is Messy
Often multiple methods required, and time consuming
Existing methods require multiple rare skills
• How to identify and “abuse” seams
• How to write characterization tests
• How to imagine a good design solution
• How to refactor in small steps
• Lots of imagination
Learning the methods takes long
Months, maybe years of practice
3 Poor Choices
• Live with the code you’re afraid to change (and slow down development)
• Refactor the code unsafely (spoilers: you will probably fail)
• Use a safe method (spoilers: it’s slow)
I’m proposing a new method
• Refactor code to pure functions
• Write data-driven or property based tests on the pure functions
• Refactor pure functions to classes (or something else)
Step 1: To pure functions
What is a pure function?
• Returns same output values for the same input values
• Does not depend on state
Example of Pure Functions
int add(const int first, const int second){
return first + second;
}
Example of Impure Function
int addToFirst(int& first, const int second){
first+=second;
return first;
}
Example of “Tolerated” Pure Function
int increment(int first){
return first; //state change, local to the function
}
Why pure functions?
When you turn your switch on, the water doesn’t start on your sink
Why pure functions?
• Pure functions are boring
• Pure functions have no dependencies
• Pure functions are easy to test
• Any program can be written as a combination of: pure functions + I/O functions
• We can take advantage of lambda operations with pure functions: functional composition,
currying, binding
Why Pure functions?
The hardest part of writing characterization tests (M. Feathers’ method) is dealing with
dependencies.
Pure functions make dependencies obvious and explicit.
Refactor to pure functions
• Pick a code block
• Extract method (refactoring)
• Make it immutable (add consts)
• Make it static and introduce parameters if needed
• Replace with lambda
Remember: this is an intermediary step. We ignore for now design and performance, we just want
to reduce dependencies and increase testability
Example
Game ::Game() : currentPlayer(0), places{}, purses{} {
for (int i = 0; i < 50; i ) {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
popQuestions.push_back(oss.str());
char str[255];
sprintf(str, ”Science Question %d”, i);
scienceQuestions.push_back(str);
char str1[255];
sprintf(str1, ”Sports Question %d”, i);
sportsQuestions.push_back(str1);
rockQuestions.push_back(createRockQuestion(i));
}
}
1. Pick a code block
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
popQuestions.push_back(oss.str());
2. Extract method
void Game ::doSomething(int i) {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
popQuestions.push_back(oss.str());
}
3. Try to make it immutable: const parameter
void Game ::doSomething(const int i) {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
popQuestions.push_back(oss.str());
}
3. Try to make it immutable: const function
void Game ::doSomething(const int i) const {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
//! Compilation Error: state change
popQuestions.push_back(oss.str());
}
Revert change
void Game ::doSomething(const int i) {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
popQuestions.push_back(oss.str());
}
Separate the pure from impure part
void Game ::doSomething(const int i) {
// Pure function
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
const string &popQuestion = oss.str();
// State change
popQuestions.push_back(popQuestion);
}
Extract pure function
void Game ::doSomething(const int i) {
string popQuestion = createPopQuestion(i);
popQuestions.push_back(popQuestion);
}
string Game ::createPopQuestion(const int i) const {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
const string &popQuestion = oss.str();
return popQuestion;
}
Inline initial function
Game ::Game() : currentPlayer(0), places{}, purses{} {
for (int i = 0; i < 50; i ) {
const string popQuestion = createPopQuestion(i);
popQuestions.push_back(popQuestion);
...
}
}
Back to the pure function
string Game ::createPopQuestion(const int i) const {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
const string &popQuestion = oss.str();
return popQuestion;
}
Some simplification
string Game ::createPopQuestion(const int i) const {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
return oss.str();
}
Transform to lambda
auto createPopQuestion_Lambda = [](const int i) -> string {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
return oss.str();
};
string Game ::createPopQuestion(const int i) const {
createPopQuestion_Lambda(i);
}
Move lambda up and inline method
Game ::Game() : currentPlayer(0), places{}, purses{} {
for (int i = 0; i < 50; i ) {
popQuestions.push_back(createPopQuestion_Lambda(i));
char str[255];
sprintf(str, ”Science Question %d”, i);
scienceQuestions.push_back(str);
...
}
}
Let’s stop and evaluate
Refactorings: extract method, inline, change signature, turn to lambda
The compiler helps us
The compiler tells us the function dependencies
Mechanical process - or engineering procedure
Mechanical process
The result
// Pure function
// No dependencies
auto createPopQuestion_Lambda = [](const int i) -> string {
ostringstream oss(ostringstream ::out);
oss << ”Pop Question ” << i;
return oss.str();
};
What about state changes?
bool Game ::add(string playerName) {
players.push_back(playerName);
places[howManyPlayers()] = 0;
purses[howManyPlayers()] = 0;
inPenaltyBox[howManyPlayers()] = false;
cout << playerName << ” was added” << endl;
cout << ”They are player number ” << players.size() << endl;
return true;
}
Pick code that makes the state change
bool Game ::add(string playerName) {
// State change
players.push_back(playerName);
// State change
places[howManyPlayers()] = 0;
// State change
purses[howManyPlayers()] = 0;
// State change
inPenaltyBox[howManyPlayers()] = false;
// I/O
cout << playerName << ” was added” << endl;
cout << ”They are player number ” << players.size() << endl;
return true;
}
Extract method
void Game ::addPlayerNameToPlayersList(const string &playerName) {
players.push_back(playerName);
}
Extract initial value
void Game ::addPlayerNameToPlayersList(const string &playerName) {
// Copy initial value of players
vector<string> &initialPlayers(players);
// Modify the copy
initialPlayers.push_back(playerName);
// Set the data member to the new value
players = initialPlayers;
}
Separate pure from impure
void Game ::addPlayerNameToPlayersList(const string &playerName) {
vector<string> initialPlayers = addPlayer_Pure(playerName);
players = initialPlayers;
}
vector<string> Game ::addPlayer_Pure(const string &playerName) const {
vector<string> initialPlayers(players);
initialPlayers.push_back(playerName);
return initialPlayers;
}
Inline computation
void Game ::addPlayerNameToPlayersList(const string &playerName) {
players = addPlayer_Pure(playerName);
}
Inline method
bool Game ::add(string playerName) {
players = addPlayer_Pure(playerName);
places[howManyPlayers()] = 0;
purses[howManyPlayers()] = 0;
inPenaltyBox[howManyPlayers()] = false;
cout << playerName << ” was added” << endl;
cout << ”They are player number ” << players.size() << endl;
return true;
}
Make static
vector<string> Game ::addPlayer_Pure(const string &playerName) {
// Compilation error: players unavailable
vector<string> initialPlayers(players);
initialPlayers.push_back(playerName);
return initialPlayers;
}
Undo static & Introduce parameter
vector<string> Game ::addPlayer_Pure(
const string &playerName,
const vector<string> &thePlayers) {
vector<string> initialPlayers(thePlayers);
initialPlayers.push_back(playerName);
return initialPlayers;
}
Move to lambda
auto addPlayerNameToCollection_Lambda =
[](const string &playerName,
const vector<string> &thePlayers)
-> vector<string> {
vector<string> initialPlayers(thePlayers);
initialPlayers.push_back(playerName);
return initialPlayers;
};
vector<string> Game ::addPlayer_Pure(const string &playerName,
const vector<string> &thePlayers) {
addPlayerNameToCollection_Lambda(playerName, thePlayers);
}
Inline
bool Game ::add(string playerName) {
players = addPlayerNameToCollection_Lambda(playerName, players);
places[howManyPlayers()] = 0;
purses[howManyPlayers()] = 0;
inPenaltyBox[howManyPlayers()] = false;
cout << playerName << ” was added” << endl;
cout << ”They are player number ” << players.size() << endl;
return true;
}
List of mechanics (work in progress)
• extract method -> make const -> make static -> introduce parameter -> turn to lambda
• state change -> newValue = computeNewValue(oldValue)
• if( ...){ ...}else{ ...} -> result = decide( ...)
• if( ...){ ...} -> std ::optional
• I/O -> void outputSomething( ...) or int readSomething( ...)
Step 2: Test pure functions
Data-driven tests
// Groovy and Spock
class MathSpec extends Specification {
def ”maximum of two numbers”(int a, int b, int c) {
expect:
Math.max(a, b) c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
C++ with doctest
// chapter 11, ”Hands-on Functional Programming with C ”
TEST_CASE(”1 raised to a power is 1”){
int exponent;
SUBCASE(”0”){
exponent = 0;
}
SUBCASE(”1”){
exponent = 1;
}
...
CAPTURE(exponent);
CHECK_EQ(1, power(1, exponent));
}
Property-based tests
// chapter 11, ”Hands-on Functional Programming with C ”
TEST_CASE(”Properties”){
cout << ”Property: 0 to power 0 is 1” << endl;
CHECK(property_0_to_power_0_is_1);
...
cout << ”Property: any int to power 1 is the value” << endl;
check_property(
generate_ints_greater_than_0,
prop_any_int_to_power_1_is_the_value, ”generate ints”
);
}
Property
// chapter 11, ”Hands-on Functional Programming with C ”
auto prop_any_int_to_power_1_is_the_value = [](const int base){
return power(base, 1) base;
};
Step 3: Pure functions to classes
Equivalence
A class is a set of partially applied, cohesive pure functions
Example
// Pseudocode
class Player{
string name;
int score;
Player(string name, int score) : name(name), score(score){}
void printName() const {
cout << name << endl;
}
}
Equivalent with
auto printName = [string name]() -> void {
cout << name << endl;
}
auto printName = [](string name) -> void {
cout << name<< endl;
}
Identify cohesive pure functions
// Similarity in names: Player
auto printPlayerName = [](string playerName) -> void {
...
}
auto computePlayerScore = []( ...) -> {
...
}
Identify cohesive pure functions
// Similarity in parameter list
auto doSomethingWithPlayerNameAndScore =
[](string playerName, int playerScore) -> {
...
}
auto doSomethingElseWithPlayerNameAndScore =
[](string playerName, int playerScore) -> {
...
}
Refactoring steps
• Create class
• Move functions into class
• Common parameters -> data members
• Add constructor
• Remember: you have tests now!
Final Thoughts
Evaluation
I believe it can be:
• faster to learn: around 10 - 20 mechanics to practice
• faster to refactor: eliminate dependencies while moving to pure functions
• easier to write tests: fundamentally data tables
• safer to refactor to classes
Careful
• Introducing unexpected side effects
• Temporal coupling
• Global state
Caveats
• discipline & practice is required
• does not solve all the problems
• the resulting design doesn’t follow “Tell, don’t ask”
Is it safe?
More testing required
More Information
“Think. Design. Work Smart.” YouTube Channel
https://www.youtube.com/channel/UCSEkgmzFb4PnaGAVXtK8dGA/
Codecast ep. 1: https://youtu.be/FyZ_Tcuujx8
Video “Pure functions as nominal form for software design” https://youtu.be/l9GOtbhYaJ8
Questions
Win a book (more at “ask the speakers”)
Photo Attribution
https://unsplash.com/@alexagorn?utm_source=unsplash&utm_medium=referral&utm_content=creditCop
https://fthmb.tqn.com/5Jqt2NdU5fjtcwhy6FwNg26yVRg=/1500x1167/filters:fill(auto,1)/yoda-
56a8f97a3df78cf772a263b4.jpg
https://aleteiaen.files.wordpress.com/2017/09/web3-praying-hands-work-computer-desk-office-
prayer-shutterstock.jpg?quality=100&strip=all
https://docs.microsoft.com/en-us/azure/architecture/patterns/_images/strangler.png
https://images.unsplash.com/photo-1526814642650-e274fe637fe7?ixlib=rb-
1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80
https://images.unsplash.com/photo-1536158525388-65ad2110afc8?ixlib=rb-
1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80
https://cdn.pixabay.com/photo/2019/11/25/21/24/switch-4653056_960_720.jpg

More Related Content

PDF
Sam wd programs
PDF
Java → kotlin: Tests Made Simple
PDF
Devoxx BE - How to fail at benchmarking
PDF
Java7 New Features and Code Examples
PDF
Excuse me, sir, do you have a moment to talk about tests in Kotlin
PDF
Kotlin: a better Java
PPTX
Code generation for alternative languages
PDF
つくってあそぼ Kotlin DSL ~拡張編~
Sam wd programs
Java → kotlin: Tests Made Simple
Devoxx BE - How to fail at benchmarking
Java7 New Features and Code Examples
Excuse me, sir, do you have a moment to talk about tests in Kotlin
Kotlin: a better Java
Code generation for alternative languages
つくってあそぼ Kotlin DSL ~拡張編~

What's hot (19)

PPTX
Next Generation Developer Testing: Parameterized Testing
PPTX
Making Java more dynamic: runtime code generation for the JVM
PDF
Kotlin: Why Do You Care?
PDF
つくってあそぼ Kotlin DSL 第2版
PDF
Spock Framework
PDF
Es.next
PPTX
Migrating to JUnit 5
PPTX
Unit testing concurrent code
PDF
Spock Testing Framework - The Next Generation
PDF
java sockets
PDF
Principles of the Play framework
PDF
Object Oriented Exploitation: New techniques in Windows mitigation bypass
PPTX
Smarter Testing With Spock
PPTX
Club of anonimous developers "Refactoring: Legacy code"
PDF
Important java programs(collection+file)
PPTX
Kotlin decompiled
PDF
Spock: Test Well and Prosper
PDF
Java 8: the good, the bad and the ugly (Oracle Code Brussels 2017)
PDF
Advanced Java Practical File
Next Generation Developer Testing: Parameterized Testing
Making Java more dynamic: runtime code generation for the JVM
Kotlin: Why Do You Care?
つくってあそぼ Kotlin DSL 第2版
Spock Framework
Es.next
Migrating to JUnit 5
Unit testing concurrent code
Spock Testing Framework - The Next Generation
java sockets
Principles of the Play framework
Object Oriented Exploitation: New techniques in Windows mitigation bypass
Smarter Testing With Spock
Club of anonimous developers "Refactoring: Legacy code"
Important java programs(collection+file)
Kotlin decompiled
Spock: Test Well and Prosper
Java 8: the good, the bad and the ugly (Oracle Code Brussels 2017)
Advanced Java Practical File
Ad

Similar to Refactor legacy code through pure functions (20)

DOCX
GSP 125 Entire Course NEW
PDF
Data Structure and Algorithm
PDF
How to make a large C++-code base manageable
PDF
The Evolution of Good Code
PPTX
The Style of C++ 11
PDF
Functional programming in C++
PDF
Thinking in Functions
PDF
Rainer Grimm, “Functional Programming in C++11”
PDF
CODEsign 2015
PDF
Programming fundamentals using c++ question paper 2014 tutorialsduniya
PPTX
Столпы функционального программирования для адептов ООП, Николай Мозговой
PDF
Computer science-2010-cbse-question-paper
PDF
Cbse question-paper-computer-science-2009
PDF
EEE 3rd year oops cat 3 ans
PPT
Working Effectively With Legacy Code
PDF
Fun with Lambdas: C++14 Style (part 1)
PDF
Object Oriented Programming (OOP) using C++ - Lecture 4
PPTX
C++11: Feel the New Language
PDF
Object Oriented Programming (OOP) using C++ - Lecture 5
PPTX
C++11 - A Change in Style - v2.0
GSP 125 Entire Course NEW
Data Structure and Algorithm
How to make a large C++-code base manageable
The Evolution of Good Code
The Style of C++ 11
Functional programming in C++
Thinking in Functions
Rainer Grimm, “Functional Programming in C++11”
CODEsign 2015
Programming fundamentals using c++ question paper 2014 tutorialsduniya
Столпы функционального программирования для адептов ООП, Николай Мозговой
Computer science-2010-cbse-question-paper
Cbse question-paper-computer-science-2009
EEE 3rd year oops cat 3 ans
Working Effectively With Legacy Code
Fun with Lambdas: C++14 Style (part 1)
Object Oriented Programming (OOP) using C++ - Lecture 4
C++11: Feel the New Language
Object Oriented Programming (OOP) using C++ - Lecture 5
C++11 - A Change in Style - v2.0
Ad

More from Alexandru Bolboaca (20)

PDF
Design Without Types
PDF
Raising the Bar
PDF
The Journey to Master Code Design
PDF
What is good software design? And why it matters?
PDF
Agile Technical Leadership
PDF
TDD As If You Meant It
PDF
Usable Software Design
PDF
Hidden loops
PDF
Removing structural duplication
PDF
Continuous delivery
PDF
Why You Should Start Using Docker
PDF
Pyramid of-developer-skills
PDF
Applied craftsmanship
PDF
Pyramid of-developer-skills
PDF
Stay focused
PDF
Kanban intro
ODP
Unit testing-patterns
ODP
Incremental design, simply explained
ODP
Exploring design-alternatives-using-tdd
ODP
Feedback - The Lost Art of Agile
Design Without Types
Raising the Bar
The Journey to Master Code Design
What is good software design? And why it matters?
Agile Technical Leadership
TDD As If You Meant It
Usable Software Design
Hidden loops
Removing structural duplication
Continuous delivery
Why You Should Start Using Docker
Pyramid of-developer-skills
Applied craftsmanship
Pyramid of-developer-skills
Stay focused
Kanban intro
Unit testing-patterns
Incremental design, simply explained
Exploring design-alternatives-using-tdd
Feedback - The Lost Art of Agile

Recently uploaded (20)

PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PDF
Per capita expenditure prediction using model stacking based on satellite ima...
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Approach and Philosophy of On baking technology
PPTX
Cloud computing and distributed systems.
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Unlocking AI with Model Context Protocol (MCP)
PPTX
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
PPT
Teaching material agriculture food technology
PDF
Machine learning based COVID-19 study performance prediction
PPTX
Big Data Technologies - Introduction.pptx
PPTX
Spectroscopy.pptx food analysis technology
PDF
Encapsulation theory and applications.pdf
PDF
cuic standard and advanced reporting.pdf
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Spectral efficient network and resource selection model in 5G networks
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Per capita expenditure prediction using model stacking based on satellite ima...
20250228 LYD VKU AI Blended-Learning.pptx
Approach and Philosophy of On baking technology
Cloud computing and distributed systems.
The Rise and Fall of 3GPP – Time for a Sabbatical?
TokAI - TikTok AI Agent : The First AI Application That Analyzes 10,000+ Vira...
NewMind AI Weekly Chronicles - August'25 Week I
Unlocking AI with Model Context Protocol (MCP)
Detection-First SIEM: Rule Types, Dashboards, and Threat-Informed Strategy
Teaching material agriculture food technology
Machine learning based COVID-19 study performance prediction
Big Data Technologies - Introduction.pptx
Spectroscopy.pptx food analysis technology
Encapsulation theory and applications.pdf
cuic standard and advanced reporting.pdf
Diabetes mellitus diagnosis method based random forest with bat algorithm
Agricultural_Statistics_at_a_Glance_2022_0.pdf
The AUB Centre for AI in Media Proposal.docx
Spectral efficient network and resource selection model in 5G networks

Refactor legacy code through pure functions

  • 1. Refactor Legacy Code Through Pure Functions Alex Bolboaca, CTO, Trainer and Coach at Mozaic Works  alexboly  https://mozaicworks.com  alex.bolboaca@mozaicworks.com . . . . . . . . . . . . . . . . . . . .
  • 2. The Problem Of Legacy Code Existing Methods Disadvantages of existing methods Step 1: To pure functions Step 2: Test pure functions Step 3: Pure functions to classes Final Thoughts
  • 3. The Problem Of Legacy Code
  • 4. Legacy Code is everywhere Depiction of legacy code
  • 5. Legacy Code erodes the will of programmers
  • 6. What is Legacy Code? Michael Feathers: “Any piece of code that doesn’t have automated tests”
  • 7. Generalized Definition Alex Bolboaca: “Any piece of code that you are afraid to change”
  • 8. Legacy Code leads to the Dark Side Yoda: “Fear leads to anger. Anger leads to hate. Hate leads to suffering.”
  • 10. “Change and pray” method If it works for you, great. Is it repeatable and transferrable though?
  • 11. “No more changes” method Freeze code, nobody touches it. Works with strangler pattern
  • 12. Strangler Pattern Strangle the existing code with a new implementation until the new implementation works fine.
  • 13. Michael Feathers’ Refactoring method Identify seams -> small, safe refactorings -> write characterization tests -> refactor code to desired design
  • 16. Legacy Code is Messy Often multiple methods required, and time consuming
  • 17. Existing methods require multiple rare skills • How to identify and “abuse” seams • How to write characterization tests • How to imagine a good design solution • How to refactor in small steps • Lots of imagination
  • 18. Learning the methods takes long Months, maybe years of practice
  • 19. 3 Poor Choices • Live with the code you’re afraid to change (and slow down development) • Refactor the code unsafely (spoilers: you will probably fail) • Use a safe method (spoilers: it’s slow)
  • 20. I’m proposing a new method • Refactor code to pure functions • Write data-driven or property based tests on the pure functions • Refactor pure functions to classes (or something else)
  • 21. Step 1: To pure functions
  • 22. What is a pure function? • Returns same output values for the same input values • Does not depend on state
  • 23. Example of Pure Functions int add(const int first, const int second){ return first + second; }
  • 24. Example of Impure Function int addToFirst(int& first, const int second){ first+=second; return first; }
  • 25. Example of “Tolerated” Pure Function int increment(int first){ return first; //state change, local to the function }
  • 26. Why pure functions? When you turn your switch on, the water doesn’t start on your sink
  • 27. Why pure functions? • Pure functions are boring • Pure functions have no dependencies • Pure functions are easy to test • Any program can be written as a combination of: pure functions + I/O functions • We can take advantage of lambda operations with pure functions: functional composition, currying, binding
  • 28. Why Pure functions? The hardest part of writing characterization tests (M. Feathers’ method) is dealing with dependencies. Pure functions make dependencies obvious and explicit.
  • 29. Refactor to pure functions • Pick a code block • Extract method (refactoring) • Make it immutable (add consts) • Make it static and introduce parameters if needed • Replace with lambda Remember: this is an intermediary step. We ignore for now design and performance, we just want to reduce dependencies and increase testability
  • 30. Example Game ::Game() : currentPlayer(0), places{}, purses{} { for (int i = 0; i < 50; i ) { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str()); char str[255]; sprintf(str, ”Science Question %d”, i); scienceQuestions.push_back(str); char str1[255]; sprintf(str1, ”Sports Question %d”, i); sportsQuestions.push_back(str1); rockQuestions.push_back(createRockQuestion(i)); } }
  • 31. 1. Pick a code block ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str());
  • 32. 2. Extract method void Game ::doSomething(int i) { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str()); }
  • 33. 3. Try to make it immutable: const parameter void Game ::doSomething(const int i) { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str()); }
  • 34. 3. Try to make it immutable: const function void Game ::doSomething(const int i) const { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; //! Compilation Error: state change popQuestions.push_back(oss.str()); }
  • 35. Revert change void Game ::doSomething(const int i) { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str()); }
  • 36. Separate the pure from impure part void Game ::doSomething(const int i) { // Pure function ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; const string &popQuestion = oss.str(); // State change popQuestions.push_back(popQuestion); }
  • 37. Extract pure function void Game ::doSomething(const int i) { string popQuestion = createPopQuestion(i); popQuestions.push_back(popQuestion); } string Game ::createPopQuestion(const int i) const { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; const string &popQuestion = oss.str(); return popQuestion; }
  • 38. Inline initial function Game ::Game() : currentPlayer(0), places{}, purses{} { for (int i = 0; i < 50; i ) { const string popQuestion = createPopQuestion(i); popQuestions.push_back(popQuestion); ... } }
  • 39. Back to the pure function string Game ::createPopQuestion(const int i) const { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; const string &popQuestion = oss.str(); return popQuestion; }
  • 40. Some simplification string Game ::createPopQuestion(const int i) const { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; return oss.str(); }
  • 41. Transform to lambda auto createPopQuestion_Lambda = [](const int i) -> string { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; return oss.str(); }; string Game ::createPopQuestion(const int i) const { createPopQuestion_Lambda(i); }
  • 42. Move lambda up and inline method Game ::Game() : currentPlayer(0), places{}, purses{} { for (int i = 0; i < 50; i ) { popQuestions.push_back(createPopQuestion_Lambda(i)); char str[255]; sprintf(str, ”Science Question %d”, i); scienceQuestions.push_back(str); ... } }
  • 43. Let’s stop and evaluate Refactorings: extract method, inline, change signature, turn to lambda
  • 44. The compiler helps us The compiler tells us the function dependencies
  • 45. Mechanical process - or engineering procedure Mechanical process
  • 46. The result // Pure function // No dependencies auto createPopQuestion_Lambda = [](const int i) -> string { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; return oss.str(); };
  • 47. What about state changes? bool Game ::add(string playerName) { players.push_back(playerName); places[howManyPlayers()] = 0; purses[howManyPlayers()] = 0; inPenaltyBox[howManyPlayers()] = false; cout << playerName << ” was added” << endl; cout << ”They are player number ” << players.size() << endl; return true; }
  • 48. Pick code that makes the state change bool Game ::add(string playerName) { // State change players.push_back(playerName); // State change places[howManyPlayers()] = 0; // State change purses[howManyPlayers()] = 0; // State change inPenaltyBox[howManyPlayers()] = false; // I/O cout << playerName << ” was added” << endl; cout << ”They are player number ” << players.size() << endl; return true; }
  • 49. Extract method void Game ::addPlayerNameToPlayersList(const string &playerName) { players.push_back(playerName); }
  • 50. Extract initial value void Game ::addPlayerNameToPlayersList(const string &playerName) { // Copy initial value of players vector<string> &initialPlayers(players); // Modify the copy initialPlayers.push_back(playerName); // Set the data member to the new value players = initialPlayers; }
  • 51. Separate pure from impure void Game ::addPlayerNameToPlayersList(const string &playerName) { vector<string> initialPlayers = addPlayer_Pure(playerName); players = initialPlayers; } vector<string> Game ::addPlayer_Pure(const string &playerName) const { vector<string> initialPlayers(players); initialPlayers.push_back(playerName); return initialPlayers; }
  • 52. Inline computation void Game ::addPlayerNameToPlayersList(const string &playerName) { players = addPlayer_Pure(playerName); }
  • 53. Inline method bool Game ::add(string playerName) { players = addPlayer_Pure(playerName); places[howManyPlayers()] = 0; purses[howManyPlayers()] = 0; inPenaltyBox[howManyPlayers()] = false; cout << playerName << ” was added” << endl; cout << ”They are player number ” << players.size() << endl; return true; }
  • 54. Make static vector<string> Game ::addPlayer_Pure(const string &playerName) { // Compilation error: players unavailable vector<string> initialPlayers(players); initialPlayers.push_back(playerName); return initialPlayers; }
  • 55. Undo static & Introduce parameter vector<string> Game ::addPlayer_Pure( const string &playerName, const vector<string> &thePlayers) { vector<string> initialPlayers(thePlayers); initialPlayers.push_back(playerName); return initialPlayers; }
  • 56. Move to lambda auto addPlayerNameToCollection_Lambda = [](const string &playerName, const vector<string> &thePlayers) -> vector<string> { vector<string> initialPlayers(thePlayers); initialPlayers.push_back(playerName); return initialPlayers; }; vector<string> Game ::addPlayer_Pure(const string &playerName, const vector<string> &thePlayers) { addPlayerNameToCollection_Lambda(playerName, thePlayers); }
  • 57. Inline bool Game ::add(string playerName) { players = addPlayerNameToCollection_Lambda(playerName, players); places[howManyPlayers()] = 0; purses[howManyPlayers()] = 0; inPenaltyBox[howManyPlayers()] = false; cout << playerName << ” was added” << endl; cout << ”They are player number ” << players.size() << endl; return true; }
  • 58. List of mechanics (work in progress) • extract method -> make const -> make static -> introduce parameter -> turn to lambda • state change -> newValue = computeNewValue(oldValue) • if( ...){ ...}else{ ...} -> result = decide( ...) • if( ...){ ...} -> std ::optional • I/O -> void outputSomething( ...) or int readSomething( ...)
  • 59. Step 2: Test pure functions
  • 60. Data-driven tests // Groovy and Spock class MathSpec extends Specification { def ”maximum of two numbers”(int a, int b, int c) { expect: Math.max(a, b) c where: a | b | c 1 | 3 | 3 7 | 4 | 7 0 | 0 | 0 } }
  • 61. C++ with doctest // chapter 11, ”Hands-on Functional Programming with C ” TEST_CASE(”1 raised to a power is 1”){ int exponent; SUBCASE(”0”){ exponent = 0; } SUBCASE(”1”){ exponent = 1; } ... CAPTURE(exponent); CHECK_EQ(1, power(1, exponent)); }
  • 62. Property-based tests // chapter 11, ”Hands-on Functional Programming with C ” TEST_CASE(”Properties”){ cout << ”Property: 0 to power 0 is 1” << endl; CHECK(property_0_to_power_0_is_1); ... cout << ”Property: any int to power 1 is the value” << endl; check_property( generate_ints_greater_than_0, prop_any_int_to_power_1_is_the_value, ”generate ints” ); }
  • 63. Property // chapter 11, ”Hands-on Functional Programming with C ” auto prop_any_int_to_power_1_is_the_value = [](const int base){ return power(base, 1) base; };
  • 64. Step 3: Pure functions to classes
  • 65. Equivalence A class is a set of partially applied, cohesive pure functions
  • 66. Example // Pseudocode class Player{ string name; int score; Player(string name, int score) : name(name), score(score){} void printName() const { cout << name << endl; } }
  • 67. Equivalent with auto printName = [string name]() -> void { cout << name << endl; } auto printName = [](string name) -> void { cout << name<< endl; }
  • 68. Identify cohesive pure functions // Similarity in names: Player auto printPlayerName = [](string playerName) -> void { ... } auto computePlayerScore = []( ...) -> { ... }
  • 69. Identify cohesive pure functions // Similarity in parameter list auto doSomethingWithPlayerNameAndScore = [](string playerName, int playerScore) -> { ... } auto doSomethingElseWithPlayerNameAndScore = [](string playerName, int playerScore) -> { ... }
  • 70. Refactoring steps • Create class • Move functions into class • Common parameters -> data members • Add constructor • Remember: you have tests now!
  • 72. Evaluation I believe it can be: • faster to learn: around 10 - 20 mechanics to practice • faster to refactor: eliminate dependencies while moving to pure functions • easier to write tests: fundamentally data tables • safer to refactor to classes
  • 73. Careful • Introducing unexpected side effects • Temporal coupling • Global state
  • 74. Caveats • discipline & practice is required • does not solve all the problems • the resulting design doesn’t follow “Tell, don’t ask”
  • 75. Is it safe? More testing required
  • 76. More Information “Think. Design. Work Smart.” YouTube Channel https://www.youtube.com/channel/UCSEkgmzFb4PnaGAVXtK8dGA/ Codecast ep. 1: https://youtu.be/FyZ_Tcuujx8 Video “Pure functions as nominal form for software design” https://youtu.be/l9GOtbhYaJ8
  • 77. Questions Win a book (more at “ask the speakers”)
  • 78. Photo Attribution https://unsplash.com/@alexagorn?utm_source=unsplash&utm_medium=referral&utm_content=creditCop https://fthmb.tqn.com/5Jqt2NdU5fjtcwhy6FwNg26yVRg=/1500x1167/filters:fill(auto,1)/yoda- 56a8f97a3df78cf772a263b4.jpg https://aleteiaen.files.wordpress.com/2017/09/web3-praying-hands-work-computer-desk-office- prayer-shutterstock.jpg?quality=100&strip=all https://docs.microsoft.com/en-us/azure/architecture/patterns/_images/strangler.png https://images.unsplash.com/photo-1526814642650-e274fe637fe7?ixlib=rb- 1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80 https://images.unsplash.com/photo-1536158525388-65ad2110afc8?ixlib=rb- 1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80 https://cdn.pixabay.com/photo/2019/11/25/21/24/switch-4653056_960_720.jpg