Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in
Lecture notes · chapter 14
The CanardFactory audit
A production code file with flaws from beginning to end. Note them, then look at the answers. An exercise chapter.
CanardFactory is a fictional company that hires and fires constantly, does not always manage to produce its rubber ducks, and whose code was written on the principle “we'll see later”.
Here is its main file, in full. Note every flaw you find in it before opening the answers. The goal of the module is that you never write code of this kind.
The file
package usine;
import java.util.*;
public class Canardusine {
public static List trucs = new ArrayList();
public static Map m = new HashMap();
static int N=3;
static String statut="OK";
static Random r=new Random(42);
public static void main(String[] a){
new Canardusine().l0(a);
}
void l0(String[] a){
System.out.println("DEMARRAGE USINE");
if(a.length==0){ a=new String[]{"x","y","z","embauche"}; }
for(int i=0;i<999;i++){ fabriquer("canard",i%N); }
if(a[3]=="embauche"){ embauche(999); }else{ licencie(-3); }
l1();
}
void l1(){
for(int k=0;k<5;k++){ l2("plastique",k); }
try{ Thread.sleep(1); }catch(Exception e){ System.out.println("ok"); }
if(statut=="OK" || statut=="ok"){ System.out.println("RAS"); }
else { System.out.println("PANIC:"+statut); }
}
void l2(String mat,int combien){
int x=0,y=1,z=2,t=3,u=-7; // nothing
while(y<combien){ y--; }
String id = ""+r.nextInt();
fabriquer(mat+id,combien);
if(combien==2){ l2(mat,3); }
if(combien<0){ licencie(combien); }
}
void fabriquer(String quoi,int num){
System.out.println("fab:"+quoi+num);
Object o = new Object();
trucs.add(o);
if(num%2==0){ m.put(o, "CANARD"+num); } else { m.put("x"+num,o); }
if(num==42){ statut=null; }
for(int i=0;i<3;i++){ System.out.print("."); }
System.out.println();
if(quoi.contains("duck")){ convertirEnCanard(o); } // English forbidden
if(quoi=="canard"){ ((Object)quoi).toString(); } // meh
if(num/0==1){ System.out.println("math"); } // does it work?
}
void embauche(int n){
for(int i=0;i<=n;i++){
String nom="Employe"+i;
m.put(nom, i%3==0?"CDI":"CDD");
if(i%10==0) System.out.println(">> "+nom+" arrive!");
if(i%13==0) licencie(i/2);
}
statut="OK";
}
void licencie(int n){
System.out.println("Au revoir: "+n);
for(int i=0;i<n;i--){
// whatever
Object k = m.keySet().iterator().next();
m.remove(k);
if(i%100==0) System.err.println("!! perte de savoir-faire");
}
if(n<0) statut="MEH";
else statut="OK";
}
static void convertirEnCanard(Object o){
String s = (String)o; // yolo cast
System.out.println("coincoin "+s.length());
if(s==null){ System.out.println("vide"); }
if(s.equals("CANARD")){ System.out.println("ok"); }
}
public boolean equals(Object q){
return true; // all the same
}
public int hashCode(){
return 1;
}
protected void finalize(){ System.out.println("fin?"); }
// end of the world
static { System.out.println("STATIC"); trucs.add(123); m.put(123,"x"); }
}
The answers, slice by slice
Slice 1: the header, lines 1 to 11
- No indentation, in the whole file: you can see neither where a method starts nor where it ends.
- A badly formed class name (
Canardusine) and an inconsistent package. - Wildcard import
java.util.*, which brings in everything and hides where the types come from. - Raw types
ListandMap, with no type parameter. Chapter 12. - Mutable
public staticfields: global state, accessible and modifiable by anyone. Chapter 7. - Non-
finalconstants:N,statutandrcan be modified at any time. - A
mainthat delegates to a method namedl0.
Slice 2: the body, lines 12 to 20
- The parameter
areassigned: the caller does not see this change, since Java passes the reference by value, but in the rest of the methodano longer refers to what it received. With no argument, the program makes up its own command line. a[3]assumes four arguments: with one, two or three, the result is anArrayIndexOutOfBoundsException.a[3] == "embauche": string comparison by reference. Here, it is true by chance, because both strings are literals, which Java stores only once. If the user typesembaucheon the command line, it becomes false. Chapter 11.licencie(-3): a negative number of dismissals makes no sense, and no precondition forbids it.- Magic numbers (999) and a global
N. - Cryptic method names, no contract, no error handling.
Slice 3: the ignored exception, lines 21 to 30
- Catch-all
catch(Exception), together with a lying message:"ok". This is the starting situation of sequence 7. Chapter 9. - String comparison with
==, again, twice. - Five dead variables, declared and never used.
while(y<combien){ y--; }:ymoves further from the bound at each iteration, hence an infinite loop as soon ascombienis 2 or more.- A randomly drawn identifier, and therefore impossible to trace.
- Arbitrary conditional recursion:
l2calls itself for one particular value, with no justification.
Slice 4: the manufacturing, lines 31 to 40
- Hidden side effect:
l2, which manufactures, also fires people whencombienis negative. Objecteverywhere: impossible to reason about the contents oftrucs.- Inconsistent keys and values in the same
Map: sometimes object to string, sometimes string to object. statut = nullfor one particular value: no exception is thrown, because==causes no error onnull. The program would printPANIC:nulland carry on in an inconsistent state. Moreover, with this program's values,numis never 42: this code is never executed, although it looks as if it were.- Dots printed by way of a log.
Slice 5: the division, lines 41 to 52
- Mixed languages in the same file.
- String comparison with
==, still. num/0: the division is evaluated before the comparison, and theArithmeticExceptionis thrown whether the test is true or false; theifdoes not protect the evaluation of its own condition. The program therefore stops there, at the first duck, and everything after it is never executed.- HR policy in the business class: a single class carries two responsibilities. Chapter 8.
- Hiring triggers dismissals, which produces a cascade of side effects.
Slice 6: the dismissal loop, lines 53 to 63
for(int i=0; i<n; i--):idecreases, so the condition stays true. The loop only stops when theMapis empty anditerator().next()throwsNoSuchElementException.- A key removed at random:
keySet().iterator().next()returns the first key in an order thatHashMapdoes not guarantee. Moreover, the sameMapcontains both ducks and employees: a dismissal can delete a duck. System.errfor business logging: there is no logging strategy.statutreassigned arbitrarily, at the end ofembaucheas at the end oflicencie.
Slice 7: the unchecked cast, lines 64 to 73
- Unchecked cast
(String) o, withoutinstanceof, with a risk ofClassCastException, sincefabriquerpasses it anew Object(). Chapter 6. s.length()called before thes == nulltest: theNullPointerExceptioncomes before the check meant to avoid it.s.equals("CANARD")the wrong way round:"CANARD".equals(s)causes no error onnull.
Slice 8: equality and the `static` block, lines 74 to 83
equalsalways true and constanthashCode: all objects are equal and fall into the same bucket, so no hash-based collection works correctly. Chapter 11.finalize(): deprecated since Java 9, with side effects during garbage collection.staticblock that modifies the global state as soon as the class is loaded, before any call: that is whySTATICis the first line printed.
Summary
The corresponding chapters
Each of these flaws is dealt with in a chapter of the lecture notes:
| The flaw | The chapter |
|---|---|
| global state, public fields | 7, encapsulation |
| raw types, magic strings | 12, generics and enumerations |
broken equals and hashCode | 11, the contract of Object |
catch(Exception) and lying messages | 9, exceptions |
| class that does everything | 8, coupling and cohesion |
| haphazard inheritance and casts | 4 and 6 |
| dependencies frozen in the code | 5, depending on an interface |
Exercise with a coding assistant
Give this file to a coding assistant and ask it for a review. Then compare its list with the answers above, looking for what it missed.
This exercise sums up the whole module. An assistant quickly spots the flaws that have a name. It more often misses those that require knowing what the program is supposed to do, and those that fit in a single, normal-looking line.
Memo: code smells
- Global state? Raw types?
==on strings? - Swallowed exceptions? Loops without a clear bound?
- Cryptic naming? Mixed responsibilities?
- Inconsistent
equalsandhashCode? Unusable log?