Empathic Programming - How to write comprehensible code
Upcoming SlideShare
Loading in...5
×

Like this? Share it with your network

Share

Empathic Programming - How to write comprehensible code

  • 2,172 views
Uploaded 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.

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.

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
2,172
On Slideshare
2,171
From Embeds
1
Number of Embeds
1

Actions

Shares
Downloads
28
Comments
0
Likes
2

Embeds 1

http://twitter.com 1

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Clean CodeHow to write comprehensible Coderegarding cognitive abilities of human mind01.03.2011XPUG Rhein / MainMario Gleichmann
  • 2. Mario Gleichmanntwitter: @mariogleichmannblog: gleichmann.wordpress.com (brain driven development)site: www.mg-informatik.demail: mario.gleichmann@mg-informatik.de
  • 3. The cost of Software Development
  • 4. Intellectual Complexity Making changes is generally easyif you exactly know what needs to be changed
  • 5. Programming is a cognitive process
  • 6. Functionality
  • 7. SimplicityIntellectually manageable programs
  • 8. Communicationexisting code is much more read than new code is written
  • 9. Fear
  • 10. Empathy
  • 11. Cognition
  • 12. Capacity
  • 13. Chunking
  • 14. Cognitive overload
  • 15. Knowledge base
  • 16. Working Memory
  • 17. Program Comprehension
  • 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. Program model
  • 20. Bottom up
  • 21. Goals & sub-goals
  • 22. Top down
  • 23. Domain Model
  • 24. G FI B C A H D G E
  • 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. Hypothesis &Verification
  • 27. Plans & beacons
  • 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. Mental Model
  • 30. Concepts
  • 31. Concepts Stack
  • 32. Concepts Push Pop Stack
  • 33. ConceptsStore Push Pop Stack
  • 34. Concepts NotStore lose Push Elements Pop Stack
  • 35. Concepts Not Store lose Push Elements PopCollection Stack
  • 36. Concepts Not Store lose Push Elements PopCollection Stack Add Remove IterateElements Elements Elements
  • 37. ConceptsStore Push Pop Stack LIFO
  • 38. ConceptsStore Push Pop Stack LIFO FIFO
  • 39. ConceptsStore Push Pop Stack LIFO Queue FIFO FIFO
  • 40. Concepts Store Push PopCollection Stack LIFO AddAddAddElements Elements Elements Queue FIFO FIFO
  • 41. Assimilation
  • 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. public interface Monoid<T> { public T unit(); public T conjunct( T t1, T t2 );}
  • 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. 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. 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. 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. 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. public reorderBook( String isbn ){ ... if( isbn.substring( 0, 2 ).equals( 978 ){ pubNr = isbn.substring( 6, 10 ); } else{ pubNr = isbn.substring( 8, 12 ); } ...}
  • 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. 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. Conture & Boundary
  • 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. 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. Dont repeat yourself
  • 56. Single Source of Truth
  • 57. public boolean isValidLaufzeit( Vertrag vertrag ){ TimeInterval interval = vertrag.getLaufzeit(); return interval.toDuration().inYears() > ONE_YEAR && interval.toDuration().inYears() < THREE_YEARS;}
  • 58. public void createOrder( int ItemId, Date shippingDate ){ Duration duration = new Duration( today(), shippingDate ); if( duration.inDays() > 14 ) rejectOrder(); // ...}
  • 59. Tell ! Dont ask
  • 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. 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. 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. Encapsulation
  • 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. PIE Principle
  • 66. SEP Principle
  • 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. 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. Abstraction
  • 70. Look, a Composite ... Directory delete File File Directory deletedelete delete File File delete delete
  • 71. Look, another Composite ... UI Panel drawUI Text UI Input UI Panel drawdraw draw UI Text UI Selection draw draw
  • 72. AbstractComposite operation() add( Composite) remove( Composite ) childs() : List<Comp.> Node Compositeoperation() operation() add( Composite) remove( Composite ) childs() : List<Comp.>
  • 73. Is this a Composite ? AtLeastOneAuthProvider authenticateLdapAuthProvider DatabaseAuth. UnanimousAuthProvider authenticate authenticate CertificateAuth. AccessAuth. authenticate authenticate
  • 74. ... and this ? CEO salaryEmployee Employee CIOsalary salary Employee Employee salary salary
  • 75. Collection generalization List Set BagQueue Stack < discrimination > FIFO LIFO
  • 76. public static Integer sum( Stack<Integer> vals ){ int sum = 0; for( int val : vals ) sum += val; return sum;}
  • 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. 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. 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. Adaption
  • 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. 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. 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. Accomodation
  • 85. Collection generalization List Set Bag Queue StackPriorityQueue ArrayQueueHPFO < discrimination > FIFO
  • 86. Appropriateness
  • 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. 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. 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. Re - Cognition
  • 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. 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. 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. 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. 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. LiM Principle
  • 97. Distinctionpublic boolean isGoodDeal( Money initialCosts, Money runCosts ){ Money totalCost = initialCosts.sum( runCosts ); Percent proportionRunCosts = totalCost.proportionOf( runCost ) return proportionRunCosts.isGreaterThan( Percent.FIFTY )}
  • 98. Single Responsibility Principle
  • 99. Mental Distance Mental Distance
  • 100. ... do you recognize the underlying concept ? interface Stepper<T>{ public boolean isExhausted(); public void step(); public T getCurrent() }
  • 101. ... do you recognize the underlying concept ? interface ElementSupplier<T>{ public boolean hasMoreElements(); T nextElement(); }
  • 102. ... do you recognize the underlying concept ? interface ElementConsumer{ public void observe( BigElement e ); public void calculate( AnotherElement e ); public void extract( YetAnotherElement ); }
  • 103. ... do you recognize the underlying concept ? interface ElementInspector{ public void inspect( BigElement e ); public void inspect( AnotherElement e ); public void inspect( YetAnotherElement ); }
  • 104. ... do you recognize the underlying concept ? interface ElementVisitor{ public void visit( BigElement e ); public void visit( AnotherElement e ); public void visit( YetAnotherElement ); }
  • 105. Language date.compareTo( otherDate ) < 0 vs. date.isBefore( otherDate )
  • 106. Codeslicing
  • 107. Locality
  • 108. Trust
  • 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. 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. 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. Open Closed Principle
  • 113. Principle of least astonishment
  • 114. Symmetry & balance
  • 115. ...void writeToFile( Output outout ){ openFile(); writeToFile( output );}
  • 116. void process{ input(); count++; output();}
  • 117. One level of abstraction
  • 118. Dependency Inversion Principle declarative vs Intention Revealing Interfaces imperative Functional Programming Yagni Immutability side effects Expressiveness Domain Driven Design Consistence
  • 119. If there are three things to keep in mind ...
  • 120. … its not only developers! developers! developers! ...
  • 121. … but Empathy! Empathy! Empathy!
  • 122. Now stop hacking code for machines start writing programs for humans… and the rest will follow ...