Clean code that works

50 %
50 %
Information about Clean code that works
Technology

Published on February 13, 2014

Author: athoshun

Source: slideshare.net

Description

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

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, CommandQuery 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("%on", 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("%on", n))&&n%m;);

A jó kód dokumentálja magát int 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("%on", n);

A jó kód dokumentálja magát int n; for (n = 2; n <= 5000; ++n) { if (!first_loop(n)) continue; } if (second_loop(n)) printf("%on", 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át int n; for (n = 2; n <= 5000; ++n) { if (first_loop(n) && second_loop(n)) printf("%on", 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át int n; for (n = 2; n <= 5000; ++n) { if (is_palindromic_in_octal_base(n) && is_prime(n)) printf("%on", 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át 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; }

A jó kód dokumentálja magát 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; }

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át int 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("%on", n); }

A jó kód dokumentálja magát 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("%on", n); }

A jó kód dokumentálja magát int 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("%on", 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 This method registers an argument transformer! /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $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 coupling interface 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 coupling interface 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 SQL adatbázis Web framework Business logic XML NoSQL

Tipikus szoftver SQL adatbázis Web framework GUI tesztek Business logic Integration tesztek Unit tesztek NoSQL XML

SOLID   SRP: Single Responsibility Principle OCP: Open/Closed Principle   LSP: Liskov Substitution Principle   Téglalap-e a négyzet? ISP: Interface Segregation Principle   Új viselkedés ↔ új kód (vs. meglévő kód reszelése) 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 modification class 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 modification class 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 modification class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) print highlightNumbers(fgets($stream)); } } DON'T TRY THIS AT HOME! class FileContentsNumberHighlighterApplication extends NumberHighlighterApplication { public function run($filename) { $stream = fopen($filename, "r"); parent::run($stream); fclose($stream); } }

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ányaival PHP Fatal error: Maximum function nesting level of '100' reached, aborting! in ~/projects/numbers.php on line 15 PHP Stack trace: PHP 1. {main}() ~/projects/numbers.php:0 PHP 2. ComplexNumber->__construct() ~/projects/numbers.php:18 PHP 3. RealNumber->__construct() ~/projects/numbers.php:7 PHP 4. ComplexNumber->__construct() ~/projects/numbers.php:15 PHP 5. RealNumber->__construct() ~/projects/numbers.php:7 PHP 6. ComplexNumber->__construct() ~/projects/numbers.php:15 PHP 7. RealNumber->__construct() ~/projects/numbers.php:7 PHP 8. ComplexNumber->__construct() ~/projects/numbers.php:15 PHP 9. RealNumber->__construct() ~/projects/numbers.php:7 PHP 10. ComplexNumber->__construct() ~/projects/numbers.php:15 PHP 11. RealNumber->__construct() ~/projects/numbers.php:7 PHP 12. ComplexNumber->__construct() ~/projects/numbers.php:15 PHP 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. Business logic NumberHighlighterApplication StandardInput

DIP  Abstractions should never depend on concretions. Business logic NumberHighlighterApplication IOStream StandardInput

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 Injection 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  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? CLI, getopt, etc. Web framework Thin integration Thin integration Desktop GUI framework Thin integration Business Logic Plain objects (domain model, use cases) Interfaces (integration) Thin integration SQL database Thin integration NoSQL database Thin integration XML

Mit adtak nekünk a SOLID elvek? CLI, getopt, etc. Web framework GUI Thin integration Thin integration tesztek Desktop GUI framework Thin integration Business Logic Integration tesztek Plain objects (domain model, use cases) Interfaces (integration) Thin integration SQL database Unit tesztek Thin integration NoSQL database Thin integration XML

Tesztek interface 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 double class 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 double class 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 double private 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 teszt 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"); } function testNumbersAreHighlightedInAllLines() { $this->assertHighlihtedLines( array("A 42 B", "C 123 D"), array("A [42] B", "C [123] D") ); }

(majdnem) Unit teszt 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"); } 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ást function 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ást 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  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 testLoginValid testLoginInvalid

Tesztek testLoginValid testLoginInvalid testEmptyUsernameTriggersError testWrongUsernameTriggersError testEmptyPasswordTriggersError testWrongPasswordTriggersError testWhenCredentialsAreCorrectThenUserIsLoggedIn testSessionFixationAttacksArePreventedByRegeneratingTheId

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 figyelmeztetnek class 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 figyelmeztetnek class 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 figyelmeztetnek class 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 figyelmeztetnek class Login { // ... public function perform($username, $password) { $account = $this->findAccountByUsername($username); High level policy 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); } Low level detail }

Tesztek  A tesztek design problémákra figyelmeztetnek interface 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 figyelmeztetnek interface 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-trollmasquerading.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-testnames/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

Add a comment

Related presentations

Related pages

timo stollenwerk — web application developer — plone ...

Dipl.-Inform. Timo Stollenwerk. Bornheimer Straße 37. 53111 Bonn. office +49 228 - 286 288 380 fax +49 228 - 286 288 389 ... Umsatzsteuer ...
Read more

About - Clean Code Development - Code Affine

Passionate clean code developers blog about their work experiences, acquired by over a decade of consulting, training and tool development.
Read more

Julian Timpner – Clean Code That Works

Julian Timpner, M.Sc. E-Mail: @.com . Julian is a Research Staff Member with the Institute of Operating Systems and Computer Networks ...
Read more

Clean Code that Works. :: Clean Code that Works.

Clean Code that Works. Home; Tag; MediaLog; LocationLog; Guestbook; Admin; Write; search. Total | 279,746; Today | 42; Yesterday | 104; Java 2015.06.01 15 ...
Read more

clean code that works. :: clean code that works.

1부에서는 Excel의 MDI를 해제하는 방법에 대해 알아봤다. 같은 Office지만 Excel과 PPT는 같은 방법으로는 MDI를 해제할 수 ...
Read more

Clean Code that work! - Clean Code that work!

스레드의 상태전이,우선순위,동기화. Skip to content 스레드의 상태전이 new 로 스레드 생성 -> start() -> Runnable 상태 ...
Read more

Clean code that works (simplicity is more complicity ...

Preface. Clean code that works, in Ron Jeffries' pithy phrase, is the goal of Test-Driven Development (TDD). Clean code that works is a worthwhile goal for ...
Read more

Clean Coders

Clean Coders is the leading producer of instructional videos for software professionals, taught in a way that both educates and entertains developers.
Read more

Clean Code That Works :: Clean Code That Works

C++0X를 사용 못할 상황이 생겼다. lambda 사용을 제거해야 하는데 매번 functor class를 만들어 주는 것도 만만치 않다.
Read more

Writing clean code - IBM - United States

from The Rational Edge: See why software developers need to learn elegance, structure, and efficiency in their code-writing efforts.
Read more