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/

F23700b51dc0c196c1dc02f84aeeecdf?s=128

Jeremy Mikola

November 28, 2014
Tweet

Transcript

  1. DEVELOPING BEAUTIFUL, MEASURABLE SOFTWARE Jeremy Mikola jmikola

  2. Agenda Training Metrics Tools

  3. Training

  4. None
  5. 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
  6. Object Calisthenics Object-oriented programming

  7. 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)
  8. These are exercises, not hard and fast rules.

  9. Only one level of indentation per method

  10. 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 ) ; } }
  11. 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
  12. 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 ; }
  13. 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 ) ; } }
  14. 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 ) ; } }
  15. Avoid using e l s e

  16. 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 ( ) ; } }
  17. 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 ( ) , ] ) ; }
  18. Wrap primitives and strings

  19. 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
  20. One dot per line T_OBJECT_OPERATOR

  21. 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
  22. 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 ( ) ;
  23. Don’t Do not abbreviate

  24. 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.
  25. Keep your entities small

  26. Keep your entities small <100 LOC per class 10 methods

    per class 15 classes per package The goal is readability and maintainability
  27. Limit classes to five member variables

  28. Limit classes to five member variables Decompose objects with many

    attributes into a hierarchy of collaborating objects Higher cohesion and better encapsulation
  29. First class collections

  30. 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)
  31. 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
  32. No getters and setters for class properties

  33. 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
  34. 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
  35. Use getters and setters for class properties Dependency injection Post-constructor

    (e.g. interface injection) Encapsulating transformations Decorator pattern (e.g. Doctrine Proxy objects)
  36. No static methods or utility classes

  37. 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
  38. Metrics

  39. None
  40. What else can we measure? Source lines of code Code

    coverage Bug density Cohesion and coupling Cyclomatic complexity Function points Custom design constraints
  41. Cyclomatic Complexity

  42. 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
  43. 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
  44. Cyclomatic Complexity CC Complexity 1–4 Low 5–7 Moderate 8–10 High

    11+ Very high
  45. NPath Complexity

  46. NPath Complexity Number of unique execution paths in a function

    Alternatively, the number of tests required for full branch coverage
  47. 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
  48. 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
  49. 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)
  50. Tools

  51. 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
  52. by Sebastian Bergmann PHPLOC Measures and analyzes a project LOC

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

    cyclomatic complexity Weighted method count Coupling analysis Code rank Dozens of additional metrics
  55. by Manuel Pichler PHP_Depend Abstraction Instability Chart

  56. by Manuel Pichler PHP Mess Detector Frontend for PHP_Depend Code

    size, complexity Design (syntax, classes) Unused code Naming conventions
  57. by Manuel Pichler PHP Mess Detector

  58. by Johannes Schmitt PHP Analyzer Code analysis and fixing Type

    inference Suspicious, unused code Deprecated functionality Coupling and cohesion Framework-specific checks
  59. Continuous Integration

  60. 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
  61. Continuous Integration by Sebastian Bergmann jenkins-php.org

  62. Continuous Integration Travis CI Scrutinizer CI

  63. by Johannes Schmitt Scrutinizer CI Automated code analysis Code rating

    Tracking changes/stability External code coverage Automated fixes Refactoring tips
  64. by Johannes Schmitt Scrutinizer CI

  65. A Final Thought

  66. 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
  67. Beyond Clean Code Good Business Value Poor Business Value Clean

    Code Excellent Bad Dirty Code Good Garbage
  68. 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
  69. 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/