Coding Guidlines
Inhalt
- Allgemein
- Java
- Java - Transpiler
- TypeScript
- Vue
- 1. Aufbau einer Single File Component
- 2. Formatierung in Tags
- 3.
v-if
,v-else
,v-show
undv-for
immer nach Tagnamen - 4. Kurzschreibweise für gleichnamige Props verwenden
- 5. Direkte Callback-Zuweisung statt Inline-Funktionen
- 6.
watch
durchcomputed
ersetzen - 7. Keine lokalen Änderungen an globalen UI-Komponenten
- 8. Funktionen auslagern, statt sie inline zu verwenden
- 9. Inline-CSS für einzelne Attribute
- 10. Tailwind-Klassen statt benutzerdefiniertem CSS
- 11. Funktionsdefinition mit
function
- 12. Strukturierung des Script-Bereichs in Vue-Komponenten
- 13. Immer Semikolon verwenden
- 14. Verzichte auf Vue-Typendefinitionen
- 15.
Iterable<T>
statt spezifischer Container-Typen - 16. Arrow-Functions für komplexe Objekte und Routen
- 17.
DeveloperNotificationException
bei Programmierfehlern - 18.
UserNotificationException
bei Benutzerfehlern - 19. ESLint statt Prettier
- Vue - Transpiler
Allgemein
1. Einzeilige Befehle ohne Klammern
Verzichte auf geschweifte Klammern bei einzeiligen Anweisungen. Schreibe die Anweisung aber dennoch in eine neue Zeile.
Richtig:
if (condition)
doSomething();
for (int i = 0; i < 10; i++)
doSomething();
Falsch:
if (condition) {
doSomething();
}
for (int i = 0; i < 10; i++) {
doSomething();
}
if (condition) doSomething();
for (int i = 0; i < 10; i+) doSomething();
2. Klammern bei gemischten Operatoren
Wenn mehrere Operatoren in einem Ausdruck verwendet werden, setze Klammern, um die Priorität klar zu definieren und Missverständnisse zu vermeiden.
Richtig:
// Klar definierte Reihenfolge durch Klammern:
if ((a && b) || c)
return;
// Deutliche Priorität zwischen instanceof und Vergleich:
boolean result = (x instanceof String) && (y > 5);
Falsch:
// Unklare Reihenfolge, da && eine höhere Priorität als || hat:
if (a && b || c)
return;
// Ohne Klammern unklar, was zuerst ausgewertet wird:
boolean result = x instanceof String && y > 5;
3. Interfaces statt konkreter Klassen
Beim Design von Software sollte die Verwendung von Interfaces gegenüber konkreten Klassen bevorzugt werden, um Flexibilität, Erweiterbarkeit und Testbarkeit zu erhöhen. Durch die Nutzung von Interfaces wird die Abhängigkeit von bestimmten Implementierungen reduziert und es wird einfacher, den Code zu erweitern oder auszutauschen, ohne andere Teile des Systems zu verändern.
Empfohlene Interfaces für Sammlungen in Java
List<E>
stattArrayList<E>
oderLinkedList<E>
Set<E>
stattHashSet<E>
oderTreeSet<E>
Map<K, V>
stattHashMap<K, V>
oderTreeMap<K, V>
Queue<E>
stattPriorityQueue<E>
Richtig:
import java.util.List;
public class DataProcessor {
public void processData(List<Integer> data) {
for (Integer number : data)
System.out.println(number);
}
}
Falsch:
import java.util.ArrayList;
public class DataProcessor {
public void processData(ArrayList<Integer> data) {
for (Integer number : data)
System.out.println(number);
}
}
4. Return-Statements immer in eine neue Zeile
Return-Statements sollten immer in einer eigenen Zeile stehen. Das gilt auch für switches.
Richtig:
if (x > 0)
return true;
Falsch:
if (x > 0) return true;
Java
1. final
für unveränderliche Variablen
Nutze das Schlüsselwort final
, um Variablen unveränderlich zu machen. Dadurch wird der Code klarer und verhindert unbeabsichtigte Änderungen an Werten, was zu sichereren Programmen führt.
Richtig:
public class Example {
public void method() {
final int x = 10; // x bleibt unverändert
}
}
Falsch:
public class Example {
public void method() {
int x = 10; // könnte verändert werden, auch wenn es nicht notwendig ist
}
}
2. JavaDoc formatieren
JavaDoc-Kommentare dienen dazu, den Code für andere Entwickler verständlicher zu machen, indem sie die Funktion und den Zweck einer Methode oder Klasse klar und strukturiert beschreiben. Ein gut formatierter JavaDoc-Kommentar besteht aus einer kurzen Beschreibung der Methode, gefolgt von sogenannten "Tags" (z.B. @param
, @return
, @throws
).
Zwischen der Methoden-/Klassenbeschreibung und den Tags muss immer eine leere Zeile stehen. Auch muss zwischen verschiedenen Arten von Tags eine Leerzeile sein.
In Javadoc-Kommentaren werden bestimmte Tags, die eine zusätzliche Beschreibung enthalten, nach folgenden Regeln formatiert:
- Die Beschreibung wird immer kleingeschrieben.
- Zwischen dem Tag-Namen und der Beschreibung wird ein Abstand von genau drei Leerzeichen zum längsten Tag-Namen eingefügt.
So entsteht eine saubere und übersichtliche Struktur der Kommentare, die die Lesbarkeit verbessert.
Richtig:
/**
* Berechnet die Summe zweier Ganzzahlen.
*
* @param shortName die erste Zahl, die addiert wird
* @param someLongName die zweite Zahl, die addiert wird
*
* @return die Summe der beiden Zahlen
*
* @throws ArithmeticException wenn eine Überlaufbedingung auftritt
*/
public int sum(int shortName, int someLongName) throws ArithmeticException {
return shortName + someLongName;
}
Falsch:
/**
* Berechnet die Summe zweier Ganzzahlen.
* @param shortName Die erste Zahl, die addiert wird
* @param someLongName Die zweite Zahl, die addiert wird
* @return Die Summe der beiden Zahlen
* @throws ArithmeticException Wenn eine Überlaufbedingung auftritt
*/
public int sum(int shortName, int someLongName) throws ArithmeticException {
return shortName + someLongName;
}
Java - Transpiler
Java-Code, der transpiliert wird, muss einige Sonderregeln beachten. Dies betrifft folgende Unterprojekte: svws-schulen
, svws-asd
, svws-core
1. @NotNull
und @AllowNull
korrekt platzieren
Die @NotNull
- und @AllowNull
-Annotation gehört direkt vor den Typ des Parameters oder der Variablen, um Klarheit zu gewährleisten und Missverständnisse zu vermeiden. Die Platzierung dieser Annotationen ist notwendig, damit korrekter TypeScript Code generiert werden kann.
Richtig:
public void setName(final @NotNull String name) {
this.name = name;
}
Falsch:
public void setName(@NotNull final String name) {
this.name = name;
}
public void setName(final String name) {
this.name = name;
}
2. Keine Streams verwenden
Streams müssen vermieden werden, da sie nicht transpiliert werden können. Nutze stattdessen klassische Schleifen.
Richtig:
for (String item : list)
process(item);
Falsch:
list.stream().forEach(item -> process(item));
3. switch-Statements
und switch-Expressions
Grundsätzlich ist die Verwendung von switch-Expressions
zu bevorzugen. Da diese aber nicht vollständig vom Transpiler unterstützt werden, kann auch die Verwendung von switch-Statements
notwendig sein.
switch-Expression (bevorzugt):
public String getDay(int day) {
return switch (day) {
case 1 -> "Monday";
case 2 -> "Tuesday";
default -> "Unknown";
};
}
switch-Statement (falls nicht transpilierbar):
public String getDay(int day) {
switch (day) {
case 1:
return "Monday";
case 2:
return "Tuesday";
default:
return "Unknown";
}
}
TypeScript
1. Explizite Null- und Undefined-Überprüfungen
Vermeide den Einsatz von !!value
, um auf null
oder undefined
zu prüfen. Nutze stattdessen explizite Vergleiche, um potenzielle Fehlerquellen auszuschließen.
Richtig:
if ((value !== null) && (value !== undefined))
// handle value
Falsch:
if (!!value)
// handle value
2. const
für unveränderliche Variablen
Nutze const
, um Variablen zu definieren, die nicht neu zugewiesen werden, und so versehentliche Änderungen zu verhindern.
Richtig:
const name = "John";
Falsch:
let name = "John";
3. for of
-Schleifen bevorzugen
Verwende for of
-Schleifen, um über Arrays und Iterable-Objekte zu iterieren.
Richtig:
for (const item of array)
// handle item
Falsch:
for (let i = 0; i < array.length; i++)
const item = array[i];
// handle item
array.forEach(item => {
// handle item
});
4. Keine High-Level-Funktionen wie map
, filter
, reduce
, forEach
Vermeide High-Level-Funktionen und nutze klassische for of
-Schleifen.
Richtig:
for (const element of array)
process(element);
Falsch:
array.forEach(item => process(item));
Vue
1. Aufbau einer Single File Component
Verwende in einer SFC stets die Reihenfolge <template>
, <script>
, <style>
.
Richtig:
<template>
<!-- Do something -->
</template>
<script setup lang="ts">
// Do Something
</script>
<style lang="postcss">
/*
Some styles
*/
</style>
Falsch:
<script setup lang="ts">
// Do Something
</script>
<style>
/*
Some styles
*/
</style>
<template>
<!-- Do something -->
</template>
2. Formatierung in Tags
Innerhalb der Vue Tags <template>
, <script>
und <style>
soll der Code eingerückt sein. Außerdem soll sich zwischen den Tags <script>
und <style>
und deren Inhalt eine Leerzeile befinden. Dies gilt nicht für <template>
(wird von ESLint sonst kritisiert).
Richtig:
<template>
<span>Text</span>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => firstName.value + ' ' + lastName.value);
</script>
<style lang="postcss">
.my-class {
@apply flex;
}
</style>
Falsch:
<template>
<span>Text</span>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => firstName.value + ' ' + lastName.value);
</script>
<style lang="postcss">
.my-class {
@apply flex;
}
</style>
3. v-if
, v-else
, v-show
und v-for
immer nach Tagnamen
Die Direktiven v-if
, v-else
, v-show
und v-for
müssen immer am Anfang des Elements stehen, um bedingte Anzeigen oder Schleifen sofort erkennen zu können.
Richtig:
<template>
<div v-if="isVisible" class="my-class">v-if</div>
<div v-else class="my-class">v-else</div>
<div v-show="isVisible" class="my-class">v-show</div>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>
Falsch:
<template>
<div class="my-class" v-if="isVisible">v-if</div>
<div class="my-class" v-else>v-else</div>
<div class="my-class" v-show="isVisible">v-show</div>
<li :key="item.id" v-for="item in items">{{ item.name }}</li>
</template>
4. Kurzschreibweise für gleichnamige Props verwenden
Wenn der Propertyname und der Wert denselben Namen haben, verwende die Kurzschreibweise.
Richtig:
<template>
<!-- Kurzschreibweise, da Property- und Wertname "title" übereinstimmen -->
<card-component :title />
</template>
Falsch:
<template>
<!-- Hier ist die explizite Bindung redundant -->
<card-component :title="title" />
</template>
5. Direkte Callback-Zuweisung statt Inline-Funktionen
Verwende direkte Callback-Zuweisungen anstelle von Inline-Funktionen.
Richtig:
<template>
<button @click="click">Click me</button>
</template>
Falsch:
<template>
<button @click="(value) => click(value)">Click me</button>
</template>
6. watch
durch computed
ersetzen
Vermeide watch
-Anweisungen, wenn dieselbe Funktionalität durch computed
-Properties abgedeckt werden kann.
Richtig:
<script setup lang="ts">
import { computed, ref } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => firstName.value + ' ' + lastName.value);
</script>
Falsch:
<script setup lang="ts">
import { ref, watch } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
let fullName = ref(firstName.value + ' ' + lastName.value);
watch(firstName, (newVal) => {
fullName.value = newVal + ' ' + lastName.value;
});
</script>
7. Keine lokalen Änderungen an globalen UI-Komponenten
Vermeide lokale Anpassungen an globalen UI-Komponenten, da diese Auswirkungen auf andere Projekte haben. Notwendige Änderungen müssen abgesprochen werden.
8. Funktionen auslagern, statt sie inline zu verwenden
Keine Logik inline verwenden, sondern diese beispielsweise in ein computed
auslagern. Ternaries sind allerdings in Ordnung.
Richtig:
<template>
<div v-if="isVisible">Visible</div> <!-- Logik in Funktion ausgelagert -->
<card-component :title="noMaintenance ? 'Please log in' : 'Maintenance'" /> <!-- Inline Ternary ist erlaubt-->
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const noMaintenance = ref(true);
const userRole = ref('admin');
const isVisible = computed(() => {
if (noMaintenance.value)
if (userRole.value === 'admin')
return true;
return false;
});
</script>
Falsch:
<template>
<!-- Theoretisch möglich, aber unübersichtlich -->
<div v-if="noMaintenance && userRole === 'admin'">Visible</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue';
const noMaintenance = ref(true);
const userRole = ref('admin');
</script>
9. Inline-CSS für einzelne Attribute
Wenn nur ein einzelnes CSS-Attribut verwendet wird, ist Inline-Styling einfacher und effizienter als eine separate Klasse. Hinweis: Die meisten Stylings können und sollen über Tailwind-Klassen gelöst werden, so auch das folgende Beispiel.
Richtig:
<template>
<div :style="{ color: 'red' }">Text</div>
</template>
Falsch:
<template>
<div class="red-text">Text</div>
</template>
<style lang="postcss">
.red-text {
color: red;
}
</style>
10. Tailwind-Klassen statt benutzerdefiniertem CSS
Bevor du eigenes CSS hinzufügst, prüfe, ob Tailwind-Klassen existieren, die denselben Effekt haben, um Redundanz zu vermeiden. Soll eine Klasse mehrere Tailwind-Klassen anwenden, ist das wie folgt möglich:
<style lang="postcss">
.my-class {
@apply flex flex-col justify-between;
}
</style>
11. Funktionsdefinition mit function
Verwende named Functions, um diese von computed
unterscheiden zu können, die mit Arrow-Funktionen definiert werden. Nur als anonyme Funktion, in Vue Lifecycle Hooks und in routeData
dürfen sie verwendet werden, wenn sie als Props an die Komponenten weitergereicht werden.
Richtig:
<script setup lang="ts">
function sum(a: number, b: number): number {
return a + b;
}
</script>
Falsch:
<script setup lang="ts">
const sum = (a: number, b: number) => {
return a + b;
};
</script>
12. Strukturierung des Script-Bereichs in Vue-Komponenten
Um die Lesbarkeit und Wartbarkeit von Vue-Komponenten zu verbessern, ist es wichtig, eine einheitliche Struktur im Script-Bereich zu wahren. Die folgende Reihenfolge sollte eingehalten werden: (shallow)ref
, computed
, functions
. In größeren Komponenten ist eine thematisches Clustering aber dennoch möglich.
Richtig:
<script setup lang="ts">
import { ref, computed } from "vue";
const username = ref("Admin");
const password = ref("");
const info = computed(() => props.info());
function connect() {
// connecting
}
</script>
Falsch:
<script setup lang="ts">
import { ref, computed } from "vue";
function connect() {
// connecting
}
const username = ref("Admin");
const info = computed(() => props.info());
const password = ref("");
</script>
13. Immer Semikolon verwenden
Um die Konsistenz und Lesbarkeit des Codes zu gewährleisten, sollte in allen Dateien das Semikolon am Ende jeder Anweisung verwendet werden. Dies hilft, potenzielle Fehler zu vermeiden und die Codebasis einheitlich zu halten.
Richtig:
<script setup lang="ts">
const name = 'Alice';
console.log(name);
</script>
Falsch:
<script setup lang="ts">
const name = 'Alice'
console.log(name)
</script>
14. Verzichte auf Vue-Typendefinitionen
Nutze keine Typendefinitionen aus Vue wie zum Beispiel Ref
, Computed
oder WriteableComputed
.
Richtig:
<script setup lang="ts">
const foo = ref<boolean>(false);
</script>
Falsch:
<script setup lang="ts">
const foo: Ref<boolean> = ref(false);
</script>
15. Iterable<T>
statt spezifischer Container-Typen
Wenn Funktionen als Props übergeben werden und Parameter wie Listen oder Arrays erwarten, sollte nach Möglichkeit Iterable<T>
anstelle von Array<T>
, List<T>
, etc. verwendet werden. Dadurch wird der Code flexibler und universeller, da Iterable<T>
sowohl Arrays als auch andere iterierbare Strukturen akzeptiert. Dies ermöglicht es, mit unterschiedlichen Datenstrukturen zu arbeiten, ohne die Funktion anpassen zu müssen.
Richtig
<script setup lang="ts">
const props = defineProps<{
processData: (data: Iterable<number>) => void;
}>();
// Props innerhalb einer Funktion verwenden, um die Reaktivität sicherzustellen:
function handleProcess() {
props.processData([1, 2, 3]); // Array
props.processData(new Set([4, 5, 6])); // Set
}
handleProcess();
</script>
Falsch
<script setup lang="ts">
const props = defineProps<{
processData: (data: Array<number>) => void;
}>();
// Props innerhalb einer Funktion verwenden, um die Reaktivität sicherzustellen:
function handleProcess() {
props.processData([1, 2, 3]); // Array
}
handleProcess();
</script>
16. Arrow-Functions für komplexe Objekte und Routen
Die Kommunikation von Child-Komponenten zu Parents geschieht in der Regel über emits. Dies ist erlaubt, wenn dabei nur einfache Typen übergeben werden (boolean
, number
, string
) und kein Event Bubbling über verschachtelte Komponenten stattfindet. Andernfalls müssen Arrow-Functions verwendet werden. Das ist auch beim Routing der Fall, um sicherzustellen, dass Funktionen im richtigen Kontext ausgeführt werden.
Beispiel Routing
SBenutzerprofilAppProps.ts
export interface BenutzerprofilAppProps {
// Komplexes Objekt als Arrow-Function:
benutzer: () => BenutzerDaten;
// Funktion als Arrow-Function prop statt emit:
patchBenutzerEMailDaten: (data: Partial<BenutzerEMailDaten>) => Promise<void>;
}
RouteBenutzerprofil.ts
public getProps(to: RouteLocationNormalized): BenutzerprofilAppProps {
return {
benutzer: () => this.data.benutzer,
patchBenutzerEMailDaten: this.data.patchBenutzerEMailDaten,
};
}
RouteDataBenutzerprofil.ts
public patchBenutzerEMailDaten = async (data: Partial<BenutzerEMailDaten>) => {
await api.server.patchBenutzerEmailDaten(data, api.schema);
}
public get benutzer(): BenutzerDaten {
return api.benutzerdaten;
}
SBenutzerprofilApp.vue
<template>
<svws-ui-text-input @change="usernameSMTP => patchBenutzerEMailDaten({name: name ?? undefined})" />
</template>
Beispiel verschachtelte Parent-Child Kommunikation
Richtig
Parent-Komponente
Gibt die Funktion handleAlert
als Arrow-Function Prop an die Intermediate-Komponente
<template>
<intermediate :on-log="(message: string) => console.log(message)" />
</template>
<script setup lang="ts">
import Intermediate from './Intermediate.vue';
</script>
Intermediate-Komponente
Gibt die Funktion handleAlert
als Arrow-Function Prop an die Child-Komponente weiter
<template>
<child :on-log />
</template>
<script setup lang="ts">
import Child from './Child.vue';
const props = defineProps<{
onLog: (message: string) => void;
}>();
</script>
Child-Komponente
Führt die handleAlert
Funktion im Parent-Kontext aus
<template>
<button @click="onLog('Hello!')">Hello!</button>
</template>
<script setup lang="ts">
const props = defineProps<{
onLog: (message: string) => void;
}>();
</script>
Falsch
Parent-Komponente
Hört auf das Event @on-log
und führt die Funktion selbst aus
<template>
<intermediate @on-log="(message: string) => console.log(message)" />
</template>
<script setup lang="ts">
import Intermediate from './Intermediate.vue';
</script>
Intermediate-Komponente
Reicht das emit
des Childs weiter an den Parent durch
<template>
<child @on-child-log="(message: string) => emit('onLog', message)" />
</template>
<script setup lang="ts">
import Child from './Child.vue';
const emit = defineEmits<{
(e: 'onLog', message: string): void;
}>();
</script>
Child-Komponente
Führt ein emit
aus, wenn der Button geklickt ist.
<template>
<button @click="emit('onChildLog', 'Hello!')">Hello!</button>
</template>
<script setup lang="ts">
const emit = defineEmits<{
(e: 'onChildLog', message: string): void;
}>();
</script>
17. DeveloperNotificationException
bei Programmierfehlern
Mögliche Programmierfehler müssen abgefangen werden und es muss die spezielle Exception DeveloperNotificationException
geworfen werden.
Richtig
<script lang="ts" setup>
function add() {
const daten = getDaten();
if (daten === null)
throw new DeveloperNotificationException("Die add-Methode darf nur aufgerufen werden, wenn ein gültiger Wert ausgewählt wurde.");
}
</script>
Falsch
<script lang="ts" setup>
function add() {
const daten = getDaten();
if (daten === null)
throw new Exception("Die add-Methode darf nur aufgerufen werden, wenn ein gültiger Wert ausgewählt wurde.");
}
</script>
18. UserNotificationException
bei Benutzerfehlern
Mögliche Benutzerfehler müssen abgefangen werden und es muss die spezielle Exception UserNotificationException
geworfen werden.
Richtig
<script lang="ts" setup>
addRaum = async (raum: Partial<StundenplanRaum>) => {
const id = this._state.value.auswahl?.id;
if ((raum.kuerzel === undefined) || raumExistsByKuerzel(raum.kuerzel))
throw new UserNotificationException('Ein Raum mit diesem Kürzel existiert bereits');
}
</script>
Falsch
<script lang="ts" setup>
addRaum = async (raum: Partial<StundenplanRaum>) => {
const id = this._state.value.auswahl?.id;
if ((raum.kuerzel === undefined) || raumExistsByKuerzel(raum.kuerzel))
throw new Error('Ein Raum mit diesem Kürzel existiert bereits');
}
</script>
19. ESLint statt Prettier
Verwende für die Formatierung des Codes keinen Prettier, sondern stattdessen die Korrekturen von ESLint.
Vue - Transpiler
Die folgenden Regelungen beziehen sich ausschließlich auf transpilierte Java-Objekte.
1. shallowRef
statt ref
für transpilierte Java-Objekte
Verwende shallowRef
anstelle von ref
für transpilierte Java-Objekte, um Probleme mit JavaScript-Proxies zu vermeiden. Gib außerdem auch immer den Typ des shallowRef
an.
Richtig:
<script setup lang="ts">
const javaObject = shallowRef<Typ>(transpiledJavaObject);
</script>
Falsch:
<script setup lang="ts">
const javaObject = ref(transpiledJavaObject);
</script>
2. Getter für reaktive Props verwenden
Um sicherzustellen, dass Props reaktiv bleiben, sollten sie über Getter übergeben werden. Dies betrifft ausschließlich transpilierte Java-Objekte, die aus Routen kommen.
Richtig:
<template>
<child-component :prop="getProp()"></child-component>
</template>
Falsch:
<template>
<child-component :prop="prop"></child-component>
</template>