Date post: | 11-May-2015 |
Category: |
Technology |
Upload: | mario-gleichmann |
View: | 1,983 times |
Download: | 0 times |
Clean Code
How to write comprehensible Code
regarding cognitive abilities of human mind
01.03.2011
XPUG Rhein / MainMario Gleichmann
Mario Gleichmann
twitter: @mariogleichmann
blog: gleichmann.wordpress.com (brain driven development)
site: www.mg-informatik.de
mail: [email protected]
The cost of Software Development
''Making changes is generally easy
if you exactly know what needs to be changed''
Intellectual Complexity
Programming is a cognitive process
Functionality
Intellectually manageable programs
Simplicity
Communication
'existing code is much more read than new code is written'
Fear
Empathy
Cognition
Capacity
Chunking
Cognitive overload
Knowledge base
Working Memory
Program Comprehension
def find( start :Char, end :Char, pths :List[ (Char,Char) ] ) :List[ (Char,Char) ] = {
var rout = (start,start) :: Nilvar 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 :: derout = rout.tail
}else{
rout = conts.iterator.next :: rte}
}rout.reverse.tail
}
What's the goal ?
Program model
Bottom up
Goals & sub-goals
Top down
Domain Model
'A'
'G'
'B'
'C'
'D'
'F'
'E''G'
'H'
'I'
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
}
What's the goal ?
Hypothesis &
Verification
Plans & beacons
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;
}}
}…
}
Mario Gleichmann MG Informatik
Mental Model
C o n c e p t s
Mario Gleichmann MG Informatik
Concepts
Stack
Mario Gleichmann MG Informatik
Concepts
Stack
Push
Pop
Mario Gleichmann MG Informatik
Concepts
Stack
Push
Pop
Store
Mario Gleichmann MG Informatik
Concepts
Stack
Push
Pop
StoreNot
'lose'Elements
Mario Gleichmann MG Informatik
Concepts
Stack
Push
Pop
StoreNot
'lose'Elements
Collection
Mario Gleichmann MG Informatik
Stack
Concepts
Store
Collection
Add Elements
Remove Elements
Not'lose'
Elements
Iterate Elements
Push
Pop
Not'lose'
Elements
Mario Gleichmann MG Informatik
Stack
Concepts
Store Push
Pop
LIFO
Mario Gleichmann MG Informatik
Stack
Concepts
Store Push
Pop
LIFO
FIFO
Mario Gleichmann MG Informatik
FIFO
Stack
Concepts
Store Push
Pop
LIFO
FIFOQueue
Store
Mario Gleichmann MG Informatik
FIFO
Stack
Concepts
Store Push
Pop
LIFO
FIFOQueue
Store
Add Elements
Add Elements
Collection
Add Elements
Mario Gleichmann MG Informatik
Assimilation
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;
}}
public interface Monoid<T> {
public T unit();
public T conjunct( T t1, T t2 );}
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 ) );
}...}
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 );
}}
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; }};
...
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; }};
...
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() );}
...
public reorderBook( String isbn ){ ...
if( isbn.substring( 0, 2 ).equals( ''978'' ){
pubNr = isbn.substring( 6, 10 );}else{
pubNr = isbn.substring( 8, 12 );}...
}
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 !!!
public interface Isbn {public Region getRegion(){ ... }public Integer getPublisherNumber(){ ... }
public Integer getTitelNumber(){ ... }public Integer getChecksum(){ ... }public boolean isValid(){ ... }
}
public reorderBook( Isbn isbn ){...isbn.getPublisherNumber();...
}
ISBN is a concept in its own right !!!
Mario Gleichmann MG Informatik
ContureBoundary&
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;
}
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();
// ...}
Don't repeat yourself
Single Source of Truth
public boolean isValidLaufzeit( Vertrag vertrag ){
TimeInterval interval = vertrag.getLaufzeit();
return interval.toDuration().inYears() > ONE_YEAR && interval.toDuration().inYears() < THREE_YEARS;
}
public void createOrder( int ItemId, Date shippingDate ){
Duration duration = new Duration( today(), shippingDate );
if( duration.inDays() > 14 ) rejectOrder();
// ...}
Tell ! Don't ask
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 ''}
...
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 ''}
...
Do you see the core idea ?(Hint: it's NOT about String Handling)
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 ''}
...
Building SQL-Statements ...… is NOT about String Handling
Encapsulation
Query personQuery = Query.onTable( ''Person'' )
personQuery.select( ''name'' )
personQuery.select( ''alter '' )
personQuery.add( criteria( ''stadt'' ).equals( stadt ) );
personQuery.add( criteria( ''stadt'' ).in( staedte ) );
...
PIE Principle
SEP Principle
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 ''}
...
… by the way ...
String separator ='' '';
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 ''}
...
… anybody missed that ?
Mario Gleichmann MG Informatik
Abstraction
''Look, a Composite ...'
Directory
File File Directory
File File
''delete'
''delete' ''delete'
''delete'
''delete' ''delete'
''Look, another Composite ...''
UI Panel
UI Text UI Input UI Panel
UI Text UI Selection
''draw' ''draw'
''draw'
''draw'
''draw'
''draw'
AbstractComposite
operation()add( Composite)remove( Composite )childs() : List<Comp.>
Composite
operation()add( Composite)remove( Composite )childs() : List<Comp.>
Node
operation()
Is this a Composite ?
AtLeastOneAuthProvider
LdapAuthProvider DatabaseAuth. UnanimousAuthProvider
CertificateAuth. AccessAuth.
''authenticate' ''authenticate'
''authenticate' ''authenticate'
''authenticate'
... and this ?
CEO
Employee Employee CIO
Employee Employee
''salary' ''salary'
''salary' ''salary'
''salary'
Collection
Queue
List
Stack
Set Bag
generalization
< discrimination >
FIFO LIFO
public static Integer sum( Stack<Integer> vals ){
int sum = 0;
for( int val : vals ) sum += val;
return sum;}
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) }};
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) }};
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) }};
Mario Gleichmann MG Informatik
Adaption
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() );
}}
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() );
}}
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() );
}}
Accomodation
Collection
Queue
List
Stack
Set Bag
generalization
< discrimination > FIFOHPFO
PriorityQueue ArrayQueue
Appropriateness
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;
}
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;
}
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;
}
Mario Gleichmann MG Informatik
Re - Cognition
Mario Gleichmann MG Informatik
Distinction
public 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;}
Mario Gleichmann MG Informatik
Distinction
public 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;}
BigDecimal as Money
Mario Gleichmann MG Informatik
Distinction
public 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;}
BigDecimal as Percent
Mario Gleichmann MG Informatik
Distinction
public 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;}
What's the result 'type' of combining 'Money' with 'Percent'
Mario Gleichmann MG Informatik
Distinction
public 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 ?
Mario Gleichmann MG Informatik
LiM Principle
Mario Gleichmann MG Informatik
Distinction
public boolean isGoodDeal( Money initialCosts, Money runCosts ){
Money totalCost = initialCosts.sum( runCosts );
Percent proportionRunCosts = totalCost.proportionOf( runCost )
return proportionRunCosts.isGreaterThan( Percent.FIFTY )}
Mario Gleichmann MG Informatik
Single Responsibility Principle
Mario Gleichmann MG Informatik
Mario Gleichmann MG Informatik
Mental DistanceMental Distance
Mario Gleichmann MG Informatik
interface Stepper<T>{
public boolean isExhausted();
public void step();
public T getCurrent()}
... do you recognize the underlying concept ?
Mario Gleichmann MG Informatik
interface ElementSupplier<T>{
public boolean hasMoreElements();
T nextElement();}
... do you recognize the underlying concept ?
Mario Gleichmann MG Informatik
interface ElementConsumer{
public void observe( BigElement e );
public void calculate( AnotherElement e );
public void extract( YetAnotherElement );}
... do you recognize the underlying concept ?
Mario Gleichmann MG Informatik
interface ElementInspector{
public void inspect( BigElement e );
public void inspect( AnotherElement e );
public void inspect( YetAnotherElement );}
... do you recognize the underlying concept ?
Mario Gleichmann MG Informatik
interface ElementVisitor{
public void visit( BigElement e );
public void visit( AnotherElement e );
public void visit( YetAnotherElement );}
... do you recognize the underlying concept ?
Mario Gleichmann MG Informatik
date.compareTo( otherDate ) < 0
vs.
date.isBefore( otherDate )
Language
Mario Gleichmann MG Informatik
Code slicing
Mario Gleichmann MG Informatik
Locality
Mario Gleichmann MG Informatik
Trust
Mario Gleichmann MG Informatik
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() ) );
Mario Gleichmann MG Informatik
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();
...}
Mario Gleichmann MG Informatik
''if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties''
Liskov Substitution Principle
Mario Gleichmann MG Informatik
Open Closed Principle
Mario Gleichmann MG Informatik
Principle of least astonishment
Mario Gleichmann MG Informatik
Symmetry & balance
...
void writeToFile( Output outout ){
openFile();
writeToFile( output );
}
void process{
input();
count++;
output();}
One level of abstraction
Expressiveness
Intention Revealing Interfaces
Immutabilityside effects
declarative vs
imperative
Domain Driven DesignConsistence
Yagni
Dependency Inversion Principle
Functional Programming
If there are three things to keep in mind ...
… it's not only ''developers! developers! developers!'' ...
… but
Empathy!
Empathy!
Empathy!
Now stop hacking code for machines
start writing programs for humans
… and the rest will follow ...