Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Empathic Programming - How to write comprehensible code

5,580 views

Published on

Slides to a (non-commercial) talk i gave 2011 at XPUG Rhein/Main (Germany) about how to write comprehensible code, regarding cognitive abilities of human mind.

Published in: Technology
  • Be the first to comment

Empathic Programming - How to write comprehensible code

  1. 1. Clean CodeHow to write comprehensible Coderegarding cognitive abilities of human mind01.03.2011XPUG Rhein / MainMario Gleichmann
  2. 2. Mario Gleichmanntwitter: @mariogleichmannblog: gleichmann.wordpress.com (brain driven development)site: www.mg-informatik.demail: mario.gleichmann@mg-informatik.de
  3. 3. The cost of Software Development
  4. 4. Intellectual Complexity Making changes is generally easyif you exactly know what needs to be changed
  5. 5. Programming is a cognitive process
  6. 6. Functionality
  7. 7. SimplicityIntellectually manageable programs
  8. 8. Communicationexisting code is much more read than new code is written
  9. 9. Fear
  10. 10. Empathy
  11. 11. Cognition
  12. 12. Capacity
  13. 13. Chunking
  14. 14. Cognitive overload
  15. 15. Knowledge base
  16. 16. Working Memory
  17. 17. Program Comprehension
  18. 18. Whats the goal ?def find( start :Char, end :Char, pths :List[ (Char,Char) ] ) :List[ (Char,Char) ] = { var rout = (start,start) :: Nil var dends :List[(Char,Char)] = Nil while( !rout.isEmpty && rout.head._2 != end ){ var conts = from( rout.head._2, pths ) .filter( pth => !dends.contains( pth ) && !rout.contains( pth ) ) if( conts.isEmpty && !rout.isEmpty ) { dends = rout.head :: de rout = rout.tail } else{ rout = conts.iterator.next :: rte } } rout.reverse.tail}
  19. 19. Program model
  20. 20. Bottom up
  21. 21. Goals & sub-goals
  22. 22. Top down
  23. 23. Domain Model
  24. 24. G FI B C A H D G E
  25. 25. Whats the goal ?def findRoute( start :WayPoint, target :WayPoint, map :Set[Path] ) :Sequence[Path] = { var route = new Stack[Path] var deadEnds :Set[Path] = new HashSet[Path] route push Path( start, start ) while( !route.isEmpty && route.top.endPoint != target ){ var continuingPaths = pathsFrom( route.top.endPoint, map ) .without( deadEnds ).without( route ) if( continuingPaths.isEmpty && !route.isEmpty ) { deadEnds += route.pop } else{ route push continuingPaths.first } } route.reverse}
  26. 26. Hypothesis &Verification
  27. 27. Plans & beacons
  28. 28. public class PrimeGenerator { static int[] sieveUpTo( int maxValue ){ if( maxValue < 2 ) return new int[0]; boolean[] grid = new boolean[maxValue + 1]; for( int i = 0; i <= maxValue; i++ ){ grid[i] = true; } grid[0] = grid[1] = false; for( int i = 2; i < Math.sqrt( maxValue + 1 ) + 1; i++ ){ if( grid[i] ){ for( int j = 2*i; j <= maxValue; j += i ){ grid[j] = false; } } } …}
  29. 29. Mental Model
  30. 30. Concepts
  31. 31. Concepts Stack
  32. 32. Concepts Push Pop Stack
  33. 33. ConceptsStore Push Pop Stack
  34. 34. Concepts NotStore lose Push Elements Pop Stack
  35. 35. Concepts Not Store lose Push Elements PopCollection Stack
  36. 36. Concepts Not Store lose Push Elements PopCollection Stack Add Remove IterateElements Elements Elements
  37. 37. ConceptsStore Push Pop Stack LIFO
  38. 38. ConceptsStore Push Pop Stack LIFO FIFO
  39. 39. ConceptsStore Push Pop Stack LIFO Queue FIFO FIFO
  40. 40. Concepts Store Push PopCollection Stack LIFO AddAddAddElements Elements Elements Queue FIFO FIFO
  41. 41. Assimilation
  42. 42. public class Folding { public static <T> T fold( List<T> list, Monoid<T> monoid ){ return list.isEmpty() ? monoid.unit() : monoid.conjunct( head( list ), fold( tail( list ), monoid ) ); } private static <T> T head( List<T> list ){ return list.isEmpty() ? null : list.get( 0 ); } private static <T> List<T> tail( List<T> list ){ return list.size() >= 2 ? list.subList( 1, list.size() ) : EMPTY_LIST; }}
  43. 43. public interface Monoid<T> { public T unit(); public T conjunct( T t1, T t2 );}
  44. 44. public class MonoidInstanceTest extends TestCase{... public void testStringAsMonoid(){ assertEquals( "helloworld", stringMonoid.conjunct( "hello", "world" ) ); assertEquals( "hollamundo", stringMonoid.conjunct( "hola", "mundo" ) ); assertEquals( "hola", stringMonoid.conjunct( "hola", stringMonoid.unit() ) ); assertEquals( "hola", stringMonoid.conjunct( stringMonoid.unit(), "hola" ) ); } public void testIntegerAsMonoid(){ assertEquals( 6, poductIntMonoid.conjunct( 2, 3 ) ); assertEquals( 42 , productIntMonoid.conjunct( 7, 6 ) ); assertEquals( 11, productIntMonoid.conjunct( 11, productIntMonoid.unit() ) ); assertEquals( 11, productIntMonoid.conjunct( productIntMonoid.unit(), 11 ) ); }...}
  45. 45. public class MonoidTest extends TestCase{... private static <T> void assertAssociativity( Monoid<T> monoid, T t1, T t2, T t3 ){ assertEquals( monoid.conjunct( t1, monoid.conjunct( t2, t3 ) ), monoid.conjunct( monoid.conjunct( t1, t2 ), t3 ) ); } private static <T> void assertNeutrality( Monoid<T> monoid, T value ){ assertEquals( monoid.conjunct( value, monoid.unit() ), value ); assertEquals( monoid.conjunct( monoid.unit(), value ), value ); } public void testMonoidInstances(){ assertAssociativity( stringMonoid, "s1", "s2", "s3" ); assertNeutrality( stringMonoid, "s1" ); ... assertAssociativity( productSumMonoid, 1, 2, 3 ); assertNeutrality( productSumMonoid, 1 ); }}
  46. 46. public class Monoids { public static Monoid<String> stringMonoid = new Monoid<String>(){ public String unit() { return ""; } public String conjunct(String t1, String t2) { return t1 + t2; } }; public static Monoid<Integer> productIntMonoid = new Monoid<Integer>(){ public Integer unit() { return 1; } public Integer conjunct(Integer t1, Integer t2) { return t1 * t2; } }; ...
  47. 47. public class Monoids { ... public static Monoid<Integer> sumIntMonoid = new Monoid<Integer>(){ public Integer unit() { return 0; } public Integer conjunct(Integer t1, Integer t2) { return t1 + t2; } }; ...
  48. 48. public class FoldTest extends TestCase{ public void testStringConcat(){ assertEquals( "Hello world !!!", fold( asList( "Hello", " ", "world" + " " + "!!!" ), stringMonoid ) ); } public void testSum(){ assertEquals( 15, fold( asList( 1, 2, 3, 4, 5 ), sumIntMonoid ).intValue() ); } public void testProduct(){ assertEquals( 120, fold( asList( 1, 2, 3, 4, 5 ), productIntMonoid ).intValue() ); }...
  49. 49. public reorderBook( String isbn ){ ... if( isbn.substring( 0, 2 ).equals( 978 ){ pubNr = isbn.substring( 6, 10 ); } else{ pubNr = isbn.substring( 8, 12 ); } ...}
  50. 50. public reorderBook( String isbn ){ ... if( isbn.substring( 0, 2 ).equals( 978 ){ pubNr = isbn.substring( 6, 10 ); } else{ pubNr = isbn.substring( 8, 12 ); } ...} ISBN is NOT a String !!!
  51. 51. public interface Isbn { public Region getRegion(){ ... } public Integer getPublisherNumber(){ ... } public Integer getTitelNumber(){ ... } public Integer getChecksum(){ ... } public boolean isValid(){ ... }} ISBN is a concept in its own right !!! public reorderBook( Isbn isbn ){ ... isbn.getPublisherNumber(); ... }
  52. 52. Conture & Boundary
  53. 53. public boolean isValidLaufzeit( Vertrag vertrag ){ GregorianCalendar start = vertrag.getStart(); GregorianCalendar end = vertrag.getEnd(); int tagesdifferenz = 0; if (start != null && end != null) { int startJahr = start.get(Calendar.YEAR); int endJahr = end.get(Calendar.YEAR); int tageImStartJahr = start.get(Calendar.DAY_OF_YEAR); int tageImEndJahr = end.get(Calendar.DAY_OF_YEAR); int aktuellesJahr = startJahr; while (aktuellesJahr <= endJahr) { if (aktuellesJahr == startJahr) { if (aktuellesJahr == endJahr) { tagesdifferenz += tageImEndJahr - tageImStartJahr; } else { tagesdifferenz += start.isLeapYear(startJahr) ? (366 - tageImStartJahr) : (365 - tageImStartJahr); } } else if (aktuellesJahr == endJahr) { tagesdifferenz += tageImEndJahr; } else { tagesdifferenz += start.isLeapYear(aktuellesJahr) ? 366 : 365; } aktuellesJahr++; } } return tagesdifferenz > 365 && tagesdifferenz < 999;}
  54. 54. public void createOrder( int itemId, Date shippingDate ){ Date today = new Date(); long start = today.getTime(); long end = shippingDate.getTime(); BigDecimal diff = new BigDecimal( start - end ); int days = diff.divideToIntegralValue( new BigDecimal( 1000 * 60 * 60 * 24 ) ).intValue(); if( days > 14 ) rejectOrder(); // ...}
  55. 55. Dont repeat yourself
  56. 56. Single Source of Truth
  57. 57. public boolean isValidLaufzeit( Vertrag vertrag ){ TimeInterval interval = vertrag.getLaufzeit(); return interval.toDuration().inYears() > ONE_YEAR && interval.toDuration().inYears() < THREE_YEARS;}
  58. 58. public void createOrder( int ItemId, Date shippingDate ){ Duration duration = new Duration( today(), shippingDate ); if( duration.inDays() > 14 ) rejectOrder(); // ...}
  59. 59. Tell ! Dont ask
  60. 60. String separator = ;String query = select ;if( ... ) query += name ;if( ... ) query = query + separator + alter , separator = , ;query += from person ;if( existFilter ){ query += where if( ... ) query += stadt = + stadt; separator = and ; else{ query += stadt in ( ; for( String stadt : staedte ) query += stadt + inSeparator; inSeparator = , ; query += ); separator = and }...
  61. 61. String separator = ;String query = select ;if( ... ) query += name ;if( ... ) query = query + separator + alter , separator = , ;query += from person ; Do you see the core idea ?if( existFilter ){ (Hint: its NOT about String Handling) query += where if( ... ) query += stadt = + stadt; separator = and ; else{ query += stadt in ( ; for( String stadt : staedte ) query += stadt + inSeparator; inSeparator = , ; query += ); separator = and }...
  62. 62. String separator = ;String query = select ;if( ... ) query += name ;if( ... ) query = query + separator + alter , separator = , ;query += from person ; Building SQL-Statements ...if( existFilter ){ … is NOT about String Handling query += where if( ... ) query += stadt = + stadt; separator = and ; else{ query += stadt in ( ; for( String stadt : staedte ) query += stadt + inSeparator; inSeparator = , ; query += ); separator = and }...
  63. 63. Encapsulation
  64. 64. Query personQuery = Query.onTable( Person )personQuery.select( name )personQuery.select( alter )personQuery.add( criteria( stadt ).equals( stadt ) );personQuery.add( criteria( stadt ).in( staedte ) );...
  65. 65. PIE Principle
  66. 66. SEP Principle
  67. 67. String separator = ; … by the way ...String query = select ;if( ... ) query += name ;if( ... ) query = query + separator + alter , separator = , ;query += from person ;if( existFilter ){ query += where if( ... ) query += stadt = + stadt; separator = and ; else{ query += stadt in ( ; for( String stadt : staedte ) query += stadt + inSeparator; inSeparator = , ; query += ); separator = and }...
  68. 68. String separator = ; … anybody missed that ?String query = select ;if( ... ){ query += name ; separator = , ; }if( ... ) query = query + separator + alter , separator = , ;query += from person ;if( existFilter ){ query += where if( ... ) query += stadt = + stadt; separator = and ; else{ query += stadt in ( ; for( String stadt : staedte ) query += stadt + inSeparator; inSeparator = , ; query += ); separator = and }...
  69. 69. Abstraction
  70. 70. Look, a Composite ... Directory delete File File Directory deletedelete delete File File delete delete
  71. 71. Look, another Composite ... UI Panel drawUI Text UI Input UI Panel drawdraw draw UI Text UI Selection draw draw
  72. 72. AbstractComposite operation() add( Composite) remove( Composite ) childs() : List<Comp.> Node Compositeoperation() operation() add( Composite) remove( Composite ) childs() : List<Comp.>
  73. 73. Is this a Composite ? AtLeastOneAuthProvider authenticateLdapAuthProvider DatabaseAuth. UnanimousAuthProvider authenticate authenticate CertificateAuth. AccessAuth. authenticate authenticate
  74. 74. ... and this ? CEO salaryEmployee Employee CIOsalary salary Employee Employee salary salary
  75. 75. Collection generalization List Set BagQueue Stack < discrimination > FIFO LIFO
  76. 76. public static Integer sum( Stack<Integer> vals ){ int sum = 0; for( int val : vals ) sum += val; return sum;}
  77. 77. public static Integer sum( Stack<Integer> vals ){ int sum = 0; for( int val : vals ) sum += val; return sum;}sum( new Stack<Integer>(){{ push(1); push(2); push(3); }};sum( new ArrayList<Integer>(){{ add(1); add(2); add(3) }};
  78. 78. public static Integer sum( List<Integer> vals ){ int sum = 0; for( int val : vals ) sum += val; return sum;}sum( new Stack<Integer>(){{ push(1); push(2); push(3); }};sum( new ArrayList<Integer>(){{ add(1); add(2); add(3) }};sum( new HashSet<Integer>(){{ add(1); add(2); add(3) }};
  79. 79. public static Integer sum( Collection<Integer> vals ){ int sum = 0; for( int val : vals ) sum += val; return sum;}sum( new Stack<Integer>(){{ push(1); push(2); push(3); }};sum( new ArrayList<Integer>(){{ add(1); add(2); add(3) }};sum( new HashSet<Integer>(){{ add(1); add(2); add(3) }};
  80. 80. Adaption
  81. 81. public class QueueTest extends TestCase { private Queue<Integer> queue = null; public void setUp() throws Exception{ queue = new ... queue.add( 10 ); queue.add( 3 ); queue.add( 7 ); queue.add( 5 ); } public testQueuePolling{ assertEquals( ? , queue.poll() ); assertEquals( ?? , queue.poll() ); assertEquals( ??? , queue.poll() ); assertEquals( ???? , queue.poll() ); }}
  82. 82. public class QueueTest extends TestCase { private Queue<Integer> queue = null; public void setUp() throws Exception{ queue = new ArrayBlockingQueue<Integer>(); queue.add( 10 ); queue.add( 3 ); queue.add( 7 ); queue.add( 5 ); } public testQueuePolling{ assertEquals( 10 , queue.poll() ); assertEquals( 3 , queue.poll() ); assertEquals( 7 , queue.poll() ); assertEquals( 5 , queue.poll() ); }}
  83. 83. public class QueueTest extends TestCase { private Queue<Integer> queue = null; public void setUp() throws Exception{ queue = new PriorityQueue<Integer>(); queue.add( 10 ); queue.add( 3 ); queue.add( 7 ); queue.add( 5 ); } public testQueuePolling{ assertEquals( 3 , queue.poll() ); assertEquals( 5 , queue.poll() ); assertEquals( 7 , queue.poll() ); assertEquals( 10 , queue.poll() ); }}
  84. 84. Accomodation
  85. 85. Collection generalization List Set Bag Queue StackPriorityQueue ArrayQueueHPFO < discrimination > FIFO
  86. 86. Appropriateness
  87. 87. public static List<Integer> multiplesOf( int factor, int limit ){ List<Integer> collect = new ArrayList<Integer>(); for( int i = 1; i * factor <= limit; i++ ){ collect.add( i * factor ); } return collect;} vs.public static Set<Integer> multiplesOf( int factor, int limit ){ Set<Integer> collect = new HashSet<Integer>(); for( int i = 1; i * factor <= limit; i++ ){ collect.add( i * factor ); } return collect;}
  88. 88. public static List<Integer> multiplesOf( int[] factors, int limit ){ List<Integer> collect = new ArrayList<Integer>(); for( int factor : factors ){ for( int i = 1; i * factor <= limit; i++ ){ collect.add( i * factor ); } } return collect;}
  89. 89. public static Set<Integer> multiplesOf( int[] factors, int limit ){ Set<Integer> collect = new HashSet<Integer>(); for( int factor : factors ){ for( int i = 1; i * factor <= limit; i++ ){ collect.add( i * factor ); } } return collect;}
  90. 90. Re - Cognition
  91. 91. Distinctionpublic boolean isGoodDeal( BigDecimal initialCosts, BigDecimal runCosts ){ BigDecimal totalCost = initialCost.add( runCost ); BigDecimal oneP = totalCost.divide( new BigDecimal( 100 ) ); BigDecimal propRunCosts = wk.divide( oneP, 2, DOWN ); return ! propRunCosts.compareTo( new BigDecimal( 50 ) ) > 1;}
  92. 92. Distinctionpublic boolean isGoodDeal( BigDecimal initialCosts, BigDecimal runCosts ){ BigDecimal totalCost = initialCost.add( runCost ); BigDecimal oneP = totalCost.divide( new BigDecimal( 100 ) ); BigDecimal as Money BigDecimal propRunCosts = wk.divide( oneP, 2, DOWN ); return ! propRunCosts.compareTo( new BigDecimal( 50 ) ) > 1;}
  93. 93. Distinctionpublic boolean isGoodDeal( BigDecimal initialCosts, BigDecimal runCosts ){ BigDecimal as Percent BigDecimal totalCost = initialCost.add( runCost ); BigDecimal oneP = totalCost.divide( new BigDecimal( 100 ) ); BigDecimal propRunCosts = wk.divide( oneP, 2, DOWN ); return ! propRunCosts.compareTo( new BigDecimal( 50 ) ) > 1;}
  94. 94. Distinctionpublic boolean isGoodDeal( BigDecimal initialCosts, BigDecimal runCosts ){ BigDecimal totalCost = initialCost.add( runCost ); BigDecimal oneP = totalCost.divide( new BigDecimal( 100 ) ); BigDecimal propRunCosts = wk.divide( oneP, 2, DOWN ); return ! propRunCosts.compareTo( new BigDecimal( 50 ) ) > 1;} Whats the result type of combining Money with Percent
  95. 95. Distinctionpublic boolean isGoodDeal( BigDecimal initialCosts, BigDecimal runCosts ){ BigDecimal totalCost = initialCost.add( runCost ); BigDecimal oneP = totalCost.divide( new BigDecimal( 100 ) ); BigDecimal propRunCosts = wk.divide( oneP, 2, DOWN ); return ! propRunCosts.compareTo( new BigDecimal( 50 ) ) > 1;} Which combinations are allowed - which not ?
  96. 96. LiM Principle
  97. 97. Distinctionpublic boolean isGoodDeal( Money initialCosts, Money runCosts ){ Money totalCost = initialCosts.sum( runCosts ); Percent proportionRunCosts = totalCost.proportionOf( runCost ) return proportionRunCosts.isGreaterThan( Percent.FIFTY )}
  98. 98. Single Responsibility Principle
  99. 99. Mental Distance Mental Distance
  100. 100. ... do you recognize the underlying concept ? interface Stepper<T>{ public boolean isExhausted(); public void step(); public T getCurrent() }
  101. 101. ... do you recognize the underlying concept ? interface ElementSupplier<T>{ public boolean hasMoreElements(); T nextElement(); }
  102. 102. ... do you recognize the underlying concept ? interface ElementConsumer{ public void observe( BigElement e ); public void calculate( AnotherElement e ); public void extract( YetAnotherElement ); }
  103. 103. ... do you recognize the underlying concept ? interface ElementInspector{ public void inspect( BigElement e ); public void inspect( AnotherElement e ); public void inspect( YetAnotherElement ); }
  104. 104. ... do you recognize the underlying concept ? interface ElementVisitor{ public void visit( BigElement e ); public void visit( AnotherElement e ); public void visit( YetAnotherElement ); }
  105. 105. Language date.compareTo( otherDate ) < 0 vs. date.isBefore( otherDate )
  106. 106. Codeslicing
  107. 107. Locality
  108. 108. Trust
  109. 109. Verifiable SpecificationsStack stack = is( new DescriptionOf <Stack>(){ public Stack isDescribedAs(){ Stack<String> stack = new Stack<String>(); stack.push( foo ); stack.push( bar ); return stack; } } ); it( "should contain foo" ); state( stack ).should( contain( foo ) ); it( "should contain bar" ); state( stack ).should( contain( bar ) ); it( "should return bar when calling pop the first time" ); state( stack.pop() ).should( returning( bar ) ); it( "should return foo when calling pop the second time" ); stack.pop(); state( stack.pop() ).should( returning( foo ) ); it( "should be empty after popping the two elements" ); stack.pop(); stack.pop(); state( stack ).should( be ( empty() ) );
  110. 110. Design by Contract@Invariant( "this.size >= 0 and this.size <= this.capazity" )public interface Stack { ... } @Postcondition( "return > 0" ) public int getCapacity(); public int getSize(); @Precondition( "elem not null and this.size < this.capacity" ) @Postcondition( "elem == this.top and this.size == old:this.size + 1" ) public void push( Object elem ); @Postcondition( "this.top == old:this.top ) " ) public Object getTop(); @Postcondition( "(old:this.size>0) ==> (return == old:this.top and this.size == old:this.size - 1)") public Object pop(); ...}
  111. 111. Liskovsubtype of T, then objects of type T in a if S is a Substitution Principle program may be replaced with objects of type S without altering any of the desirable properties
  112. 112. Open Closed Principle
  113. 113. Principle of least astonishment
  114. 114. Symmetry & balance
  115. 115. ...void writeToFile( Output outout ){ openFile(); writeToFile( output );}
  116. 116. void process{ input(); count++; output();}
  117. 117. One level of abstraction
  118. 118. Dependency Inversion Principle declarative vs Intention Revealing Interfaces imperative Functional Programming Yagni Immutability side effects Expressiveness Domain Driven Design Consistence
  119. 119. If there are three things to keep in mind ...
  120. 120. … its not only developers! developers! developers! ...
  121. 121. … but Empathy! Empathy! Empathy!
  122. 122. Now stop hacking code for machines start writing programs for humans… and the rest will follow ...

×