Aliyansayz/agent-skills

Améliorer la qualité du code TypeScript en remplaçant les noms flous, les types non sécurisés, la logique dupliquée, les responsabilités mélangées, les valeurs magiques et la gestion fragile des erreurs par un code lisible, maintenable et fortement typé. À utiliser lors de la révision, du refactoring, du débogage ou de l'extension d'applications TypeScript existantes, en particulier les systèmes CRM de vente.

Qu'est-ce que agent-skills ?

Cette compétence pour agent IA se concentre sur l'amélioration de la qualité du code TypeScript dans les applications existantes, en mettant l'accent sur les systèmes CRM de vente. Elle guide l'agent pour identifier et corriger les problèmes courants tels que les noms de variables vagues (ex: `x`, `data`), les types non sécurisés (`any`), la logique métier dupliquée, les responsabilités mélangées au sein d'une même fonction, les valeurs magiques non documentées, et une gestion des erreurs trop générique. L'objectif est de rendre le code plus compréhensible, maintenable, testable et fiable sans en modifier le comportement attendu. La compétence fournit un cadre de travail en trois étapes : comprendre le contexte existant, documenter le comportement actuel, puis appliquer des améliorations de nommage, de typage fort avec des types explicites et des unions discriminées, et de séparation des responsabilités. Elle s'appuie sur un exemple concret de code CRM dégradé pour illustrer les problèmes et leurs solutions. Cette compétence est conçue pour être utilisée lors de révisions de code, de sessions de refactoring, de débogage ou d'extension de fonctionnalités, et elle est compatible avec tous les environnements de développement utilisant TypeScript.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aliyansayz/agent-skills/tree/HEAD/typescript-code-quality

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Qualité de Code Contextuelle — Compétence Agent TypeScript

1. Objectif

Agir en tant qu'agent d'amélioration de la qualité du code TypeScript pour rendre le code existant plus facile à comprendre, maintenir, tester et étendre.

N'effectuez pas un refactoring uniquement pour rendre le code sophistiqué. Améliorez-le afin que les développeurs puissent comprendre :

  • Ce que fait chaque fonction.
  • Pourquoi une règle métier existe.
  • Ce que représente chaque variable.
  • Quelles données une fonction attend et retourne.
  • Ce qui se produit lorsque des données sont manquantes ou invalides.
  • Quelles parties de l'application sont responsables de la logique métier, de l'accès aux données et de l'interaction utilisateur.
  • Comment une modification affecte le comportement existant de l'application.

Utilisez un langage spécifique au domaine plutôt que des noms de variables et de fonctions vagues.

Lorsque vous travaillez avec du code CRM de vente, utilisez des concepts métier clairs tels que Lead, Contact, Opportunity, SalesRepresentative, LeadStatus, PipelineStage et FollowUpDate.

L'objectif est un logiciel compréhensible, fortement typé, fiable, testable et maintenable.

2. Quand Activer

Utilisez cette compétence lorsque :

  • Le code TypeScript contient des variables telles que x, data, obj, temp, thing ou result sans contexte suffisant.
  • Les fonctions ont des noms vagues comme process(), handle(), manage() ou doStuff().
  • Une fonction effectue à la fois la validation, les calculs métier, les mises à jour de base de données, les notifications et la journalisation.
  • L'application repose sur any, des assertions de type non sécurisées, des chaînes de caractères non expliquées ou des nombres magiques.
  • Les règles métier sont dupliquées à plusieurs endroits.
  • Les erreurs sont silencieusement ignorées ou converties en réponses de succès trompeuses.
  • Les fonctions contiennent des conditions profondément imbriquées ou un branchement excessif.
  • La terminologie CRM est incohérente entre les composants, les points d'accès API et les modèles de base de données.
  • Le code est difficile à tester sans se connecter à une base de données réelle ou à un service externe.
  • Un développeur souhaite améliorer le code existant sans modifier son comportement prévu.

3. Principes Fondamentaux

3.1 Préférer les noms spécifiques au domaine

Les noms doivent expliquer leur signification dans l'application.

Préférez :

const leadId = "lead_123";
const salesRepresentativeId = "user_456";
const nextFollowUpDate = new Date();
const qualifiedLeadCount = 12;

Évitez :

const id = "lead_123";
const user = "user_456";
const date = new Date();
const count = 12;

Les noms courts sont acceptables lorsque leur signification est évidente dans un petit contexte, comme i dans une courte boucle. Ne développez pas chaque nom mécaniquement ; développez les noms lorsque le contexte supplémentaire améliore la compréhension.

3.2 Utiliser TypeScript pour représenter les concepts métier

Préférez les types explicites, les interfaces, les unions discriminées et les modèles de domaine nommés aux objets non structurés.

Évitez d'utiliser any comme échappatoire. Utilisez unknown pour les données véritablement non fiables et validez-les avant utilisation.

3.3 Séparer les responsabilités

Chaque fonction ou module doit avoir une responsabilité principale claire.

Un service CRM ne doit pas avoir à construire des réponses HTTP, manipuler des composants d'interface utilisateur, envoyer des e-mails et exécuter des requêtes de base de données dans une seule fonction.

3.4 Préserver le comportement prévu

Avant le refactoring, identifiez les règles métier existantes et les résultats attendus.

Ne modifiez pas silencieusement :

  • Les critères de qualification des leads.
  • Les règles de transition du pipeline.
  • Les affectations des représentants commerciaux.
  • Les règles de planification des suivis.
  • Le comportement de détection des doublons.
  • Les autorisations et les contrôles d'accès.
  • Les formes des réponses API.
  • La sémantique de la base de données.

Si le comportement existant semble incorrect ou ambigu, identifiez le problème explicitement au lieu de déguiser un changement de comportement en refactoring.

3.5 Préférer un code simple aux abstractions inutiles

N'introduisez pas de modèles de conception, de classes, de génériques, de fabriques ou de dépendances supplémentaires à moins qu'ils ne résolvent un problème réel.

Une fonction simple avec un nom descriptif est souvent meilleure qu'une abstraction inutilement complexe.


4. Exemple de Code CRM de Vente de Mauvaise Qualité

Le code suivant est intentionnellement problématique. Il illustre les problèmes courants de qualité de code dans une application de gestion de leads.

4.1 Avant : Code de mauvaise qualité

// bad-crm.ts

export async function go(d: any, db: any, mail: any) {
    try {
        let x = d.name;
        let e = d.email;
        let p = d.phone || "";
        let s = d.status || "new";
        let v = Number(d.value || 0);
        let u = d.user || null;

        if (!x || !e) {
            return { ok: false, msg: "bad data" };
        }

        let old = await db.leads.findOne({ email: e });

        if (old && old.status != "lost") {
            old.v = old.v + v;
            old.s = v > 5000 ? "hot" : old.s;
            old.p = p || old.p;
            await db.leads.update(old.id, old);

            if (old.s == "hot") {
                await mail.send(
                    old.email,
                    "Hot lead",
                    "Lead is hot!"
                );
            }

            return { ok: true, data: old };
        }

        let a = {
            name: x,
            email: e,
            phone: p,
            status: s,
            value: v,
            user: u,
            created: new Date(),
            updated: new Date(),
        };

        let r = await db.leads.insert(a);

        if (v > 5000) {
            await mail.send(
                e,
                "Hot lead",
                "Lead is hot!"
            );
        }

        return { ok: true, data: r };
    } catch (e) {
        console.log(e);
        return { ok: false, msg: "error" };
    }
}

4.2 Problèmes que l'agent doit identifier

ProblèmeExemplePourquoi c'est importantAmélioration
Nom de fonction flougo()Ne communique pas l'opération métierUtilisez createOrUpdateLead() si cela décrit précisément le comportement
Variables flouesd, x, e, p, s, v, u, a, rLes développeurs doivent déduire le but de chaque variableUtilisez des noms spécifiques au domaine
Types non sécurisésany pour l'entrée, la base de données et le service d'e-mailSupprime la capacité de TypeScript à détecter de nombreuses erreursDéfinissez des interfaces et des types d'entrée explicites
Règle métier cachéev > 5000Le seuil de qualification n'est pas expliquéDéfinissez une constante nommée ou une règle métier documentée
Terminologie incohérentestatus, hot, lostLe statut du lead et la qualification peuvent représenter des concepts différentsDéfinissez un modèle de statut de lead explicite
Responsabilités mélangéesUne fonction valide, interroge, met à jour, crée, envoie des e-mails et gère les erreursLes comportements individuels sont difficiles à testerSéparez les responsabilités applicatives, de domaine et d'infrastructure
Incohérence potentielle des donnéesUne mise à jour de base de données peut réussir avant qu'un e-mail échoueL'appelant peut recevoir une erreur même si le lead a été enregistréDéfinissez explicitement le comportement en cas d'échec de notification
Validation faibleVérifie uniquement x et eDes adresses e-mail invalides, des mauvaises valeurs et des champs malformés peuvent entrer dans le systèmeValidez l'entrée et les invariants métier
Règles de statut implicitesLes leads existants sont mis à jour sauf si leur statut est "lost"La signification de cette règle est cachéeNommez et documentez la politique de mise à jour
Contrat de persistance floudb.leads.findOne() et .update() utilisent des formes non documentéesLes hypothèses de base de données sont difficiles à vérifierDéfinissez une interface de référentiel
Réponse d'erreur générique"error"Perd un contexte utile et complique le dépannageGérez les échecs attendus explicitement et journalisez les erreurs inattendues de manière appropriée
Réponses incohérentes{ ok, msg } et { ok, data }Les appelants doivent déduire quels champs existent dans chaque casUtilisez un type de résultat discriminé ou un modèle d'erreur d'application cohérent
Données mutables et peu structuréesold.v, old.s, old.pLes noms de champs ne correspondent pas au modèle persistant apparentDéfinissez un type Lead cohérent
Politique de doublon floueLes enregistrements existants sont fusionnés par e-mailLa signification exacte de la fusion est cachéeDocumentez quels champs peuvent être mis à jour et comment la valeur est agrégée
Dépendances difficiles à testerLa base de données et le service d'e-mail sont passés en anyLes contrats et les cas d'échec ne sont pas vérifiés par le compilateurDéfinissez des interfaces étroites et injectez des dépendances

Important : Ceci est un exemple illustratif, pas une implémentation CRM de production complète. Un refactoring doit d'abord confirmer le comportement réel du CRM existant, le modèle de données, la sémantique de persistance et les règles métier.


5. Workflow de Refactoring

Suivez ce workflow lors de l'amélioration du code TypeScript existant.

Étape 1 : Comprendre le contexte

Inspectez le code concerné avant de le modifier.

Identifiez :

  • Le but de la fonction.
  • Ses appelants et dépendances.
  • Ses formats d'entrée et de sortie.
  • Le schéma de base de données ou le contrat de référentiel.
  • Les entités du domaine et la terminologie métier.
  • Les règles de validation et d'autorisation existantes.
  • Les tests actuels et le comportement attendu.
  • La gestion des erreurs et les effets secondaires.

Lisez les modules environnants si nécessaire. Ne déduisez pas le contrat complet d'une fonction à partir de son seul nom.

Étape 2 : Enregistrer le comportement actuel

Décrivez la logique existante en langage simple.

Pour l'exemple ci-dessus, le comportement observé est :

  1. Lire les détails du lead à partir de l'entrée.
  2. Rejeter une requête si le nom ou l'e-mail est manquant.
  3. Trouver un lead existant par e-mail.
  4. Si un lead correspondant existe et que son statut n'est pas "lost", ajouter la valeur soumise à la valeur existante.
  5. Marquer ce lead existant comme "hot" lorsque la valeur résultante dépasse le seuil.
  6. Remplacer son numéro de téléphone lorsqu'un nouveau numéro de téléphone non vide est fourni.
  7. Mettre à jour l'enregistrement et envoyer une notification si le statut résultant est "hot".
  8. Sinon, créer un nouvel enregistrement avec les horodatages.
  9. Notifier le lead lorsque sa valeur soumise dépasse le seuil.
  10. Retourner un objet de succès ou d'échec.

Cette description distingue intentionnellement le comportement d'implémentation observé du comportement métier prévu. Par exemple, le code teste la valeur résultante de l'enregistrement existant pour un chemin de notification, mais la valeur soumise pour un autre. Ne traitez pas silencieusement ces règles comme équivalentes.

Étape 3 : Améliorer le nommage

Remplacez les noms génériques par des noms qui expriment leur rôle.

Mauvais nomMeilleur nom
gocreateOrUpdateLead
dleadInput
xleadName
eemailAddress, ou error dans un bloc catch
pphoneNumber
sleadStatus
vleadValue
usalesRepresentativeId
oldexistingLead
anewLead
rcreatedLead
dbleadRepository ou databaseClient, selon son rôle réel
mailemailNotificationService

Le nom amélioré doit représenter avec précision la valeur. Ne renommez pas une valeur en salesRepresentative si elle contient un identifiant numérique. Utilisez salesRepresentativeId dans ce cas.

Concentrez-vous sur la clarté de l'intention, pas sur la longueur du nom.

Étape 4 : Remplacer les types non sécurisés

Remplacez les paramètres any par des types spécifiques.

Définissez une interface pour l'entrée :

interface LeadInput {
    leadName: string;
    emailAddress: string;
    phoneNumber?: string;
    leadStatus?: LeadStatus;
    leadValue?: number;
    salesRepresentativeId?: string;
}

Définissez des types pour les entités du domaine :

type LeadStatus = "new" | "contacted" | "qualified" | "hot" | "lost" | "converted";

interface Lead {
    id: string;
    leadName: string;
    emailAddress: string;
    phoneNumber: string;
    leadStatus: LeadStatus;
    leadValue: number;
    salesRepresentativeId: string | null;
    createdAt: Date;
    updatedAt: Date;
}

Si la dépendance de base de données a un comportement complexe, définissez une interface de référentiel :

interface LeadRepository {
    findByEmail(email: string): Promise<Lead | null>;
    update(lead: Lead): Promise<Lead>;
    insert(lead: Omit<Lead, "id" | "createdAt">): Promise<Lead>;
}

Utilisez des unions discriminées pour les résultats d'opération au lieu d'un objet { ok, data, msg } générique :

type CreateOrUpdateLeadResult =
    | { type: "validation_error"; message: string }
    | { type: "lead_updated"; lead: Lead }
    | { type: "lead_created"; lead: Lead }
    | { type: "notification_failed"; cause: Error; lead: Lead };

Ces types décrivent explicitement chaque résultat possible, ce qui rend le code plus facile à suivre, à tester et à étendre.

Étape 5 : Séparer les responsabilités

Divisez la fonction originale en fonctions plus petites et ciblées.

Séparez :

  • Validation : Vérifier que l'entrée est complète et correcte.
  • Récupération : Charger l'état existant à partir du référentiel.
  • Logique métier : Appliquer les règles de mise à jour ou de création.
  • Persistance : Enregistrer les modifications dans la base de données.
  • Notification : Envoyer des alertes si nécessaire.
  • Coordination : Une fonction orchestratrice qui appelle les étapes ci-dessus dans l'ordre et compose les résultats.

Ce modèle de séparation rend chaque étape testable individuellement et rend explicite l'ordre exact des opérations.

Étape 6 : Rendre les règles métier explicites

Remplacez les valeurs magiques et les chaînes de caractères non documentées par des constantes nommées.

const HOT_LEAD_THRESHOLD = 5000;
const EMAIL_TEMPLATE_HOT_LEAD = "hot-lead-notification";

Documentez la règle métier dans le code ou dans un commentaire adjacent si elle n'est pas évidente à partir du nom de la constante.

Lors de l'écriture de documents de conception ou de spécifications, utilisez un langage qui relie le code à l'intention métier. Par exemple, une spécification de règle métier pourrait être :

Règle de requalification des leads : Lorsqu'un lead existant (dont le statut n'est pas "lost") reçoit une nouvelle valeur de lead, la valeur du lead est mise à jour en ajoutant la nouvelle valeur à la valeur existante. Si la valeur résultante dépasse le seuil de lead chaud (5 000), le statut du lead passe à "hot" et une notification est envoyée.

Gardez les règles métier proches des données qu'elles gouvernent. Évitez de les répartir dans des fichiers non liés ou des modules d'utilitaires vagues.

Étape 7 : Améliorer la gestion des erreurs

Définissez des classes d'erreur ou des types d'échec explicites au lieu de propager des chaînes génériques.

class ValidationError extends Error {
    constructor(message: string) {
        super(message);
        this.name = "ValidationError";
    }
}

class NotificationFailure extends Error {
    constructor(public readonly cause: Error) {
        super("Failed to send notification");
        this.name = "NotificationFailure";
    }
}

Traitez les échecs de notification comme des problèmes séparés des échecs de persistance. N'autorisez pas un échec de notification à faire échouer silencieusement une mise à jour de base de données.

Étape 8 : Améliorer les tests

Utilisez l'injection de dépendances via des interfaces étroites plutôt que de passer la base de données et le service de messagerie directement en tant que any. De cette façon, les tests unitaires peuvent facilement simuler ces dépendances sans avoir besoin d'une base de données réelle ou d'un serveur SMTP.

// Exemple de test
const mockRepository: LeadRepository = {
    findByEmail: async () => null,
    insert: async (lead) => ({ ...lead, id: "lead-1", createdAt: new Date() }),
    update: async (lead) => lead,
};

const mockEmailService: EmailNotificationService = {
    send: async () => {},
};

const result = await createOrUpdateLead(input, mockRepository, mockEmailService);

Étape 9 : Valider sans changer de comportement

Après le refactoring, exécutez les tests existants. Comparez les sorties de l'ancien et du nouveau code à l'aide de tests d'instantanés ou de tests de régression déterministes.

Si le comportement existant n'est pas testé, créez d'abord des tests de caractérisation qui capturent le comportement réel, puis refactorez.


6. Exemple de Code Refactoré

Voici une version refactorée de l'exemple précédent, illustrant les principes de cette compétence. Il s'agit d'une amélioration, pas d'une réécriture complète. Dans un projet réel, il faudrait adapter davantage l'implémentation à l'architecture existante de l'application.

// refactored-crm.ts

import { Lead, LeadInput, LeadRepository, EmailNotificationService, CreateOrUpdateLeadResult } from "./domain";

const HOT_LEAD_THRESHOLD = 5000;

function validateLeadInput(input: LeadInput): ValidationError | null {
    if (!input.leadName || !input.emailAddress) {
        return new ValidationError("leadName and emailAddress are required");
    }
    return null;
}

function applyBusinessRules(existingLead: Lead, input: LeadInput): Lead {
    const updatedLead = { ...existingLead };
    updatedLead.leadValue += input.leadValue || 0;
    if (updatedLead.leadValue > HOT_LEAD_THRESHOLD) {
        updatedLead.leadStatus = "hot";
    }
    if (input.phoneNumber) {
        updatedLead.phoneNumber = input.phoneNumber;
    }
    updatedLead.updatedAt = new Date();
    return updatedLead;
}

export async function createOrUpdateLead(
    input: LeadInput,
    leadRepository: LeadRepository,
    emailNotificationService: EmailNotificationService
): Promise<CreateOrUpdateLeadResult> {
    const validationError = validateLeadInput(input);
    if (validationError) {
        return { type: "validation_error", message: validationError.message };
    }

    const existingLead = await leadRepository.findByEmail(input.emailAddress);

    if (existingLead && existingLead.leadStatus !== "lost") {
        const updatedLead = applyBusinessRules(existingLead, input);
        await leadRepository.update(updatedLead);

        if (updatedLead.leadStatus === "hot") {
            try {
                await emailNotificationService.send(updatedLead.emailAddress, EMAIL_TEMPLATE_HOT_LEAD, { lead: updatedLead });
            } catch (error) {
                return { type: "notification_failed", cause: error, lead: updatedLead };
            }
        }
        return { type: "lead_updated", lead: updatedLead };
    }

    const newLead: Omit<Lead, "id" | "createdAt"> = {
        leadName: input.leadName,
        emailAddress: input.emailAddress,
        phoneNumber: input.phoneNumber || "",
        leadStatus: input.leadStatus || "new",
        leadValue: input.leadValue || 0,
        salesRepresentativeId: input.salesRepresentativeId || null,
        updatedAt: new Date(),
    };

    const createdLead = await leadRepository.insert(newLead);

    if (createdLead.leadValue > HOT_LEAD_THRESHOLD) {
        try {
            await emailNotificationService.send(createdLead.emailAddress, EMAIL_TEMPLATE_HOT_LEAD, { lead: createdLead });
        } catch (error) {
            return { type: "notification_failed", cause: error, lead: createdLead };
        }
    }
    return { type: "lead_created", lead: createdLead };
}

7. Limitations et Mises en Garde

Le refactoring peut modifier involontairement le comportement. Vérifiez toujours la logique existante avant d'appliquer des modifications. Les correctifs automatisés de cette compétence sont indicatifs et doivent être examinés par un humain.

Cette compétence est conçue pour du code TypeScript dans un environnement de référentiel de code natif. Les codebases legacy compilées en JavaScript peuvent nécessiter des ajustements.

Dans les systèmes CRM existants, les modèles de données sont souvent incohérents en raison de migrations et d'exigences évolutives. Clarifiez l'état actuel avant de supposer un modèle cible.

Les changements de nommage peuvent casser les sérialiseurs, les fonctions de mappage ou les outils externes qui s'attendent à des noms de propriétés spécifiques. Mettez à jour ces dépendances si nécessaire.

Cette compétence se concentre sur l'amélioration de la qualité du code, pas sur l'optimisation des performances. N'introduisez pas de complexité inutile au nom de la "qualité" sans une raison métier vérifiable.

Gardez à l'esprit la pile technologique sous-jacente, les capacités de l'équipe et les objectifs du projet. Ce qui est une "amélioration" dans un contexte peut être un inconvénient dans un autre.

Skills associés