KEMBAR78
Refactor legacy code through pure functions | PDF
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

Refactor legacy code through pure functions

  • 1.
    Refactor Legacy CodeThrough Pure Functions Alex Bolboaca, CTO, Trainer and Coach at Mozaic Works  alexboly  https://mozaicworks.com  alex.bolboaca@mozaicworks.com . . . . . . . . . . . . . . . . . . . .
  • 2.
    The Problem OfLegacy 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 OfLegacy Code
  • 4.
    Legacy Code iseverywhere Depiction of legacy code
  • 5.
    Legacy Code erodesthe will of programmers
  • 6.
    What is LegacyCode? 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 leadsto the Dark Side Yoda: “Fear leads to anger. Anger leads to hate. Hate leads to suffering.”
  • 9.
  • 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 theexisting code with a new implementation until the new implementation works fine.
  • 13.
    Michael Feathers’ Refactoringmethod Identify seams -> small, safe refactorings -> write characterization tests -> refactor code to desired design
  • 14.
  • 15.
  • 16.
    Legacy Code isMessy Often multiple methods required, and time consuming
  • 17.
    Existing methods requiremultiple 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 methodstakes 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 anew 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: Topure functions
  • 22.
    What is apure function? • Returns same output values for the same input values • Does not depend on state
  • 23.
    Example of PureFunctions int add(const int first, const int second){ return first + second; }
  • 24.
    Example of ImpureFunction 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? Whenyou 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? Thehardest part of writing characterization tests (M. Feathers’ method) is dealing with dependencies. Pure functions make dependencies obvious and explicit.
  • 29.
    Refactor to purefunctions • 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 acode block ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str());
  • 32.
    2. Extract method voidGame ::doSomething(int i) { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; popQuestions.push_back(oss.str()); }
  • 33.
    3. Try tomake 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 tomake 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 purefrom 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 voidGame ::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 thepure 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 autocreatePopQuestion_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 upand 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 andevaluate Refactorings: extract method, inline, change signature, turn to lambda
  • 44.
    The compiler helpsus The compiler tells us the function dependencies
  • 45.
    Mechanical process -or engineering procedure Mechanical process
  • 46.
    The result // Purefunction // No dependencies auto createPopQuestion_Lambda = [](const int i) -> string { ostringstream oss(ostringstream ::out); oss << ”Pop Question ” << i; return oss.str(); };
  • 47.
    What about statechanges? 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 thatmakes 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 voidGame ::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 fromimpure 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 autoaddPlayerNameToCollection_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(stringplayerName) { 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: Testpure functions
  • 60.
    Data-driven tests // Groovyand 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 // chapter11, ”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: Purefunctions to classes
  • 65.
    Equivalence A class isa set of partially applied, cohesive pure functions
  • 66.
    Example // Pseudocode class Player{ stringname; 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 purefunctions // Similarity in names: Player auto printPlayerName = [](string playerName) -> void { ... } auto computePlayerScore = []( ...) -> { ... }
  • 69.
    Identify cohesive purefunctions // Similarity in parameter list auto doSomethingWithPlayerNameAndScore = [](string playerName, int playerScore) -> { ... } auto doSomethingElseWithPlayerNameAndScore = [](string playerName, int playerScore) -> { ... }
  • 70.
    Refactoring steps • Createclass • Move functions into class • Common parameters -> data members • Add constructor • Remember: you have tests now!
  • 71.
  • 72.
    Evaluation I believe itcan 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 unexpectedside 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? Moretesting 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