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
37. Waarom?
Overerving breekt encapsulatie
Subklassen hangen af van implementatie superklasse
Superklasse wijzigen problemen in subklassen
Compilatie
Verkeerd gedrag
Beveiligingsproblemen
Oplossing: wrapper class
37
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