Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing Beautiful, Measurable Software

Developing Beautiful, Measurable Software

Presented November 28, 2014 at SymfonyCon Madrid: https://joind.in/talk/view/12953

Presented October 9, 2014 at Symfony Live NYC: https://joind.in/talk/view/12195

Presented September 13, 2014 at Madison PHP: https://joind.in/talk/view/11738

Presented August 19, 2014 at ShorePHP: https://joind.in/talk/view/11659

Presented April 25, 2014 at Lone Star PHP: http://joind.in/talk/view/10808

Reveal.js presentation published at: http://jmikola.github.io/slides/beautiful_measurable_software/

Jeremy Mikola

November 28, 2014
Tweet

More Decks by Jeremy Mikola

Other Decks in Programming

Transcript

  1. 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
  2. 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)
  3. 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 ) ; } }
  4. 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
  5. 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 ; }
  6. 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 ) ; } }
  7. 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 ) ; } }
  8. 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 ( ) ; } }
  9. 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 ( ) , ] ) ; }
  10. 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
  11. 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
  12. 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 ( ) ;
  13. 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.
  14. Keep your entities small <100 LOC per class 10 methods

    per class 15 classes per package The goal is readability and maintainability
  15. Limit classes to five member variables Decompose objects with many

    attributes into a hierarchy of collaborating objects Higher cohesion and better encapsulation
  16. 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)
  17. 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
  18. 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
  19. 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
  20. Use getters and setters for class properties Dependency injection Post-constructor

    (e.g. interface injection) Encapsulating transformations Decorator pattern (e.g. Doctrine Proxy objects)
  21. 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
  22. What else can we measure? Source lines of code Code

    coverage Bug density Cohesion and coupling Cyclomatic complexity Function points Custom design constraints
  23. 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
  24. 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
  25. NPath Complexity Number of unique execution paths in a function

    Alternatively, the number of tests required for full branch coverage
  26. 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
  27. 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
  28. 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)
  29. 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
  30. by Sebastian Bergmann PHPLOC Measures and analyzes a project LOC

    stats per class, method Cyclomatic complexity Dependencies on methods, attributes Code structure
  31. 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
  32. by Manuel Pichler PHP_Depend Code analysis and metrics NPath and

    cyclomatic complexity Weighted method count Coupling analysis Code rank Dozens of additional metrics
  33. by Manuel Pichler PHP Mess Detector Frontend for PHP_Depend Code

    size, complexity Design (syntax, classes) Unused code Naming conventions
  34. by Johannes Schmitt PHP Analyzer Code analysis and fixing Type

    inference Suspicious, unused code Deprecated functionality Coupling and cohesion Framework-specific checks
  35. 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
  36. by Johannes Schmitt Scrutinizer CI Automated code analysis Code rating

    Tracking changes/stability External code coverage Automated fixes Refactoring tips
  37. 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
  38. Beyond Clean Code Good Business Value Poor Business Value Clean

    Code Excellent Bad Dirty Code Good Garbage
  39. 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