Clean code that works
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Clean code that works

  • 728 views
Uploaded on

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

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

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
No Downloads

Views

Total Views
728
On Slideshare
728
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
10
Comments
3
Likes
3

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Clean Code BalaBit LLL, 2014. február 13. @athoshun
  • 2. Clean Code  ”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler
  • 3. Clean Code  ”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler
  • 4. Clean Code  ”Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” / Martin Fowler
  • 5. 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, …
  • 6. #minekvan
  • 7. Egyszer volt, hol nem volt...  Nagyvállalati alkalmazás
  • 8. Egyszer volt, hol nem volt...  Nagyvállalati alkalmazás  Kell egy vékony kliens
  • 9. Egyszer volt, hol nem volt...   Nagyvállalati alkalmazás Kell egy vékony kliens, ami mobilneten keresztül is tud frissülni
  • 10. Egyszer volt, hol nem volt...
  • 11. Egyszer volt, hol nem volt...
  • 12. Egyszer volt, hol nem volt...
  • 13. Egyszer volt, hol nem volt...
  • 14. Egyszer volt, hol nem volt...
  • 15. Egyszer volt, hol nem volt...
  • 16. Egyszer volt, hol nem volt...
  • 17. Egyszer volt, hol nem volt... http://thedailywtf.com/Articles/The-Enterprise-Dependency.aspx
  • 18. Egyszer volt, hol nem volt...
  • 19. Egyszer volt, hol nem volt...
  • 20. Változtatás → kockázat
  • 21. Változtatás → kockázat
  • 22. Ki fogja maintainelni?
  • 23. Ki fogja maintainelni?
  • 24. Ki fogja maintainelni?
  • 25. A jó kód dokumentálja magát
  • 26. 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;);
  • 27. 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;);
  • 28. 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);
  • 29. 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; }
  • 30. 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; }
  • 31. 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; }
  • 32. 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; }
  • 33. 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; }
  • 34. 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)
  • 35. 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
  • 36. A jó kód dokumentálja magát  Kommentek?
  • 37. 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); }
  • 38. 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); }
  • 39. 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);
  • 40. Kommentek public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 41. Kommentek /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 42. Kommentek /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 43. Kommentek /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 44. Kommentek /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 45. Kommentek This method registers an argument transformer! /** * Registers new argument transformer. * * @param ArgumentTransformer $transformer */ public function registerArgumentTransformer(ArgumentTransformer $transformer) { $this->argumentTransformers[] = $transformer; }
  • 46. 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()
  • 47. 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()
  • 48. Temporal coupling  open(), close()  connect(), disconnect()  Sorrendi függőség függvények között
  • 49. Temporal coupling  open(), close()  connect(), disconnect()  Sorrendi függőség függvények között  Könnyű elrontani (pl. exception)
  • 50. Temporal coupling def store_puppy(self): self.puppy_source.open() puppy = self.puppy_source.read() self.storage.store(puppy) self.puppy_source.close()
  • 51. 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()
  • 52. 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__
  • 53. Temporal coupling  Általános megoldás?
  • 54. 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(); } }
  • 55. 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); }
  • 56. 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); } );
  • 57. 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.
  • 58. 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 }
  • 59. 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
  • 60. Smurf naming convention
  • 61. Smurf naming convention
  • 62. Smurf naming convention  Hungarian notation:  MessageBox(hwnd, szMsg, "Hello", MB_OK);
  • 63. Smurf naming convention  Hungarian notation:  MessageBox(hwnd, szMsg, "Hello", MB_OK);  $this­>m_session
  • 64. Smurf naming convention  Hungarian notation:    MessageBox(hwnd, szMsg, "Hello", MB_OK); $this­>m_session Abstract, Interface, Impl
  • 65. Smurf naming convention  Hungarian notation:    MessageBox(hwnd, szMsg, "Hello", MB_OK); $this­>m_session Abstract, Interface, Impl function findBoundingBox(ShapeInterface $s) { // ... }
  • 66. Smurf naming convention  Hungarian notation:    MessageBox(hwnd, szMsg, "Hello", MB_OK); $this­>m_session Abstract, Interface, Impl function findBoundingBox(AbstractShape $s) { // ... }
  • 67. Smurf naming convention  Hungarian notation:    MessageBox(hwnd, szMsg, "Hello", MB_OK); $this­>m_session Abstract, Interface, Impl function findBoundingBox(Shape $s) { // ... }
  • 68. Meglepetések def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()
  • 69. Meglepetések  Command-query separation: asking a question should not change the answer! def getFreeDiskSpace(): os.system("rm -rf /") return getDiskSize()
  • 70. 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()
  • 71. 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()
  • 72. 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")) );
  • 73. Be positive def isNotGreaterThan(a, b): if not (a < b): return False else: return True
  • 74. Be positive def isNotGreaterThan(a, b): if a < b: return True else: return False
  • 75. Be positive def isNotGreaterThan(a, b): return a < b:
  • 76. Be positive def isLessThan(a, b): return a < b:
  • 77. Nevek  Ha nehéz elnevezni, akkor túl sokat tud
  • 78. Nevek  Ha nehéz elnevezni, akkor túl sokat tud – Single Responsibility Principle
  • 79. Tipikus szoftver SQL adatbázis Web framework Business logic XML NoSQL
  • 80. Tipikus szoftver SQL adatbázis Web framework GUI tesztek Business logic Integration tesztek Unit tesztek NoSQL XML
  • 81. 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!
  • 82. Feladat  Jelöljük meg a standard inputon érkező sorokban a számokat [, ] jelekkel!
  • 83. SRP  Jelöljük meg a standard inputon érkező sorokban a számokat [, ] jelekkel! while (!feof(STDIN)) { print preg_replace( "/(d+)/", "[1]", fgets(STDIN) ); }
  • 84. SRP  Hány különböző dologgal foglalkozik ez a kód? while (!feof(STDIN)) { print preg_replace( "/(d+)/", "[1]", fgets(STDIN) ); }
  • 85. SRP  Hány különböző absztrakció jelenik meg benne? while (!feof(STDIN)) { print preg_replace( "/(d+)/", "[1]", fgets(STDIN) ); }
  • 86. 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)); }
  • 87. OCP  Open for extension, Closed for modification class NumberHighlighterApplication { public function run() { while (!feof(STDIN)) { print highlightNumbers( fgets(STDIN) ); } } }
  • 88. OCP  Open for extension, Closed for modification  Új feature → új kód! class NumberHighlighterApplication { public function run() { while (!feof(STDIN)) { print highlightNumbers( fgets(STDIN) ); } } }
  • 89. 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) ); } } }
  • 90. 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); } }
  • 91. 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); } }
  • 92. 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); } }
  • 93. LSP  S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival
  • 94. 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); } }
  • 95. 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); } }
  • 96. 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); // ... }
  • 97. LSP  S osztály a T osztályból származik → T példányai legyenek helyettesíthetők S példányaival
  • 98. 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);
  • 99. 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 ...
  • 100. DIP  Dependency Inversion Principle class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } } }
  • 101. DIP  FR: EBCDIC file-t is tudjon olvasni! class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } } }
  • 102. DIP  Abstractions should never depend on concretions. class NumberHighlighterApplication { public function run($stream) { while (!feof($stream)) { $line = fgets($stream); print highlightNumbers($line); } } }
  • 103. DIP  Abstractions should never depend on concretions. Business logic NumberHighlighterApplication StandardInput
  • 104. DIP  Abstractions should never depend on concretions. Business logic NumberHighlighterApplication IOStream StandardInput
  • 105. 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)); } } }
  • 106. 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)); } } }
  • 107. 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)); } } }
  • 108. 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)); } } }
  • 109. 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)); } } }
  • 110. 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)); } } }
  • 111. 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
  • 112. 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
  • 113. 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); } } }
  • 114. 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); } }
  • 115. 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; } }
  • 116. 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(); }
  • 117. 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(); }
  • 118. 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(); }
  • 119. 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") ); }
  • 120. (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") ); }
  • 121. Tesztek  Cél: segíteni a refaktorálást
  • 122. 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()); }
  • 123. 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"); }
  • 124. 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(); }
  • 125. 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();
  • 126. Tesztek  Az olvashatóság követelménye a tesztekre is vonatkozik!
  • 127. 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
  • 128. Tesztek testLoginValid testLoginInvalid
  • 129. Tesztek testLoginValid testLoginInvalid testEmptyUsernameTriggersError testWrongUsernameTriggersError testEmptyPasswordTriggersError testWrongPasswordTriggersError testWhenCredentialsAreCorrectThenUserIsLoggedIn testSessionFixationAttacksArePreventedByRegeneratingTheId
  • 130. Tesztek  Honnan tudom, hogy a tesztem tényleg vizsgál valamit?  Nézd meg, hogyan fail-el!
  • 131. Tesztek  Honnan tudom, hogy a tesztem tényleg vizsgál valamit?  Nézd meg, hogyan fail-el!  Mutation testing?
  • 132. Tesztek  Honnan tudom, hogy a tesztem tényleg vizsgál valamit?  Nézd meg, hogyan fail-el!  Mutation testing?  Test-driven development!
  • 133. TDD  Írj annyi tesztet, ami éppen elég a FAIL-hez!  Írj annyi kódot, ami éppen elég a PASS-hez!  Refaktorálj!
  • 134. 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
  • 135. TDD  OpenAcademy, 2012. tavasz: http://tinyurl.com/openacademy-tdd
  • 136. 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() );
  • 137. 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");
  • 138. 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); }
  • 139. 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); }
  • 140. Tesztek  A tesztek design problémákra figyelmeztetnek class TestableLogin extends Login { protected function findAccountByUsername($username) { return new UserAccount(array("Alice", "5af6b73c3...")); } }
  • 141. 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); }
  • 142. 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 }
  • 143. 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);
  • 144. 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); } }
  • 145. Bővebben
  • 146. 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
  • 147. 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
  • 148. Coding kata
  • 149. Coding kata
  • 150. 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)
  • 151. Code Retreat  Február 22. (Legacy CodeRetreat)  http://www.meetup.com/Coderetreat-Budapest/events/166131862/
  • 152. Kérdés? http://www.slideshare.net/athoshun
  • 153. Köszönöm a figyelmet! http://www.slideshare.net/athoshun