Site WWW de Laurent Bloch
Slogan du site

ISSN 2271-3905
Cliquez ici si vous voulez visiter mon autre site, orienté vers des sujets moins techniques.

Pour recevoir (au plus une fois par semaine) les nouveautés de ce site, indiquez ici votre adresse électronique :

Programmation TypeScript avec ChatGPT, quatrième épisode
Un piège de Commander, le typage !
Article mis en ligne le 21 octobre 2025

par Laurent Bloch

La question

Bonjour, j’ai écrit une classe de calcul de nombres de Fibonacci :

export class Fib {
    public fib_iter(a: number, b: number, compteur: number) {
	if (compteur === 1) {
            console.log(a);
            return a;
	} else {
            this.fib_iter((a + b), a, (compteur - 1));
	}
    }
}

que j’appelle ainsi :

#!/usr/bin/env -S npx tsx
import { Command } from "commander";
import * as readline from "readline";
import { Fib } from "./class_fib.js";
let fibFib = new Fib;

const program = new Command();

program
    .name("fibonacci")
    .description("Calculer récursivement un nombre de Fibonnaci")
    .argument("<num>", "le nombre") 
    .action ((num: number) => {
        fibonacci(num);
    });

program.parse(); // ← déclenche le parsing des arguments

function fibonacci(num: number) {
    if (num === 0 || num === 1) {
        console.log(num);
    } else {
        console.log(fibFib.fib_iter(1, 0, num));
    }
}

Tout marche bien, sauf pour num === 0 ou pour num === 1, où la méthode fib_iter est invoquée, alors qu’elle ne devrait pas. Pourquoi appelle-t-on fib_iter dans ce cas, qui provoque une erreur “RangeError : Maximum call stack size exceeded” ?

Réponse 1

Excellente question, Laurent — ton code est très proche du but, et l’erreur vient d’un petit piège classique lié au typage de Commander et à la conversion des arguments.

Regardons pas à pas :

Le problème réel : num n’est pas un number

Quand tu écris :

.action((num: number) => {
    fibonacci(num);
});

tu crois que Commander va passer un nombre… mais en réalité Commander passe toujours des chaînes de caractères (string), sauf si tu fais explicitement la conversion.

Donc quand tu tapes, par exemple :

./fibonacci 1

la fonction reçoit en réalité num = "1" (une chaîne).

Et en TypeScript, le test suivant :

if (num === 0 || num === 1)

échoue, car "1" === 1 est faux.

Donc tu passes dans le else, qui appelle fib_iter, d’où la récursion infinie et ton erreur :

RangeError : Maximum call stack size exceed

La correction : convertir num en nombre

Il suffit de faire une conversion explicite au moment de l’appel :

.action((num) => {
    fibonacci(Number(num));
});

ou directement dans la fonction :

function fibonacci(numStr: string) {
    const num = Number(numStr);
    if (num === 0 || num === 1) {
        console.log(num);
    } else {
        console.log(fibFib.fib_iter(1, 0, num));
    }
}

Petite remarque sur ta méthode fib_iter

Ta méthode renvoie bien a quand compteur === 1, mais elle ne retourne rien dans le cas récursif.
Ce n’est pas bloquant ici (puisque tu affiches le résultat), mais pour avoir une vraie fonction de calcul, tu devrais retourner le résultat de l’appel récursif :

public fib_iter(a: number, b: number, compteur: number): number {
    if (compteur === 1) {
        return a;
    } else {
        return this.fib_iter(a + b, a, compteur - 1);
    }
}

Version corrigée complète

// class_fib.ts
export class Fib {
    public fib_iter(a: number, b: number, compteur: number): number {
        if (compteur === 1) {
            return a;
        } else {
            return this.fib_iter(a + b, a, compteur - 1);
        }
    }
}
#!/usr/bin/env -S npx tsx
import { Command } from "commander";
import { Fib } from "./class_fib.js";

let fibFib = new Fib();

const program = new Command();

program
    .name("fibonacci")
    .description("Calculer récursivement un nombre de Fibonacci")
    .argument("<num>", "le nombre")
    .action((num) => {
        fibonacci(Number(num));
    });

program.parse();

function fibonacci(num: number) {
    if (num === 0 || num === 1) {
        console.log(num);
    } else {
        console.log(fibFib.fib_iter(1, 0, num));
    }
}

Souhaites-tu que je t’écrive aussi une version itérative (non récursive) de fib_iter, pour comparaison de performance et de style ?

Version itérative

Voici donc une version itérative (non récursive) de ta classe Fib, parfaitement équivalente sur le plan du résultat, mais sans appel récursif.

Elle est généralement plus efficace (pas de coût d’appels de pile) et plus sûre (pas de risque de RangeError).

// class_fib.ts
export class Fib {
    // Version itérative
    public fib_iter(a: number, b: number, compteur: number): number {
        for (let i = 1; i < compteur; i++) {
            [a, b] = [a + b, a];
        }
        return a;
    }
}

Explication

La boucle for fait exactement ce que ta récursion faisait :

 à chaque itération, on calcule la nouvelle paire (a, b) ;

 a devient a + b, et b devient l’ancien a ;

 on répète cela compteur - 1 fois.

À la fin, a contient la valeur du nombre de Fibonacci d’indice compteur.

Programme principal (inchangé) :

#!/usr/bin/env -S npx tsx
import { Command } from "commander";
import { Fib } from "./class_fib.js";

let fibFib = new Fib();

const program = new Command();

program
    .name("fibonacci")
    .description("Calculer un nombre de Fibonacci de manière itérative")
    .argument("<num>", "le nombre")
    .action((num) => {
        fibonacci(Number(num));
    });

program.parse();

function fibonacci(num: number) {
    if (num === 0 || num === 1) {
        console.log(num);
    } else {
        console.log(fibFib.fib_iter(1, 0, num));
    }
}

ChatGPT est vraiment un de mes meilleurs amis !