clean code that works
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