0
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 underst...
Clean Code


”Any fool can write code that a computer can
understand. Good programmers write code that
humans can underst...
Clean Code


”Any fool can write code that a computer can
understand. Good programmers write code that
humans can underst...
Clean Code


Names, Comments, Structure, Object Oriented
Programming, Functional Programming, Don't Repeat
Yourself, Sing...
#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ü...
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%...
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%...
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 /= ...
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))
pri...
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);
}
...
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...
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...
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...
Optimalizáció != obfuszkáció
int n;
for (n = 2; n <= 5000; ++n) {
if (is_palindromic_in_octal_base(n) && is_prime(n))
prin...
Optimalizáció != obfuszkáció
int n;
for (n = 2; n <= 5000; ++n) {
if (is_palindromic_in_octal_base(n) && is_prime(n))
prin...
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 /= ...
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 /= ...
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...
Kommentek

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

/**
* Registers new argument transformer.
*
* @param ArgumentTransformer $transformer
*/
public function regist...
Kommentek

/**
* Registers new argument transformer.
*
* @param ArgumentTransformer $transformer
*/
public function regist...
Kommentek

/**
* Registers new argument transformer.
*
* @param ArgumentTransformer $transformer
*/
public function regist...
Kommentek

/**
* Registers new argument transformer.
*
* @param ArgumentTransformer $transformer
*/
public function regist...
Kommentek

This method registers an
argument transformer!
/**
* Registers new argument transformer.
*
* @param ArgumentTra...
Kommentek
def store_puppy(self):
# self.puppy_source is already opened
# by some_unrelated_fucntion()
puppy = self.puppy_s...
Kommentek
def store_puppy(self):
# self.puppy_source is already opened
# by some_unrelated_fucntion()
puppy = self.puppy_s...
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ű elronta...
Temporal coupling
def store_puppy(self):
self.puppy_source.open()
puppy = self.puppy_source.read()
self.storage.store(pupp...
Temporal coupling
def store_puppy(self):
self.puppy_source.open()
try:
puppy = self.puppy_source.read()
self.storage.store...
Temporal coupling
def store_puppy(self):
with self.puppy_source:
puppy = self.puppy_source.read()
self.storage.store(puppy...
Temporal coupling


Általános megoldás?
Temporal coupling
interface PuppySourceCommand {
public function run(PuppySource $ps);
}
public function withPuppySource(P...
Temporal coupling
interface PuppySourceCommand {
public function run(PuppySource $ps);
}
public function withPuppySource(P...
Temporal coupling
public function withPuppySource(callable $command)
{
$this->puppy_source->open();
try {
$command($this->...
Nevek


Cél/szándék vs. implementáció



Névterek, osztályok: általában főnevek



Változók: főnevek, predikátumok



...
Nevek
function main()
{
tag_file="$1"
source_file="$2"
if can_be_updated "$tag_file"
then
update_tag_file "$tag_file" "$so...
Nevek


Kimondható nevek!



Tömörség != rövidség




strpbrk(), strverscmp()

Név hossza vs. láthatóság:


Függény, ...
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

Abstrac...
Smurf naming convention


Hungarian notation:






MessageBox(hwnd, szMsg, "Hello", MB_OK);
$this­>m_session

Abstrac...
Smurf naming convention


Hungarian notation:






MessageBox(hwnd, szMsg, "Hello", MB_OK);
$this­>m_session

Abstrac...
Smurf naming convention


Hungarian notation:






MessageBox(hwnd, szMsg, "Hello", MB_OK);
$this­>m_session

Abstrac...
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.syst...
Meglepetések


Command-query separation: vagy változtass
állapotot, vagy adj vissza értéket, de a kettőt
egyszerre ne csi...
Meglepetések




Command-query separation: vagy változtass
állapotot, vagy adj vissza értéket, de a kettőt
egyszerre ne ...
Method chaining, fluent interfaces
customer.newOrder()
.with(6, "TAL")
.with(5, "HPK").skippable()
.with(3, "LGV")
.priori...
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




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

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

while (!feof(STDIN)) {
print preg_replace(
"/(d+)/",
"[1]",
fgets(ST...
SRP


Hány különböző absztrakció jelenik meg
benne?
while (!feof(STDIN)) {
print preg_replace(
"/(d+)/",
"[1]",
fgets(STD...
SRP


Hány különböző absztrakció jelenik meg
benne?
function highlightNumbers($text)
{
return preg_replace(
"/(d+)/",
"[1...
OCP


Open for extension, Closed for modification
class NumberHighlighterApplication {
public function run() {
while (!fe...
OCP


Open for extension, Closed for modification



Új feature → új kód!
class NumberHighlighterApplication {
public fu...
OCP


Open for extension, Closed for modification



FR: tetszőleges file-t is kezeljen!
class NumberHighlighterApplicat...
OCP


Open for extension, Closed for modification
class NumberHighlighterApplication {
public function run($stream) {
whi...
OCP


Open for extension, Closed for modification
class NumberHighlighterApplication {
public function run($stream) {
whi...
OCP


Open for extension, Closed for modification
class NumberHighlighterApplication {
public function run($stream) {
whi...
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 NumberHighlighterAp...
LSP


S osztály a T osztályból származik → T példányai
legyenek helyettesíthetők S példányaival
class NumberHighlighterAp...
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 { /* ... ...
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 {
pri...
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 ...
DIP


Dependency Inversion Principle
class NumberHighlighterApplication
{
public function run($stream)
{
while (!feof($st...
DIP


FR: EBCDIC file-t is tudjon olvasni!
class NumberHighlighterApplication
{
public function run($stream)
{
while (!fe...
DIP


Abstractions should never depend on concretions.
class NumberHighlighterApplication
{
public function run($stream)
...
DIP


Abstractions should never depend on concretions.
Business logic

NumberHighlighterApplication

StandardInput
DIP


Abstractions should never depend on concretions.
Business logic

NumberHighlighterApplication

IOStream

StandardIn...
DIP


Abstractions should never depend on concretions.
interface IOStream {
public function open();
public function eof()...
DIP


Pl: Dependency Injection
interface IOStream {
public function open();
public function eof();
public function readLi...
DIP


NEM a DI containert injektáljuk az osztályba!
interface IOStream {
public function open();
public function eof();
p...
ISP


FR: JSON-ból olvasson, MySQL-be írjon!
interface IOStream {
public function open();
public function eof();
public f...
ISP


Ne függj olyan olyasmitől, amit nem használsz!
interface IOStream {
public function open();
public function eof();
...
ISP


Ne függj olyan olyasmitől, amit nem használsz!
interface Input {
public function hasMore();
public function read();...
Mit adtak nekünk a SOLID elvek?
CLI, getopt, etc.

Web framework

Thin integration

Thin integration

Desktop GUI framewor...
Mit adtak nekünk a SOLID elvek?
CLI, getopt, etc.

Web framework

GUI
Thin integration

Thin integration

tesztek

Desktop...
Tesztek
interface Input
interface Output
{
{
public function hasMore();
public function write($text);
public function read...
Test double
class FakeInput implements Input
{
private $lines;
private $next_line_index;
public function __construct(array...
Test double
class FakeOutput implements Output
{
private $lines;
public function __construct()
{
$this->lines = array();
}...
Test double

private function highlight(array $input_lines)
{
$input = new FakeInput($input_lines);
$output = new FakeOutp...
Test double

private function assertHighlihtedLines(array $input, array $expected)
{
$this->assertEquals($expected, $this-...
Test double
private function assertHighlightedLine($input, $expected)
{
$this->assertHighlightedLines(array($input), array...
Unit teszt
function testWhenThereIsNoNumberInALineThenItIsUnchanged()
{
$this->assertHighlihtedLine("No numbers", "No numb...
(majdnem) Unit teszt
function testWhenThereIsNoNumberInALineThenItIsUnchanged()
{
$this->assertHighlihtedLine("No numbers"...
Tesztek


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

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

function testWhenThereIsNoNumberInALineThenItIsUnchanged() {
$input = new FakeIn...
Tesztek


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

function testWhenThereIsNoNumberInALineThenItIsUnchanged()
{
$this->assertHighli...
Tesztek


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

private function highlight(array $input_lines)
{
$input = new FakeInput($input_l...
Tesztek


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

$input = $this->getMock("Input");
$input->expects($this->exactly(2))
->method("h...
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!



St...
Tesztek
testLoginValid
testLoginInvalid
Tesztek
testLoginValid
testLoginInvalid
testEmptyUsernameTriggersError
testWrongUsernameTriggersError
testEmptyPasswordTri...
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?



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



...
TDD


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

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

public function testHighlight()
{
$input = new FakeI...
Tesztek


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

function testWhenThereIsNoNumberInALineThenItIsUncha...
Tesztek


A tesztek design problémákra figyelmeztetnek

class Login {
// ...
public function perform($username, $password...
Tesztek


A tesztek design problémákra figyelmeztetnek

class Login {
// ...
public function perform($username, $password...
Tesztek


A tesztek design problémákra figyelmeztetnek

class TestableLogin extends Login
{
protected function findAccoun...
Tesztek


A tesztek design problémákra figyelmeztetnek

class Login {
// ...
public function perform($username, $password...
Tesztek


A tesztek design problémákra figyelmeztetnek

class Login {
// ...
public function perform($username, $password...
Tesztek


A tesztek design problémákra figyelmeztetnek

interface UserAccountRepository {
public function findByUsername(...
Tesztek


A tesztek design problémákra figyelmeztetnek

interface UserAccountRepository {
public function findByUsername(...
Bővebben
Bővebben








http://cleancoders.com
Robert C. Martin: Architecture the Lost Years (1:07)
http://www.youtube.com/wa...
Bővebben



















http://blog.rocketpoweredjetpants.com/2014/01/a-ranty-and-dogmatic-trollmasquera...
Coding kata
Coding kata
Coding kata


FizzBuzz



Prime factors



Bowling game



Római számok → arab számok



WordWrap



Conway's Game o...
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
Clean code that works
Upcoming SlideShare
Loading in...5
×

Clean code that works

775

Published on

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

Published in: Technology
3 Comments
3 Likes
Statistics
Notes
No Downloads
Views
Total Views
775
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
12
Comments
3
Likes
3
Embeds 0
No embeds

No notes for slide

Transcript of "Clean code that works"

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

    Clipping is a handy way to collect important slides you want to go back to later.

×