Maison > interface Web > js tutoriel > Du chaos à la clarté : une approche déclarative de la composition des fonctions et des pipelines en JavaScript

Du chaos à la clarté : une approche déclarative de la composition des fonctions et des pipelines en JavaScript

Patricia Arquette
Libérer: 2025-01-08 18:40:41
original
233 Les gens l'ont consulté

From Chaos to Clarity: A Declarative Approach to Function Composition and Pipelines in JavaScript

Table des matières

  • L'art du code propre
  • La magie des fonctions pures
  • Construire des ponts avec la composition des fonctions
  • Rationaliser le code avec les pipelines
  • Adapter les pipelines à l'évolution des besoins
  • Éviter les pièges de la composition des fonctions
  • Un voyage vers l'élégance

L’art du code propre ?

Avez-vous déjà regardé le code de quelqu'un d'autre et pensé : « De quel genre de sorcellerie s'agit-il ? » Au lieu de résoudre de vrais problèmes, vous êtes perdu dans un labyrinthe de boucles, de conditions et de variables. C'est le combat auquel tous les développeurs sont confrontés : la bataille éternelle entre le chaos et la clarté.

Le code doit être écrit pour que les humains puissent le lire, et seulement accessoirement pour que les machines l'exécutent. — Harold Abelson

Mais n’ayez crainte ! Le Code propre n'est pas un trésor mythique caché dans le donjon d'un développeur : c'est une compétence que vous pouvez maîtriser. À la base se trouve la programmation déclarative, où l'accent est mis sur ce que fait votre code, laissant le comment en arrière-plan.

Rendons cela réel avec un exemple. Supposons que vous deviez trouver tous les nombres pairs dans une liste. Voici combien d’entre nous ont commencé avec une approche impérative :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evenNumbers.push(numbers[i]);
  }
}
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Bien sûr, ça marche. Mais soyons honnêtes : c'est bruyant : boucles manuelles, suivi d'index et gestion d'état inutile. En un coup d’œil, il est difficile de voir ce que fait réellement le code. Maintenant, comparons cela à une approche déclarative :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Une ligne, pas de fouillis, juste une intention claire : « Filtrer les nombres pairs. » C'est la différence entre la simplicité et la concentration par rapport à la complexité et au bruit.

Pourquoi le code propre est important ?‍ ?

Un code propre ne consiste pas seulement à être joli, il s'agit également de travailler plus intelligemment. Six mois plus tard, préféreriez-vous vous battre dans un labyrinthe de logique déroutante ou lire un code qui s'explique pratiquement tout seul ?

Bien que le code impératif ait sa place, surtout lorsque les performances sont critiques,le code déclaratif gagne souvent par sa lisibilité et sa facilité de maintenance.

Voici une comparaison rapide côte à côte :

Imperative Declarative
Lots of boilerplate Clean and focused
Step-by-step instructions Expresses intent clearly
Harder to refactor or extend Easier to adjust and maintain

Une fois que vous aurez adopté un code propre et déclaratif, vous vous demanderez comment vous avez pu vous en passer. C’est la clé pour construire des systèmes prévisibles et maintenables, et tout commence par la magie des fonctions pures. Alors prenez votre baguette de codage (ou un café fort ☕) et rejoignez le voyage vers un code plus propre et plus puissant. ?✨


La magie des fonctions pures ?

Avez-vous déjà rencontré une fonction qui essaie de tout faire : récupérer des données, traiter les entrées, enregistrer les sorties et peut-être même préparer du café ? Ces bêtes multitâches peuvent sembler efficaces, mais ce sont des artefacts maudits : fragiles, alambiqués et un cauchemar à entretenir. Il doit sûrement y avoir une meilleure façon.

La simplicité est une condition préalable à la fiabilité. — Edsger W. Dijkstra

L'essence de la pureté ⚗️

Une fonction pure, c'est comme lancer un sort parfaitement conçu : elle donne toujours le même résultat pour la même entrée, sans effets secondaires. Cette magie simplifie les tests, facilite le débogage et résume la complexité pour garantir la réutilisation.

Pour voir la différence, voici une fonction impure :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evenNumbers.push(numbers[i]);
  }
}
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Cette fonction modifie l’état global – comme un sort qui tourne mal, elle est peu fiable et frustrante. Sa sortie repose sur la variable de remise changeante, transformant le débogage et la réutilisation en un défi fastidieux.

Maintenant, créons plutôt une fonction pure :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Sans état global, cette fonction est prévisible et autonome. Les tests deviennent simples et sont prêts à être réutilisés ou étendus dans le cadre de flux de travail plus vastes.

En divisant les tâches en petites fonctions pures, vous créez une base de code à la fois robuste et agréable à utiliser. Alors, la prochaine fois que vous écrirez une fonction, demandez-vous : "Ce sort est-il ciblé et fiable, ou deviendra-t-il un artefact maudit prêt à déclencher le chaos ?"


Construire des ponts avec la composition fonctionnelle ?

Avec des fonctions pures en main, nous maîtrisons le métier de la simplicité. Comme les briques Lego ?, elles sont autonomes, mais les briques à elles seules ne construisent pas un château. La magie réside dans leur combinaison : l'essence de la composition de fonctions, où les flux de travail résolvent les problèmes tout en faisant abstraction des détails d'implémentation.

Voyons comment cela fonctionne avec un exemple simple : calculer le total d'un panier. Tout d'abord, nous définissons les fonctions utilitaires réutilisables comme éléments de base :

let discount = 0;   

const applyDiscount = (price: number) => {
  discount += 1; // Modifies a global variable! ?
  return price - discount;
};

// Repeated calls yield inconsistent results, even with same input!
console.log(applyDiscount(100)); // Output: 99
console.log(applyDiscount(100)); // Output: 98
discount = 100;
console.log(applyDiscount(100)); // Output: -1 ?
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, nous composons ces fonctions utilitaires en un seul flux de travail :

const applyDiscount = (price: number, discountRate: number) => 
  price * (1 - discountRate);

// Always consistent for the same inputs
console.log(applyDiscount(100, 0.1)); // 90
console.log(applyDiscount(100, 0.1)); // 90
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ici, chaque fonction a un objectif clair : additionner les prix, appliquer des remises et arrondir le résultat. Ensemble, ils forment un flux logique dans lequel les résultats de l’un alimentent le suivant. La logique du domaine est claire : calculez le total du paiement avec les remises.

Ce workflow capture la puissance de la composition des fonctions : se concentrer sur le quoi (l'intention derrière votre code) tout en laissant le comment (les détails de l'implémentation) s'effacer en arrière-plan.


Rationaliser le code avec les pipelines ✨

La composition des fonctions est puissante, mais à mesure que les flux de travail se développent, les compositions profondément imbriquées peuvent devenir difficiles à suivre, comme le déballage des poupées russes ?. Les pipelines poussent l'abstraction plus loin, offrant une séquence linéaire de transformations qui reflète le raisonnement naturel.

Construire un utilitaire de canalisation simple ?️

De nombreuses bibliothèques JavaScript (bonjour, fans de programmation fonctionnelle ! ?) proposent des utilitaires de pipeline, mais créer la vôtre est étonnamment simple :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evenNumbers.push(numbers[i]);
  }
}
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Ce service public enchaîne les opérations en un flux clair et progressif. La refactorisation de notre exemple de paiement précédent avec pipe nous donne :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le résultat est presque poétique : chaque étape s'appuie sur la précédente. Cette cohérence n'est pas seulement belle : elle est pratique, car elle rend le flux de travail suffisamment intuitif pour que même les non-développeurs puissent suivre et comprendre ce qui se passe.

Un partenariat parfait avec TypeScript ?

TypeScript garantit la sécurité des types dans les pipelines en définissant des relations entrée-sortie strictes. À l'aide de surcharges de fonctions, vous pouvez taper un utilitaire de canal comme celui-ci :

let discount = 0;   

const applyDiscount = (price: number) => {
  discount += 1; // Modifies a global variable! ?
  return price - discount;
};

// Repeated calls yield inconsistent results, even with same input!
console.log(applyDiscount(100)); // Output: 99
console.log(applyDiscount(100)); // Output: 98
discount = 100;
console.log(applyDiscount(100)); // Output: -1 ?
Copier après la connexion
Copier après la connexion
Copier après la connexion

Un aperçu du futur ?

Bien que créer votre propre utilitaire soit utile, l'opérateur de pipeline proposé par JavaScript (|>) rendra les transformations de chaînage encore plus simples grâce à la syntaxe native.

const applyDiscount = (price: number, discountRate: number) => 
  price * (1 - discountRate);

// Always consistent for the same inputs
console.log(applyDiscount(100, 0.1)); // 90
console.log(applyDiscount(100, 0.1)); // 90
Copier après la connexion
Copier après la connexion
Copier après la connexion

Les pipelines ne se contentent pas de rationaliser les flux de travail : ils réduisent la charge cognitive, offrant une clarté et une simplicité qui trouvent un écho au-delà du code.


Adapter les pipelines à l'évolution des besoins ?

Dans le développement de logiciels, les exigences peuvent changer en un instant. Les pipelines facilitent l'adaptation, que vous ajoutiez une nouvelle fonctionnalité, réorganisiez les processus ou affiniez la logique. Explorons comment les pipelines gèrent l'évolution des besoins avec quelques scénarios pratiques.

Ajouter le calcul des taxes ?️

Supposons que nous devions inclure la taxe de vente dans le processus de paiement. Les pipelines facilitent cela : il suffit de définir la nouvelle étape et de l'insérer au bon endroit :

type CartItem = { price: number };

const roundToTwoDecimals = (value: number) =>
  Math.round(value * 100) / 100;

const calculateTotal = (cart: CartItem[]) =>
  cart.reduce((total, item) => total + item.price, 0);

const applyDiscount = (discountRate: number) => 
  (total: number) => total * (1 - discountRate);
Copier après la connexion
Copier après la connexion

Si les exigences changent (comme l'application de la taxe de vente avant les remises), les pipelines s'adaptent sans effort :

// Domain-specific logic derived from reusable utility functions
const applyStandardDiscount = applyDiscount(0.2);

const checkout = (cart: CartItem[]) =>
  roundToTwoDecimals(
    applyStandardDiscount(
      calculateTotal(cart)
    )
  );

const cart: CartItem[] = [
  { price: 19.99 },
  { price: 45.5 },
  { price: 3.49 },
];

console.log(checkout(cart)); // Output: 55.18
Copier après la connexion
Copier après la connexion

Ajout de fonctionnalités conditionnelles : réductions pour les membres ?️

Les pipelines peuvent également gérer facilement la logique conditionnelle. Imaginez appliquer une réduction supplémentaire aux membres. Tout d’abord, définissez un utilitaire pour appliquer conditionnellement des transformations :

const pipe =
  (...fns: Function[]) =>
  (input: any) => fns.reduce((acc, fn) => fn(acc), input);
Copier après la connexion
Copier après la connexion

Ensuite, intégrez-le dynamiquement dans le pipeline :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = [];
for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] % 2 === 0) {
    evenNumbers.push(numbers[i]);
  }
}
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

La fonction d'identité agit comme une opération no-op, la rendant réutilisable pour d'autres transformations conditionnelles. Cette flexibilité permet aux pipelines de s'adapter de manière transparente à des conditions variables sans ajouter de complexité au flux de travail.

Extension des pipelines pour le débogage ?

Le débogage des pipelines peut sembler délicat, comme chercher une aiguille dans une botte de foin, à moins de vous équiper des bons outils. Une astuce simple mais efficace consiste à insérer des fonctions de journalisation pour éclairer chaque étape :

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // Output: [2, 4]
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Bien que les pipelines et la composition des fonctions offrent une flexibilité remarquable, comprendre leurs bizarreries vous permet d'exercer leur puissance sans tomber dans les pièges courants.


Éviter les pièges de la composition fonctionnelle ?️

La composition des fonctions et les pipelines apportent clarté et élégance à votre code, mais comme toute magie puissante, ils peuvent avoir des pièges cachés. Découvrons-les et apprenons à les éviter sans effort.

Le piège n°1 : les effets secondaires involontaires ?

Des effets secondaires peuvent se faufiler dans vos compositions, transformant des flux de travail prévisibles en flux chaotiques. Modifier l'état partagé ou s'appuyer sur des variables externes peut rendre votre code imprévisible.

let discount = 0;   

const applyDiscount = (price: number) => {
  discount += 1; // Modifies a global variable! ?
  return price - discount;
};

// Repeated calls yield inconsistent results, even with same input!
console.log(applyDiscount(100)); // Output: 99
console.log(applyDiscount(100)); // Output: 98
discount = 100;
console.log(applyDiscount(100)); // Output: -1 ?
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le correctif : assurez-vous que toutes les fonctions de votre pipeline sont pures.

const applyDiscount = (price: number, discountRate: number) => 
  price * (1 - discountRate);

// Always consistent for the same inputs
console.log(applyDiscount(100, 0.1)); // 90
console.log(applyDiscount(100, 0.1)); // 90
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le piège n°2 : des pipelines trop compliqués ?

Les pipelines sont parfaits pour décomposer des flux de travail complexes, mais en faire trop peut conduire à une chaîne déroutante et difficile à suivre.

type CartItem = { price: number };

const roundToTwoDecimals = (value: number) =>
  Math.round(value * 100) / 100;

const calculateTotal = (cart: CartItem[]) =>
  cart.reduce((total, item) => total + item.price, 0);

const applyDiscount = (discountRate: number) => 
  (total: number) => total * (1 - discountRate);
Copier après la connexion
Copier après la connexion

Le correctif : regroupez les étapes associées dans des fonctions d'ordre supérieur qui encapsulent l'intention.

// Domain-specific logic derived from reusable utility functions
const applyStandardDiscount = applyDiscount(0.2);

const checkout = (cart: CartItem[]) =>
  roundToTwoDecimals(
    applyStandardDiscount(
      calculateTotal(cart)
    )
  );

const cart: CartItem[] = [
  { price: 19.99 },
  { price: 45.5 },
  { price: 3.49 },
];

console.log(checkout(cart)); // Output: 55.18
Copier après la connexion
Copier après la connexion

Le piège n°3 : Déboguer les angles morts ?

Lors du débogage d'un pipeline, il peut être difficile de déterminer quelle étape a causé un problème, en particulier dans les chaînes longues.

Le correctif : Injectez des fonctions de journalisation ou de surveillance pour suivre les états intermédiaires, comme nous l'avons vu plus tôt avec la fonction de journalisation qui imprime les messages et les valeurs à chaque étape.

Le piège n°4 : perte de contexte dans les méthodes de classe ?

Lorsque vous composez des méthodes à partir d'une classe, vous risquez de perdre le contexte nécessaire pour les exécuter correctement.

const pipe =
  (...fns: Function[]) =>
  (input: any) => fns.reduce((acc, fn) => fn(acc), input);
Copier après la connexion
Copier après la connexion

Le correctif : utilisez les fonctions .bind(this) ou fléchées pour préserver le contexte.

const checkout = pipe(
  calculateTotal,
  applyStandardDiscount,
  roundToTwoDecimals
);
Copier après la connexion

En étant conscient de ces pièges et en suivant les meilleures pratiques, vous vous assurerez que vos compositions et vos pipelines restent aussi efficaces qu'élégants, quelle que soit l'évolution de vos besoins.


Un voyage vers l'élégance ?

Maîtriser la composition des fonctions et les pipelines ne consiste pas seulement à écrire un meilleur code, il s'agit également de faire évoluer votre état d'esprit pour penser au-delà de la mise en œuvre. Il s'agit de créer des systèmes qui résolvent des problèmes, se lisent comme une histoire bien racontée et inspirent par l'abstraction et la conception intuitive.

Pas besoin de réinventer la roue ?

Des bibliothèques comme RxJS, Ramda et lodash-fp proposent des utilitaires prêts pour la production et testés au combat, soutenus par des communautés actives. Ils vous permettent de vous concentrer sur la résolution de problèmes spécifiques au domaine au lieu de vous soucier des détails de mise en œuvre.

Des points à retenir pour guider votre pratique ?️

  • Clean Code : Le code propre n'est pas seulement une question d'apparence, il s'agit également de travailler plus intelligemment. Il crée des solutions qui simplifient le débogage, la collaboration et la maintenance. Six mois plus tard, vous vous remercierez d'avoir écrit du code qui s'explique pratiquement tout seul.
  • Composition des fonctions : combinez des fonctions pures et ciblées pour créer des flux de travail qui abordent avec élégance et clarté des problèmes complexes.
  • Pipelines : complexité abstraite en façonnant votre logique en flux clairs et intuitifs. Bien exécutés, les pipelines augmentent la productivité des développeurs et rendent les flux de travail si clairs que même les non-développeurs peuvent les comprendre.

En fin de compte, votre code est plus qu’une série d’instructions : c’est une histoire que vous racontez, un sort que vous lancez. Fabriquez-le avec soin et laissez l’élégance guider votre voyage. ?✨

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal