In deze presentatie die ik gaf aan collega's, licht ik enkele topics toe uit deze boeken:
- Robert C. Martin, Clean Code
- Joshua Bloch, Effective Java
17. Klassen
● EJ4 “Classes and Interfaces”
● CC6 “Objects and Data Structures”
● CC10 “Classes”
17
18. Richtlijnen voor klassen (CC10)
● Klassen moeten klein zijn
● Geen “God-klassen” (vb. DomainContoller!)
● “Single Responsibility Principle”: er is maar 1 reden
om de klasse te wijzigen
● Cohesie: klein aantal instantievariabelen, methods
manipuleren meerdere instantievariabelen
18
19. Beperk “mutability” (EJ#15)
● Geen mutators
● Laat geen overerving toe
● Alle velden final
● Alle velden private
● Geen toegang tot wijzigbare componenten
19
24. Onveranderlijke Breuken
public final class Fraction {
private final int numerator;
private final int denominator;
public Fraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
public int getNumerator() { return numerator; }
public int getDenominator() { return denominator; }
}
24
25. Of misschien zelfs
public final class Fraction {
public final int numerator;
public final int denominator;
public Fraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
}
25
26. Terzijde: hetzelfde in Scala ;-)
class Fraction(val numerator: Int, val denominator: Int)
26
27. Rekenen met onveranderlijke
breuken
public Fraction add(Fraction that) {
return new Fraction(
this.numerator * that.denominator
+ that.numerator * this.denominator,
this.denominator * that.denominator);
}
public Fraction multiply(Fraction that) {
return new Fraction(
this.numerator * that.numerator,
this.denominator * that.denominator);
}
27
28. Wijzigbare componenten
public class Farm {
private Field[][] fields;
public Farm(int width) {
this.fields = new Field[width][width];
initFields();
}
private void initFields() { … }
public Field[][] getFields() {
return fields;
}
}
Farm farm = new Farm(4);
Field[][] fields = farm.getFields();
fields[2][3] = null; // zou niet mogen!
28
29. Wijzigbare componenten
● Geef individuele elementen terug, vb.
public Field getField(int row, int col) {
return fields[row][col];
}
● Maak een “defensieve kopie”
29
30. Let op! (EJ#39)
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
this.start = start;
this.end = end;
}
public Date getStart() { return start; }
public Date getEnd() { return end; }
}
30
31. Aanval op interne toestand Period
Date start = new Date(2012, 06, 01);
Date end = new Date(2012, 06, 30);
Period p = new Period(start, end);
end.setYear(2013);
// Deze test zal falen!
assertEquals(2012, p.getEnd().getYear());
31
33. Immutability – voordelen
● Simpel: één toestand
● Makkelijker testen
● Altijd thread-safe!
● Kan je hergebruiken
public static final Fraction ZERO = new Fraction(0,0);
public static final Fraction ONE = new Fraction(1,1);
● Kopies maken eigenlijk overbodig
● Bouwstenen voor andere objecten
33
34. Immutability – nadelen
● Veel objecten aanmaken
● op te lossen, bv. met static factories (zie verder)
34
36. Verkies compositie boven overerving
(EJ#16)
● Overerving kan
● binnen zelfde package, onder controle van zelfde
programmeurs
● van specifiek daarvoor ontworpen klassen
● van interfaces
● Overerving vermijden
● van “gewone” concrete klassen over packages heen
36
38. Verkies interfaces boven abstracte
klassen (EJ#18)
● Bestaande klassen kunnen makkelijk aangepast
worden om nieuwe interface te implementeren
● Interfaces zijn ideaal voor het definiëren van
“mixins” (vgl. Scala Traits, Ruby Modules)
● Interfaces maken niet-hierarchische
typeframeworks mogelijk
● Veilige manier om functionaliteit uit te breiden
38
40. Nadelen
● Geen implementatie
● voorzie basisimplementatie (“skeletal”)
● kan jouw klasse niet overerven van basisimpl.?
“simulated multiple inheritance”
● Eens een interface gepubliceerd is, kan je niet
meer wijzigen
40
42. Objecten vs Datastructuren (CC6)
● Objecten
● Verbergen data/implementatie achter abstracties
● Hebben functies om deze data te bewerken
● Datastructuren
● Hebben data
● Hebben geen functies van belang
42
43. Vb: 2 implementaties voor vormen
● Datastructuren/procedureel
public class Square {
public Point topLeft;
public double side;
}
public class Circle {
public Point center;
public double radius;
}
43
44. public class Geometry {
public double area(Object shape) {
if (shape instanceof Square) {
Square s = (Square)shape;
return s.side * s.side;
} else if(shape instanceof Circle) {
Circle c = (Circle) shape;
return c.radius * c.radius * Math.PI;
} else {
throw new IllegalArgumentException(
"Not a known shape");
}
}
}
Jamaar, da's geen OO! 44
45. Vb: 2 implementaties voor vormen
● Objectgeorienteerd
public interface Shape {
public double area();
}
public class Square implements Shape {
private Point topLeft;
private double side;
@Override public double area() {
return side * side;
}
}
45
46. public class Circle implements Shape {
private Point center;
private double radius;
@Override public double area() {
return Math.PI * radius * radius;
}
}
46
47. Twee soorten refactorings
● Functie toevoegen (bv. perimeter())
● Procedureel: enkel Geometry aanpassen
● Shapes en hun “clients” blijven ongewijzigd!
● OO: ALLE Shapes aanpassen
● Shape toevoegen (bv. Rectangle)
● Procedureel: ALLE functies in Geometry aanpassen
● OO: enkel Rectangle-klasse schrijven
47
48. Is de “procedurele” aanpak uit het vorige voorbeeld
soms toelaatbaar/aangewezen?
48
50. Functies / methods
● CC3 Functions
● EJ2 Creating and destroying objects
● EJ3 Methods common to all objects
● EJ5 Methods
50
51. Functies mogen maar één ding doen
Ze moeten dat goed doen
Ze mogen alleen dat doen
51
52. Richtlijnen voor functies (CC3)
● Kort! => verstaanbaar
● geen geneste controlestructuren
● ingewikkelde tests in aparte functie
● Eén niveau van abstractie per functie
● Beschrijvende namen
● voor functies en variabelen/parameters
● Leesbaar van boven naar beneden
● beginnen met “hoofdfunctie”, daarna hulpfuncties
52
53. Richtlijnen voor functies (CC3)
● Géén neveneffecten
● zwakkere betekenis: één ding doen
public boolean checkPwd(String user, String passwd) {
…
if(hash.equals(storedHash)) {
session.initialize();
return true;
}
}
● sterkere betekenis: geen data muteren
● = basisgedachte functioneel programmeren
● N.B. System.out.println() is een neveneffect
53
54. Richtlijnen voor functies (CC3)
● “Command/query separation”
● ofwel iets doen, ofwel een antwoord geven
● niet beide
● Géén “output arguments”
● vb. Arrays.fill(boolean[] a, boolean val)
● Exceptions ipv “foutcodes” of null (zie ook
EJ#43)
● Don't repeat yourself
54
55. Functie-argumenten (CC3)
● Aantal:
● 0 argumenten is best
● 1 argument (monad) is het op één na beste
● 2 argumenten (dyad) is al moeilijker te begrijpen
● 3 argumenten (triad) is ongeveer het maximum
toelaatbare aantal
● Zie ook EJ#40
55
56. Functie-argumenten (CC3)
● Geen vlag-argumenten
● = booleans die gedrag veranderen
● Schrijf 2 functies!
● Lange argumentenlijsten
● Gebruik argument-objecten
Circle makeCircle(double x, double y, double radius)
Circle makeCircle(Point center, double radius)
● Varargs tellen als één argument
void monad(Integer... args)
void dyad(String name, Integer... args)
56
57. Creëren van objecten (EJ2)
● Static factory methods ipv constructors (EJ#1)
public static Fraction
valueOf(int numerator, int denominator) {
int g = gcd(numerator, denominator);
return new Fraction(numerator / g, denominator / g);
}
private Fraction(int numerator, int denominator) {
this.numerator = numerator;
this.denominator = denominator;
}
57
58. Voordelen van static factory methods
● Hebben naam, returntype
● Creëren niet noodzakelijk een nieuw object
● caching
● “instance-controlled” klasse, bv. Boolean
● laat toe om te garanderen dat bij immutable klassen
geldt: a.equals(b) als en slechts als a == b
● Kunnen object van subtype teruggeven
58
59. Nadelen van static factory methods
● Onmogelijk overerven van klassen zonder
publieke/protected constructors
● misschien niet echt een nadeel
● niet te onderscheiden van andere static methods
● naamgeving: valueOf(), of(), newInstance(),
getInstance()
59
60. Creëren van objecten (EJ2)
● Builder pattern: voor constructors met
● teveel parameters
● optionele/default parameters
● verschillende parameters van zelfde type
60
65. Voordelen van Builders
● Autocomplete helpt bij invullen van parameters
● Niet meer onthouden in welke volgorde
parameters komen
● Vermijden verschillende ctors voor default-
waarden
● Opleggen van invariants bij objectcreatie
● Verschillende varargs mogelijk
● Makkelijker parameters toevoegen
65
66. Let op bij implementeren equals()
(EJ#8)
● Enkel implementeren wanneer nodig
● Typisch voor “waarde-objecten,” bv. Date,
Integer, Fraction
● Respecteer het “contract” van equals()
66
67. equals() is een equivalentierelatie
● Reflexief: x ≠ null: x.equals(x)
● Symmetrisch: x,y ≠ null: x.equals(y)
y.equals(x)
●
Transitief: x,y,z ≠ null: x.equals(y) en
y.equals(z) x.equals(z)
● Consistent: x,y ≠ null: x.equals(y) geeft
telkens zelfde waarde terug
●
x ≠ null: x.equals(null) geeft altijd false terug
67
68. Recept voor equals(Object o)
1.Controleer of o referentie naar dit object is
● zo ja true
2.Controleer met instanceof of o het correcte
type heeft,
● zo niet false
● zo ja, casten naar juiste type
3.Controleer of elk “significant” attribuut van o
overeenkomt met het corresponderende
attribuut van dit object 68
69. vb. Fraction
(gegenereerd door Eclipse!)
@Override
public boolean equals(Object obj) {
if (this == obj) // 1
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) // 2
return false;
Fraction other = (Fraction) obj;
if (denominator != other.denominator) // 3
return false;
if (numerator != other.numerator)
return false;
return true;
}
69
70. Overschrijf hashCode() als je equals()
overschrijft (EJ#9)
● Zoniet overtreed je contract van
Object.hashCode()
● vb. x,y ≠ null: x.equals(y) x.hashCode() ==
y.hashCode()
● Klasse zal niet werken in HashMap, HashSet,
Hashtable
●
default impl.: verschillend object verschillende
hashCodes
70
72. vb. Fraction
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + denominator;
result = prime * result + numerator;
return result;
}
72
73. hashCode voor ingewikkeld
immutable object
● “Lazily initialized, cached hashCode”
private volatile int hashCode;
@Override public int hashCode() {
if(hashCode == 0) {
final int prime = 31;
int result = 1;
result = prime * result + denominator;
result = prime * result + numerator;
hashCode = result;
}
return hashCode;
}
73
74. Altijd toString() overschrijven (EJ#10)
● Klasse makkelijker te gebruiken
vb. Fraction: “3/5” ipv “Fraction@163b94”
● Bevat zo mogelijk alle interessante info uit object
● Documenteer formaat in javadoc
● Alle info in de string is via accessors/publieke
velden te verkrijgen
74
75. hashCode(), equals() en toString() in
Scala ;-)
case class Fraction(
val numerator: Int,
val denominator: Int)
75
76. Argumenten controleren (EJ#38)
● “Client-code is de vijand”
● Expliciet maken van veronderstellingen over
gebruik van de method
● Vermijden van problemen bij geven van
verkeerde/onverwachte input
● Sneller fouten opsporen
76
77. Argumenten controleren (EJ#38)
● Publieke methods:
● Gebruik IllegalArgumentException en duidelijke
foutboodschap
● Documenteer met Javadoc @throws
● Niet-publieke methods
● Gebruik assert
● Wordt enkel gecompileerd met optie -ea
77
78. Schrijf nooit “return null;”
● Client-code verplicht uitzondering te behandelen
● “null-checks” vervuilen je code
● Aanleiding tot NullPointerException
● Alternatief:
● Exception
● “Leeg” object, bv. Collections.emptyList()
● Opl. in Scala: Option[T] → Some[T] of None
78
79. Exceptions
● EJ#60: bij voorkeur standaard-exceptions
gebruiken
● EJ#62: alle mogelijke exceptions documenteren
met @throws
● EJ#65: niet onder de mat vegen
●
try { … }
catch(SomeException e) {}
●
try { … }
catch(SomeException e)
{ e.printStackTrace(); } 79
80. Checked vs Unchecked Exceptions
● Tegenspraak tussen EJ en CC
● EJ#58: Checked exceptions voor uitzonderlijke
condities, runtime exceptions voor bugs
● daarvoor zijn ze ontworpen!
● CC7: “The debate is over. Use Unchecked
Exceptions.”
● “doorbreken encapsulatie”
● ontbreken van checked exceptions staan robuuste
code niet in de weg
80
81. Checked vs Unchecked Exceptions
● EJ#59. Onnodig gebruik van checked exceptions
vermijden
● “lastig” voor gebruiker API
● nodigt uit slechte foutafhandeling te schrijven
● EJ#64. Streven naar “atomair falen”
● Exception → laat object in toestand van vóór method
call
● cfr. ACID bij databases
81
83. 3 wetten van Test Driven
Development (CC9)
1. Schrijf geen productiecode vóór een
mislukkende unit test
2. Schrijf niet meer in een unit test dan voldoende
om te falen (niet compileren = falen)
3. Schrijf niet meer productiecode dan voldoende
om de falende test te laten slagen
83
84. Aanbevelingen voor Unit tests
● Hou de test-code “clean”, leesbaar
● testcode is even belangrijk als productiecode
● Domeinspecifieke test-taal
● = “utility methods” die testcode leesbaarder maken
● Eén assert per test
● Niet in steen gebeiteld, maar hou minimaal
● → Eén concept per test
● Gebruik code coverage tool & streef naar 100%
● Private method package local maken om te kunnen testen
mag! 84
85. F.I.R.S.T. principe voor Unit Tests
● Fast: je moet tests vaak willen draaien
● Independent: “waterval” van problemen
vermijden
● Repeatable: in ontwikkelings/QA/UA/productie-
omgevingen
● Self-Validating: “wit-zwart”, geen “grijs”
● Timely: tijdig schrijven zorgt voor testbare code
85
87. “Emergent design” (CC12)
● Een ontwerp is “eenvoudig” als het volgende
regels volgt:
● draait alle tests
● bevat geen duplicatie
● is expressief, drukt de bedoeling van de
programmeur uit
● minimaliseert het aantal klassen en functies
● < Kent Beck, “Extreme Programming Explained”
87
88. “Emergent design”
● Met deze regels “ontstaat” een goed ontwerp
“als vanzelf” tijdens het programmeren
● Maakt het makkelijker bv. “Single Responsibility
Principle” of “Dependency Inversion Principle” te
volgen
88
89. Alle tests draaien
● Een goed ontwerp produceert een systeem dat
zich gedraagt zoals bedoeld was
● Zorgen voor testbare code zorgt voor beter
ontwerp
● leidt tot “high cohesion – low coupling”
● Code opkuisen zal functionaliteit niet breken
89
90. Geen duplicatie
● Makkelijkst: identieke lijnen code
● Ook bvb. int size() vs boolean isEmpty()
● met aparte implementatie voor beide
● “Template methods” gebruiken
● Wat met identieke implementatie, maar
verschillende intentie?
90
91. Intentie vs implementatie
(RubySlim voorbeeld)
● “Slim” methodnaam naar Ruby methodnaam:
def slim_to_ruby_method(method_name)
value = method_name[0..0].downcase + method_name[1..-1]
value.gsub(/[A-Z]/) { |cap| "_#{cap.downcase}" }
end
● “Slim” packagenaam omzetten naar
bestandsnaam
def to_file_name(module_name)
value = module_name[0..0].downcase + module_name[1..-1]
value.gsub(/[A-Z]/) { |cap| "_#{cap.downcase}" }
end
http://www.informit.com/articles/article.aspx?p=1313447 91
92. Intentie vs implementatie
● Naar elkaar laten verwijzen?
● Nee: to_file_name moet niets weten van
methodnamen en v.v.
● Hernoemen naar to_camel_case?
● Nee: client-code moet niets weten van
implementatiedetails
● Aparte method to_camel_case + oorspronkelijke
2 er naar laten verwijzen
● = toepassing één niveau van abstractie
92
93. Intentie vs implementatie
def slim_to_ruby_method(method_name)
camel_to_underscore(method_name)
end
def to_file_name(module_name)
camel_to_underscore(module_name)
end
def camel_to_underscore(camel_namme)
value = camel_name[0..0].downcase + camel_name[1..-1]
value.gsub(/[A-Z]/) { |cap| "_#{cap.downcase}" }
end
93
94. Expressiviteit
● Maak systeem makkelijk begrijpbaar
● code drukt uit wat de programmeur bedoelt
● Goede naamgeving
● weergave van verantwoordelijkheden
● gestandaardiseerde naamen (bv. patterns)
● Goed geschreven Unit Test
● = documentatie a.h.v. voorbeeld
94
95. Expressiviteit
● Voldoende aandacht besteden hieraan
● Niet verder doen met iets anders zodra het “werkt”
● Fierheid over je vakmanschap
95
96. Minimaal aantal klassen en methods
● Tegenspraak met “kleine klassen”?
● kan te ver gedreven worden
● mag geen “dogma” zijn (vb. scheiden van data- &
gedrag-klassen)
● evenwicht
● Tests, elimineren duplicatie, expressiviteit zijn
belangrijker
96
98. Commentaar (CC4)
● Commentaar is geen oplossing voor slechte code
● Druk je intentie uit in code
98
99. Goede commentaar
● Wettelijke bepalingen (vb. licentie, copyrigth)
● Informatieve commentaar
// Returns the numerator of this Fraction.
public int getNumerator() {
return numerator;
}
● functienaam zegt het al!
● bv. wél uitleg bij ingewikkelde regexp
99
100. Goede commentaar
● Intentie uitleggen
● Verduidelijking
● vb. betekenis argument/return-waarde
● kan best op andere manier in je eigen code
● bij API-calls geen keuze
● Waarschuwing consequenties
● bv. test die lang duurt
100
101. Goede commentaar
● TODO
● worden bijgehouden in Eclipse, Netbeans
● Javadoc publieke API (cfr. EJ#44)
● Let op, Javadocs kunnen even misleidend zijn dan
andere (slechte) commentaar
● Is dit goede commentaar?
/** Returns the denominator of this Fraction.
* @return the denominator of this Fraction.
*/
public int getDenominator() { return denominator; }
101
102. Slechte commentaar
● “Gebrabbel”
● Redundante commentaar
● zegt hetzelfde als de code (maar dan minder precies)
● legt niets uit over intentie code
● Misleidende commentaar
● te vaag om te kloppen
● Verplichte commentaar
● vb. Javadoc van triviale methods
102
103. Slechte commentaar
● “Log” van wijzigingen
● is werk voor versiebeheersysteem!
● Commentaar als vervanging van goede
variabele-/methodnaam
● Positiemarkeringen
//---------- Accessors --------------------------
● Commentaar bij sluiten accolade
103
104. Slechte commentaar
● Vermeldingen auteurs
● Hoort in versiebeheersysteem
● Code in commentaar
● Vervuilt de code
● Wat is de intentie? Waarom in commentaar?
● Versiebeheer!
● HTML commentaar
104
105. Slechte commentaar
● Niet-lokale informatie
● Teveel informatie
● Onduidelijke link met code
● Functie-headers
● Javadocs in niet-publieke code
105
106. Naamgeving (CC2)
● Namen geven intentie bloot
●
int d; // elapsed time in days
● Vermijd desinformatie
●
private Person[] personList;
● kleine L (l of 1?), hoofletter O (O of 0?)
● Namen zijn uitspreekbaar
106
107. Naamgeving
● Zinvol onderscheid tussen namen
● variabele niet verkeerd spellen om onderscheid te
maken met andere, vb. class ↔ klass
● geen getalseries, vb. a1, a2, a3, …
● redundante namen, vb. denominatorVariable
● Namen zijn zoekbaar
● hoe breder de scope, hoe langer de naam
● variabelen met 1 letter enkel lokaal in korte methods
107
108. Naamgeving
● Vermijd “coderingen”
● “Hongaarse notatie” met type in de naam, vb.
phoneString
● “Member prefix” voor onderscheid met functie-
argumenten, vb. private int mNumerator;
● Interface prefix, vb. IShapeFactory
● Probeer niet grappig te zijn
108
109. Naamgeving
● Consistent: één woord per concept
● fetch ↔ retrieve ↔ get
● Controller ↔ Manager ↔ Driver
● Namen uit oplossingsdomein (vakjargon,
patterns, wiskundige termen, …)
● Namen uit probleemdomein
109
110. Naamgeving
● Klassenamen
● gebaseerd op zelfstandige naamwoorden, vb.
Customer, WikiPage, AddressParser
● vermijd te algemene woorden als Manager,
Processor, Data, Info, Controller
● geen werkwoord
110
111. Naamgeving
● Methodnamen
● gebaseerd op werkwoorden
● accessors/mutators beginnen met get/set
● predicaten beginnen met is
● constructor overloading → factory methods die
argument beschrijven
● vb. Complex(double) →
Complex.fromRealNumber(double)
111
113. Waarom de boeken nog
lezen/kopen?
● Verschillende topics niet aan bod gekomen
● Concurrency (CC13 & appendix A, EJ10)
● Praktijkvoorbeelden refactoring (CC14, CC16)
● “Smells and Heuristics” (CC17)
113