Stylecheck
Fangen wir mit dem einfacheren Thema an: Stylechecks. Im Kern geht es darum, den gleichen Stil über die gesamte Anwendung beizubehalten. Welcher Stil das ist, ist dabei gar nicht so wichtig: Hauptsache, er ist überall gleich!
Warum?
- Pattern-Matching: Unabhängig vom Erfahrungsstand ist es einfacher, Muster zu erkennen, wenn sie immer gleich aufgebaut sind.
- Git-Diffs: Unterschiedliche Stile führen zu einem ständigen Hin-und-Her in den Diffs, wenn verschiedene Styles aufeinandertreffen.
- Werkzeuge: Ein Tool, das Code automatisch formatiert und Regeln direkt anwendet, spart enorm viel Zeit!
Tools
- prettier ist das bekanntere Werkzeug dafür
- Ich bevorzuge aber biome, da es schneller ist und auch Linting übernehmen kann
- Es ist zu 97 % kompatibel mit Prettier (gleiche Regeln), die Unterschiede sind nachvollziehbar
- Es kann formatieren: JavaScript, TypeScript, JSX, TSX, JSON, CSS und GraphQL
Beispiele
- Trivial: einheitliche Anführungszeichen
'vs"oder konsistente Einrückung - Lesbarkeit: Lange Import-Zeilen auf mehrere Zeilen aufteilen
Trivial: Leerzeichen
// VORHER
@Injectable()
export class JwtService {
decodeToken (token: string): any {
return jwt.decode(token);
}
}
// NACHHER
@Injectable()
export class JwtService {
decodeToken(token: string): any {
return jwt.decode(token);
}
}oder Tabs vs. Spaces:
// VORHER: 2 Spaces
describe('ReportsController', () => {
let controller: ReportsController;
beforeEach(async () => {
// NACHHER: Tabs
describe('ReportsController', () => {
let controller: ReportsController;
beforeEach(async () => {* Ich persönlich bevorzuge Spaces, aber Konvention über Sympathie!
Trivial: Zeilenumbrüche
// VORHER
@Module({
controllers: [IntegrationController],
providers: [IntegrationService, PrismaService, ScrapingService, CustomerIoService, JwtService]
})
// NACHHER
@Module({
controllers: [IntegrationController],
providers: [
IntegrationService,
PrismaService,
ScrapingService,
CustomerIoService,
JwtService,
],
})Es gibt auch den Analyzer, der Imports in einer vorhersehbaren Reihenfolge sortiert. Das ist optional, aber gibt jedem Datei-Header die gleiche Struktur. Details zur Reihenfolge gibt es hier.
Linting
Linting gibt uns einen Vorsprung, bevor Kunden Bugs finden. Jeder Linter hat natürlich eine kleine False-Positive-Rate, aber das Regelwerk schließt ganze Klassen von Fehlern aus, bevor sie überhaupt passieren!
Das vollständige Regelwerk von Biome findet sich hier.
Es hat triviale Checks wie no distracting elements (verbietet marquee und blink), aber auch deutlich fortgeschrittenere Regeln.
Meine Lieblingskategorie ist nursery, die veraltete JS-Features enthält, die durch modernere Syntax ersetzt wurden.
Linter-Warnungen: Beispiele
// 1. no banned types
code-block.ts:1:10 lint/complexity/noBannedTypes ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Don't use '{}' as a type.
> 1 │ const n: {} = 0
│ ^^
// 2. complexity
code-block.js:1:10 lint/complexity/noExcessiveCognitiveComplexity ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠ Excessive complexity of 21 detected (max: 15).
> 1 │ function tooComplex() {
│ ^^^^^^^^^^
2 │ for (let x = 0; x < 10; x++) {
3 │ for (let y = 0; y < 10; y++) {
// 3. for .. of vs Array.forEach
code-block.js:1:1 lint/complexity/noForEach ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Prefer for…of instead of forEach.
> 1 │ els.forEach((el) => {
│ ^^^^^^^^^^^^^^^^^^^^^
> 2 │ f(el);
> 3 │ })
│ ^^
// 4. no useless catch
code-block.js:4:5 lint/complexity/noUselessCatch ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ The catch clause that only rethrows the original error is useless.
2 │ doSomething();
3 │ } catch(e) {
> 4 │ throw e;
│ ^^^^^^^^
// 5. no useless ternary
const a = foo === 1 ? false : true; // einfach foo !== 1 schreiben
code-block.js:1:9 lint/complexity/noUselessTernary FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ Unnecessary use of boolean literals in conditional expression.
> 1 │ const a = foo === 1 ? false : true;
│ ^^^^^^^^^^^^^^^^^^^^^^^^- Trivial, aber wichtig: keine unnötigen/schädlichen Typen
- Nutzt Cyclomatic Complexity als Obergrenze für komplexen Code. Zu komplex → vereinfachen. Mehr Details unter McCabe Metrik. Wir sollten unter 10 bleiben (10 mögliche Datenpfade durch eine Funktion) und MÜSSEN unter 15 bleiben.
- Sprachfeatures:
for .. of ..ist leichter zu lesen alsArray.forEach(...). Auch ein Performance-Vorteil! - Quasi toter Code ohne Funktion
- Ein schönes Beispiel für
FIXABLE-Lints: Dieser wird automatisch korrigiert!
... Und noch vieles mehr! Linter sollten in jeder Phase des Entwicklungsprozesses eingesetzt werden. Je früher, desto besser.
Biome im Editor
Für alle genannten Gründe sollten wir Biome überall einsetzen, wo es möglich ist.
Für die gängigsten Editoren hier die Extension/Plugin-Links, damit die roten Wellenlinien direkt verschwinden und Auto-Formatierung aktiv ist:
- Allgemein und Vim/Emacs/etc.: biomejs.dev
- WebStorm: plugins.jetbrains.com
- VSCode: VSCode Marketplace
- CLI:
npm install --save-dev --save-exact @biomejs/biome- Formatieren:
biome format ORDNER/DATEIoderbiome format --write ORDNER/DATEI - Linten:
biome lint ORDNER/DATEIoderbiome lint --write ORDNER/DATEI - Alle Checks (Format + Lint):
biome check ORDNER/DATEI
Danach unbedingt:
- Auto-Formatierung aktivieren
- Die Linter-Fehler tatsächlich lesen und beheben