Aller au contenu
Filtrer les règles par style d’implémentation
Style
Les deux styles
Sévérité
majeure
S’applique à
  • Modèle (domain)
  • Use cases (application)

UC-11 · Un Result est testé ou renvoyé, jamais ignoré ​

Style
Les deux styles
Sévérité
majeure
S’applique à
  • Modèle (domain)
  • Use cases (application)

Signal ​

  • Un appel qui renvoie un Result, écrit comme une instruction : wallet.block(reference, at);, await this.#ledger.post(entry);.
  • Un Result rangé dans une variable que rien ne lit.
  • Une promesse de Result jamais attendue.
  • Un Result lu sans que son ok soit testé : return ok(posted).

Pourquoi ​

Un refus est une valeur, et TypeScript n’oblige pas à lire une valeur. Une exception oubliée remonte jusqu’à l’appelant. Un Result oublié ne fait aucun bruit.

L’aggregate a refusé, et il n’a pas changé. Le use case enregistre quand même, puis renvoie ok : le client croit que l’opération a eu lieu. Rien n’a échoué, rien n’est annulé, la réponse est simplement fausse.

Exceptions ​

Aucune. Un refus qu’on choisit vraiment d’ignorer se dit par un waiver, avec sa raison.

Correction ​

Tester ok juste après l’appel. Renvoyer le refus tel quel, ou le traduire en un refus nommé pour l’appelant. Une suite d’appels qui peuvent refuser se teste un par un, ou se combine (combine, andThen).

Adaptation ​

Notre choix : @alveolus/lint-config le vérifie avec ESLint. alveolus/uc-11-must-use-result lit le type de chaque instruction, et signale celle qui jette un Result : une affectation le garde, elle passe. alveolus/uc-11-no-unused-vars signale la variable locale qui tient un Result et que rien ne lit, et alveolus/uc-11-no-floating-promises la promesse de Result oubliée. Les trois ne regardent que les Result : un paramètre inutilisé ou une autre promesse oubliée ne relève pas de cette règle. Un Result lu sans être testé reste à la review : le décider sans faux positif demanderait de suivre chaque valeur.

Le tsconfig active aussi noUnusedLocals, pour qu’une variable jamais lue casse la compilation. À l’exécution, un run imbriqué qui refuse empêche la transaction de valider (REP-13), même quand l’appelant oublie le refus.

Voir aussi ​

UC-07 fait de l’absence un refus nommé. REP-03 donne à chaque issue d’un port son canal : un refus en Result, une absence en undefined, une panne en exception. REP-13 annule une transaction dont un run imbriqué a refusé.

Exemple ​

ts
// Avoid: the refusal of the wallet is dropped, and the use case answers ok
const wallet = await this.#wallets.findById(walletId);

if (!wallet) {
	return err(new WalletNotFound(walletId));
}

wallet.block(reference, this.#clock.now());

return this.#wallets.save(wallet);
ts
// Preferred: the use case returns the refusal of the wallet
const wallet = await this.#wallets.findById(walletId);

if (!wallet) {
	return err(new WalletNotFound(walletId));
}

const blocked = wallet.block(reference, this.#clock.now());

if (!blocked.ok) {
	return blocked;
}

return this.#wallets.save(wallet);

Dans le premier bloc, un wallet déjà fermé refuse le blocage. Le use case l’enregistre tel quel et renvoie ok : le client croit son wallet bloqué.