clean code that works

154
Clean Code BalaBit LLL, 2014. február 13. @athoshun

Upload: attila-magyar

Post on 06-May-2015

731 views

Category:

Technology


1 download

DESCRIPTION

My talk (Hungarian) at BalaBit IT Security's Life Long Learning club. :-)

TRANSCRIPT

Page 1: Clean code that works

Clean Code

BalaBit LLL, 2014. február 13.@athoshun

Page 2: Clean code that works

Clean Code

”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler

Page 3: Clean code that works

Clean Code

”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler

Page 4: Clean code that works

Clean Code

”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler

Page 5: Clean code that works

Clean Code

Names, Comments, Structure, Object Oriented Programming, Functional Programming, Don't Repeat Yourself, Single Responsibility Principle, Open/Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle, Tell Don't Ask, Law of Demeter, Command-Query Separation, Composition over Inheritance, Screaming Architecture, Test First, Test Driven Development, Behavior Driven Development, Keep It Simple and Stupid, You Ain't Gonna Need It, Test doubles, Arrange-Act-Assert-Annihilate, Continuous Integration, Concurrency, Continuous Refactoring, Cyclomatic Complexity, NPath Complexity, …

Page 6: Clean code that works

#minekvan

Page 7: Clean code that works

Egyszer volt, hol nem volt...

Nagyvállalati alkalmazás

Page 8: Clean code that works

Egyszer volt, hol nem volt...

Nagyvállalati alkalmazás Kell egy vékony kliens

Page 9: Clean code that works

Egyszer volt, hol nem volt...

Nagyvállalati alkalmazás Kell egy vékony kliens, ami mobilneten

keresztül is tud frissülni

Page 10: Clean code that works

Egyszer volt, hol nem volt...

Page 11: Clean code that works

Egyszer volt, hol nem volt...

Page 12: Clean code that works

Egyszer volt, hol nem volt...

Page 13: Clean code that works

Egyszer volt, hol nem volt...

Page 14: Clean code that works

Egyszer volt, hol nem volt...

Page 15: Clean code that works

Egyszer volt, hol nem volt...

Page 16: Clean code that works

Egyszer volt, hol nem volt...

Page 17: Clean code that works

Egyszer volt, hol nem volt...

http://thedailywtf.com/Articles/The-Enterprise-Dependency.aspx

Page 18: Clean code that works

Egyszer volt, hol nem volt...

Page 19: Clean code that works

Egyszer volt, hol nem volt...

Page 20: Clean code that works

Változtatás → kockázat

Page 21: Clean code that works

Változtatás → kockázat

Page 22: Clean code that works

Ki fogja maintainelni?

Page 23: Clean code that works

Ki fogja maintainelni?

Page 24: Clean code that works

Ki fogja maintainelni?

Page 25: Clean code that works
Page 26: Clean code that works

A jó kód dokumentálja magát

Page 27: Clean code that works

A jó kód dokumentálja magát

while((i=++n)<=5000)for(a=0;a<i?a=a*8+i%8,i/=8,m=a==i|a/8==i,1:(n-++m||printf("%o\n",n))&&n%m;);

Page 28: Clean code that works

A jó kód dokumentálja magát

while((i=++n)<=5000)for(a=0;a<i?a=a*8+i%8,i/=8,m=a==i|a/8==i,1:(n-++m||printf("%o\n",n))&&n%m;);

Page 29: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { int a = 0, i = n, m; while (a < i) { a = a*8 + i%8; i /= 8; }

if (!((a == i) || (a/8 == i))) continue;

m = 2; while (0 != n%m) ++m;

if (m == n) printf("%o\n", n);}

Page 30: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (!first_loop(n)) continue;

if (second_loop(n)) printf("%o\n", n);}int first_loop(int n) { int a = 0, i = n; while (a < i) { a = a*8 + i%8; i /= 8; } return (a == i) || (a/8 == i);}int second_loop(int n) { int m = 2; while (0 != n%m) ++m; return n == m;}

Page 31: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (first_loop(n) && second_loop(n)) printf("%o\n", n);}

int first_loop(int n) { int a = 0, i = n; while (a < i) { a = a*8 + i%8; i /= 8; } return (a == i) || (a/8 == i);}int second_loop(int n) { int m = 2; while (0 != n%m) ++m; return n == m;}

Page 32: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) printf("%o\n", n);}int is_palindromic_in_octal_base(int number) { int reversed_digits = 0, remaining_digits = number;

while (reversed_digits < remaining_digits) { reversed_digits = reversed_digits * 8 + remaining_digits % 8; remaining_digits /= 8; }

return (reversed_digits == remaining_digits) || (reversed_digits / 8 == remaining_digits);}

int is_prime(int number) { int divisor_candidate = 2; while (0 != number % divisor_candidate) ++divisor_candidate; return number == divisor_candidate;}

Page 33: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) print_octal(n);}int is_palindromic_in_octal_base(int number) { int reversed_digits = 0, remaining_digits = number;

while (reversed_digits < remaining_digits) { int last_digit = get_last_octal_digit(remaining_digits); reversed_digits = append_octal_digit(reversed_digits, last_digit); remaining_digits = remove_last_octal_digit(remaining_digits); }

return (reversed_digits == remaining_digits) || (remove_last_octal_digit(reversed_digits) == remaining_digits);}

int is_prime(int number) { int divisor_candidate = 2; while (0 != number % divisor_candidate) ++divisor_candidate; return number == divisor_candidate;}

Page 34: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) print_octal(n);}int is_palindromic_in_octal_base(int number) { int reversed_digits = 0, remaining_digits = number;

while (reversed_digits < remaining_digits) { int last_digit = get_last_octal_digit(remaining_digits); reversed_digits = append_octal_digit(reversed_digits, last_digit); remaining_digits = remove_last_octal_digit(remaining_digits); }

return (reversed_digits == remaining_digits) || (remove_last_octal_digit(reversed_digits) == remaining_digits);}

int is_prime(int number) { int divisor_candidate = 2; while (0 != number % divisor_candidate) ++divisor_candidate; return number == divisor_candidate;}

Page 35: Clean code that works

Optimalizáció != obfuszkációint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) print_octal(n);}int is_palindromic_in_octal_base(int number) { int reversed_digits = 0, remaining_digits = number;

while (reversed_digits < remaining_digits) { int last_digit = get_last_octal_digit(remaining_digits); reversed_digits = append_octal_digit(reversed_digits, last_digit); remaining_digits = remove_last_octal_digit(remaining_digits); }

return (reversed_digits == remaining_digits) || (remove_last_octal_digit(reversed_digits) == remaining_digits);}

int is_prime(int number) { int divisor_candidate = 2; while (0 != number % divisor_candidate) ++divisor_candidate; return number == divisor_candidate;}

O(n) → O(√n)

Page 36: Clean code that works

Optimalizáció != obfuszkációint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) print_octal(n);}int is_palindromic_in_octal_base(int number) { int reversed_digits = 0, remaining_digits = number;

while (reversed_digits < remaining_digits) { int last_digit = get_last_octal_digit(remaining_digits); reversed_digits = append_octal_digit(reversed_digits, last_digit); remaining_digits = remove_last_octal_digit(remaining_digits); }

return (reversed_digits == remaining_digits) || (remove_last_octal_digit(reversed_digits) == remaining_digits);}

int is_prime(int number) { int divisor_candidate = 2; while (0 != number % divisor_candidate) ++divisor_candidate; return number == divisor_candidate;}

O(√n) → AKS

Page 37: Clean code that works

A jó kód dokumentálja magát

Kommentek?

Page 38: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { int a = 0, i = n, m; while (a < i) { a = a*8 + i%8; i /= 8; }

if (!((a == i) || (a/8 == i))) continue;

m = 2; while (0 != n%m) ++m;

if (m == n) printf("%o\n", n);}

Page 39: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { int a = 0, i = n, m; while (a < i) { a = a*8 + i%8; i /= 8; }

// skip if not palindromic in octal base if (!((a == i) || (a/8 == i))) continue;

m = 2; while (0 != n%m) ++m;

// print if prime if (m == n) printf("%o\n", n);}

Page 40: Clean code that works

A jó kód dokumentálja magátint n;for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) print_octal(n);}

int n;for (n = 2; n <= 5000; ++n) { int a = 0, i = n, m; while (a < i) { a = a*8 + i%8; i /= 8; }

// skip if not palindromic in octal base if (!((a == i) || (a/8 == i))) continue;

m = 2; while (0 != n%m) ++m;

// print if prime if (m == n) printf("%o\n", n);}

Page 41: Clean code that works

Kommentek

public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

Page 42: Clean code that works

Kommentek

/** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

Page 43: Clean code that works

Kommentek

/** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

Page 44: Clean code that works

Kommentek

/** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

Page 45: Clean code that works

Kommentek

/** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

Page 46: Clean code that works

Kommentek

/** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */public function registerArgumentTransformer(ArgumentTransformer $transformer){ $this->argumentTransformers[] = $transformer;}

This method registers anargument transformer!

Page 47: Clean code that works

Kommentek

def store_puppy(self): # self.puppy_source is already opened # by some_unrelated_fucntion() puppy = self.puppy_source.read() self.storage.store(puppy) self.puppy_source.close()

Page 48: Clean code that works

Kommentek

def store_puppy(self): # self.puppy_source is already opened # by some_unrelated_fucntion() puppy = self.puppy_source.read() self.storage.store(puppy) self.puppy_source.close()

Page 49: Clean code that works

Temporal coupling

open(), close()

connect(), disconnect()

Sorrendi függőség függvények között

Page 50: Clean code that works

Temporal coupling

open(), close()

connect(), disconnect()

Sorrendi függőség függvények között Könnyű elrontani (pl. exception)

Page 51: Clean code that works

Temporal coupling

def store_puppy(self): self.puppy_source.open()

puppy = self.puppy_source.read() self.storage.store(puppy)

self.puppy_source.close()

Page 52: Clean code that works

Temporal coupling

def store_puppy(self): self.puppy_source.open()

try: puppy = self.puppy_source.read() self.storage.store(puppy)

finally: self.puppy_source.close()

Page 53: Clean code that works

Temporal coupling

def store_puppy(self): with self.puppy_source: puppy = self.puppy_source.read() self.storage.store(puppy)

# puppy_source.__enter__,# puppy_source.__exit__

Page 54: Clean code that works

Temporal coupling

Általános megoldás?

Page 55: Clean code that works

Temporal couplinginterface PuppySourceCommand { public function run(PuppySource $ps);}public function withPuppySource(PuppySourceCommand $command){ $this->puppy_source->open();

try { $command->run($this->puppy_source); } finally { $this->puppy_source->close(); }}

Page 56: Clean code that works

Temporal couplinginterface PuppySourceCommand { public function run(PuppySource $ps);}public function withPuppySource(PuppySourceCommand $command){ $this->puppy_source->open();

try { $command->run($this->puppy_source); } finally { $this->puppy_source->close(); }} class StorePuppyCommand

implements PuppySourceCommand{ public function run(PuppySource $ps) { $puppy = $ps->read(); $this->storage->store($puppy); }}

Page 57: Clean code that works

Temporal coupling

public function withPuppySource(callable $command){ $this->puppy_source->open();

try { $command($this->puppy_source); } finally { $this->puppy_source->close(); }}

$pscm->withPuppySource( function (PuppySource $ps) { $puppy = $ps->read(); $this->storage->store($puppy); });

Page 58: Clean code that works

Nevek

Cél/szándék vs. implementáció Névterek, osztályok: általában főnevek Változók: főnevek, predikátumok Függvények, metódusok:

Általában igével kezdődnek (readLine(), generateReport(), getUser())

Boolean fv-ek: predikátumok (isLeapYear(), hasEntries())

Egyebek: sin(), cos(), DSL-ek, stb.

Page 59: Clean code that works

Nevek

function main(){ tag_file="$1" source_file="$2"

if can_be_updated "$tag_file" then update_tag_file "$tag_file" "$source_file" else rebuild_tag_file "$tag_file" fi}

Page 60: Clean code that works

Nevek

Kimondható nevek! Tömörség != rövidség

strpbrk(), strverscmp()

Név hossza vs. láthatóság: Függény, metódus: nagy scope → tömör név Változó: nagy scope → részletes név

Page 61: Clean code that works

Smurf naming convention

Page 62: Clean code that works

Smurf naming convention

Page 63: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

Page 64: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

$this­>m_session

Page 65: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

$this­>m_session

Abstract, Interface, Impl

Page 66: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

$this­>m_session

Abstract, Interface, Impl

function findBoundingBox(ShapeInterface $s){ // ...}

Page 67: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

$this­>m_session

Abstract, Interface, Impl

function findBoundingBox(AbstractShape $s){ // ...}

Page 68: Clean code that works

Smurf naming convention

Hungarian notation: MessageBox(hwnd, szMsg, "Hello", MB_OK);

$this­>m_session

Abstract, Interface, Impl

function findBoundingBox(Shape $s){ // ...}

Page 69: Clean code that works

Meglepetések

def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()

Page 70: Clean code that works

Meglepetések

Command-query separation: asking a question should not change the answer!

def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()

Page 71: Clean code that works

Meglepetések

Command-query separation: vagy változtass állapotot, vagy adj vissza értéket, de a kettőt egyszerre ne csináld!

def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()

Page 72: Clean code that works

Meglepetések

Command-query separation: vagy változtass állapotot, vagy adj vissza értéket, de a kettőt egyszerre ne csináld!

Párhuzamossággal vigyázni!

def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()

Page 73: Clean code that works

Method chaining, fluent interfaces

customer.newOrder() .with(6, "TAL") .with(5, "HPK").skippable() .with(3, "LGV") .priorityRush();

mock.expects(once()) .method("m") .with( or( stringContains("hello"), stringContains("howdy")) );

Page 74: Clean code that works

Be positive

def isNotGreaterThan(a, b): if not (a < b): return False else: return True

Page 75: Clean code that works

Be positive

def isNotGreaterThan(a, b): if a < b: return True else: return False

Page 76: Clean code that works

Be positive

def isNotGreaterThan(a, b): return a < b:

Page 77: Clean code that works

Be positive

def isLessThan(a, b): return a < b:

Page 78: Clean code that works

Nevek

Ha nehéz elnevezni, akkor túl sokat tud

Page 79: Clean code that works

Nevek

Ha nehéz elnevezni, akkor túl sokat tud – Single Responsibility Principle

Page 80: Clean code that works

Tipikus szoftver

Web framework

SQL adatbázis

NoSQL

XML

Business logic

Page 81: Clean code that works

Tipikus szoftver

Web framework

SQL adatbázis

NoSQL

XML

Business logic

GUI tesztek

Integration tesztek

Unittesztek

Page 82: Clean code that works

SOLID

SRP: Single Responsibility Principle

OCP: Open/Closed Principle Új viselkedés ↔ új kód (vs. meglévő kód reszelése)

LSP: Liskov Substitution Principle Téglalap-e a négyzet?

ISP: Interface Segregation Principle Ne függj olyan dolgoktól, amiket nem használsz!

DIP: Depencency Inversion Principle Ne az absztrakt logika függjön a konkrétumoktól!

Page 83: Clean code that works

Feladat

Jelöljük meg a standard inputon érkező sorokban a számokat [, ] jelekkel!

Page 84: Clean code that works

SRP

Jelöljük meg a standard inputon érkező sorokban a számokat [, ] jelekkel!

while (!feof(STDIN)) { print preg_replace( "/(\\d+)/", "[\\1]", fgets(STDIN) );}

Page 85: Clean code that works

SRP

Hány különböző dologgal foglalkozik ez a kód?

while (!feof(STDIN)) { print preg_replace( "/(\\d+)/", "[\\1]", fgets(STDIN) );}

Page 86: Clean code that works

SRP

Hány különböző absztrakció jelenik meg benne?

while (!feof(STDIN)) { print preg_replace( "/(\\d+)/", "[\\1]", fgets(STDIN) );}

Page 87: Clean code that works

SRP

Hány különböző absztrakció jelenik meg benne?function highlightNumbers($text){ return preg_replace( "/(\\d+)/", "[\\1]", $text );}while (!feof(STDIN)) { print highlightNumbers(fgets(STDIN));}

Page 88: Clean code that works

OCP

Open for extension, Closed for modification

class NumberHighlighterApplication { public function run() { while (!feof(STDIN)) { print highlightNumbers( fgets(STDIN) ); } }}

Page 89: Clean code that works

OCP

Open for extension, Closed for modification Új feature → új kód!class NumberHighlighterApplication { public function run() { while (!feof(STDIN)) { print highlightNumbers( fgets(STDIN) ); } }}

Page 90: Clean code that works

OCP

Open for extension, Closed for modification FR: tetszőleges file-t is kezeljen!class NumberHighlighterApplication { public function run() { while (!feof(STDIN)) { print highlightNumbers( fgets(STDIN) ); } }}

Page 91: Clean code that works

OCP

Open for extension, Closed for modificationclass NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); }}

class StandardInputNumberHighlighterApplication extends NumberHighlighterApplication { public function run() { parent::run(STDIN); }}

Page 92: Clean code that works

OCP

Open for extension, Closed for modificationclass NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); }}

class FileContentsNumberHighlighterApplication extends NumberHighlighterApplication { public function run($filename) { $stream = fopen($filename, "r"); parent::run($stream); fclose($stream); }}

Page 93: Clean code that works

OCP

Open for extension, Closed for modificationclass NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); }}

class FileContentsNumberHighlighterApplication extends NumberHighlighterApplication { public function run($filename) { $stream = fopen($filename, "r"); parent::run($stream); fclose($stream); }}

DON'T TRY THISAT HOME!

Page 94: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

Page 95: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); }}

class StandardInputNumberHighlighterApplication extends NumberHighlighterApplication { public function run() { parent::run(STDIN); }}

Page 96: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); }}

class StandardInputNumberHighlighterApplication { public function run() { new NumberHighlighterApplication() ->run(STDIN); }}

Page 97: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

class Rectangle { /* ... */ }class Square extends Rectangle { /* ... */ }

public function foo(Rectangle $r){ // ... $r->setWidth($new_width); $r->setHeight($new_height); // ...}

Page 98: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

Page 99: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival

class ComplexNumber { private $real, $imaginary;

public function __construct($real, $imaginary) { $this->real = new RealNumber($real); $this->imaginary = new RealNumber($imaginary); }}class RealNumber extends ComplexNumber { private $number;

public function __construct($number) { parent::__construct($number, 0); }}new ComplexNumber(0, 0);

Page 100: Clean code that works

LSP

S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaivalPHP Fatal error: Maximum function nesting level of '100'reached, aborting! in ~/projects/numbers.php on line 15PHP Stack trace:PHP 1. {main}() ~/projects/numbers.php:0PHP 2. ComplexNumber->__construct() ~/projects/numbers.php:18PHP 3. RealNumber->__construct() ~/projects/numbers.php:7PHP 4. ComplexNumber->__construct() ~/projects/numbers.php:15PHP 5. RealNumber->__construct() ~/projects/numbers.php:7PHP 6. ComplexNumber->__construct() ~/projects/numbers.php:15PHP 7. RealNumber->__construct() ~/projects/numbers.php:7PHP 8. ComplexNumber->__construct() ~/projects/numbers.php:15PHP 9. RealNumber->__construct() ~/projects/numbers.php:7PHP 10. ComplexNumber->__construct() ~/projects/numbers.php:15PHP 11. RealNumber->__construct() ~/projects/numbers.php:7PHP 12. ComplexNumber->__construct() ~/projects/numbers.php:15PHP 13. RealNumber->__construct() ~/projects/numbers.php:7...

Page 101: Clean code that works

DIP

Dependency Inversion Principle

class NumberHighlighterApplication{ public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } }}

Page 102: Clean code that works

DIP

FR: EBCDIC file-t is tudjon olvasni!

class NumberHighlighterApplication{ public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } }}

Page 103: Clean code that works

DIP

Abstractions should never depend on concretions.

class NumberHighlighterApplication{ public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } }}

Page 104: Clean code that works

DIP

Abstractions should never depend on concretions.

NumberHighlighterApplication

StandardInput

Business logic

Page 105: Clean code that works

DIP

Abstractions should never depend on concretions.

NumberHighlighterApplication

IOStream

StandardInput

Business logic

Page 106: Clean code that works

DIP

Abstractions should never depend on concretions.interface IOStream { public function open(); public function eof(); public function readLine(); public function write($text); public function close();}class NumberHighlighterApplication { private $input, $output;

public function __construct(IOStream $i, IOStream $o) { $this->input = $i; $this->output = $o; }

public function run() { while (!$this->input->eof()) { $line = $this->input->readLine(); $this->output->write(highlightNumbers($line)); } }}

Page 107: Clean code that works

DIP

Pl: Dependency Injectioninterface IOStream { public function open(); public function eof(); public function readLine(); public function write($text); public function close();}class NumberHighlighterApplication { private $input, $output;

public function __construct(IOStream $i, IOStream $o) { $this->input = $i; $this->output = $o; }

public function run() { while (!$this->input->eof()) { $line = $this->input->readLine(); $this->output->write(highlightNumbers($line)); } }}

Page 108: Clean code that works

DIP

NEM a DI containert injektáljuk az osztályba!interface IOStream { public function open(); public function eof(); public function readLine(); public function write($text); public function close();}class NumberHighlighterApplication { private $input, $output;

public function __construct(IOStream $i, IOStream $o) { $this->input = $i; $this->output = $o; }

public function run() { while (!$this->input->eof()) { $line = $this->input->readLine(); $this->output->write(highlightNumbers($line)); } }}

Page 109: Clean code that works

ISP

FR: JSON-ból olvasson, MySQL-be írjon!interface IOStream { public function open(); public function eof(); public function readLine(); public function write($text); public function close();}class NumberHighlighterApplication { private $input, $output;

public function __construct(IOStream $i, IOStream $o) { $this->input = $i; $this->output = $o; }

public function run() { while (!$this->input->eof()) { $line = $this->input->readLine(); $this->output->write(highlightNumbers($line)); } }}

Page 110: Clean code that works

ISP

Ne függj olyan olyasmitől, amit nem használsz!interface IOStream { public function open(); public function eof(); public function readLine(); public function write($text); public function close();}class NumberHighlighterApplication { private $input, $output;

public function __construct(IOStream $i, IOStream $o) { $this->input = $i; $this->output = $o; }

public function run() { while (!$this->input->eof()) { $line = $this->input->readLine(); $this->output->write(highlightNumbers($line)); } }}

Page 111: Clean code that works

ISP

Ne függj olyan olyasmitől, amit nem használsz!interface Input { public function hasMore(); public function read();}interface Output { public function write($text);}class NumberHighlighterApplication { private $input, $output;

public function __construct(Input $i, Output $o) { $this->input = $i; $this->output = $o; }

public function run() { while ($this->input->hasMore()) { $text = $this->input->read(); $this->output->write(highlightNumbers($text)); } }}

Page 112: Clean code that works

Mit adtak nekünk a SOLID elvek?

Business Logic

Plain objects (domain model, use cases)Interfaces (integration)

SQL database XML

Web framework Desktop GUI frameworkCLI, getopt, etc.

NoSQL database

Thin integration Thin integrationThin integration

Thin integration Thin integration Thin integration

Page 113: Clean code that works

Mit adtak nekünk a SOLID elvek?

Business Logic

Plain objects (domain model, use cases)Interfaces (integration)

SQL database XML

Web framework Desktop GUI frameworkCLI, getopt, etc.

NoSQL database

Thin integration Thin integrationThin integration

Thin integration Thin integration Thin integration

GUItesztek

Integration tesztek

Unit tesztek

Page 114: Clean code that works

Tesztekinterface Input interface Output{ { public function hasMore(); public function write($text); public function read(); }}class NumberHighlighterApplication{ private $input, $output;

public function __construct(Input $i, Output $o) { $this->input = $i; $this->output = $o; }

public function run() { while ($this->input->hasMore()) { $text = $this->input->read(); $highlighted = highlightNumbers($text); $this->output->write($highlighted); } }}

Page 115: Clean code that works

Test doubleclass FakeInput implements Input{ private $lines; private $next_line_index;

public function __construct(array $lines) { $this->lines = $lines; $this->next_line_index = 0; }

public function read() { return $this->lines[$this->next_line_index++]; }

public function hasMore() { return $this->next_line_index < count($this->lines); }}

Page 116: Clean code that works

Test doubleclass FakeOutput implements Output{ private $lines;

public function __construct() { $this->lines = array(); }

public function write($text) { $this->lines[] = $text; }

public function getWrittenLines() { return $this->lines; }}

Page 117: Clean code that works

Test double

private function highlight(array $input_lines){ $input = new FakeInput($input_lines); $output = new FakeOutput();

$application = new NumberHighlighterApplication($input, $output); $application->run();

return $output->getWrittenLines();}

Page 118: Clean code that works

Test double

private function assertHighlihtedLines(array $input, array $expected){ $this->assertEquals($expected, $this->highlight($input));}

private function highlight(array $input_lines){ $input = new FakeInput($input_lines); $output = new FakeOutput();

$application = new NumberHighlighterApplication($input, $output); $application->run();

return $output->getWrittenLines();}

Page 119: Clean code that works

Test doubleprivate function assertHighlightedLine($input, $expected){ $this->assertHighlightedLines(array($input), array($expected));}

private function assertHighlihtedLines(array $input, array $expected){ $this->assertEquals($expected, $this->highlight($input));}

private function highlight(array $input_lines){ $input = new FakeInput($input_lines); $output = new FakeOutput();

$application = new NumberHighlighterApplication($input, $output); $application->run();

return $output->getWrittenLines();}

Page 120: Clean code that works

Unit tesztfunction testWhenThereIsNoNumberInALineThenItIsUnchanged(){ $this->assertHighlihtedLine("No numbers", "No numbers");}

function testWhenThereIsANumberInALineThenItIsSurroundedWithBrackets(){ $this->assertHighlihtedLine("42", "[42]");}

function testWhenThereAreManyNumbersInALineThenAllAreSurroundedWithBrackets(){ $this->assertHighlihtedLine("42 123", "[42] [123]");}

function testNonNumericTextIsUnchanged(){ $this->assertHighlihtedLine("A 42 B", "A [42] B");}

function testNumbersAreHighlightedInAllLines(){ $this->assertHighlihtedLines( array("A 42 B", "C 123 D"), array("A [42] B", "C [123] D") );}

Page 121: Clean code that works

(majdnem) Unit tesztfunction testWhenThereIsNoNumberInALineThenItIsUnchanged(){ $this->assertHighlihtedLine("No numbers", "No numbers");}

function testWhenThereIsANumberInALineThenItIsSurroundedWithBrackets(){ $this->assertHighlihtedLine("42", "[42]");}

function testWhenThereAreManyNumbersInALineThenAllAreSurroundedWithBrackets(){ $this->assertHighlihtedLine("42 123", "[42] [123]");}

function testNonNumericTextIsUnchanged(){ $this->assertHighlihtedLine("A 42 B", "A [42] B");}

function testNumbersAreHighlightedInAllLines(){ $this->assertHighlihtedLines( array("A 42 B", "C 123 D"), array("A [42] B", "C [123] D") );}

Page 122: Clean code that works

Tesztek

Cél: segíteni a refaktorálást

Page 123: Clean code that works

Tesztek

Cél: segíteni a refaktorálástfunction testWhenThereIsNoNumberInALineThenItIsUnchanged() { $input = new FakeInput(array("No numbers")); $output = new FakeOutput(); $application = new NumberHighlighterApplication($input, $output); $application->run(); $this->assertEquals(array("No numbers"), $output->getWrittenLines());}function testWhenThereIsANumberInALineThenItIsSurroundedWithBrackets() { $input = new FakeInput(array("42")); $output = new FakeOutput(); $application = new NumberHighlighterApplication($input, $output); $application->run(); $this->assertEquals(array("[42]"), $output->getWrittenLines());}function testWhenThereAreManyNumbersInALineThenAllAreSurroundedWithBrackets() { $input = new FakeInput(array("42 123")); $output = new FakeOutput(); $application = new NumberHighlighterApplication($input, $output); $application->run(); $this->assertEquals(array("[42] [123]"), $output->getWrittenLines());}function testNonNumericTextIsUnchanged() { $input = new FakeInput(array("A 42 B")); $output = new FakeOutput(); $application = new NumberHighlighterApplication($input, $output); $application->run(); $this->assertEquals(array("A [42] B"), $output->getWrittenLines());}

Page 124: Clean code that works

Tesztek

Cél: segíteni a refaktorálástfunction testWhenThereIsNoNumberInALineThenItIsUnchanged(){ $this->assertHighlihtedLine("No numbers", "No numbers");}

function testWhenThereIsANumberInALineThenItIsSurroundedWithBrackets(){ $this->assertHighlihtedLine("42", "[42]");}

function testWhenThereAreManyNumbersInALineThenAllAreSurroundedWithBrackets(){ $this->assertHighlihtedLine("42 123", "[42] [123]");}

function testNonNumericTextIsUnchanged(){ $this->assertHighlihtedLine("A 42 B", "A [42] B");}

Page 125: Clean code that works

Tesztek

Cél: segíteni a refaktorálást

private function highlight(array $input_lines){ $input = new FakeInput($input_lines); $output = new FakeOutput();

$application = new NumberHighlighterApplication($input, $output); $application->run();

return $output->getWrittenLines();}

Page 126: Clean code that works

Tesztek

Cél: segíteni a refaktorálást

$input = $this->getMock("Input");$input->expects($this->exactly(2)) ->method("hasMore") ->will($this->onConsecutiveCalls(array(true, false)));$input->expects($this->once()) ->method("read") ->will($this->returnValue("A 42 B"));

$output = $this->getMock("Output");$output->expects($this->once()) ->method("write") ->with("A [42] B");

$application = new NumberHighlighterApplication($input, $output);$application->run();

Page 127: Clean code that works

Tesztek

Az olvashatóság követelménye a tesztekre is vonatkozik!

Page 128: Clean code that works

Tesztek

Az olvashatóság követelménye a tesztekre is vonatkozik!

Plusz még néhány: Gyors! Élő példakód! Stabilitás Független tesztek Megbízhatóság Reprodukálhatóság

Page 129: Clean code that works

Tesztek

testLoginValidtestLoginInvalid

Page 130: Clean code that works

Tesztek

testLoginValidtestLoginInvalid

testEmptyUsernameTriggersErrortestWrongUsernameTriggersErrortestEmptyPasswordTriggersErrortestWrongPasswordTriggersErrortestWhenCredentialsAreCorrectThenUserIsLoggedIntestSessionFixationAttacksArePreventedByRegeneratingTheId

Page 131: Clean code that works

Tesztek

Honnan tudom, hogy a tesztem tényleg vizsgál valamit?

Nézd meg, hogyan fail-el!

Page 132: Clean code that works

Tesztek

Honnan tudom, hogy a tesztem tényleg vizsgál valamit?

Nézd meg, hogyan fail-el! Mutation testing?

Page 133: Clean code that works

Tesztek

Honnan tudom, hogy a tesztem tényleg vizsgál valamit?

Nézd meg, hogyan fail-el! Mutation testing? Test-driven development!

Page 134: Clean code that works

TDD

Írj annyi tesztet, ami éppen elég a FAIL-hez! Írj annyi kódot, ami éppen elég a PASS-hez! Refaktorálj!

Page 135: Clean code that works

TDD

Írj annyi tesztet, ami éppen elég a FAIL-hez! Írj annyi kódot, ami éppen elég a PASS-hez! Refaktorálj! Kis lépések → kevésbé fájdalmas visszalépni

és más irányba indulni Interruptok, context switch-ek kevésbé fájnak Ha minden tesztet láttál törni, megbízhatsz

bennük

Page 136: Clean code that works

TDD

OpenAcademy, 2012. tavasz: http://tinyurl.com/openacademy-tdd

Page 137: Clean code that works

Tesztek

Viselkedéseket, követelményeket tesztelj, ne metódusokat!

public function testHighlight(){ $input = new FakeInput( array("No numbers", "42", "A 42 B", "A 42 B 123 C") ); $output = new FakeOutput();

$application = new NumberHighlighterApplication($input,$output); $application->run();

$this->assertEquals( array("No numbers", "[42]", "A [42] B", "A [42] B [123] C"), $output->getWrittenLines() );}

Page 138: Clean code that works

Tesztek

Viselkedéseket, követelményeket tesztelj, ne metódusokat!

function testWhenThereIsNoNumberInALineThenItIsUnchanged(){ $this->assertHighlihtedLine("No numbers", "No numbers");}

function testWhenThereIsANumberInALineThenItIsSurroundedWithBrackets(){ $this->assertHighlihtedLine("42", "[42]");}

function testWhenThereAreManyNumbersInALineThenAllAreSurroundedWithBrackets(){ $this->assertHighlihtedLine("42 123", "[42] [123]");}

function testNonNumericTextIsUnchanged(){ $this->assertHighlihtedLine("A 42 B", "A [42] B");}

Page 139: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekclass Login { // ... public function perform($username, $password) { $account = $this->findAccountByUsername($username);

if ($account->isValidPassword($password)) return $this->makeSuccessResponse($username, $account);

return $this->makeErrorResponse($username); } private function findAccountByUsername($username) { $account_data = $this->sql->query( "SELECT * FROM users WHERE ...", array("username" => $username) ); // ... return new UserAccount($account_data); }}

Page 140: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekclass Login { // ... public function perform($username, $password) { $account = $this->findAccountByUsername($username);

if ($account->isValidPassword($password)) return $this->makeSuccessResponse($username, $account);

return $this->makeErrorResponse($username); } protected function findAccountByUsername($username) { $account_data = $this->sql->query( "SELECT * FROM users WHERE ...", array("username" => $username) ); // ... return new UserAccount($account_data); }}

Page 141: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnek

class TestableLogin extends Login{ protected function findAccountByUsername($username) { return new UserAccount(array("Alice", "5af6b73c3...")); }}

Page 142: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekclass Login { // ... public function perform($username, $password) { $account = $this->findAccountByUsername($username);

if ($account->isValidPassword($password)) return $this->makeSuccessResponse($username, $account);

return $this->makeErrorResponse($username); } private function findAccountByUsername($username) { $account_data = $this->sql->query( "SELECT * FROM users WHERE ...", array("username" => $username) ); // ... return new UserAccount($account_data); }}

Page 143: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekclass Login { // ... public function perform($username, $password) { $account = $this->findAccountByUsername($username);

if ($account->isValidPassword($password)) return $this->makeSuccessResponse($username, $account);

return $this->makeErrorResponse($username); } private function findAccountByUsername($username) { $account_data = $this->sql->query( "SELECT * FROM users WHERE ...", array("username" => $username) ); // ... return new UserAccount($account_data); }}

High level policy

Low level detail

Page 144: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekinterface UserAccountRepository { public function findByUsername($username);}class Login { private $accounts;

public function __construct(UserAccountRepository $r) { $this->accounts = $r; }

public function perform($username, $password) { $account = $this->accounts->findByUsername($username);

if ($account->isValidPassword($password)) return $this->makeSuccessResponse($username, $account);

return $this->makeErrorResponse($username); }}

Page 145: Clean code that works

Tesztek

A tesztek design problémákra figyelmeztetnekinterface UserAccountRepository { public function findByUsername($username);}

class SqlUserAccountRepository implements UserAccountRepository{ public function findByUsername($username) { $account_data = $this->sql->query( "SELECT * FROM users WHERE ...", array("username" => $username) ); // ... return new UserAccount($account_data); }}

Page 146: Clean code that works

Bővebben

Page 147: Clean code that works

Bővebben http://cleancoders.com

Robert C. Martin: Architecture the Lost Years (1:07)http://www.youtube.com/watch?v=WpkDN78P884

Gary Bernhardt: Fast Test, Slow Test (0:32)http://www.youtube.com/watch?v=RAxiiRPHS9k

Gary Bernhardt: Boundaries (0:46)http://www.youtube.com/watch?v=yTkzNHF6rMs

Page 148: Clean code that works

Bővebben http://blog.rocketpoweredjetpants.com/2014/01/a-ranty-and-dogmatic-troll-

masquerading.html http://martinfowler.com/articles/dipInTheWild.html http://googletesting.blogspot.hu/2008/07/breaking-law-of-demeter-is-like-looking.html?

spref=tw http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html http://googletesting.blogspot.hu/2013/08/testing-on-toilet-test-behavior-not.html?spref=tw https://www.facebook.com/notes/kent-beck/shorts-not-always-sweet-the-case-for-long-test-

names/564493423583526 http://dannorth.net/introducing-bdd/ https://michaelfeathers.silvrback.com/when-it-s-okay-for-a-method-to-do-nothing http://googletesting.blogspot.hu/2008/07/how-to-write-3v1l-untestable-code.html?spref=tw http://googletesting.blogspot.hu/2013/05/testing-on-toilet-dont-overuse-mocks.html?

spref=tw http://codemanship.co.uk/parlezuml/blog/?postid=1170 http://thedailywtf.com/Articles/The-Enterprise-Dependency.aspx https://athos.blogs.balabit.com/2011/11/ioccc-vs-clean-code/ http://martinfowler.com/bliki/FluentInterface.html

Page 149: Clean code that works

Coding kata

Page 150: Clean code that works

Coding kata

Page 151: Clean code that works

Coding kata

FizzBuzz Prime factors Bowling game Római számok → arab számok WordWrap Conway's Game of Life … http://en.wikipedia.org/wiki/Kata_(programming)

Page 152: Clean code that works

Code Retreat

Február 22. (Legacy CodeRetreat) http://www.meetup.com/Coderetreat-Budapest/events/166131862/

Page 153: Clean code that works

Kérdés?

http://www.slideshare.net/athoshun

Page 154: Clean code that works

Köszönöm a figyelmet!

http://www.slideshare.net/athoshun