Les proves de la lliçó anterior responen a la pregunta "funciona?". Queda una altra pregunta, diferent i també important: "és aquest codi una cosa amb la qual puguem conviure d'aquí a un any?". Hi ha una família sencera de problemes que cap prova no detecta —codi incoherent, variables sense fer servir, promeses sense await, funcions de dues-centes línies, la mateixa lògica copiada en tres llocs— i que es paga a cada lectura futura. L'anàlisi estàtica examina el codi sense executar-lo i en detecta bona part en segons. En aquesta lliçó veurem per què aquesta feina pertany al pipeline i no a la revisió humana, en què es diferencien exactament Prettier, ESLint i tsc --noEmit amb exemples concrets del que cadascun atrapa i els altres no, com es configura el job qualitat de Reservalia amb --max-warnings 0, què és un quality gate i per què el criteri guanyador és "nou codi net" en comptes d'arreglar tot el deute de cop. Acabarem amb els ganxos de pre-commit i les convencions de commit. L'anàlisi de vulnerabilitats i el SAST de seguretat no hi entren: són la lliçó 04-03.

Contingut

  1. Per què l'anàlisi estàtica pertany al pipeline
  2. Formatatge, linting i comprovació de tipus: tres eines, tres problemes
  3. La configuració de Reservalia i el job qualitat
  4. Anàlisi més profunda: complexitat, duplicació i code smells
  5. Quality gates i el criteri de "nou codi net"
  6. Ganxos de pre-commit amb Husky i lint-staged
  7. Convencions de commit i la seva verificació automàtica
  8. Com començar en un projecte amb milers d'avisos
  9. Errors Comuns i Consells
  10. Exercicis
  11. Conclusió

  1. Per què l'anàlisi estàtica pertany al pipeline

La Marta revisa un pull request d'en Diego i escriu sis comentaris: quatre sobre cometes simples davant de dobles i sagnat, un sobre una variable sense fer servir i un que assenyala que la nova ruta no valida l'identificador del negoci i permetria llegir cites alienes.

Els cinc primers comentaris són malbaratament. Els pot produir una màquina en dos segons, sense cansar-se, sense excepcions i sense que ningú s'ho prengui com una cosa personal. El sisè és la raó per la qual existeix la revisió humana.

El principi. Automatitza tot allò objectivable perquè la revisió humana pugui parlar de disseny, de seguretat i de si el problema s'ha entès bé.

Hi ha tres raons addicionals, i totes tres importen. Coherència sense discussions: quan l'eina decideix el format, deixa d'existir el debat —cap configuració de Prettier no és objectivament millor que una altra; el que és valuós és que n'hi hagi una—. Sense biaix ni cansament: una persona que revisa el cinquè PR del dia ja no veu les variables sense fer servir; l'eina veu sempre el mateix. Retroalimentació immediata i sense cost emocional: que un linter t'expliqui que falta un await és informació; que t'ho digui un company en un comentari públic és informació i una petita factura social.

  1. Formatatge, linting i comprovació de tipus: tres eines, tres problemes

Es confonen constantment. Són tres capes independents i complementàries.

Prettier (formatatge) ESLint (linting) tsc --noEmit (tipus)
Pregunta que respon Està escrit de manera uniforme? Hi ha patrons problemàtics? Encaixen els tipus?
Com decideix Reimprimeix el codi des del seu AST Regles sobre l'AST Sistema de tipus complet
Corregeix sol? Sempre A vegades (--fix) Mai
És opinable? Sí, però tant se val quina triïs Sí, es configura per equip No: és correcte o no ho és
Velocitat a Reservalia ~2 s ~15 s ~20 s

Vegem què detecta cadascun i els altres no.

Només Prettier. Aquest codi és perfectament vàlid i no té cap error de tipus ni cap mala pràctica; simplement està escrit de manera diferent a tota la resta del repositori:

const cita={negociId:1,   inici:"2026-03-02T10:00:00+01:00",
    duracioMin : 30}

Prettier el reescriu sense preguntar. Ni ESLint ni tsc no dirien res.

Només ESLint. Aquí no hi ha cap error de tipus i el format és impecable, però el codi està malament:

async function cancelarCita(id: number) {
  enviarCorreuCancellacio(id);        // ← retorna Promise i ningu no l espera
  await marcarCancellada(id);
}

tsc no protesta: cridar una funció asíncrona sense await és legal. Prettier tampoc: el format està bé. La regla @typescript-eslint/no-floating-promises ho detecta a l'instant, i és una fallada real: si l'enviament del correu falla, l'error es perd en el buit i ningú no se n'assabenta.

Només tsc. Aquí el format és correcte i no hi ha cap patró sospitós, però el programa està trencat:

import type { Cita } from '@reservalia/tipus-compartits';

function resum(cita: Cita): string {
  return `${cita.negoci.nom} · ${cita.iniciHora}`;   // ← el camp es diu 'inici'
}

Només el sistema de tipus sap que Cita no té un camp iniciHora. És exactament el tipus d'error que en producció es manifesta com un undefined a la interfície del client.

La conclusió pràctica és que les tres capes són necessàries i que cap no substitueix les altres. I n'hi ha una quarta, complementària, que no hi competeix: l'anàlisi de complexitat i duplicació de l'apartat 4.

  1. La configuració de Reservalia i el job qualitat

Prettier es configura a .prettierrc.json, a l'arrel del monorepo:

{ "semi": true, "singleQuote": true, "printWidth": 100, "trailingComma": "all" }

Quatre decisions que ningú no tornarà a discutir. L'important no és el seu contingut, sinó que estiguin escrites al repositori.

ESLint viu a eslint.config.js. Aquest és el nucli de la configuració de Reservalia:

export default [{
  files: ['**/*.ts', '**/*.tsx'],
  languageOptions: { parser: tsParser, parserOptions: { project: './tsconfig.json' } },
  rules: {
    '@typescript-eslint/no-floating-promises': 'error',   // 1
    '@typescript-eslint/no-explicit-any':      'warn',    // 2
    'no-unused-vars':                          'error',
    'complexity':               ['warn', 12],             // 3
    'max-lines-per-function':   ['warn', 80],
    'eqeqeq':                                  'error',   // 4
  },
}];
  1. no-floating-promises és la regla de l'exemple anterior. En una aplicació plena d'operacions asíncrones —correus, base de dades, passarel·la de pagament— és la que atrapa més errors reals.
  2. no-explicit-any com a avís, no error: convertir-la en error de cop trencaria el projecte sencer. El pla per pujar-la a error és l'apartat 8.
  3. complexity i max-lines-per-function són senyals de disseny, no de correcció; per això són avisos.
  4. eqeqeq prohibeix ==. En JavaScript, '0' == 0 és cert i null == undefined també: comparacions que semblen innocents i no ho són.

I ara el job del pipeline:

  qualitat:
    name: Qualitat
    runs-on: ubuntu-22.04
    timeout-minutes: 10
    steps:
      # ... checkout, setup-node i npm ci ...
      - name: Format
        run: npx prettier --check .              # 1

      - name: Linting
        run: npm run lint                        # 2  → eslint ... --max-warnings 0

      - name: Comprovacio de tipus
        run: npm run typecheck                   # 3  → tsc --noEmit
  1. prettier --check, no --write. En CI volem comprovar, no modificar: si el pipeline reformatés i tornés a empènyer, canviaria el commit que s'està verificant i crearia un embolic de traçabilitat. El missatge d'error indica el fitxer i l'ordre exacta per arreglar-ho en local.
  2. --max-warnings 0 és la política més important del job, i ja venia als package.json de la lliçó 01-04. Sense ella, ESLint acaba amb codi de sortida 0 encara que hi hagi 300 avisos, el pipeline es posa verd i els avisos s'acumulen fins que ningú no els mira. Amb ella, el nombre d'avisos no pot créixer: cada avís nou posa el PR en vermell. La distinció warn / error continua sent útil per classificar la gravetat, però al pipeline tots dos bloquegen.
  3. typecheck va a part del build. tsc --noEmit només comprova i no escriu res, així que pot córrer en paral·lel amb les proves i dona retroalimentació abans.

Els tres passos són ràpids: el job qualitat complet triga menys d'un minut, i com que és un job independent, corre en paral·lel amb test i build.

  1. Anàlisi més profunda: complexitat, duplicació i code smells

Els linters miren fitxer a fitxer. Hi ha problemes que només es veuen amb perspectiva de projecte:

Mesura Què indica Llindar orientatiu
Complexitat ciclomàtica Nombre de camins possibles dins d'una funció > 10-15 convé dividir
Complexitat cognitiva Quant costa entendre-la (penalitza l'imbricament) > 15 és difícil de llegir
Duplicació Percentatge de línies repetides al projecte > 3-5 % mereix atenció
Deute tècnic estimat Temps que costaria arreglar el que s'ha detectat Serveix per comparar mòduls
Code smells Patrons que no són errors però avisen La seva tendència, no el seu valor

Un exemple real de Reservalia. La funció calcularForats va començar amb 20 línies; entre horaris partits, festius, durades variables i bloquejos manuals, ha arribat a 140 línies i a una complexitat ciclomàtica de 23. No falla cap prova —de fet està ben coberta— però cada canvi nou triga el doble i fa el doble de por. Això és exactament el que mesura la complexitat: el cost futur de tocar-la.

Compte amb els llindars: són senyals per conversar, no veritats. Una funció de complexitat 18 que tradueix una taula de tarifes pot ser perfectament llegible, i una de complexitat 8 amb tres nivells d'imbricament i noms críptics pot ser infernal.

  1. Quality gates i el criteri de "nou codi net"

Un quality gate és un conjunt de condicions que un canvi ha de complir per considerar-se acceptable. Eines com SonarQube (autogestionat) o SonarCloud (SaaS) l'avaluen i retornen un veredicte que el pipeline pot fer servir per bloquejar el merge.

La configuració mínima és un fitxer sonar-project.properties a l'arrel:

sonar.projectKey=reservalia_monorepo
sonar.sources=apps/api/src,apps/web/src,packages/tipus-compartits/src
sonar.tests=apps/api/tests,apps/web/tests
sonar.javascript.lcov.reportPaths=apps/api/coverage/lcov.info
sonar.qualitygate.wait=true

sonar.qualitygate.wait=true fa que el step esperi el veredicte en comptes d'acabar tan bon punt puja les dades; sense això, el pipeline passaria sempre i el gate no bloquejaria res. El pas al job qualitat necessita un secret:

      - name: Analisi de qualitat
        uses: SonarSource/sonarcloud-github-action@v2
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

I aquí arriba la decisió que separa les adopcions que funcionen de les que fracassen. Reservalia té tres anys de codi i, el primer cop que passen Sonar, apareixen 1.847 avisos. Hi ha dos camins:

Estratègia Què exigeix Resultat real
Arreglar tot el deute Aturar-se setmanes a netejar 1.847 avisos No es fa mai; el gate es desactiva
Nou codi net Només el codi que aquest PR toca ha de complir S'adopta el mateix dia i el deute baixa sol

El criteri de "nou codi net" (clean as you code) aplica el gate únicament a les línies afegides o modificades al PR. Les seves condicions típiques:

  • 0 errors nous i 0 vulnerabilitats noves al codi nou.
  • Cobertura del codi nou ≥ 80 %.
  • Duplicació del codi nou < 3 %.

Els avantatges són enormes: és assolible des del primer dia, l'equip no s'ha d'aturar, i com que cada PR toca codi antic per modificar-lo, el deute es redueix de manera natural per les zones que de debò es toquen —que són les que importen—. Un mòdul que ningú no ha obert en dos anys pot tenir 300 avisos; si ningú no el toca, no molesten ningú.

  1. Ganxos de pre-commit amb Husky i lint-staged

Un ganxo de pre-commit executa comprovacions a la teva màquina abans de crear el commit. El seu valor és el cicle curt: descobrir una fallada de format en 2 segons en comptes de en 3 minuts de pipeline. S'instal·la amb npm i -D husky lint-staged, npx husky init i un .husky/pre-commit que contingui npx lint-staged. La configuració va al package.json de l'arrel:

"lint-staged": {
  "*.{ts,tsx}": ["prettier --write", "eslint --max-warnings 0 --fix"],
  "*.{json,md,yml}": ["prettier --write"]
}

La clau és al nom: lint-staged només processa els fitxers que has posat a l'índex, no tot el repositori. Per això triga un parell de segons en comptes de vint, i per això la gent no el desactiva.

Ara l'advertiment important: els ganxos locals NO substitueixen MAI el control del pipeline. Tres motius:

  1. Es poden saltar. git commit --no-verify els omet, i amb pressa s'utilitza.
  2. Són a la màquina de cadascú. Qui cloni el repositori i no executi la instal·lació no té ganxos, i no se n'assabenta.
  3. Només veuen el que has tocat tu. Un canvi a tipus-compartits pot trencar apps/web sense que cap fitxer d'apps/web sigui al teu índex.

El model correcte: els ganxos són una comoditat (retroalimentació ràpida i opcional); el pipeline és el control (obligatori i no evitable). Si n'has de triar només un, tria el pipeline.

  1. Convencions de commit i la seva verificació automàtica

Un historial de missatges tipus arranjaments, wip, ara sí no serveix de res. Commits convencionals imposa un format mínim:

<tipus>(<ambit>): <descripcio>

feat(cites): permetre reserves recurrents setmanals
fix(agenda): no oferir forats que se solapin amb el descans
chore(deps): actualitzar vitest a 1.6.0

Els tipus habituals són feat, fix, chore, docs, test, refactor i perf. La verificació s'automatitza amb commitlint, tant en local (ganxo commit-msg) com al pipeline sobre els commits del PR. La configuració cap en un fitxer:

{ "extends": ["@commitlint/config-conventional"] }

Per què molestar-s'hi? Perquè un format llegible per màquines habilita tres coses concretes que veurem més endavant: generar el changelog automàticament, deduir la versió següent (feat → menor, fix → pedaç, BREAKING CHANGE → major) —totes dues a la lliçó 02-06— i filtrar l'historial per respondre a "quines correccions van entrar en producció aquesta setmana?".

Un matís d'estil que convé fixar: si l'equip fa servir squash en fusionar (lliçó 02-07), el missatge que compta és el títol del PR, no els commits intermedis. En aquest cas, valida el títol del PR i deixa lliures els commits de treball.

  1. Com començar en un projecte amb milers d'avisos

Reservalia té 1.847 avisos de Sonar i uns 400 d'ESLint. Aquest és el pla que funciona, en quatre passos:

Pas 1: congelar la situació. Activa les eines amb --max-warnings 0 sobre una línia base existent, de manera que el número no pugui créixer. ESLint permet generar un fitxer de referència amb els avisos actuals; alternativament, comença aplicant les regles només a les carpetes ja netes i incorpora-hi les altres per fases.

Pas 2: formatar-ho tot d'una vegada. Executa npx prettier --write . en un commit propi, sense cap canvi funcional, i afegeix el seu SHA a .git-blame-ignore-revs perquè git blame continuï sent útil:

npx prettier --write .
git commit -am "chore: aplicar Prettier a tot el repositori"
git rev-parse HEAD >> .git-blame-ignore-revs
git config blame.ignoreRevsFile .git-blame-ignore-revs

És l'únic canvi massiu recomanable, perquè és mecànic i verificable: les proves continuen passant i cap línia no canvia de significat.

Pas 3: activar el gate només sobre el codi nou. El criteri de l'apartat 5. Des d'aquell moment, tot el que hi entra està net.

Pas 4: apujar el llistó per esglaons. Cada mes, una regla passa de warn a error quan el seu nombre d'incompliments ja és a prop de zero. A Reservalia l'ordre acordat és: no-floating-promises (ja en error), després no-unused-vars, després no-explicit-any.

El que no funciona: la setmana de neteja general. Produeix un diff de 20.000 línies impossible de revisar, un risc de regressió alt, i al cap de tres mesos el problema ha tornat perquè res no impedia que tornés.

Errors Comuns i Consells

Error 1: revisar format als pull requests. Si el teu equip discuteix cometes als comentaris de revisió, no li falta disciplina: li falta Prettier al pipeline.

Error 2: linter sense --max-warnings 0. El pipeline surt verd amb centenars d'avisos a dins i tothom aprèn a ignorar-los. És la manera més silenciosa de tenir anàlisi estàtica que no serveix de res.

Error 3: que el pipeline reformati i empenyi el resultat. Canvia el commit que s'està verificant, dispara noves execucions i enreda la traçabilitat de l'artefacte. En CI, --check; en local, --write.

Error 4: confiar només en els ganxos de pre-commit. Se salten amb --no-verify, no existeixen per a qui no els va instal·lar i només veuen els fitxers de l'índex.

Error 5: exigir el 100 % de netedat des del primer dia. Un gate inassolible es desactiva en dues setmanes i s'endú per davant també la part que sí que funcionava.

Consell 1: la mateixa configuració en local i en CI. Un .prettierrc.json i un eslint.config.js versionats, i les mateixes ordres npm als dos llocs. Si l'editor de la Marta formata diferent del pipeline, la guerra és eterna.

Consell 2: mesura el deute com a tendència. El nombre absolut d'avisos no diu res; que baixi mes a mes, sí. Publica'l al resum del job amb $GITHUB_STEP_SUMMARY.

Consell 3: quan desactivis una regla, deixa escrit per què. Un // eslint-disable-next-line sense explicació és una decisió perduda; amb una frase, és una decisió documentada.

Exercicis

Exercici 1

Per a cada fragment, digues quina eina el detecta (Prettier, ESLint o tsc --noEmit) i per què les altres dues no:

// A
const total = preu * quantitat
    + descompte;

// B
function buscar(id: string) { return cites.find(c => c.id === id); }   // c.id es number

// C
cites.forEach(async (c) => { await notificar(c); });                   // el forEach no espera

Exercici 2

L'equip activa Sonar i apareixen 1.847 avisos. El responsable proposa: "dues setmanes d'aturada per deixar-ho a zero, i a partir d'aquí el gate exigeix zero avisos a tot el projecte". Argumenta-hi en contra i proposa un pla alternatiu amb passos concrets.

Exercici 3

En Diego proposa treure el job qualitat del pipeline perquè "ja tenim Husky amb lint-staged en local, i així estalviem un minut per PR". Dona tres arguments pels quals la proposta és perillosa.

Solucions

Solució 1.

  • A → Prettier. És un problema purament de format: falta un punt i coma i el salt de línia és arbitrari. El codi és vàlid i amb tipus correctes, així que tsc calla; no hi ha cap patró problemàtic, així que ESLint calla.
  • B → tsc --noEmit. Comparar c.id (número) amb id (cadena) mitjançant === dona sempre false. Només el sistema de tipus coneix la forma de Cita; el format és correcte i la construcció és idiomàtica, així que ni Prettier ni ESLint no hi tenen res a dir. (Amb la informació de tipus activada, alguna regla de @typescript-eslint també ho pot detectar, precisament perquè fa servir el mateix motor de tipus.)
  • C → ESLint. forEach no espera les funcions asíncrones: el bucle acaba abans que s'enviï cap notificació. És codi vàlid (tsc calla) i ben formatat (Prettier calla), però està malament. Ho detecta no-misused-promises.

Solució 2. Arguments en contra: (a) dues setmanes sense lliurar valor són un cost enorme i difícil de justificar; (b) un diff de milers de línies és irrevisable, i el risc d'introduir regressions en zones sense cobertura és alt; (c) exigir zero avisos a tot el projecte converteix qualsevol PR que fregui un fitxer antic en una tasca de neteja no planificada, cosa que empeny l'equip a esquivar el gate; (d) bona part d'aquests avisos són en codi que ningú no toca, així que arreglar-los no aporta valor real.

Pla alternatiu: (1) aplicar Prettier a tot en un commit mecànic i afegir-lo a .git-blame-ignore-revs; (2) activar --max-warnings 0 sobre la línia base actual perquè el número no pugui créixer; (3) configurar el quality gate en mode nou codi net amb cobertura ≥ 80 % i 0 errors nous; (4) pujar una regla de warn a error cada mes, començant per les de més impacte; (5) publicar la tendència mensual d'avisos perquè la millora sigui visible.

Solució 3. Tres arguments: (1) els ganxos se salten amb git commit --no-verify i no existeixen per a qui cloni el repositori sense instal·lar-los, així que no són un control sinó una comoditat; (2) lint-staged només analitza els fitxers de l'índex, de manera que un canvi a packages/tipus-compartits que trenqui apps/web passaria desapercebut —només un typecheck complet al pipeline ho detecta—; (3) sense el check obligatori al PR, la regla de protecció de branca de la 02-07 no té res a exigir, i el criteri de qualitat passa a dependre de la bona voluntat de cada persona. A més, l'estalvi és fals: el job qualitat corre en paral·lel amb test i build, així que no afegeix cap minut al temps total del pipeline.

Conclusió

El pipeline de Reservalia ja no només comprova que el codi funcioni, sinó que sigui sostenible:

  • L'anàlisi estàtica pertany al pipeline i no a la revisió humana: automatitzar allò objectivable allibera la revisió per parlar de disseny, de seguretat i de si el problema s'ha entès bé.
  • Prettier, ESLint i tsc --noEmit resolen tres problemes diferents i cap no substitueix els altres: format uniforme, patrons problemàtics com una promesa sense await, i coherència de tipus com un camp que no existeix.
  • El job qualitat executa prettier --check (mai --write en CI), npm run lint amb --max-warnings 0 —la política que impedeix que el deute creixi— i npm run typecheck, en menys d'un minut i en paral·lel amb test i build.
  • L'anàlisi profunda aporta complexitat, duplicació i code smells: mesures del cost futur de tocar el codi, útils com a senyal per conversar i no com a veritat absoluta.
  • Els quality gates només s'adopten si són assolibles. El criteri guanyador és "nou codi net": aplicar les condicions únicament a les línies que el PR afegeix o modifica, amb la qual cosa el deute es redueix sol per les zones que de debò es toquen.
  • Els ganxos de pre-commit (Husky + lint-staged) donen retroalimentació en dos segons, però se salten amb --no-verify, no existeixen per a qui no els va instal·lar i només veuen l'índex: són comoditat, mai control.
  • Les convencions de commit verificades amb commitlint fan l'historial llegible per màquines, i això habilita el changelog automàtic i el versionat deduït dels commits.
  • I hi ha un pla realista per a un projecte amb 1.847 avisos: congelar la línia base, formatar-ho tot en un commit mecànic, aplicar el gate només al codi nou i apujar el llistó per esglaons.

Reservalia ja té tres jobs —qualitat, test i build— i una certesa raonable sobre cada commit. El que encara no té és alguna cosa per guardar: la imatge que construeix el job build s'evapora en acabar el runner. A la lliçó següent, Artefactes, Versionat i Promoció, veurem el principi de construir una vegada, desplegar moltes vegades, per què un artefacte immutable és la base de la traçabilitat, quina estratègia de versionat triar i per què latest és perillosa, com es promociona el mateix reservalia/api:a3f9c21 de dev a staging i a prod, i com saber des d'una màquina en producció quin commit està executant. I afegirem al ci.yml el job publicar.

Curs de CI/CD: Integració i Desplegament Continu

Mòdul 1: Introducció al CI/CD

Mòdul 2: Integració Contínua (CI)

Mòdul 3: Desplegament Continu (CD)

Mòdul 4: Pràctiques Avançades de CI/CD

Mòdul 5: Implementació de CI/CD en Projectes Reals

Mòdul 6: Eines i Tecnologies

Mòdul 7: Exercicis Pràctics

Mòdul 8: Recursos Addicionals

© Copyright 2026. Tots els drets reservats