clean code that works

Post on 06-May-2015

729 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

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

TRANSCRIPT

Clean Code

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

Clean Code

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

Clean Code

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

Clean Code

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

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, …

#minekvan

Egyszer volt, hol nem volt...

Nagyvállalati alkalmazás

Egyszer volt, hol nem volt...

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

Egyszer volt, hol nem volt...

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

keresztül is tud frissülni

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

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

Egyszer volt, hol nem volt...

Egyszer volt, hol nem volt...

Változtatás → kockázat

Változtatás → kockázat

Ki fogja maintainelni?

Ki fogja maintainelni?

Ki fogja maintainelni?

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

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;);

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;);

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);}

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;}

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;}

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;}

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;}

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;}

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)

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

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

Kommentek?

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);}

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);}

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);}

Kommentek

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

Kommentek

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

Kommentek

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

Kommentek

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

Kommentek

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

Kommentek

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

This method registers anargument transformer!

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()

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()

Temporal coupling

open(), close()

connect(), disconnect()

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

Temporal coupling

open(), close()

connect(), disconnect()

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

Temporal coupling

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

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

self.puppy_source.close()

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()

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__

Temporal coupling

Általános megoldás?

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(); }}

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); }}

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); });

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.

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}

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

Smurf naming convention

Smurf naming convention

Smurf naming convention

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

Smurf naming convention

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

$this­>m_session

Smurf naming convention

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

$this­>m_session

Abstract, Interface, Impl

Smurf naming convention

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

$this­>m_session

Abstract, Interface, Impl

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

Smurf naming convention

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

$this­>m_session

Abstract, Interface, Impl

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

Smurf naming convention

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

$this­>m_session

Abstract, Interface, Impl

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

Meglepetések

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

Meglepetések

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

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

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()

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()

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")) );

Be positive

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

Be positive

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

Be positive

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

Be positive

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

Nevek

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

Nevek

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

Tipikus szoftver

Web framework

SQL adatbázis

NoSQL

XML

Business logic

Tipikus szoftver

Web framework

SQL adatbázis

NoSQL

XML

Business logic

GUI tesztek

Integration tesztek

Unittesztek

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!

Feladat

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

SRP

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

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

SRP

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

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

SRP

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

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

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));}

OCP

Open for extension, Closed for modification

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

OCP

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

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) ); } }}

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); }}

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); }}

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!

LSP

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

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); }}

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); }}

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); // ...}

LSP

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

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);

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...

DIP

Dependency Inversion Principle

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

DIP

FR: EBCDIC file-t is tudjon olvasni!

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

DIP

Abstractions should never depend on concretions.

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

DIP

Abstractions should never depend on concretions.

NumberHighlighterApplication

StandardInput

Business logic

DIP

Abstractions should never depend on concretions.

NumberHighlighterApplication

IOStream

StandardInput

Business logic

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)); } }}

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)); } }}

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)); } }}

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)); } }}

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)); } }}

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)); } }}

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

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

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); } }}

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); }}

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; }}

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();}

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();}

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();}

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") );}

(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") );}

Tesztek

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

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());}

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");}

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();}

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();

Tesztek

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

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

Tesztek

testLoginValidtestLoginInvalid

Tesztek

testLoginValidtestLoginInvalid

testEmptyUsernameTriggersErrortestWrongUsernameTriggersErrortestEmptyPasswordTriggersErrortestWrongPasswordTriggersErrortestWhenCredentialsAreCorrectThenUserIsLoggedIntestSessionFixationAttacksArePreventedByRegeneratingTheId

Tesztek

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

Nézd meg, hogyan fail-el!

Tesztek

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

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

Tesztek

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

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

TDD

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

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

TDD

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

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() );}

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");}

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); }}

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); }}

Tesztek

A tesztek design problémákra figyelmeztetnek

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

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); }}

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

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); }}

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); }}

Bővebben

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

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

Coding kata

Coding kata

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)

Code Retreat

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

Kérdés?

http://www.slideshare.net/athoshun

Köszönöm a figyelmet!

http://www.slideshare.net/athoshun

top related