docs: Coding Standards Fundament — Regelwerk + Implementierungsplan
This commit is contained in:
@@ -0,0 +1,687 @@
|
||||
# BSN Coding Standards — Das verbindliche Regelwerk
|
||||
|
||||
> **Version:** 1.0 — 21. Juni 2026
|
||||
> **Autor:** Cody (Coding-Agent für Daniel Krause, brettspiel-news.de)
|
||||
> **Verbindlichkeit:** Diese Regeln werden durch automatisierte Tools (pre-commit, Ruff, mypy) durchgesetzt. Kein Merge ohne bestandene Checks.
|
||||
|
||||
---
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Allgemeine Prinzipien](#1-allgemeine-prinzipien)
|
||||
2. [Code-Stil](#2-code-stil)
|
||||
3. [Typ-Annotationen](#3-typ-annotationen)
|
||||
4. [Namenskonventionen](#4-namenskonventionen)
|
||||
5. [Kommentare & Docstrings](#5-kommentare--docstrings)
|
||||
6. [Projektstruktur & Architektur](#6-projektstruktur--architektur)
|
||||
7. [Sicherheit](#7-sicherheit)
|
||||
8. [Testing](#8-testing)
|
||||
9. [Flask-spezifische Regeln](#9-flask-spezifische-regeln)
|
||||
10. [Git & Commits](#10-git--commits)
|
||||
11. [Toolchain & Durchsetzung](#11-toolchain--durchsetzung)
|
||||
|
||||
---
|
||||
|
||||
## 1. Allgemeine Prinzipien
|
||||
|
||||
### 1.1 Das Zen of Python (PEP 20)
|
||||
|
||||
```python
|
||||
import this
|
||||
```
|
||||
|
||||
Die wichtigsten Prinzipien für dieses Projekt:
|
||||
|
||||
| Prinzip | Bedeutung |
|
||||
|---|---|
|
||||
| **Readability counts.** | Code wird 10× öfter gelesen als geschrieben. Schreibe für den Leser. |
|
||||
| **Explicit is better than implicit.** | Kein Magic. Kein `**kwargs`-Missbrauch. Typen immer sichtbar. |
|
||||
| **Simple is better than complex.** | YAGNI — You Ain't Gonna Need It. Keine überflüssigen Abstraktionen. |
|
||||
| **Errors should never pass silently.** | Immer `try/except` mit spezifischen Exceptions + Logging. |
|
||||
| **There should be one obvious way to do it.** | Einheitlicher Stil im gesamten Projekt. |
|
||||
|
||||
### 1.2 Konsistenz-Regel (PEP 8)
|
||||
|
||||
> **Konsistenz mit diesem Styleguide ist wichtig. Konsistenz innerhalb des Projekts ist wichtiger. Konsistenz innerhalb eines Moduls ist am wichtigsten.**
|
||||
|
||||
Wenn du eine Regel brechen musst, dokumentiere WARUM in einem Kommentar.
|
||||
|
||||
---
|
||||
|
||||
## 2. Code-Stil
|
||||
|
||||
**Durchsetzung:** Ruff (Linter + Formatter) in `pyproject.toml`
|
||||
**Basis:** PEP 8 mit projektspezifischen Anpassungen
|
||||
|
||||
### 2.1 Zeilenlänge
|
||||
|
||||
```
|
||||
Maximal 100 Zeichen pro Zeile.
|
||||
```
|
||||
|
||||
**Begründung:** 88 (Black-Default) ist zu knapp für Flask-Routen und SQLAlchemy-Queries. 100 Zeichen erlauben lesbare Code-Blöcke ohne horizontales Scrollen.
|
||||
|
||||
### 2.2 Einrückung
|
||||
|
||||
- **4 Leerzeichen** pro Ebene — **NIE Tabs**
|
||||
- Fortsetzungszeilen: 8 Leerzeichen (doppelte Einrückung) oder vertikale Ausrichtung
|
||||
|
||||
```python
|
||||
# ✅ Richtig — vertikale Ausrichtung
|
||||
result = some_function(
|
||||
argument_one,
|
||||
argument_two,
|
||||
argument_three,
|
||||
)
|
||||
|
||||
# ✅ Richtig — doppelte Einrückung bei langen Bedingungen
|
||||
if (some_very_long_condition
|
||||
and another_long_condition):
|
||||
do_something()
|
||||
|
||||
# ❌ Falsch — nur 4 Leerzeichen (nicht unterscheidbar)
|
||||
if (some_very_long_condition
|
||||
and another_long_condition):
|
||||
do_something()
|
||||
```
|
||||
|
||||
### 2.3 Quotes
|
||||
|
||||
- **Doppelte Anführungszeichen** (`"`) für Strings — Black/Ruff-Standard
|
||||
- Einfache Anführungszeichen nur, wenn der String selbst `"` enthält
|
||||
- Docstrings: **immer** `"""triple double quotes"""` (PEP 257)
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
name = "Brettspiel-News"
|
||||
message = f"Willkommen bei {name}!"
|
||||
|
||||
# ✅ Richtig — enthält doppelte Quotes
|
||||
quote = 'Er sagte: "Das ist ein gutes Spiel!"'
|
||||
|
||||
# ✅ Richtig
|
||||
def get_games() -> list[dict]:
|
||||
"""Holt alle Spiele aus der Datenbank."""
|
||||
...
|
||||
```
|
||||
|
||||
### 2.4 Imports
|
||||
|
||||
**Reihenfolge** (automatisch durch Ruff/isort):
|
||||
|
||||
1. Standard-Library (`os`, `json`, `datetime`)
|
||||
2. Drittanbieter (`flask`, `sqlalchemy`, `requests`)
|
||||
3. Projekt-interne (`from .models import ...`)
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import Flask, request, jsonify
|
||||
from sqlalchemy import create_engine
|
||||
|
||||
from .models import Game, User
|
||||
from .utils import validate_email
|
||||
```
|
||||
|
||||
**Verboten:**
|
||||
- `from module import *` — verschmutzt den Namespace
|
||||
- Zirkuläre Imports — deuten auf Architekturprobleme hin
|
||||
|
||||
### 2.5 Leerzeichen
|
||||
|
||||
| Regel | Beispiel |
|
||||
|---|---|
|
||||
| **Nach Komma** ein Leerzeichen | `[1, 2, 3]` nicht `[1,2,3]` |
|
||||
| **Um Operatoren** ein Leerzeichen | `x = 1 + 2` nicht `x=1+2` |
|
||||
| **Kein Leerzeichen** vor `(` bei Funktion | `def foo():` nicht `def foo ():` |
|
||||
| **Kein Leerzeichen** vor `:` bei Slice | `list[1:3]` nicht `list[1 : 3]` |
|
||||
| **Kein Leerzeichen** vor `=` bei Keyword-Arg | `foo(x=1)` nicht `foo(x = 1)` |
|
||||
|
||||
### 2.6 Leerzeilen
|
||||
|
||||
- 2 Leerzeilen vor Top-Level-Funktionen und -Klassen
|
||||
- 1 Leerzeile vor Methoden innerhalb einer Klasse
|
||||
- 1 Leerzeile zwischen logischen Blöcken innerhalb einer Funktion
|
||||
|
||||
### 2.7 Trailing Commas
|
||||
|
||||
- **Immer** trailing comma bei mehrzeiligen Listen/Dicts/Tupeln
|
||||
- Erleichtert Diffs und verhindert Fehler beim Hinzufügen von Elementen
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
items = [
|
||||
"Brettspiel",
|
||||
"Kartenspiel",
|
||||
"Würfelspiel",
|
||||
]
|
||||
|
||||
config = {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"debug": False,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Typ-Annotationen
|
||||
|
||||
**Durchsetzung:** mypy in `strict`-Mode
|
||||
|
||||
### 3.1 Grundregel
|
||||
|
||||
**Jede Funktion und Methode MUSS vollständige Typ-Annotationen haben.**
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
def get_game_by_id(game_id: int) -> dict[str, str | int] | None:
|
||||
"""Holt ein Spiel anhand seiner ID."""
|
||||
...
|
||||
|
||||
# ❌ Falsch — keine Typen
|
||||
def get_game_by_id(game_id):
|
||||
...
|
||||
```
|
||||
|
||||
### 3.2 Spezifische Regeln
|
||||
|
||||
| Regel | Richtig | Falsch |
|
||||
|---|---|---|
|
||||
| `Optional` statt `X \| None` (Konsistenz) | `Optional[str]` | `str \| None` |
|
||||
| `list`/`dict` statt `List`/`Dict` (Python 3.9+) | `list[str]` | `List[str]` |
|
||||
| `Any` nur in Ausnahmefällen | `dict[str, Any]` (API-Response) | `Any` als Rückgabetyp |
|
||||
| `-> None` bei Funktionen ohne Return | `def log(msg: str) -> None:` | `def log(msg: str):` |
|
||||
|
||||
```python
|
||||
from typing import Optional, Any
|
||||
|
||||
# ✅ Richtig
|
||||
def find_user(email: str) -> Optional[dict[str, Any]]:
|
||||
"""Sucht einen User per E-Mail. Gibt None zurück, wenn nicht gefunden."""
|
||||
...
|
||||
|
||||
# ✅ Richtig — Flask-Route
|
||||
@app.route("/api/games/<int:game_id>")
|
||||
def get_game(game_id: int) -> tuple[dict, int]:
|
||||
"""Gibt ein einzelnes Spiel zurück."""
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Namenskonventionen
|
||||
|
||||
### 4.1 Übersicht
|
||||
|
||||
| Element | Konvention | Beispiel |
|
||||
|---|---|---|
|
||||
| **Module/Packages** | `snake_case` | `game_service.py`, `user_models.py` |
|
||||
| **Klassen** | `PascalCase` | `GameRepository`, `UserService` |
|
||||
| **Funktionen/Methoden** | `snake_case` | `get_game_by_id()`, `calculate_score()` |
|
||||
| **Variablen** | `snake_case` | `game_count`, `user_email` |
|
||||
| **Konstanten** | `UPPER_SNAKE_CASE` | `MAX_RESULTS = 50`, `DEFAULT_TIMEOUT = 30` |
|
||||
| **Private (intern)** | `_leading_underscore` | `def _parse_response():`, `self._cache` |
|
||||
| **"Private" (Name Mangling)** | `__double_underscore` | `self.__db_connection` (nur bei Vererbungskonflikten) |
|
||||
|
||||
### 4.2 Spezifische Regeln
|
||||
|
||||
- **Boolesche Variablen** mit `is_`, `has_`, `should_`-Präfix: `is_active`, `has_permission`, `should_retry`
|
||||
- **Funktionen** als Verben: `get_`, `create_`, `update_`, `delete_`, `calculate_`, `validate_`
|
||||
- **Keine Ein-Buchstaben-Variablen** außer in trivialen Comprehensions (`[x for x in items]`)
|
||||
- **Keine kryptischen Abkürzungen**: `user_count` statt `uc`, `response` statt `resp`
|
||||
|
||||
---
|
||||
|
||||
## 5. Kommentare & Docstrings
|
||||
|
||||
### 5.1 Sprache
|
||||
|
||||
**Alle Kommentare und Docstrings werden auf DEUTSCH geschrieben.**
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
def berechne_durchschnitt(werteliste: list[float]) -> float:
|
||||
"""Berechnet den arithmetischen Durchschnitt einer Werteliste.
|
||||
|
||||
Args:
|
||||
werteliste: Liste von Gleitkommazahlen.
|
||||
|
||||
Returns:
|
||||
Der Durchschnittswert.
|
||||
|
||||
Raises:
|
||||
ValueError: Wenn die Liste leer ist.
|
||||
"""
|
||||
if not werteliste:
|
||||
raise ValueError("Die Werteliste darf nicht leer sein.")
|
||||
return sum(werteliste) / len(werteliste)
|
||||
```
|
||||
|
||||
### 5.2 Docstring-Format
|
||||
|
||||
**Google-Style Docstrings** (maschinenlesbar, von den meisten Tools unterstützt):
|
||||
|
||||
```python
|
||||
def komplexe_funktion(
|
||||
name: str,
|
||||
werte: list[int],
|
||||
optional: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Kurzbeschreibung (eine Zeile).
|
||||
|
||||
Ausführliche Beschreibung. Kann mehrere Sätze umfassen.
|
||||
Beschreibt das WARUM, nicht das WAS.
|
||||
|
||||
Args:
|
||||
name: Beschreibung des Parameters.
|
||||
werte: Liste von Werten, die verarbeitet werden.
|
||||
optional: Optionaler Zusatzparameter.
|
||||
|
||||
Returns:
|
||||
Ein Dictionary mit den verarbeiteten Ergebnissen.
|
||||
|
||||
Raises:
|
||||
ValueError: Wenn name leer ist.
|
||||
TypeError: Wenn werte keine Liste von ints ist.
|
||||
|
||||
Example:
|
||||
>>> komplexe_funktion("Test", [1, 2, 3])
|
||||
{"name": "Test", "summe": 6}
|
||||
"""
|
||||
```
|
||||
|
||||
### 5.3 Wann kommentieren?
|
||||
|
||||
- **Immer:** Docstrings für alle öffentlichen Funktionen/Klassen
|
||||
- **Oft:** `# TODO:`, `# FIXME:`, `# HACK:` mit Erklärung
|
||||
- **Manchmal:** Erklärung von nicht-offensichtlichem Code (Algorithmus, Workaround)
|
||||
- **Nie:** Code auskommentieren — bei Bedarf durch Git-History wiederherstellen
|
||||
|
||||
```python
|
||||
# ✅ Richtig — erklärt WARUM der Workaround
|
||||
# Workaround: SQLAlchemy 2.0 deprecated `query` — bis Migration nutzen wir `session.execute()`
|
||||
result = session.execute(select(Game).where(Game.id == game_id))
|
||||
|
||||
# ❌ Falsch — erklärt WAS (offensichtlich)
|
||||
# Holt das Spiel mit der ID
|
||||
result = session.execute(select(Game).where(Game.id == game_id))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Projektstruktur & Architektur
|
||||
|
||||
### 6.1 Maximalgrößen
|
||||
|
||||
| Einheit | Maximal |
|
||||
|---|---|
|
||||
| **Modul (.py-Datei)** | 500 Zeilen |
|
||||
| **Funktion/Methode** | 50 Zeilen |
|
||||
| **Klasse** | 300 Zeilen |
|
||||
| **Zeile** | 100 Zeichen |
|
||||
|
||||
**Begründung:** Kleine, fokussierte Einheiten sind leichter zu testen, zu verstehen und zu warten.
|
||||
|
||||
### 6.2 Separation of Concerns
|
||||
|
||||
```
|
||||
project/
|
||||
├── app.py # Flask-App-Factory + Config
|
||||
├── routes/ # Nur Request-Handling, keine Business-Logik
|
||||
│ ├── __init__.py
|
||||
│ ├── games.py # /api/games/*
|
||||
│ └── users.py # /api/users/*
|
||||
├── services/ # Business-Logik
|
||||
│ ├── __init__.py
|
||||
│ └── game_service.py
|
||||
├── models/ # Datenbank-Modelle
|
||||
│ ├── __init__.py
|
||||
│ └── game.py
|
||||
├── utils/ # Hilfsfunktionen
|
||||
│ ├── __init__.py
|
||||
│ └── validators.py
|
||||
├── templates/ # Jinja2-Templates
|
||||
├── static/ # CSS, JS, Bilder
|
||||
├── tests/ # Tests (spiegelt src/-Struktur)
|
||||
│ ├── test_routes/
|
||||
│ ├── test_services/
|
||||
│ └── conftest.py
|
||||
├── migrations/ # DB-Migrationen (Alembic)
|
||||
├── pyproject.toml # ALLE Projekt-Konfiguration
|
||||
├── .pre-commit-config.yaml
|
||||
├── README.md
|
||||
└── CODING_STANDARDS.md
|
||||
```
|
||||
|
||||
### 6.3 Prinzipien
|
||||
|
||||
- **Keine God-Files:** `app.py` darf nicht 6000 Zeilen haben. Auslagern!
|
||||
- **Routes sind dumm:** Nur Request-Parsing → Service aufrufen → Response formatieren
|
||||
- **Services enthalten Logik:** Validierung, Berechnung, DB-Zugriffe
|
||||
- **Models nur Schema:** Tabellen-Definition, keine Business-Logik
|
||||
- **Utils sind pure functions:** Kein Zustand, keine Seiteneffekte, keine DB-Zugriffe
|
||||
|
||||
```python
|
||||
# ✅ Richtig — Route delegiert an Service
|
||||
@app.route("/api/games/<int:game_id>")
|
||||
def get_game(game_id: int) -> tuple[dict, int]:
|
||||
"""Gibt ein einzelnes Spiel zurück."""
|
||||
game = game_service.get_by_id(game_id)
|
||||
if not game:
|
||||
return {"error": "Spiel nicht gefunden"}, 404
|
||||
return game.to_dict(), 200
|
||||
|
||||
# ❌ Falsch — Route enthält Business-Logik
|
||||
@app.route("/api/games/<int:game_id>")
|
||||
def get_game(game_id):
|
||||
game = db.session.execute(
|
||||
select(Game).where(Game.id == game_id)
|
||||
).scalar_one_or_none()
|
||||
if not game:
|
||||
return {"error": "Not found"}, 404
|
||||
# 50 Zeilen Berechnungen hier...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Sicherheit
|
||||
|
||||
**Durchsetzung:** Ruff (Sicherheits-Regeln) + Bandit
|
||||
|
||||
### 7.1 Secrets
|
||||
|
||||
**NIEMALS Secrets im Code!** Immer über Environment Variables oder `.env`-Datei.
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
import os
|
||||
API_KEY = os.environ["BSN_API_KEY"]
|
||||
|
||||
# ❌ Falsch
|
||||
API_KEY = "sk-abc123def456" # SOFORT LÖSCHEN + Key rotieren!
|
||||
```
|
||||
|
||||
**`.env` und `.db`-Dateien MÜSSEN in `.gitignore` stehen.**
|
||||
|
||||
### 7.2 Input-Validierung
|
||||
|
||||
- **Alle User-Inputs validieren**, bevor sie verarbeitet werden
|
||||
- Flask-Werkzeuge nutzen: `request.args.get()`, `request.json.get()` mit Defaults
|
||||
- SQL-Injection verhindern: Parameterisierte Queries (SQLAlchemy macht das automatisch)
|
||||
|
||||
```python
|
||||
# ✅ Richtig — validiert und sanitized
|
||||
from flask import request, abort
|
||||
|
||||
@app.route("/api/search")
|
||||
def search_games() -> dict:
|
||||
query = request.args.get("q", "").strip()
|
||||
if not query or len(query) > 100:
|
||||
abort(400, description="Ungültiger Suchbegriff")
|
||||
return game_service.search(query)
|
||||
|
||||
# ❌ Falsch — keine Validierung, roher Input in Query
|
||||
@app.route("/api/search")
|
||||
def search_games():
|
||||
query = request.args.get("q")
|
||||
db.session.execute(f"SELECT * FROM games WHERE name LIKE '%{query}%'")
|
||||
```
|
||||
|
||||
### 7.3 Weitere Regeln
|
||||
|
||||
- **Keine assert-Statements** in Produktionscode (werden mit `-O` deaktiviert)
|
||||
- **Keine `pickle`** für ungetrustete Daten
|
||||
- **`subprocess` mit `shell=False`** (Default)
|
||||
- **Hash-Funktionen für Passwörter:** `bcrypt` oder `argon2`, nie `md5`/`sha1`
|
||||
|
||||
---
|
||||
|
||||
## 8. Testing
|
||||
|
||||
**Durchsetzung:** pytest mit Coverage-Check
|
||||
|
||||
### 8.1 Grundregeln
|
||||
|
||||
1. **Jede neue Funktion → Test** (TDD: erst Test, dann Code)
|
||||
2. **80% Code Coverage** als Minimum
|
||||
3. **pytest** als Test-Framework
|
||||
4. **Tests in `tests/`**, spiegelt die Projektstruktur
|
||||
|
||||
### 8.2 Test-Struktur
|
||||
|
||||
```python
|
||||
# tests/test_services/test_game_service.py
|
||||
import pytest
|
||||
from services.game_service import GameService
|
||||
|
||||
|
||||
class TestGameService:
|
||||
"""Tests für den GameService."""
|
||||
|
||||
def test_get_by_id_returns_game_when_found(self, db_session):
|
||||
"""Gibt ein Spiel zurück, wenn die ID existiert."""
|
||||
# Arrange
|
||||
service = GameService(db_session)
|
||||
|
||||
# Act
|
||||
result = service.get_by_id(1)
|
||||
|
||||
# Assert
|
||||
assert result is not None
|
||||
assert result.name == "Catan"
|
||||
|
||||
def test_get_by_id_returns_none_when_missing(self, db_session):
|
||||
"""Gibt None zurück, wenn die ID nicht existiert."""
|
||||
service = GameService(db_session)
|
||||
result = service.get_by_id(999999)
|
||||
assert result is None
|
||||
|
||||
def test_get_by_id_raises_on_negative_id(self, db_session):
|
||||
"""Wirft ValueError bei negativer ID."""
|
||||
service = GameService(db_session)
|
||||
with pytest.raises(ValueError, match="ID darf nicht negativ sein"):
|
||||
service.get_by_id(-1)
|
||||
```
|
||||
|
||||
### 8.3 Test-Namen
|
||||
|
||||
- **`test_<was>_<erwartetes_ergebnis>`**
|
||||
- Deutsch oder Englisch — aber **einheitlich** im gesamten Projekt
|
||||
|
||||
---
|
||||
|
||||
## 9. Flask-spezifische Regeln
|
||||
|
||||
### 9.1 Routes
|
||||
|
||||
```python
|
||||
# ✅ Immer `methods` explizit angeben
|
||||
@app.route("/api/games", methods=["GET"])
|
||||
def list_games() -> tuple[dict, int]:
|
||||
...
|
||||
|
||||
# ✅ POST-Daten aus request.get_json(), nicht request.args
|
||||
@app.route("/api/games", methods=["POST"])
|
||||
def create_game() -> tuple[dict, int]:
|
||||
data = request.get_json(silent=True)
|
||||
if not data:
|
||||
return {"error": "Kein JSON-Body"}, 400
|
||||
...
|
||||
|
||||
# ❌ Falsch — GET-Parameter in POST lesen
|
||||
data = request.args.get("name") # NIEMALS für POST-Daten
|
||||
```
|
||||
|
||||
### 9.2 Response-Format
|
||||
|
||||
- Immer **JSON** zurückgeben: `flask.jsonify()` oder `dict` mit Statuscode
|
||||
- Statuscodes **explizit** setzen
|
||||
- Fehler-Responses einheitlich: `{"error": "Nachricht"}` mit passendem Statuscode
|
||||
|
||||
```python
|
||||
# ✅ Richtig
|
||||
@app.route("/api/games/<int:game_id>")
|
||||
def get_game(game_id: int) -> tuple[dict, int]:
|
||||
game = game_service.get_by_id(game_id)
|
||||
if not game:
|
||||
return {"error": "Spiel nicht gefunden"}, 404
|
||||
return game.to_dict(), 200
|
||||
```
|
||||
|
||||
### 9.3 Config
|
||||
|
||||
- Flask-Config über `app.config.from_prefixed_env()` (Flask 3.1+)
|
||||
- Secrets nie in `app.config` hartkodieren
|
||||
|
||||
```python
|
||||
# ✅ Richtig — Config aus Environment
|
||||
app = Flask(__name__)
|
||||
app.config.from_prefixed_env()
|
||||
# Zugriff: os.environ["FLASK_SECRET_KEY"], os.environ["FLASK_DATABASE_URL"]
|
||||
```
|
||||
|
||||
### 9.4 Templates
|
||||
|
||||
- CSS **immer inline** in JCE-Templates (self-contained, keine separaten CSS-Dateien)
|
||||
- Mobile-first Design
|
||||
- BSN-Blau `#20228a` als Primärfarbe
|
||||
- Touch-Buttons min 64px, Overlays min 85vh
|
||||
|
||||
---
|
||||
|
||||
## 10. Git & Commits
|
||||
|
||||
### 10.1 Conventional Commits
|
||||
|
||||
Jeder Commit folgt dem **Conventional Commits**-Standard:
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer]
|
||||
```
|
||||
|
||||
| Typ | Verwendung |
|
||||
|---|---|
|
||||
| `feat` | Neues Feature |
|
||||
| `fix` | Bugfix |
|
||||
| `docs` | Dokumentation |
|
||||
| `style` | Formatierung (kein Code-Change) |
|
||||
| `refactor` | Code-Umbau (kein Feature, kein Fix) |
|
||||
| `test` | Tests hinzugefügt/geändert |
|
||||
| `chore` | Build, Dependencies, CI |
|
||||
| `perf` | Performance-Verbesserung |
|
||||
| `security` | Sicherheitsrelevante Änderung |
|
||||
|
||||
**Beispiele:**
|
||||
```
|
||||
feat(games): Spiele-Suche mit Volltext-Filter
|
||||
fix(auth): Login bricht bei leeren Feldern nicht mehr ab
|
||||
docs: CODING_STANDARDS.md hinzugefügt
|
||||
chore(deps): Ruff auf 0.11.3 aktualisiert
|
||||
```
|
||||
|
||||
### 10.2 Commit-Frequenz
|
||||
|
||||
- **Nach jeder abgeschlossenen Aufgabe** committen
|
||||
- Keine Mega-Commits mit 50 geänderten Dateien
|
||||
- Ein Commit = eine logische Änderung
|
||||
|
||||
### 10.3 Vor jedem Commit
|
||||
|
||||
```bash
|
||||
pre-commit run # Linting + Formatierung + Typ-Check
|
||||
pytest # Tests müssen grün sein
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Toolchain & Durchsetzung
|
||||
|
||||
### 11.1 Übersicht
|
||||
|
||||
| Tool | Zweck | Konfiguration |
|
||||
|---|---|---|
|
||||
| **[Ruff](https://docs.astral.sh/ruff/)** | Linting + Formatierung | `pyproject.toml` → `[tool.ruff]` |
|
||||
| **[mypy](https://mypy-lang.org/)** | Statische Typ-Prüfung | `pyproject.toml` → `[tool.mypy]` |
|
||||
| **[pre-commit](https://pre-commit.com/)** | Git-Hooks zur Durchsetzung | `.pre-commit-config.yaml` |
|
||||
| **[pytest](https://pytest.org/)** | Testing | `pyproject.toml` → `[tool.pytest.ini_options]` |
|
||||
| **[Bandit](https://bandit.readthedocs.io/)** | Sicherheits-Scans | `pyproject.toml` → `[tool.bandit]` |
|
||||
|
||||
### 11.2 Quick-Start (neues Projekt)
|
||||
|
||||
```bash
|
||||
# 1. Tools installieren
|
||||
pip install ruff mypy pre-commit pytest bandit
|
||||
|
||||
# 2. Pre-commit-Hooks aktivieren
|
||||
pre-commit install
|
||||
|
||||
# 3. Erstmalig alles prüfen
|
||||
ruff check . --fix
|
||||
ruff format .
|
||||
mypy .
|
||||
pytest
|
||||
|
||||
# 4. Vor jedem Commit automatisch
|
||||
git commit -m "feat: ..."
|
||||
# → Ruff prüft + formatiert automatisch
|
||||
# → mypy prüft die Typen
|
||||
# → Bandit scannt auf Sicherheitslücken
|
||||
```
|
||||
|
||||
### 11.3 CI-Setup (später)
|
||||
|
||||
```yaml
|
||||
# .github/workflows/quality.yml
|
||||
- name: Lint
|
||||
run: ruff check .
|
||||
- name: Type Check
|
||||
run: mypy .
|
||||
- name: Security Scan
|
||||
run: bandit -r src/ -c pyproject.toml
|
||||
- name: Tests
|
||||
run: pytest --cov --cov-fail-under=80
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anhang A: Checkliste für Code Reviews
|
||||
|
||||
- [ ] PEP 8 eingehalten? (Ruff-Check bestanden)
|
||||
- [ ] Alle Funktionen haben Typ-Annotationen? (mypy bestanden)
|
||||
- [ ] Docstrings auf Deutsch für alle öffentlichen Funktionen?
|
||||
- [ ] Keine Secrets im Code?
|
||||
- [ ] User-Input validiert?
|
||||
- [ ] Tests geschrieben und grün?
|
||||
- [ ] Keine God-Files (>500 Zeilen)?
|
||||
- [ ] Keine God-Funktionen (>50 Zeilen)?
|
||||
- [ ] Route enthält keine Business-Logik?
|
||||
- [ ] Commit folgt Conventional Commits?
|
||||
|
||||
---
|
||||
|
||||
## Anhang B: Warum diese Regeln?
|
||||
|
||||
> *"Die Basis muss stimmen — wie bei einem Haus, das Fundament ist das wichtigste."* — Daniel Krause
|
||||
|
||||
Diese Regeln sind nicht willkürlich. Sie basieren auf:
|
||||
|
||||
1. **PEP 8** — dem offiziellen Python-Styleguide (seit 2001)
|
||||
2. **Google Python Style Guide** — bewährt in tausenden Projekten
|
||||
3. **The Zen of Python** — Guido van Rossums Design-Prinzipien
|
||||
4. **Erfahrungen aus dem BSN-Chatbot-Projekt** — was funktioniert hat, was nicht
|
||||
5. **Moderner Tooling-Konsens 2026** — Ruff + mypy + pre-commit sind Industriestandard
|
||||
|
||||
Jede Regel ist **automatisiert durchsetzbar**. Das bedeutet: Du musst sie nicht im Kopf behalten — die Tools sagen dir, wenn etwas falsch ist. Und sie verhindern, dass fehlerhafter Code überhaupt ins Repository kommt.
|
||||
|
||||
---
|
||||
|
||||
> **Letzte Aktualisierung:** 21. Juni 2026
|
||||
> **Nächste Überprüfung:** Bei Projektstart oder nach 6 Monaten
|
||||
@@ -0,0 +1,80 @@
|
||||
# BSN Coding Standards — Fundament-Plan
|
||||
|
||||
> **Für Hermes:** Direkt umsetzen, keine Subagents nötig — kompaktes Setup.
|
||||
|
||||
**Ziel:** Verbindliche Coding-Standards für das BSN-Community-Projekt, automatisch durchgesetzt durch Tooling.
|
||||
|
||||
**Architektur:** Drei-Schichten-Modell:
|
||||
1. **Regelwerk** (`CODING_STANDARDS.md`) — Deutsche Dokumentation, menschenlesbar
|
||||
2. **Konfiguration** (`pyproject.toml`) — Ruff (Linter+Formatter), mypy, pytest
|
||||
3. **Durchsetzung** (`.pre-commit-config.yaml`) — Automatische Prüfung vor jedem Commit
|
||||
|
||||
**Tech Stack:** Ruff 0.11+, mypy 1.15+, pytest 8+, pre-commit 4+
|
||||
|
||||
---
|
||||
|
||||
## Recherche-Ergebnisse (Stand Juni 2026)
|
||||
|
||||
### Konsens der Python-Community
|
||||
- **Ruff** hat Black + isort + Flake8 + pyupgrade + Bandit nahezu vollständig abgelöst
|
||||
- **mypy** bleibt der Standard für statische Typ-Prüfung
|
||||
- **pyproject.toml** ist der zentrale Konfigurationspunkt (PEP 621)
|
||||
- **pre-commit** ist der De-facto-Standard für Git-Hooks
|
||||
- Line-Length: 88 (Black-Default) oder 100 (moderner Konsens für Web-Apps)
|
||||
|
||||
### Für Flask-Webprojekte spezifisch
|
||||
- Routes schlank halten (nur Request-Handling, keine Business-Logik)
|
||||
- `@app.route()` immer mit expliziten `methods=[...]`
|
||||
- Niemals `request.args` für POST-Daten verwenden
|
||||
- Immer `send_file()` mit korrektem mimetype
|
||||
- Secrets nie im Code — immer über Environment Variables
|
||||
|
||||
---
|
||||
|
||||
## Aufgaben
|
||||
|
||||
### Task 1: CODING_STANDARDS.md schreiben
|
||||
**Datei:** `CODING_STANDARDS.md` (Projekt-Root)
|
||||
|
||||
Deutsches Regelwerk mit folgenden Abschnitten:
|
||||
1. Allgemeine Prinzipien (Lesbarkeit, Konsistenz, Zen of Python)
|
||||
2. Code-Stil (PEP 8, Zeilenlänge 100, Quotes, Imports)
|
||||
3. Typ-Annotationen (immer, strict, `Optional` statt `| None` für Python <3.10)
|
||||
4. Namenskonventionen (snake_case, PascalCase, UPPER_CASE)
|
||||
5. Kommentare & Docstrings (Deutsch, Google-Style)
|
||||
6. Projektstruktur (keine God-Files, max 500 Zeilen/Modul)
|
||||
7. Sicherheit (keine Secrets, Input-Validierung, SQL-Injection)
|
||||
8. Testing (pytest, 80% Coverage, TDD)
|
||||
9. Flask-spezifische Regeln
|
||||
10. Git-Commit-Regeln (Conventional Commits)
|
||||
|
||||
### Task 2: pyproject.toml erstellen
|
||||
**Datei:** `pyproject.toml` (Projekt-Root)
|
||||
|
||||
```toml
|
||||
[build-system]
|
||||
[tool.ruff] — line-length=100, target-version=py313
|
||||
[tool.ruff.lint] — Alle kritischen Rules aktiv
|
||||
[tool.ruff.format] — quote-style="double"
|
||||
[tool.mypy] — strict=true
|
||||
[tool.pytest.ini_options] — testpaths, coverage
|
||||
[tool.bandit] — Sicherheits-Scans
|
||||
```
|
||||
|
||||
### Task 3: .pre-commit-config.yaml erstellen
|
||||
**Datei:** `.pre-commit-config.yaml` (Projekt-Root)
|
||||
|
||||
Hooks: ruff (lint+format), mypy, Bandit, check-added-large-files, check-merge-conflict, trailing-whitespace
|
||||
|
||||
### Task 4: Committen
|
||||
```bash
|
||||
git add CODING_STANDARDS.md pyproject.toml .pre-commit-config.yaml
|
||||
git commit -m "feat: coding standards fundament — Ruff + mypy + pre-commit"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verifikation
|
||||
- `ruff check .` findet keine unerwünschten Issues
|
||||
- `mypy .` läuft ohne Fehler (auf neuem Code)
|
||||
- `pre-commit run --all-files` läuft durch
|
||||
Reference in New Issue
Block a user