Le dogme SESE : « Un seul return par fonction »

Bonjour les gens,

Je vois encore passer, en 2025, du code comme ça :

def is_prime(n):
    if n <= 1:
        is_prime = False
    else:
        is_prime = True
        for i in range(2, int(n**0.5) + 1):
            if n % i == 0:
                is_prime = False
    return is_prime

Mais POURQUOI ? (ajouter un break résoudrait un problème de perfs, mais il paraît que chez les vrais puristes du SESE, break et continue sont interdits aussi, peu m’importe ici, ce n’est pas mon sujet).

Ma question est, pourquoi ne pas l’écrire de manière lisible :

def is_prime(n):
    if n == 1:
        return False
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            return False
    return True

Étant fan du early return, lire une fonction entièrement codée dans un gros if ou dans un gros else juste parce qu’on s’interdit un return ça me chagrine.

Le return alternatif de FORTRAN IV (1961)

J’ai lu quelque part que cette coutume nous viendrait des développeurs FORTRAN, qui avaient, il y a fort fort longtemps, un return alternatif, une démo s’impose :

   CALL CHECK(A, B, *10, *20, C)
   ...
10 ...
20 ...
   SUBROUTINE CHECK(X, Y, *, *, C)
   ...
50   IF (X) 60, 70, 80
60   RETURN
70   RETURN 1
80   RETURN 2
   END
  • Le premier RETURN est un return normal, donc après la fin de l’exécution de la fonction CHECK l’exécution de la fonction qui a appelé CHECK reprend comme on a l’habitude.
  • Le RETURN 1 renvoie au premier return alternatif qui vaut *10 : donc c’est un saut au label 10.
  • Le RETURN 2 saute au label 20 (indiquée par le *20 côté appelant).

J’imagine (probablement pas assez, ne l’ayant pas vécu) l’enfer, et la règle est apparue « Alternate RETURNs should not be used. ». Le return alternatif aura vécu de 1961 (Fortran IV) à 1990 (F90), l’année de naissance de Python.

Et ça serait peut-être cette règle qui se serait transformée en « un seul return par fonction » qui pollue encore notre code de nos jours.

Clairement « plusieurs return » (différents points de sortie dans la fonction appelée) et « return altenatifs » (plusieurs points d’atterrissage dans la fonction appelante) n’ont rien à voir : on ne peut pas « sauver » la règle Single-Exit en la réécrivant « un seul return par fonction ».

L’entrée alternative de FORTRAN

Dans Single-Entry Single-Exit (SESE) on lit single-entry, tiens c’est quoi ça ?

Ça date de la même époque, en FORTRAN il existait un mot clé ENTRY permettant d’ajouter des points d’entrée alternatifs à une fonction.

Le fait que Single-Entry signifie « n’utilisez pas de points d’entrée alternatifs » me conforte dans l’idée que Single-Exit signifie « n’utilisez pas de return alternatifs ».

Le dogme

Si c’est le cas, c’est donc littéralement un dogme, j’imagine la scène :

Dis, Maître, pourquoi SonarCube dit qu’il faut pas deux return dans ma fonction ?

La sagesse des anciens dit « Single-Entry, Single-Exit ». Nous on fait du code de qualité, on respecte la sagesse des anciens, on respecte MISRA et l’IEC 61508, sécurité, maintenabilité, lisibilité ! Si t’es contre t’es un jeune qui va droit dans le mur, je te le dis moi, y’en a qu’on essayé, ils ont eu des problèmes… Si tu ne veux pas finir comme les développeurs du THERAC 25, réécrit ta fonction à la norme et pose pas de questions.

Maître, c’est quoi alors Single-Entry ?

J’sais pas, retourne bosser, respecte la norme, et pose pas d’questions.

Alors que SESE ne peut pas s’appliquer aux langages qui n’ont pas d’entrée alternatives ou de return alternatifs, la règle ne peut s’appliquer à aucun langage contemporain (même fortran depuis F90 n’a plus ni l’un, ni l’autre).

Le dogme survit

Les tenant du « un seul return » se sont convaincus, et le font vivre, avec des arguments variés :

  • MISRA a dit que
  • L’IEC 61508 a dit que.
  • Dijkstra a dit que, rep à ça !
  • Ça permet de mettre un assert au moment du return.
  • Ça permet de mettre un breakpoint au moment du return.
  • Ça permet de libérer des ressources au moment du return.
  • Le code après un return pourrait ne pas être exécuté.

Ma conclusion

Libérez-vous des dogmes des années 70 !

Rédigez le code qui est le plus lisible à vos yeux en utilisant tous les outils à votre disposition pour ça. Si ça implique parfois de n’utiliser qu’un seul return, d’accord.

Mais si ça implique de tordre le code pour arriver à un seul return, pas d’accord.

Plus de lecture

2 « J'aime »

Première fois que j’entends parler de ce dogme :open_mouth:

Les ignorants sont bénis.

Early return adopter here.

Sans déconner, je préfère mon dogme au leur.

Pareil, plutôt évacuer au plus vite ce qui peut l’être.
Surtout qu’avec les with ou finally y a pas vraiment de raison de s’imposer d’atteindre la dernière ligne de la fonction pour y exécuter des actions particulières, et qu’avec une touche de typage on peut aussi s’assurer que tous les return sont conhérents.

Je l’ai souvent vu appliqué là où les standards de codage étaient faits par des personnes qui ne codaient plus depuis longtemps. J’ai surtout vu ça en C embarqué. Heureusement, je ne le vois pas beaucoup en Python…

1 « J'aime »

J’ai eu droit au “multi return à éviter” en 2010 de la part du CTO de la boîte où je faisais mon stage. Je l’ai appliqué sans conviction jusqu’à ce que le CTO ne regarde plus dans ma direction, puis je me suis remise au early return et je n’ai pas arrêté depuis.

3 « J'aime »

Je répond très en retard mais j’ai la réponse. Ce dogme vient de gens qui travaillaient en C très bas niveau. Et c’est parce que c’est plus simple quand tu debug en hexa/assembleur car ça évite les goto de partout. En python ça n’a pas de sens on finira jamais à se taper de l’assembleur pour comprendre un bug.

1 « J'aime »

Je ne comprend pas cet argument : un return une fois compilé c’est un ret : c’est propre et lisible. Exemple, un early return en début de fonction :

int fct(int arg) {
    if (arg < 2) {
        return arg == 1; 
    }
    ...

Peut se compiler en :

fct:
    cmp    rdi, 2
    ja     .above_2
    cmp    rdi, 1
    sete   rax
    ret
.above_2:
    ...

C’est assez littéral comme compilation, un return done un ret, le early-return se fait au début de la fonction en C, il se fait au début de la fonction en assembleur, les cas particuliers sont vite éliminés dans la tête du relecteur : pas de surprises.

Aussi je ne comprend pas l’argument car les compilateurs se prennent une sacré liberté à la compilation, je viens de faire un test avec :

bool multi_return(size_t x) {
    if (x <= 2) {
        return x == 1;
    }
    double limit = sqrt(x) + 1;
    for (size_t i = 2; i <= limit; i++) {
        if (x % i == 0)
            return false;
    }
    return true;
}

bool single_return(size_t x) {
    bool is_prime = true;

    if (x <= 1) {
        is_prime = false;
    }
    else if (x == 2) {
        is_prime = true;
    }
    else {
        double limit = sqrt(x) + 1;
        for (size_t i = 2; i <= limit; i++) {
            if (x % i == 0) {
                is_prime = false;
                break;
            }
        }
    }
    return is_prime;
}

les deux donnent un assembleur assez proche, avec gcc 15.2, les deux fonctions utilisent un ret dès le début pour gérer les cas particuliers.

Tu parles peut-être de compilateurs anciens ? Tu as des sources qu’on puisse étudier ?

Désolé j’ai oublié de préciser dans mon message précédent que c’était sur des vieux trucs.

Donc oui c’était sur des très vieux trucs genre avant GCC 4 et y a même de forte chance que ce ne soit même pas sur GCC qu’on voyait ça. Si je dis pas de bêtise c’était peut-être sur 68000 mais à prendre avec des pincettes c’était y a plus de 20 ans et je n’ai pas vraiment pratiqué juste vu et expliqué par des profs. Je dirais que c’était pendant des cours sur OS-9 (pas celui d’apple) ou alors peut être sur AS400.

Hélas mes sources étaient ces profs qui sont à la retraite depuis bien longtemps.

Je pense qu’effectivement ça n’a pas lieu de se prendre la tête là-dessus, mais ce sont plus des habitudes qui ont été transmises. Surtout que le bas niveau on en mange plus tous les matins, car les compilos sont bien meilleurs que nous et qu’au final il existe des outils qui sont bien plus pratiques de nos jours que d’aller voir l’assembleur. Certes il peut rester des cas où ça arrive mais perso le dernier cas que j’ai croisé c’était y a 15 ans. Oui je ne fais plus de C depuis 10 ans mais les gens autour de moi qui continue n’utilisent plus non plus l’assembleur pour debugger.

L’avantage de savoir d’où ça vient ça permet de comprendre si c’est utile ou pas à l’heure actuelle. Après y a des gens dogmatiques et ça sera toujours compliqué de parler avec mais au moins le fait d’avoir la compréhension permet de prendre du recul par rapport à ces dogmes. Et vu l’age des gens qui ont travaillé avec ces problemes ça devrait pas durer eternellement :slight_smile: .