# Kvalitetsport

## Hvad det er

En kvalitetsport er en række betingelser, en ændring skal opfylde, før den må gå
videre til næste trin, med ét resultat til sidst: bestået eller fejlet. Trinnet
kan være et commit, en merge til hovedgrenen, en release eller en udrulning.
Betingelserne er alt, hvad en maskine selv kan afgøre: en ren lint-kørsel,
tests, der består, en dækningsgrad, et build, der gennemføres.

Det afgørende ord er port. Et tjek rapporterer; en port beslutter, og
beslutningen håndhæves af det, der styrer næste trin. En linter, der skriver
fyrre advarsler i en log, er et tjek. Den samme linter koblet sådan, at
merge-knappen forbliver deaktiveret, indtil outputtet er rent, er en del af en
port.

SonarQube Cloud, hvor begrebet er en navngiven funktion, beskriver en
kvalitetsport som betingelser, koden måles op imod under analysen. Hver
betingelse parrer en metrik med en sammenligningsoperator og en
fejlværdi, så »reliability issues greater than 0« er en betingelse, og er blot
én af betingelserne opfyldt, fejler porten. En betingelse gælder enten ny kode
eller koden som helhed.

Hostingplatformene bygger samme idé ind i deres merge-flow:

- **GitHub** har en indstilling til branch protection, »Require status checks
  before merging«. Et påkrævet tjek skal have status success, skipped eller
  neutral, før ændringer når den beskyttede gren.
- **GitLab** har et merge-tjek, der hedder »Pipelines must succeed«. Når det er
  slået til, kan en merge request ikke merges, mens dens pipeline fejler, og
  heller ikke, når der slet ingen pipeline er.
- **På udviklerens egen maskine** installerer pre-commit tjek som Git-hooks, der
  kører ved commit eller push, og en task runner som Task giver hele sættet ét
  navn.

## Hvorfor det betyder noget

En standard, der kun findes i et dokument, bliver forhandlet væk én undtagelse
ad gangen. En standard, der får buildet til at fejle, bliver fulgt af alle, også
af den, der skrev den, den eftermiddag, hvor vedkommende helst ville springe den
over. En port gør aftalte regler eksekverbare, så det at overholde dem holder op
med at afhænge af hukommelse og humør.

Den ændrer også, hvad review mellem mennesker er til for. Når en maskine
allerede har fanget formatering, ubrugte imports, døde links og lækkede
hemmeligheder, kan revieweren bruge sin opmærksomhed på hensigt og design, som
en maskine ikke kan vurdere.

Idéen om ny kode gør en port mulig at indføre på en gammel kodebase. Sonar way,
standardporten i SonarQube Cloud, fokuserer på ny kode frem for at udbedre
gammel kode, med betingelser som mindst 80,0 % testdækning og højst 3,0 %
duplikering i ny kode. På en pull request bruges kun betingelserne om ny kode.
Et projekt med tusindvis af gamle fund kan derfor slå porten til uden først at
afbetale efterslæbet.

Hastighed afgør, om folk lader porten være slået til. Task kører en opgaves
afhængigheder samtidigt, så uafhængige tjek kan køre side om side. Det hjælper
også at dele tjekkene efter pris: den billige halvdel ved hvert commit, hele
sættet, før koden forlader maskinen.

Dette site har sin egen port: `task check`, kørt på den maskine, sitet skrives
på, og ikke hos en hostet byggetjeneste. Hver diagnostik tæller som en fejl,
sitegeneratoren kører med `--panicOnWarning`, så selv en udfasningsmeddelelse
standser buildet, og inline-undertrykkelser som `# noqa` eller
`stylelint-disable` er forbudt, så en fejl bliver rettet ved årsagen i stedet
for at blive gjort tavs. En pre-commit-hook kører de fil-nære tjek ved hvert
commit, og en pre-push-hook kører hele `task check`, som tager omkring seks
minutter. En grøn kørsel fra et rent træ stempler træet, og udrulningsscriptet
afviser et træ uden stempel. Den eneste tilsidesættelse er en miljøvariabel, der
skriver en tydelig advarsel, så den, der springer porten over i en nødsituation,
må sige det højt.

## Sådan ser det ud i praksis

Sitets [kolofon](/da/credits/) oplister værktøjerne i porten: vnu til HTML,
Stylelint, axe-core på én side pr. skabelon, Lighthouse, lychee til links,
Prettier, markdownlint, ESLint, Ruff, basedpyright, yamllint, ShellCheck,
codespell og gitleaks, og Task kører det hele under ét navn pr. opgave.
Siden nævner også tidsbudgettet: browsertjekkene, som Puppeteer styrer,
tager omkring tyve minutter, så porten holder den hurtige halvdel, og et
menneske kører den langsomme efter behov.

Et enmandsprojekt har slet ingen reviewer, og det er den situation, en side i
porteføljen tager udgangspunkt i. Standarderne skulle være eksekverbare, så et
brud får et commit til at fejle i stedet for at vente på, at nogen opdager det.
Svaret blev
[en gate med 22 lintere uden noget advarselsniveau](/da/portfolio/built-a-22-linter-commit-gate-127/),
plus projektspecifikke tjek, som intet standardværktøj har en holdning til, for
eksempel at det udrullede træ ikke indeholder private stier eller private
værtsnavne, og at teksten overholder tonereglerne på alle tre sprog. En fejl, der
fandtes i forvejen, rettes i samme omgang. Siden nævner prisen ligeud: det er
langsomt at committe, og porten er den eneste reviewer, projektet har.

En port kan også være uenig med sig selv. En anden side beskriver et projekt med
en commit-hook og en fuld kvalitetsport, et dokument, der sagde, at hooken kørte
porten, og to lister over tjek, vedligeholdt i hånden i to filer.
[En test, der udleder, hvilke tjek de to lister reelt kalder](/da/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/),
viste fem tjek, der kun kørte i porten, og det var netop dem, der læser prosaen.
Hvert tjek, der beskytter kode, kørte automatisk; hvert tjek, der beskytter
teksten, kørte kun, når nogen huskede det. De fem blev flyttet ind i hooken, to
af dem blev gjort hurtige nok til at holde sig inden for dens tidsbudget, og
sammenligningstesten blev stående, så listerne ikke kan glide fra hinanden igen.

## Hvor det går galt

De fleste kvalitetsporte svigter ved at lukke ting igennem og samtidig vise
grønt.

**Advarsler hober sig op bag et bestået.** En port, der kun fejler på fejl,
lader advarsler samle sig, til loggen er for lang til at læse, og så kommer et
reelt problem som advarsel nummer to hundrede og en. Fjern advarselsniveauet:
enten er en regel værd at fejle på, eller også er den slået fra.

**Et oversprunget tjek tæller som bestået.** På GitHub opfylder status skipped
et påkrævet tjek, og et job, der springes over af sin `if:`-betingelse, melder
success. Et job, der afhænger af et fejlet job, springes også over og blokerer
måske ikke merge. GitHubs råd er at bruge `always()` sammen med `needs`: kræv ét
job, der altid kører og kun består, når alt, hvad det afhænger af, lykkedes.

```yaml
# .github/workflows/check.yml (uddrag)
jobs:
    gate:
        if: always()
        needs: [lint, test, build]
        runs-on: ubuntu-latest
        steps:
            - run: |
                  for result in ${{ join(needs.*.result, ' ') }}; do
                      test "$result" = success || exit 1
                  done
```

Den modsatte fælde findes også: et påkrævet workflow, der springes over af et
sti- eller grenfilter, efterlader sine tjek som pending og blokerer hver merge.
Undgå at kræve workflows, der kan springes over.

**To lister glider fra hinanden.** Hooken kører ét sæt, serveren et andet, og et
dokument påstår, at de er ens. Giv tjekkene én definition, og kald den fra hvert
sted, eller hold en test, der sammenligner listerne og fejler, når de afviger.

**Porten bliver langsom, og folk går udenom.** Et flag, der springer hooks over,
er lige ved hånden, og på GitHub gælder branch protection som standard ikke for
administratorer af repositoriet; »Do not allow bypassing the above settings«
ændrer det. Mål, hvor tiden går, hold commit-halvdelen lille, og flyt et reelt
langsomt tjek til et dokumenteret manuelt trin.

**Et tjek består uden at have tjekket noget.** En linter rettet mod en glob, der
rammer nul filer, eller et ignore-mønster, der sluger en hel mappe, afslutter
rent, og en ren afslutning ser ud som ren kode. Test selve porten: giv hvert
tjek en fil, der vides at være forkert, og forvent, at tjekket fejler.

## Læs mere

- [Introduction to quality gates](https://docs.sonarsource.com/sonarqube-cloud/standards/managing-quality-gates/introduction-to-quality-gates)
  — betingelser, standardporten Sonar way og fokus på ny kode.
- [About protected branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches)
  — påkrævede statustjek, og hvem der kan omgå dem.
- [Troubleshooting required status checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks)
  — hvilke statusser der tæller som bestået, og hvad et oversprunget workflow
  eller job gør.
- [Auto-merge](https://docs.gitlab.com/user/project/merge_requests/auto_merge/)
  — GitLabs tjek »Pipelines must succeed«, og hvordan det behandler oversprungne
  pipelines.
- [pre-commit](https://pre-commit.com/) — tjek erklæret én gang og installeret
  som Git-hooks til commit og push.
- [Task dependencies](https://taskfile.dev/docs/guide/dependencies) — hvordan
  Task kører en opgaves afhængigheder samtidigt og dens kommandoer i rækkefølge.

<https://engineer.company/da/glossary/quality-gate/>
