Qualité de code

Amélioration de la lisibilité du code

Any stupid can write the program that computer understands but only good programmers write code that humans understand

— Martin Fowler

Programs must be written for people to read, and only incidentally for machines to execute

— Harold Abelson

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

Introduction

Définition générale de «Qualité»

Degré pour lequel un système, un composant ou un processus répond aux besoins spécifiés.

Différents points de vue

utilisateurs, développeurs, gestionnaires, etc.

Différents facteurs

robustesse, efficacité, portabilité, maintenabilité, etc.

La qualité est une question de point de vue

  • Ici, nous nous intéressons au point de vue des développeurs et à la maintenabilité du code.

    Critères de maintenabilité

    concision, cohérence, modularité, simplicité, documentation interne.

Principes

  • Plus le code est simple à lire, plus il est maintenable.

  • Le code doit réduire le temps de lecture et compréhension pour les autres développeurs.

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

Conventions de codage

La plupart des langages n’ont pas de règles complexes pour le nommage de fonctions et variables:
  • N’importe quelle chaine de caractères alphanumériques est un identifiant valide.

   

Cependant, tous les langages ont des règles d’écriture appelées «conventions de codage».
  • Proposées par les créateurs du langage ou par la communauté de développeurs.

Conventions de codage (Cont.)

Parfois, plusieurs conventions sont disponibles pour un même langage:
  • Pour assurer la cohérence, chaque projet ne doit adopter qu’une seule convention.

  • Utiliser la même convention à l’intérieur d’un projet est plus important qu’adopter la "meilleure" convention.

En TypeScript

  • Variables et fonctions utilisent le "camelCase": on utilise les majuscules pour séparer les noms composés:

Nom

camelCase

"first name"

firstName

"stop threads and quit"

stopThreadsAndQuit

Conseillé:

let message : string = 'Hello world!'
let maxNumberOfWords = 15
function writeLine(line : string) {...}

Déconseillé:

let MESSAGE : string = 'Hello world!'
let max_number_of_words = 15
function WriteLine(line : string) {...}
  • Interfaces et Classes utilisent le "PascalCase": la première lettre de chaque mot est en majuscule:

Nom

PascalCase

"gregorian calendar"

GregorianCalendar

"book library"

BookLibrary

Conseillé:

interface LabelledValue {...}
interface Point3D {...}
interface GraduateStudent {...}

Déconseillé:

interface Labelled_Value {...}
interface POINT3D {...}
interface graduatestudent {...}

Autres Styles de Nommage

Kebab Case
kebab-case-looks-like-this
max-parallel-events
Snake Case
snake_case_looks_like_this
another_variable_name
Screaming Snake Case
ALSO_KNOWN_AS_UPPER_CASE_EMBEDDED_UNDESCORE
COMMONLY_REFERS_TO_CONSTANTS

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

Nommage de variables et de fonctions.

Une question de taille

Évitez les identifiants d’un seul caractère:
  • p. ex. a, b, f.

La taille doit être proportionnelle à la portée:
  • Petite portée, noms courts.

  • Large portée, noms longs.

Une question de taille (Cont.)

Une variable globale
declare var maximumNumberOfActiveThreads: number;
Une variable dont la portée est restreinte
function sumArray(values : number[]): number {
    let sum: number;
    for (let each of list) {
        sum = sum + each;
    }
    return sum;
}

Une question de précision

binary tree
  • Les noms des identifiants doivent être précis.

    • p. ex. une fonction qui calcule la taille d’un arbre:

function size(tree: Tree): number {
    //(...)
}
  • De quoi s’agit-il? De la hauteur? Du nombre d’éléments? De l’empreinte mémoire?

Une question de précision (Cont.)

binary tree

Alternatives:

function height(tree: Tree): number {}

function numberOfNodes(tree: Tree): number {}

function footprint(tree: Tree): number {}

Évitez les noms génériques

  • Identifiants génériques tels que tmp, aux ou retval veulent souvent dire:

— Je ne sais pas comment appeler ma variable !

 

function euclideanNorm(values: number[]): number {
    let retval: number = 0;
    for(let i : number = 0; i  < values.length ; i+=1) {
        retval += values[i] * values[i]
    }
    return Math.sqrt(retval);
}

Évitez les noms génériques (Cont.)

  • Le code précédent est lisible, mais l’identifiant de la variable (retval) n’apporte aucune information.

  • Maintenant, supposons que le développeur ait remplacé par erreur * par +:

retval += values[i] + values[i]
sumOfSquares += values[i] + values[i]

Quelle erreur est la plus visible ?

Exception

Parfois tmp désigne vraiment une valeur temporaire:
    if(begin > end) {
        let tmp: number = end;
        end = begin;
        begin = tmp;
    }

Itérateurs de boucle

  • Traditionnellement, les identifiants i, j et k désignent des itérateurs de boucle:

for(let i : number = 0; i < 10; i+=1) {
    for(let j : number = 0; j < 20; j+=1) {
        for(let k : number = 0; k < 40; k+=1) {
            // instructions
        }
    }
}

Valeurs avec unités

  • Parfois, ajouter l’unité de mesure simplifie la lisibilité.

  • p. ex. la fonction wait(delay):

function wait(delay: number){}
  • De quelle unité de temps s’agit-il? Secondes? Millisecondes?

  • Alternative:

function wait(delayMilliseconds: number){}

Valeurs avec unités (Cont.)

Considérez par exemple:

let start: number = now();
treatmentThatTakesTime();
let elapsed = now() - start;
console.log("The execution took " + elapsed.toString() + "seconds")

Et:

let startMilliseconds: number = now();
treatmentThatTakesTime();
let elapsedMilliseconds = now() - startMilliseconds;
console.log("The execution took " +
    elapsedMilliseconds.toString() + "seconds")

Lequel des deux morceaux de code révèle plus facilement l’erreur?

Autres exemples

IdentifiantIdentifiant enrichi

name

unicodeName

path

absoluteFilePath, relativeFilePath

password

encodedPassword

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

Mauvaises interprétations

  • Certains identifiants sont source de confusion.

  • Par ex.:

results = filter(clients, "birthyear  > 2000")
Qu’est-ce qu’on filtre exactement?
  • Les clients nés après l’année 2000?

  • Ou le contraire?

    • Si le premier cas: select() est un meilleur nom.

    • Si le deuxième, reject() ou exclude()?

– Il s’appelle Juste Leblanc.

– Ah bon, il n’a pas de prénom ?

– Je viens de vous le dire Juste Leblanc… Votre prénom c’est François, c’est juste ? Eh bien lui c’est pareil, c’est Juste.

Le dîner de cons
— Pierre Brochant et François Pignon

Limites

  • Utilisez min et max pour les limites

  • Supposez que vous souhaitez limiter à 24 le nombre d’articles dans un panier et que vous déclarez la constante suivante:

const tooManyCartItems: number = 24;

Le problème ici c’est que l’on ne peut pas dire s’il s’agit une limite inclusive ou pas. Est-ce que le panier peut avoir 24 articles ou pas?

Un meilleur identifiant:
const maxNumberOfCartItems: number = 24;
  • Ici, la limite est clairement inclusive.

Intervalles

  • Utilisez first et last, plutôt que start, stop, pour les intervalles inclusifs:

first last

Intervalles (Cont.)

  • Parfois, il est utile d’utiliser des intervalles inclusifs/exclusifs

  • Par exemple, ils est plus simple d’écrire:

todayEvents = eventsInRange("22/1/2018 00:00", "23/1/2018 00:00")

que

todayEvents = eventsInRange("22/1/2018 00:00", "22/1/2018 23:59")
  • Dans ces cas, utilisez begin et end pour les intervalles inclusifs/exclusifs:

begin end

Booléens

  • Traditionnellement, les identifiants booléens sont précédés par «is», «has» ou «need» pour éviter l’ambiguïté.

Par exemple:
let readPassword: boolean = true;
  • Est-ce que la variable dit que le mot de passe a été lu, ou qu’il doit être lu?

De meilleurs choix:
let isAuthenticated : boolean = true;
let needPassword    : boolean = true;

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

No matter how slow you are writing clean code, you will always be slower if you make a mess.

— Uncle Bob Martin

Esthétique

  • Un programme bien formaté est plus lisible qu’on programme mal formaté.

Exemple: calcul du pgcd:
function pgcd(a: number, b: number): number
{
	while (a!=b){
	if (a>b){ a-=b ;}
	else {b-=a;}
	}
	return a;}

Esthétique (Cont.)

Exemple: calcul du pgcd
function pgcd(a: number, b: number): number
{
	while (a!=b){
	if (a>b){ a-=b ;}
	else {b-=a;}
	}
	return a;}
La même fonction bien formatée
// Calcul du plus grand commun diviseur de
// deux entiers
function pgcd(a: number, b: number): number {
    while (a != b) {
        if (a > b) {
            a = a - b;
        }
        else {
            b = b - a;
        }
    }
    return a;
}

Tabulations

Sans:

title = request.POST.get('title')
email = request.POST.get('email')
location = request.POST.get('location')
email = request.POST.get('email')
url = request.POST.get('url')
details = request.POST.get('details')

Avec:

title    = request.POST.get('title')
email    = request.POST.get('email')
location = request.POST.get('location')
email    = request.POST.get('email')
url      = request.POST.get('url')
details  = request.POST.get('details')

Laissez respirer le code

function validateEmail(email: Email): string {
 let error:string="";
 let tfl:string = email.value.trim();
 let emailFilter:RegExp = /^[^@]+@[^@.]+\.[^@]*\w\w$/ ;
 let illegalChars:RegExp = /[\(\)\<\>\,\;\:\\\"\[\]]/ ;
 if (email.value == "") {
     email.style.background = 'Yellow';
     error = "You didn't enter an email address.\n";
 } else if (!emailFilter.test(tfld)) {
     email.style.background = 'Yellow';
     error = "Please enter a valid email address.\n";
 } else if (email.value.match(illegalChars)) {
     email.style.background = 'Yellow';
     error = "The email address contains illegal characters.\n";
 } else {
     email.style.background = 'White';
 }
 return error;
}
  • Utilisez les lignes vides pour créer des strophes de code:

function validateEmail(email: Email): string {
    const emailFilter : RegExp = /^[^@]+@[^@.]+\.[^@]*\w\w$/;
    const illegalChars: RegExp = /[\(\)\<\>\,\;\:\\\"\[\]]/;

    let error   : string = "";
    let tfl     : string = email.value.trim();

    if (email.value == "") {
        email.style.background = 'Yellow';
        error = "You didn't enter an email address.\n";

    } else if (!emailFilter.test(tfld)) {
        email.style.background = 'Yellow';
        error = "Please enter a valid email address.\n";

    } else if (email.value.match(illegalChars)) {
        email.style.background = 'Yellow';
        error = "The email address contains illegal characters.\n";

    } else {
        email.style.background = 'White';
    }

    return error;
}

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

A programmer was arrested for writing unreadable code.

He refused to comment.

Commentaires

  • Un code bien commenté est plus lisible qu’un code sans commentaires.

   

beaucoup commenter ≠ bien commenter !

   

  • Un code avec trop de commentaires inutiles est souvent plus difficile à lire qu’un code sans commentaires.

Un mauvais commentaire paraphrase le code

// Function declaration
function factorial(num: number): number {
    let factorial:number = 1; // Assigns 1 to factorial
    let i: number = num; // Assigns num to i

    while(i >= 1) {  // loops while i >= 1
        // assigns factorial with de product of factorial and i
        factorial = factorial * i;
        i--; // decrements i
     }
     return factorial; // return factorial
}
Tous les commentaires ci-dessus sont inutiles.

Quelques conseils

  • Ne commentez pas ce qui peut être déduit directement du code.

  • Ne commentez pas juste pour commenter.

  • Soyez précis!

  • Mettez-vous à la place de la personne qui lira votre code:

    • Anticipez les questions probables

    • Annoncez les pièges

    • Donnez une vision d’ensemble (résumé de l’algorithme)

Ne commentez pas les mauvais identifiants: renommez-les !

// Total number of active threads for handling requets
const threads: number = 5

Un changement du nom de l’identifiant peut remplacer un commentaire:

const numberOfActiveThreads = 5

Inclure les «commentaires du réalisateur»

  • Les «commentaires du réalisateur» sont des informations additionnelles dont seul le développeur dispose:

// I compared the performance several times using from 1 to 20 active
// threads and find out that 5 active threads achieves the best
// performance for 8 cores CPU.
const numberOfActiveThreads = 5;
/*
after hours of consulting the tome of google
i have discovered that by the will of unknown forces
without the below line, IE7 believes that 6px = 12px
*/
font-size: 0px;
  • Ce genre de commentaire peut aussi expliquer l’état du code:

// After adding new features for version 3.0 (November 2017), the
// code became fragile. Maybe we should split this function into
// two others, displayRaw() and displayHTML().
function display(){}
//
// Dear maintainer:
//
// Once you are done trying to 'optimize' this routine,
// and have realized what a terrible mistake that was,
// please increment the following counter as a warning
// to the next guy:
//
// total_hours_wasted_here = 42
//

Commentez les failles du code

Table 1. Quelques commentaires spéciaux
CommentaireDescription

XXX

Mauvais code, mais qui fonctionne

FIXME

Mauvais code, qui ne fonctionne pas

TODO

A faire

HACK

Code réservé aux spécialistes

Commentaires internes et externes

  • Les commentaires externes à une fonction s’adressent aux utilisateurs.

  • Les commentaires internes s’adressent aux autres développeurs.

// Calculates the factorial of a number.
function factorial(num: number): number {
    let factorial:number = 1;
    let i: number = num;

    // A loop has better performance than recursive calls.
    while(i >= 1) {
        factorial = factorial * i;
        i--;
     }
     return factorial;
}

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

Expressions booléennes

  • L’ordre de lecture d’une expression est importante:

if (length > 10)
  • est plus simple à lire que:

if (10 < length)

Opérateur ternaire

  • TypeScript propose un opérateur ternaire (opérateur conditionnel en une seule ligne).

  • Syntaxe: cond ? a : b (si condition alors a, sinon b)

  • Dans certains cas, le code est lisible et compact:

timeString = (hour > 12) ? 'pm' : 'am';
  • Mais dans la plupart des cas, le code devient plus complexe:

let isLeapYear: boolean = ((year % 400) == 0) ? true :
                            (((year % 100) == 0)? false :
                            (((year % 4) == 0)  ? true : false));

Variables explicatives

  • L’introduction de variables auxiliaires explicatives permettent d’améliorer la lisibilité d’une expression complexe.

  • Ces variables sont souvent plus utiles que les commentaires.

  • Par exemple, considérez le code C++ suivant:

float _x = abs(x - deviceInfo->position.x) / scale;
int directionCode;
if (0 < _x && x != deviceInfo->position.x) {
  if (0 > x - deviceInfo->position.x) {
    directionCode = 0x04 /*left*/;
  } else if (0 < x - deviceInfo->position.x) {
    directionCode = 0x02 /*right*/;
  }
}
  • Le même code, sans commentaires, mais avec des variables explicatives:

static const int DIRECTIONCODE_RIGHT = 0x02;
static const int DIRECTIONCODE_LEFT = 0x04;
static const int DIRECTIONCODE_NONE = 0x00;

int oldX = deviceInfo->position.x;
int directionCode
  = (x > oldX)? DIRECTIONCODE_RIGHT
  : (x < oldX)? DIRECTIONCODE_LEFT
  : DIRECTIONCODE_NONE;

Retour anticipé d’erreurs

  • Souvent, il est recommandé de n’avoir qu’une instruction de retour dans une fonction.

  • Dans certains cas, comme dans la vérification des paramètres, le retour anticipé simplifie la lecture:

function contains(str: string , substr: string ): boolean {
    if (str == null || substr == null) return false;
    if (substr === "") return true;

    // (...)
}

Expressions conditionnelles

  • Utilisez les expressions positives plutôt que les négatives.

  • Préférez:

if (a == b) {
    // Cas 1
} else {
    // Cas 2
}

à:

if (a != b) {
    // Cas 2
} else {
    // Cas 1
}

Remplacez:

if (<booleanExpression>) {
    return true;
} else {
    return false;
}

Par:

return <booleanExpression>;

Par exemple, remplacez:

if (a > b) {
    return true;
} else {
    return false;
}

Par:

return (a > b);

Plan

  • Introduction

  • Conventions de codage

  • Nommage de variables et de fonctions

  • Éviter des mauvaises interprétations

  • Esthétique

  • Commentaires

  • Simplification du flot de contrôle

  • Conclusion

L’intention, la raison et la manière

  • Les commentaires externes doivent expliquer ce que fait une fonction (l’intention).

  • Les commentaires internes doivent expliquer pourquoi le code a été écrit comme ça (la raison).

  • Le code source doit être assez clair pour expliquer comment il s’exécute (la manière).

Conclusion

  • La lisibilité du code a un impact important sur la maintenabilité d’un logiciel.

  • La lisibilité ne concerne pas seulement la qualité des commentaires:

    • le choix des identifiants et la simplicité du flot de contrôle sont tout aussi importants.

Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do. .

Essay on Literate Programming
— Douglas Knuth

Final Quote

psychopath

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

— Martin Golding