high level principles, micro-patterns and anti-patterns
DESCRIPTION
The presentation from VMWare Bulgaria, that was done during their guest lecture for the "Programming 101" course in Hack Bulgaria. The presentation was done by Dimitar DimitrovTRANSCRIPT
© 2014 VMware Inc. All rights reserved
High-level principles, Micro-patterns and anti-patterns
Dimitar Dimitrov Software engineer
Лекцията накратко
2
Лекцията накратко
! High-level principles • Добри практики на по-високо ниво (от обичайните design patterns)
2
Лекцията накратко
! High-level principles • Добри практики на по-високо ниво (от обичайните design patterns)
! Micro-patterns and code idioms • Добри практики на гранулярно ниво
2
Лекцията накратко
! High-level principles • Добри практики на по-високо ниво (от обичайните design patterns)
! Micro-patterns and code idioms • Добри практики на гранулярно ниво
! Anti-patterns • Често срещани лоши практики или конструкции, за които съществува алтернативен подход
2
Лекцията накратко
! High-level principles • Добри практики на по-високо ниво (от обичайните design patterns)
! Micro-patterns and code idioms • Добри практики на гранулярно ниво
! Anti-patterns • Често срещани лоши практики или конструкции, за които съществува алтернативен подход
! Examples of design patterns • Примери от Java 7 SE API !
2
Лекцията накратко
! High-level principles • Добри практики на по-високо ниво (от обичайните design patterns)
! Micro-patterns and code idioms • Добри практики на гранулярно ниво
! Anti-patterns • Често срещани лоши практики или конструкции, за които съществува алтернативен подход
! Examples of design patterns • Примери от Java 7 SE API !
! Disclaimer: Безогледна смес на български и английски ahead. Приемете на доверие, че това спомага за краткостта и яснотата на изложението. Авторът приема критики за грамотността си и преводаческите си способности.
2
High-level principles
3
High-level principles
! Като цяло не са обвързани с конкретен език или платформа.
3
High-level principles
! Като цяло не са обвързани с конкретен език или платформа.
! Могат да бъдат както implementation-centric, така и practice-centric.
3
High-level principles
! Като цяло не са обвързани с конкретен език или платформа.
! Могат да бъдат както implementation-centric, така и practice-centric.
! Design pattern-ите, които имат сходна структура и/или употреба, обикновено изхождат от един и същ implementation-centric принцип.
3
High-level principles
! Като цяло не са обвързани с конкретен език или платформа.
! Могат да бъдат както implementation-centric, така и practice-centric.
! Design pattern-ите, които имат сходна структура и/или употреба, обикновено изхождат от един и същ implementation-centric принцип.
! Добрите практики, свързани с процеса на проектиране, изграждане и поддържане на софтуер, обикновено изхождат от practice-centric принципи. • The Pragmatic Programmer, ISBN 978-0-2016-1622-4
3
High-level principles
! Като цяло не са обвързани с конкретен език или платформа.
! Могат да бъдат както implementation-centric, така и practice-centric.
! Design pattern-ите, които имат сходна структура и/или употреба, обикновено изхождат от един и същ implementation-centric принцип.
! Добрите практики, свързани с процеса на проектиране, изграждане и поддържане на софтуер, обикновено изхождат от practice-centric принципи. • The Pragmatic Programmer, ISBN 978-0-2016-1622-4
! Disclaimer: Термините implementation-centric и practice-centric са измислени от автора и имат определени недостатъци.
3
Implementation-centric principles
4
Implementation-centric principles
! Четирите основни принципа на ООП • Abstraction
• Encapsulation
• Inheritance
• Polymorphism
4
Implementation-centric principles
! Четирите основни принципа на ООП • Abstraction
• Encapsulation
• Inheritance
• Polymorphism
! Separation of concerns • Свързан с encapsulation, loose coupling и strong cohesion
4
Implementation-centric principles
! Четирите основни принципа на ООП • Abstraction
• Encapsulation
• Inheritance
• Polymorphism
! Separation of concerns • Свързан с encapsulation, loose coupling и strong cohesion
! Single responsibility principle
4
Implementation-centric principles
! Четирите основни принципа на ООП • Abstraction
• Encapsulation
• Inheritance
• Polymorphism
! Separation of concerns • Свързан с encapsulation, loose coupling и strong cohesion
! Single responsibility principle! DRY principle
• Свързан със single responsibility principle и maintainability
4
Implementation-centric principles
! Четирите основни принципа на ООП • Abstraction
• Encapsulation
• Inheritance
• Polymorphism
! Separation of concerns • Свързан с encapsulation, loose coupling и strong cohesion
! Single responsibility principle! DRY principle
• Свързан със single responsibility principle и maintainability
! Design by contract • Pre-conditions, post-conditions, invariants
• Особено важен при проектирането и създаването на API
4
Implementation-centric principles (2)
5
Implementation-centric principles (2)
! Open-closed principle • Компонентите трябва да са отворени за разширяване
• Но затворени за модификация
5
Implementation-centric principles (2)
! Open-closed principle • Компонентите трябва да са отворени за разширяване
• Но затворени за модификация
! Liskov substitution principle • Обект от даден тип трябва да може да се използва прозрачно навсякъде, където се очаква обект от базов на дадения тип (µs и ns)
5
Implementation-centric principles (2)
! Open-closed principle • Компонентите трябва да са отворени за разширяване
• Но затворени за модификация
! Liskov substitution principle • Обект от даден тип трябва да може да се използва прозрачно навсякъде, където се очаква обект от базов на дадения тип (µs и ns)
! Law of Demeter • “Не говори със съседа на съседа си”
• Ограничава методи на кои обекти могат да бъдат изпълнявани директно
5
Implementation-centric principles (2)
! Open-closed principle • Компонентите трябва да са отворени за разширяване
• Но затворени за модификация
! Liskov substitution principle • Обект от даден тип трябва да може да се използва прозрачно навсякъде, където се очаква обект от базов на дадения тип (µs и ns)
! Law of Demeter • “Не говори със съседа на съседа си”
• Ограничава методи на кои обекти могат да бъдат изпълнявани директно
! Inversion of control + Dependency injection • Абстракцията не трябва да зависи от детайлите, детайлите трябва да зависят от абстракцията
• Предпочитане на интерфейси пред абстрактни класове
5
Implementation-centric principles (3)
6
Implementation-centric principles (3)
! Interface segregation principle • Абстракции и зависимости трябва да бъдат ограничавани до възможно най-малък интерфейс • Ползвайте маркер интерфейси и интерфейси с един метод
6
Implementation-centric principles (3)
! Interface segregation principle • Абстракции и зависимости трябва да бъдат ограничавани до възможно най-малък интерфейс • Ползвайте маркер интерфейси и интерфейси с един метод
! Acyclic dependency principle • Структурата на зависимостите между компонентите не трябва да съдържа циклични зависимости
6
Implementation-centric principles (3)
! Interface segregation principle • Абстракции и зависимости трябва да бъдат ограничавани до възможно най-малък интерфейс • Ползвайте маркер интерфейси и интерфейси с един метод
! Acyclic dependency principle • Структурата на зависимостите между компонентите не трябва да съдържа циклични зависимости
! Principle of least astonishment • Операциите трябва да имат ясен и консистентен резултат, разбираем от името, документацията и очакваното приложение на метода или класа • Много важен, когато имплементирате API
6
Implementation-centric principles (3)
! Interface segregation principle • Абстракции и зависимости трябва да бъдат ограничавани до възможно най-малък интерфейс • Ползвайте маркер интерфейси и интерфейси с един метод
! Acyclic dependency principle • Структурата на зависимостите между компонентите не трябва да съдържа циклични зависимости
! Principle of least astonishment • Операциите трябва да имат ясен и консистентен резултат, разбираем от името, документацията и очакваното приложение на метода или класа • Много важен, когато имплементирате API
! Principle of least resource usage • Използвайте минимално количество ресурси
• Оптимизирайте само на базата на резултати от тестове, за да избегнете premature optimization
6
Practice-centric principles
7
Practice-centric principles
! Яснота и простота преди всичко • Модулите, класовете, методите и др. трябва да бъдат възможно най-малки, но не по-малки (Еinstein reference)
• Пазете се от семантично замърсяване или “излишен шум” в методите и класовете си (припомнете си какво са cross-cutting concerns)
7
Practice-centric principles
! Яснота и простота преди всичко • Модулите, класовете, методите и др. трябва да бъдат възможно най-малки, но не по-малки (Еinstein reference)
• Пазете се от семантично замърсяване или “излишен шум” в методите и класовете си (припомнете си какво са cross-cutting concerns)
! Принцип на Парето • 80% от ефектите се причиняват от 20% от факторите
• Научете се да идентифицирате важните 20%
7
Practice-centric principles
! Яснота и простота преди всичко • Модулите, класовете, методите и др. трябва да бъдат възможно най-малки, но не по-малки (Еinstein reference)
• Пазете се от семантично замърсяване или “излишен шум” в методите и класовете си (припомнете си какво са cross-cutting concerns)
! Принцип на Парето • 80% от ефектите се причиняват от 20% от факторите
• Научете се да идентифицирате важните 20%
! Спазвайте създадените правила и конвенции • Консистентността води до четимост и разбираемост
• Не нарушавайте конвенциите си заради малки оптимизации (защо?)
7
Practice-centric principles
! Яснота и простота преди всичко • Модулите, класовете, методите и др. трябва да бъдат възможно най-малки, но не по-малки (Еinstein reference)
• Пазете се от семантично замърсяване или “излишен шум” в методите и класовете си (припомнете си какво са cross-cutting concerns)
! Принцип на Парето • 80% от ефектите се причиняват от 20% от факторите
• Научете се да идентифицирате важните 20%
! Спазвайте създадените правила и конвенции • Консистентността води до четимост и разбираемост
• Не нарушавайте конвенциите си заради малки оптимизации (защо?)
! Възползвайте се максимално от наличните инструменти • Отделете време, за да оптимизирате максимално тези 20% от дейностите си, които изпълнявате през 80% от времетo си
7
Practice-centric principles (2)
8
Practice-centric principles (2)
! Хващайте грешки, колкото се може по-рано • Свързано не само с имплементацията, но и с целия процес на разработка
(Quality Engineering) • Важно изискване: Признавайте грешките си
8
Practice-centric principles (2)
! Хващайте грешки, колкото се може по-рано • Свързано не само с имплементацията, но и с целия процес на разработка
(Quality Engineering) • Важно изискване: Признавайте грешките си
! Винаги планирайте и изпълнявайте, имайки предвид контекста на вашия проект • Пример: И производителността, и user experience-а са важни, но ако се наложи, с кое от двете бихте направили компромис?
8
Practice-centric principles (2)
! Хващайте грешки, колкото се може по-рано • Свързано не само с имплементацията, но и с целия процес на разработка
(Quality Engineering) • Важно изискване: Признавайте грешките си
! Винаги планирайте и изпълнявайте, имайки предвид контекста на вашия проект • Пример: И производителността, и user experience-а са важни, но ако се наложи, с кое от двете бихте направили компромис?
! Познавайте спецификите на проблемната област • Познавайте лошите практики и техническите предизвикателства
• Идентифицирайте коя платформа ви върши най-добра работа, използвайте идиомите на езика и проблемната област
8
Practice-centric principles (2)
! Хващайте грешки, колкото се може по-рано • Свързано не само с имплементацията, но и с целия процес на разработка
(Quality Engineering) • Важно изискване: Признавайте грешките си
! Винаги планирайте и изпълнявайте, имайки предвид контекста на вашия проект • Пример: И производителността, и user experience-а са важни, но ако се наложи, с кое от двете бихте направили компромис?
! Познавайте спецификите на проблемната област • Познавайте лошите практики и техническите предизвикателства
• Идентифицирайте коя платформа ви върши най-добра работа, използвайте идиомите на езика и проблемната област
! Нито един принцип не е абсолютен • Не следвайте сляпо принципи, които не ви носят добавена стойност
8
Micro-patterns and code idioms
9
Micro-patterns and code idioms
! Като цяло обхващат добри практики със scope не повече от тялото на един метод. • Понякога разграничаването между pattern и micro-pattern е нетривиално
(Lazy loading)
9
Micro-patterns and code idioms
! Като цяло обхващат добри практики със scope не повече от тялото на един метод. • Понякога разграничаването между pattern и micro-pattern е нетривиално
(Lazy loading)
! Често са обвързани с платформата или езика, за които се прилагат. • Под code idiom често се разбира специфична за езика конструкция
(idiomatic code)
9
Micro-patterns and code idioms
! Като цяло обхващат добри практики със scope не повече от тялото на един метод. • Понякога разграничаването между pattern и micro-pattern е нетривиално
(Lazy loading)
! Често са обвързани с платформата или езика, за които се прилагат. • Под code idiom често се разбира специфична за езика конструкция
(idiomatic code)
! За разлика от design principles, code idioms по-често биват неправилно използвани. • Най-често всичко започва с доброто намерение на програмиста
• Понякога обаче това нарушава “Яснота и простота преди всичко”, “Спазвайте създадените правила и конвенции” или “Не следвайте сляпо принципи, които не ви носят добавена стойност”
9
Примери - micro-patterns
10
Примери - micro-patterns
! Създаване и използване на immutable обекти • Изключително полезно при multithreaded приложения, и не само там
• Какво недостатъци може да има?
10
Примери - micro-patterns
! Създаване и използване на immutable обекти • Изключително полезно при multithreaded приложения, и не само там
• Какво недостатъци може да има?
! Ограничаване на scope-a на локалните променливи • Ограничава възможността да злоупотребим, подпомага garbage collect-ването
• А ако пишем на JavaScript (как се определя scope-a там)?
10
Примери - micro-patterns
! Създаване и използване на immutable обекти • Изключително полезно при multithreaded приложения, и не само там
• Какво недостатъци може да има?
! Ограничаване на scope-a на локалните променливи • Ограничава възможността да злоупотребим, подпомага garbage collect-ването
• А ако пишем на JavaScript (как се определя scope-a там)?
! Lazy loading • Може да подобри бързината и заеманата памет на приложението
• Рядко подобрението на бързината е фактор (виж http://goo.gl/5L4fi)
10
Примери - micro-patterns
! Създаване и използване на immutable обекти • Изключително полезно при multithreaded приложения, и не само там
• Какво недостатъци може да има?
! Ограничаване на scope-a на локалните променливи • Ограничава възможността да злоупотребим, подпомага garbage collect-ването
• А ако пишем на JavaScript (как се определя scope-a там)?
! Lazy loading • Може да подобри бързината и заеманата памет на приложението
• Рядко подобрението на бързината е фактор (виж http://goo.gl/5L4fi)
! Defensive copying • Може негативно да повлияе на бързината и заеманата памет, ако не се изисква
• Все пак по-добре да загубите производителност, отколкото да нарушите encapsulation-а
10
Примери - micro-patterns
! Създаване и използване на immutable обекти • Изключително полезно при multithreaded приложения, и не само там
• Какво недостатъци може да има?
! Ограничаване на scope-a на локалните променливи • Ограничава възможността да злоупотребим, подпомага garbage collect-ването
• А ако пишем на JavaScript (как се определя scope-a там)?
! Lazy loading • Може да подобри бързината и заеманата памет на приложението
• Рядко подобрението на бързината е фактор (виж http://goo.gl/5L4fi)
! Defensive copying • Може негативно да повлияе на бързината и заеманата памет, ако не се изисква
• Все пак по-добре да загубите производителност, отколкото да нарушите encapsulation-а
! Добавяне на static final модификатори • Ползвайте го заради семантичния ефект, а не като performance optimization
• По подразбиране методите са виртуални в Java и невиртуални в C#
10
Примери - micro-patterns (2)
11
Примери - micro-patterns (2)
! Short-circuit булеви оператори • mostLikelyFalse() && mostLikelyTrue()• alwaysTrue() || alwaysThrowsException()
11
Примери - micro-patterns (2)
! Short-circuit булеви оператори • mostLikelyFalse() && mostLikelyTrue()• alwaysTrue() || alwaysThrowsException()
! Избягване на null референции • CONST.equals(nullRef) и return itemArray != null ? itemArray : [];
• Може да бъде ненужно или нежелано • ако прикрива по-късен NullPointerException по време на изпълнение
• ако между null и [] има семантична разлика
11
Примери - micro-patterns (2)
! Short-circuit булеви оператори • mostLikelyFalse() && mostLikelyTrue()• alwaysTrue() || alwaysThrowsException()
! Избягване на null референции • CONST.equals(nullRef) и return itemArray != null ? itemArray : [];
• Може да бъде ненужно или нежелано • ако прикрива по-късен NullPointerException по време на изпълнение
• ако между null и [] има семантична разлика
! Достъпване и промяна на полета през методи • Почти винаги трябва да се използва
• Какво става, ако за класа Person, трябва да връщате не this.age, a this.age - 10, ако FEMALE.equals(this.sex)?
• Изключение правят чистите data обекти с ограничена (непублична) видимост
11
Anti-patterns
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
• code duplication
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
• code duplication
• прекалено дълги методи
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
• code duplication
• прекалено дълги методи
• и разбира се, прекалено дълги класове
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
• code duplication
• прекалено дълги методи
• и разбира се, прекалено дълги класове
• код с няколко нива на conditional nesting
12
Anti-patterns
! Често срещани шаблони с идентифицирани недостатъци, за които съществува алтернативен подход.
! Могат да бъдат:• Контрапродуктивни навици
• Лоши конструкции, създадени с добри намерения
• Лоши конструкции, появяващи се с течение на разработката
! Code smells• индикация или симптом за съществуването на по-сериозен проблем
• code duplication
• прекалено дълги методи
• и разбира се, прекалено дълги класове
• код с няколко нива на conditional nesting
• методи с прекалено много аргументи
12
Design Patterns и Java 7 SE API
13
Design Patterns и Java 7 SE API
! Singleton • System.console(), Runtime.getRuntime(), Collections.EMPTY_MAP
13
Design Patterns и Java 7 SE API
! Singleton • System.console(), Runtime.getRuntime(), Collections.EMPTY_MAP
! Factory method/Factory • Boolean.valueOf(String s), Class.newInstance() (макар че Class вече е параметризиран тип, типът на създадената инстанция е конкретен)
13
Design Patterns и Java 7 SE API
! Singleton • System.console(), Runtime.getRuntime(), Collections.EMPTY_MAP
! Factory method/Factory • Boolean.valueOf(String s), Class.newInstance() (макар че Class вече е параметризиран тип, типът на създадената инстанция е конкретен)
! Abstract Factory • URL.openConnection(), Arrays.asList(T... a) (винаги връща инстанция на
ArrayList), Collections.unmodifiableList(List<? extends T> c) (винаги връща инстанция на inner class, невидим извън самия JDK)
13
Design Patterns и Java 7 SE API
! Singleton • System.console(), Runtime.getRuntime(), Collections.EMPTY_MAP
! Factory method/Factory • Boolean.valueOf(String s), Class.newInstance() (макар че Class вече е параметризиран тип, типът на създадената инстанция е конкретен)
! Abstract Factory • URL.openConnection(), Arrays.asList(T... a) (винаги връща инстанция на
ArrayList), Collections.unmodifiableList(List<? extends T> c) (винаги връща инстанция на inner class, невидим извън самия JDK)
! Prototype • Object.clone() (клонирането на обекти в Java е доста “дълбока” тема; виж
Effective Java, 2nd Edition, Item 11)
13
Design Patterns и Java 7 SE API
! Singleton • System.console(), Runtime.getRuntime(), Collections.EMPTY_MAP
! Factory method/Factory • Boolean.valueOf(String s), Class.newInstance() (макар че Class вече е параметризиран тип, типът на създадената инстанция е конкретен)
! Abstract Factory • URL.openConnection(), Arrays.asList(T... a) (винаги връща инстанция на
ArrayList), Collections.unmodifiableList(List<? extends T> c) (винаги връща инстанция на inner class, невидим извън самия JDK)
! Prototype • Object.clone() (клонирането на обекти в Java е доста “дълбока” тема; виж
Effective Java, 2nd Edition, Item 11)
! Flyweight • Boolean.valueOf(String s), Collections.emptyMap()
13
Design Patterns и Java 7 SE API (2)
14
Design Patterns и Java 7 SE API (2)
! Adapter • InputStreamReader(InputStream)
14
Design Patterns и Java 7 SE API (2)
! Adapter • InputStreamReader(InputStream)
! Composite • java.awt.Container.add(Component comp) (връща инстанция на Component)
• Collection.addAll(Collection<? extends e> c) is not Composite (добавяната колекция не бива запазена)
14
Design Patterns и Java 7 SE API (2)
! Adapter • InputStreamReader(InputStream)
! Composite • java.awt.Container.add(Component comp) (връща инстанция на Component)
• Collection.addAll(Collection<? extends e> c) is not Composite (добавяната колекция не бива запазена)
! Decorator • Collections.unmodifiableList(List<? extends T> c) (тъй като поведението се променя)
14
Design Patterns и Java 7 SE API (2)
! Adapter • InputStreamReader(InputStream)
! Composite • java.awt.Container.add(Component comp) (връща инстанция на Component)
• Collection.addAll(Collection<? extends e> c) is not Composite (добавяната колекция не бива запазена)
! Decorator • Collections.unmodifiableList(List<? extends T> c) (тъй като поведението се променя)
! Proxy • subtypes of java.lang.reflect.Proxy
• създавани чрез статичния factory method newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
14
Ресурси
15
Ресурси
!• http://c2.com/cgi/wiki?OoDesignPrinciples • http://www.slideshare.net/clintedmonson/advanced-
oop-laws-principles-idioms-presentation • http://www.oodesign.com/design-principles.html • http://sourcemaking.com/antipatterns • http://stackoverflow.com/questions/1673841/examples-
of-gof-design-patterns/ • http://msdn.microsoft.com/en-us/magazine/
cc188707.aspx (старо, но все още валидно) • Effective Java, 2nd Edition, ISBN 978-0-3213-5668-0 • The Pragmatic Programmer, ISBN 978-0-2016-1622-4
15