Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter
Support de cours · chapitre 14
L'audit de CanardFactory
Un fichier de code de production qui comporte des défauts du début à la fin. Relevez-les, puis consultez les corrigés. Chapitre d'exercice.
CanardFactory est une entreprise fictive qui embauche et licencie sans arrêt, qui ne réussit pas toujours à produire ses canards en plastique, et dont le code a été écrit selon le principe « on verra plus tard ».
Voici son fichier principal, en entier. Relevez tous les défauts que vous y trouvez avant d'ouvrir les corrigés. L'objectif du module est que vous n'écriviez jamais de code de ce genre.
Le fichier
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; // rien
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); } // anglais interdit
if(quoi=="canard"){ ((Object)quoi).toString(); } // bof
if(num/0==1){ System.out.println("math"); } // ça passe ?
}
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--){
// au pif
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; // cast yolo
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; // tout pareil
}
public int hashCode(){
return 1;
}
protected void finalize(){ System.out.println("fin?"); }
// fin du monde
static { System.out.println("STATIC"); trucs.add(123); m.put(123,"x"); }
}
Les corrigés, tranche par tranche
Tranche 1 : l'en-tête, lignes 1 à 11
- Aucune indentation, sur tout le fichier : on ne voit ni où commence une méthode, ni où elle finit.
- Nom de classe mal formé (
Canardusine) et paquetage incohérent. - Import global
java.util.*, qui ramène tout et masque l'origine des types. - Types bruts
ListetMap, sans paramètre de type. Chapitre 12. - Champs
public staticmutables : de l'état global, accessible et modifiable par n'importe qui. Chapitre 7. - Constantes non
final:N,statutetrpeuvent être modifiées à tout moment. - Un
mainqui délègue à une méthode nomméel0.
Tranche 2 : le corps, lignes 12 à 20
- Le paramètre
aréaffecté : l'appelant ne voit pas ce changement, puisque Java passe la référence par valeur, mais dans la suite de la méthodeane désigne plus ce qu'elle a reçu. Sans argument, le programme se fabrique une ligne de commande. a[3]suppose quatre arguments : avec un, deux ou trois, on obtient uneArrayIndexOutOfBoundsException.a[3] == "embauche": comparaison de chaînes par référence. Ici, elle est vraie par hasard, parce que les deux chaînes sont des littéraux, que Java ne stocke qu'une fois. Si l'utilisateur tapeembauchesur la ligne de commande, elle devient fausse. Chapitre 11.licencie(-3): un nombre négatif de licenciements n'a pas de sens, et aucune précondition ne l'interdit.- Nombres magiques (999) et
Nglobal. - Noms de méthodes cryptiques, aucun contrat, aucune gestion d'erreur.
Tranche 3 : l'exception ignorée, lignes 21 à 30
catch(Exception)fourre-tout, accompagné d'un message mensonger :"ok". C'est la situation de départ de la séquence 7. Chapitre 9.- Comparaison de chaînes par
==, encore, deux fois. - Cinq variables mortes, déclarées puis jamais utilisées.
while(y<combien){ y--; }:ys'éloigne de la borne à chaque tour, d'où une boucle infinie dès quecombienvaut 2 ou plus.- Un identifiant tiré au hasard, donc impossible à tracer.
- Récursion conditionnelle arbitraire :
l2s'appelle elle-même pour une valeur particulière, sans justification.
Tranche 4 : la fabrication, lignes 31 à 40
- Effet de bord caché :
l2, qui fabrique, licencie aussi quandcombienest négatif. Objectpartout : impossible de raisonner sur le contenu detrucs.- Clés et valeurs incohérentes dans la même
Map: tantôt objet vers chaîne, tantôt chaîne vers objet. statut = nullpour une valeur particulière : aucune exception n'est levée, car==ne provoque pas d'erreur surnull. Le programme afficheraitPANIC:nullet continuerait dans un état incohérent. Par ailleurs, avec les valeurs de ce programme,numne vaut jamais 42 : ce code n'est jamais exécuté, bien qu'il en ait l'air.- Des points affichés en guise de journal.
Tranche 5 : la division, lignes 41 à 52
- Mélange des langues dans le même fichier.
- Comparaison de chaînes par
==, toujours. num/0: la division est évaluée avant la comparaison, et l'ArithmeticExceptionest levée que le test soit vrai ou faux ; leifne protège pas l'évaluation de sa propre condition. Le programme s'arrête donc là, dès le premier canard, et tout ce qui suit n'est jamais exécuté.- La politique RH dans la classe métier : une même classe porte deux responsabilités. Chapitre 8.
- Embaucher déclenche des licenciements, ce qui produit des effets de bord en cascade.
Tranche 6 : la boucle de licenciement, lignes 53 à 63
for(int i=0; i<n; i--):idécroît, donc la condition reste vraie. La boucle ne s'arrête que lorsque laMapest vide, et qu'iterator().next()lèveNoSuchElementException.- Une clé supprimée au hasard :
keySet().iterator().next()rend la première clé dans un ordre queHashMapne garantit pas. De plus, la mêmeMapcontient les canards et les employés : un licenciement peut supprimer un canard. System.errpour la journalisation métier : il n'y a aucune stratégie de journalisation.statutréaffecté arbitrairement, à la fin d'embauchecomme à la fin delicencie.
Tranche 7 : le cast non vérifié, lignes 64 à 73
- Cast non vérifié
(String) o, sansinstanceof, avec un risque deClassCastException, puisquefabriquerlui passe unnew Object(). Chapitre 6. s.length()appelé avant le tests == null: laNullPointerExceptionprécède la vérification censée l'éviter.s.equals("CANARD")dans le mauvais sens :"CANARD".equals(s)ne provoque pas d'erreur surnull.
Tranche 8 : l'égalité et le bloc `static`, lignes 74 à 83
equalstoujours vrai ethashCodeconstant : tous les objets sont égaux et tombent dans le même seau, si bien qu'aucune collection fondée sur le hachage ne fonctionne correctement. Chapitre 11.finalize(): obsolète depuis Java 9, avec des effets de bord au ramassage des miettes.- Bloc
staticqui modifie l'état global au simple chargement de la classe, avant tout appel : c'est pourquoiSTATICest la première ligne affichée.
Bilan
Les chapitres correspondants
Chacun de ces défauts est traité dans un chapitre du polycopié :
| Le défaut | Le chapitre |
|---|---|
| état global, champs publics | 7, encapsulation |
| types bruts, chaînes magiques | 12, génériques et énumérations |
equals et hashCode cassés | 11, le contrat d'Object |
catch(Exception) et messages mensongers | 9, les exceptions |
| classe qui fait tout | 8, couplage et cohésion |
| héritage et casts hasardeux | 4 et 6 |
| dépendances figées dans le code | 5, dépendre d'une interface |
Exercice avec un assistant de code
Donnez ce fichier à un assistant de code et demandez-lui une revue. Comparez ensuite sa liste aux corrigés ci-dessus, en cherchant ce qu'il a manqué.
Cet exercice résume l'ensemble du module. Un assistant repère vite les défauts qui portent un nom. Il manque plus souvent ceux qui demandent de savoir ce que le programme est censé faire, et ceux qui tiennent dans une seule ligne d'apparence normale.
Mémo : les odeurs de code
- État global ? Types bruts ?
==sur des chaînes ? - Exceptions avalées ? Boucles sans borne claire ?
- Nommage cryptique ? Responsabilités mêlées ?
equalsethashCodeincohérents ? Journal inexploitable ?