Slide 1

Slide 1 text

DEVELOPING BEAUTIFUL, MEASURABLE SOFTWARE Jeremy Mikola jmikola

Slide 2

Slide 2 text

Agenda Training Metrics Tools

Slide 3

Slide 3 text

Training

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

The core concepts behind good design are well understood. Cohesion, loose coupling, no redundancy, encapsulation, testability, readability, and focus. Yet it’s hard to put those concepts into practice. “ — Jeff Bay in Object Calisthenics

Slide 6

Slide 6 text

Object Calisthenics Object-oriented programming

Slide 7

Slide 7 text

Object Calisthenics /ˌkalәsˈTHeniks/ – noun gymnastic exercises to achieve bodily fitness and grace of movement. early 19th century: from Greek kallos (beauty) + sthenos (strength)

Slide 8

Slide 8 text

These are exercises, not hard and fast rules.

Slide 9

Slide 9 text

Only one level of indentation per method

Slide 10

Slide 10 text

Only one level of indentation per method f u n c t i o n m i g r a t e S u p p l i e r s ( \ M o n g o C o l l e c t i o n $ s u p p l i e r C o l l e c t i o n ) { $ t y p e M a p = [ 1 = > ' b i l l i n g A d d r e s s ' , 2 = > ' r e t u r n s A d d r e s s ' , 3 = > ' s h i p p i n g A d d r e s s ' , ] ; f o r e a c h ( $ s u p p l i e r C o l l e c t i o n - > f i n d ( ) - > s n a p s h o t ( ) a s $ s u p p l i e r ) { $ n e w O b j = [ ' $ u n s e t ' = > [ ' a d d r e s s e s ' = > 1 ] ] ; / / $ s e t o t h e r f i e l d s … i f ( i s s e t ( $ s u p p l i e r [ ' a d d r e s s e s ' ] ) ) { f o r e a c h ( $ s u p p l i e r [ ' a d d r e s s e s ' ] a s $ a d d r e s s ) { $ n e w O b j [ ' $ s e t ' ] [ $ t y p e M a p [ $ a d d r e s s [ ' s u b t y p e ' ] ] ] = [ ' n a m e ' = > i s s e t ( $ a d d r e s s [ ' n a m e ' ] ) ? $ a d d r e s s [ ' n a m e ' ] : n u l l , ' a d d r e s s 1 ' = > i s s e t ( $ a d d r e s s [ ' a d d r e s s 1 ' ] ) ? $ a d d r e s s [ ' a d d r e s s 1 ' ] : n u l l , ' a d d r e s s 2 ' = > i s s e t ( $ a d d r e s s [ ' a d d r e s s 2 ' ] ) ? $ a d d r e s s [ ' a d d r e s s 2 ' ] : n u l l , ' c i t y ' = > i s s e t ( $ a d d r e s s [ ' c i t y ' ] ) ? $ a d d r e s s [ ' c i t y ' ] : n u l l , ' s t a t e ' = > i s s e t ( $ a d d r e s s [ ' s t a t e ' ] ) ? $ a d d r e s s [ ' s t a t e ' ] : n u l l , ' z i p ' = > i s s e t ( $ a d d r e s s [ ' p o s t a l C o d e ' ] ) ? $ a d d r e s s [ ' p o s t a l C o d e ' ] : n u l l , ] ; } } $ s u p p l i e r s - > u p d a t e ( [ ' _ i d ' = > $ s u p p l i e r [ ' _ i d ' ] ] , $ n e w O b j ) ; } }

Slide 11

Slide 11 text

Only one level of indentation per method Extract Method You have a code fragment that can be grouped together. Turn the fragment into a method whose name explains the purpose of the method. “ — Martin Fowler in Refactoring

Slide 12

Slide 12 text

Only one level of indentation per method f u n c t i o n c r e a t e S u p p l i e r A d d r e s s S e t ( $ a d d r e s s e s ) { $ t y p e M a p = [ 1 = > ' b i l l i n g A d d r e s s ' , 2 = > ' r e t u r n s A d d r e s s ' , 3 = > ' s h i p p i n g A d d r e s s ' , ] ; $ s e t = [ ] ; f o r e a c h ( $ a d d r e s s e s a s $ a d d r e s s ) { $ s e t [ $ t y p e M a p [ $ a d d r e s s [ ' s u b t y p e ' ] ] ] = [ ' n a m e ' = > i s s e t ( $ a d d r e s s [ ' n a m e ' ] ) ? $ a d d r e s s [ ' n a m e ' ] : n u l l , ' a d d r e s s 1 ' = > i s s e t ( $ a d d r e s s [ ' a d d r e s s 1 ' ] ) ? $ a d d r e s s [ ' a d d r e s s 1 ' ] : n u l l , ' a d d r e s s 2 ' = > i s s e t ( $ a d d r e s s [ ' a d d r e s s 2 ' ] ) ? $ a d d r e s s [ ' a d d r e s s 2 ' ] : n u l l , ' c i t y ' = > i s s e t ( $ a d d r e s s [ ' c i t y ' ] ) ? $ a d d r e s s [ ' c i t y ' ] : n u l l , ' s t a t e ' = > i s s e t ( $ a d d r e s s [ ' s t a t e ' ] ) ? $ a d d r e s s [ ' s t a t e ' ] : n u l l , ' z i p ' = > i s s e t ( $ a d d r e s s [ ' p o s t a l C o d e ' ] ) ? $ a d d r e s s [ ' p o s t a l C o d e ' ] : n u l l , ] ; } r e t u r n $ s e t ; }

Slide 13

Slide 13 text

Only one level of indentation per method f u n c t i o n m i g r a t e S u p p l i e r s ( \ M o n g o C o l l e c t i o n $ s u p p l i e r C o l l e c t i o n ) { f o r e a c h ( $ s u p p l i e r C o l l e c t i o n - > f i n d ( ) - > s n a p s h o t ( ) a s $ s u p p l i e r ) { $ n e w O b j = [ ' $ s e t ' = > [ ] , ' $ u n s e t ' = > [ ' a d d r e s s e s ' = > 1 ] , ] ; / / $ s e t o t h e r f i e l d s … i f ( i s s e t ( $ s u p p l i e r [ ' a d d r e s s e s ' ] ) ) { $ n e w O b j [ ' $ s e t ' ] = a r r a y _ m e r g e ( $ n e w O b j [ ' $ s e t ' ] , c r e a t e S u p p l i e r A d d r e s s S e t ( $ s u p p l i e r [ ' a d d r e s s e s ' ] ) ) ; } $ s u p p l i e r s - > u p d a t e ( [ ' _ i d ' = > $ s u p p l i e r [ ' _ i d ' ] ] , $ n e w O b j ) ; } }

Slide 14

Slide 14 text

Only one level of indentation per method f u n c t i o n m i g r a t e S u p p l i e r s ( \ M o n g o C o l l e c t i o n $ s u p p l i e r C o l l e c t i o n ) { $ c u r s o r = $ s u p p l i e r C o l l e c t i o n - > f i n d ( [ ' $ e x i s t s ' = > [ ' a d d r e s s e s ' = > 1 ] ] ) - > s n a p s h o t ; f o r e a c h ( $ c u r s o r a s $ s u p p l i e r ) { $ n e w O b j = [ ' $ s e t ' = > [ ] , ' $ u n s e t ' = > [ ' a d d r e s s e s ' = > 1 ] , ] ; / / $ s e t o t h e r f i e l d s … $ n e w O b j [ ' $ s e t ' ] = a r r a y _ m e r g e ( $ n e w O b j [ ' $ s e t ' ] , c r e a t e S u p p l i e r A d d r e s s S e t ( $ s u p p l i e r [ ' a d d r e s s e s ' ] ) ) ; $ s u p p l i e r s - > u p d a t e ( [ ' _ i d ' = > $ s u p p l i e r [ ' _ i d ' ] ] , $ n e w O b j ) ; } }

Slide 15

Slide 15 text

Avoid using e l s e

Slide 16

Slide 16 text

Avoid using e l s e p u b l i c f u n c t i o n a d d P r o d u c t ( $ s l u g , R e q u e s t $ r e q u e s t ) { $ p r o d u c t = $ t h i s - > p r o d u c t R e p o s i t o r y - > f i n d O n e B y S l u g ( $ s l u g ) ; i f ( $ p r o d u c t ! = = n u l l ) { $ f o r m = $ t h i s - > f o r m F a c t o r y - > c r e a t e ( ' a d d _ p r o d u c t ' ) ; $ f o r m - > h a n d l e R e q u e s t ( $ r e q u e s t ) ; i f ( $ f o r m - > i s V a l i d ( ) ) { i f ( $ r e q u e s t - > g e t ( ' w i s h l i s t ' , f a l s e ) ) { $ c o n t r o l l e r = ' w i s h l i s t . c o n t r o l l e r : a d d P r o d u c t ' ; } e l s e { $ c o n t r o l l e r = ' c a r t . c o n t r o l l e r : a d d P r o d u c t ' ; } r e t u r n $ t h i s - > f o r w a r d ( $ c o n t r o l l e r , [ ' i d ' = > $ p r o d u c t - > g e t I d ( ) , ] ) ; } e l s e { r e t u r n $ t h i s - > r e n d e r ( ' p r o d u c t / a d d . h t m l . t w i g ' , [ ' f o r m ' = > $ f o r m - > c r e a t e V i e w ( ) , ' p r o d u c t ' = > $ p r o d u c t , ] ) ; } } e l s e { t h r o w n e w N o t F o u n d H t t p E x c e p t i o n ( ) ; } }

Slide 17

Slide 17 text

Avoid using e l s e p u b l i c f u n c t i o n a d d P r o d u c t ( $ s l u g , R e q u e s t $ r e q u e s t ) { $ p r o d u c t = $ t h i s - > p r o d u c t R e p o s i t o r y - > f i n d O n e B y S l u g ( $ s l u g ) ; i f ( $ p r o d u c t = = = n u l l ) { t h r o w n e w N o t F o u n d H t t p E x c e p t i o n ( ) ; } $ f o r m = $ t h i s - > f o r m F a c t o r y - > c r e a t e ( ' a d d _ p r o d u c t ' ) ; $ f o r m - > h a n d l e R e q u e s t ( $ r e q u e s t ) ; i f ( ! $ f o r m - > i s V a l i d ( ) ) { r e t u r n $ t h i s - > r e n d e r ( ' p r o d u c t / a d d . h t m l . t w i g ' , [ ' f o r m ' = > $ f o r m - > c r e a t e V i e w ( ) , ' p r o d u c t ' = > $ p r o d u c t , ] ) ; } $ c o n t r o l l e r = $ r e q u e s t - > g e t ( ' w i s h l i s t ' , f a l s e ) ? ' w i s h l i s t . c o n t r o l l e r : a d d P r o d u c t ' : ' c a r t . c o n t r o l l e r : a d d P r o d u c t ' r e t u r n $ t h i s - > f o r w a r d ( $ c o n t r o l l e r , [ ' i d ' = > $ p r o d u c t - > g e t I d ( ) , ] ) ; }

Slide 18

Slide 18 text

Wrap primitives and strings

Slide 19

Slide 19 text

Wrap primitives and strings But PHP objects are expensive! Avoid (i.e. using primitives to represent domain ideas) primitive obsession Replace with (i.e. immutable entities with some behavior) value objects

Slide 20

Slide 20 text

One dot per line T_OBJECT_OPERATOR

Slide 21

Slide 21 text

One dot per line Law of Demeter (Principle of Least Knowledge) Only talk to friends Don’t circumvent encapsulation and are still OK Method chaining fluent APIs

Slide 22

Slide 22 text

One dot per line $ u s e r = $ t h i s - > c o n t a i n e r - > g e t ( ' s e c u r i t y . c o n t e x t ' ) - > g e t T o k e n ( ) - > g e t U s e r ( ) ; $ i m a g e = $ u s e r - > g e t P r o f i l e ( ) - > g e t A v a t a r ( ) - > g e t U r l ( ) ; vs. $ u s e r = $ t h i s - > g e t U s e r ( ) ; $ i m a g e = $ u s e r - > g e t A v a t a r U r l ( ) ;

Slide 23

Slide 23 text

Don’t Do not abbreviate

Slide 24

Slide 24 text

Do not abbreviate Why do you want to abbreviate? Repeating a name too frequently hints at code duplication. Method names being too long hints at misplaced responsibility.

Slide 25

Slide 25 text

Keep your entities small

Slide 26

Slide 26 text

Keep your entities small <100 LOC per class 10 methods per class 15 classes per package The goal is readability and maintainability

Slide 27

Slide 27 text

Limit classes to five member variables

Slide 28

Slide 28 text

Limit classes to five member variables Decompose objects with many attributes into a hierarchy of collaborating objects Higher cohesion and better encapsulation

Slide 29

Slide 29 text

First class collections

Slide 30

Slide 30 text

First class collections If a class contains a collection, it should not be doing much else. Behaviors for the collection have a home (e.g. combining, filtering, mapping)

Slide 31

Slide 31 text

First class collections Doctrine\Common\Collections\ArrayCollection _ _ c o n s t r u c t ( a r r a y $ e l e m e n t s ) c o u n t ( ) g e t I t e r a t o r ( ) s l i c e ( $ o f f s e t , $ l e n g t h ) m a t c h i n g ( C r i t e r i a $ c r i t e r i a ) e x i s t s ( C l o s u r e $ p ) f i l t e r ( C l o s u r e $ p ) f o r A l l ( C l o s u r e $ p ) m a p ( C l o s u r e $ p ) p a r t i t i o n ( C l o s u r e $ p ) Collection interface extends Countable, IteratorAggregate, ArrayAccess

Slide 32

Slide 32 text

No getters and setters for class properties

Slide 33

Slide 33 text

No getters and setters for class properties Strong encapsulation boundaries force behaviors to be placed in the object model Decisions based on an object’s state should be made within that object

Slide 34

Slide 34 text

No getters and setters for class properties Tell, don’t ask Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. “ — Alec Sharp in Smalltalk by Example

Slide 35

Slide 35 text

Use getters and setters for class properties Dependency injection Post-constructor (e.g. interface injection) Encapsulating transformations Decorator pattern (e.g. Doctrine Proxy objects)

Slide 36

Slide 36 text

No static methods or utility classes

Slide 37

Slide 37 text

No static methods or utility classes Classes without state have no identity, no raison d'être They manipulate data from other objects and frequently violate encapsulation

Slide 38

Slide 38 text

Metrics

Slide 39

Slide 39 text

No content

Slide 40

Slide 40 text

What else can we measure? Source lines of code Code coverage Bug density Cohesion and coupling Cyclomatic complexity Function points Custom design constraints

Slide 41

Slide 41 text

Cyclomatic Complexity

Slide 42

Slide 42 text

Cyclomatic Complexity Number of decision points in a function (e.g. i f , w h i l e , f o r , c a s e ) General indication of program complexity Spoiler alert: positive correlation with defects

Slide 43

Slide 43 text

Cyclomatic Complexity p u b l i c f u n c t i o n f o o ( $ a , $ b ) { i f ( $ a ) { i f ( $ b ) { e c h o ' a a n d b ' ; } e l s e { e c h o ' a ' ; } } e l s e i f ( $ b ) { e c h o ' b ' ; } e l s e { e c h o ' n e i t h e r ' ; } } One entry point and three i f conditions: CC = 4

Slide 44

Slide 44 text

Cyclomatic Complexity CC Complexity 1–4 Low 5–7 Moderate 8–10 High 11+ Very high

Slide 45

Slide 45 text

NPath Complexity

Slide 46

Slide 46 text

NPath Complexity Number of unique execution paths in a function Alternatively, the number of tests required for full branch coverage

Slide 47

Slide 47 text

NPath Complexity p u b l i c f u n c t i o n f o o ( $ a , $ b ) { i f ( $ a ) { i f ( $ b ) { e c h o ' a a n d b ' ; } e l s e { e c h o ' a ' ; } } e l s e i f ( $ b ) { e c h o ' b ' ; } e l s e { e c h o ' n e i t h e r ' ; } } CC = 4 and NPath = 4

Slide 48

Slide 48 text

NPath Complexity p u b l i c f u n c t i o n f o o ( $ a , $ b , $ c ) { i f ( $ a ) { i f ( $ b ) { e c h o ' a a n d b ' ; } e l s e { e c h o ' a ' ; } } e l s e i f ( $ b ) { e c h o ' b ' ; } e l s e { e c h o ' n e i t h e r ' ; } i f ( $ c ) { e c h o ' c ' ; } } CC += 1 and NPath ×= 2

Slide 49

Slide 49 text

NPath Complexity p u b l i c f u n c t i o n f o o ( $ a , $ b , $ c , $ d ) { i f ( $ a ) { e c h o ' a ' ; } i f ( $ b ) { e c h o ' b ' ; } i f ( $ c ) { e c h o ' c ' ; } i f ( $ d ) { e c h o ' d ' ; } } CC = 5 and NPath = 16! Worst case: NPath = 2(CC - 1)

Slide 50

Slide 50 text

Tools

Slide 51

Slide 51 text

PHP Quality Assurance Toolchain by Sebastian Bergmann PHPUnit PHPLOC PHP Copy/Paste Detector PHP Dead Code Detector Behat PHP_Depend PHP Mess Detector PHP_CodeSniffer

Slide 52

Slide 52 text

by Sebastian Bergmann PHPLOC Measures and analyzes a project LOC stats per class, method Cyclomatic complexity Dependencies on methods, attributes Code structure

Slide 53

Slide 53 text

by Greg Sherwood PHP_CodeSniffer $ p h p c s / p a t h / t o / c o d e / F I L E : / p a t h / t o / c o d e / e x a m p l e . p h p - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - F O U N D 4 E R R O R ( S ) A F F E C T I N G 4 L I N E ( S ) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 | E R R O R | M i s s i n g f i l e d o c c o m m e n t 4 7 | E R R O R | L i n e n o t i n d e n t e d c o r r e c t l y ; e x p e c t e d 4 s p a c e s b u t f o u n d 1 5 1 | E R R O R | M i s s i n g f u n c t i o n d o c c o m m e n t 8 8 | E R R O R | L i n e n o t i n d e n t e d c o r r e c t l y ; e x p e c t e d 9 s p a c e s b u t f o u n d 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Profiles for PSR-1 and PSR-2 included Works well with PHP Coding Standards Fixer

Slide 54

Slide 54 text

by Manuel Pichler PHP_Depend Code analysis and metrics NPath and cyclomatic complexity Weighted method count Coupling analysis Code rank Dozens of additional metrics

Slide 55

Slide 55 text

by Manuel Pichler PHP_Depend Abstraction Instability Chart

Slide 56

Slide 56 text

by Manuel Pichler PHP Mess Detector Frontend for PHP_Depend Code size, complexity Design (syntax, classes) Unused code Naming conventions

Slide 57

Slide 57 text

by Manuel Pichler PHP Mess Detector

Slide 58

Slide 58 text

by Johannes Schmitt PHP Analyzer Code analysis and fixing Type inference Suspicious, unused code Deprecated functionality Coupling and cohesion Framework-specific checks

Slide 59

Slide 59 text

Continuous Integration

Slide 60

Slide 60 text

Continuous Integration is practice where members of a team integrate their work frequently. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. “ — Martin Fowler in Continuous Integration

Slide 61

Slide 61 text

Continuous Integration by Sebastian Bergmann jenkins-php.org

Slide 62

Slide 62 text

Continuous Integration Travis CI Scrutinizer CI

Slide 63

Slide 63 text

by Johannes Schmitt Scrutinizer CI Automated code analysis Code rating Tracking changes/stability External code coverage Automated fixes Refactoring tips

Slide 64

Slide 64 text

by Johannes Schmitt Scrutinizer CI

Slide 65

Slide 65 text

A Final Thought

Slide 66

Slide 66 text

There are only two types of code, code that delivers business value, and code that doesn’t. The cleanest code that doesn’t deliver value is still crap. “ — Anthony Ferrara in Beyond Clean Code

Slide 67

Slide 67 text

Beyond Clean Code Good Business Value Poor Business Value Clean Code Excellent Bad Dirty Code Good Garbage

Slide 68

Slide 68 text

THANKS! by Rafael Dohms by Guilherme Blanco by William Durand by Anthony Ferrara Your Code Sucks, Let’s Fix It! Object Calisthenics Applied to PHP Object Calisthenics Development by the Numbers

Slide 69

Slide 69 text

Image Credits by http://xkcd.com/844/ http://xkcd.com/292/ http://imagery.pragprog.com/products/105/twa.jpg http://icons8.com http://www.visualpharm.com/