Files
playground/docs/CONVENTIONS.md
T
paulhorn da43859b97 feat: Projektstruktur nach P1204R0
Layout, Build und Konventionen analog zu mydb und cxx_scaffold_cli:
Header und Quellen nebeneinander in playground/, Unit-Tests als
.test.c/.test.cpp daneben, Integrationstests in tests/.

Geruest-Module counter (C, Unity) und notebook (C++, doctest) zeigen
jede Regel einmal an lauffaehigem Code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 16:07:03 +02:00

6.1 KiB

Konventionen

Was clang-format und clang-tidy nicht erzwingen können, steht hier. Mechanisch erzwungen wird bisher nur, was clang-format und clang-tidy abdecken. Ein check_style.sh gibt es in diesem Repo nicht (die CONVENTIONS.md von mydb verweist darauf, aber die Datei existiert dort ebenfalls nicht) — die folgenden Regeln gelten also per Konvention.

Projektstruktur (P1204R0)

playground/
├── playground/           <- Quellcode UND Header zusammen
│   ├── counter.h           public API (C)
│   ├── counter.c
│   ├── counter.test.c      Unit-Test, direkt neben dem Modul
│   ├── notebook.hpp        public API (C++)
│   ├── notebook.cpp
│   ├── notebook.test.cpp
│   ├── main.cpp
│   └── details/            Implementation Details, nicht public API
├── tests/basics/         Integrationstests gegen die öffentliche API
├── third_party/          vendorter Fremdcode, nie editieren
├── cmake/                Warnings.cmake, Sanitizers.cmake
└── docs/                 diese Datei, ADRs

counter und notebook sind Gerüst: sie zeigen jede Regel einmal an einem lauffähigen Beispiel und sind dazu da, ersetzt zu werden. Was bleiben soll, ist die Form, nicht der Inhalt.

Kein include/ + src/. Header und Implementierung liegen nebeneinander. Bei Templates, inline und Modulen ist die Trennung ohnehin nicht sauber zu ziehen, und der Verzeichnisname playground/ übernimmt die Rolle des Namespace im Dateisystem.

Includes

Eigene Header immer mit spitzen Klammern und Projektpräfix:

#include <playground/counter.h>    /* richtig */
#include "counter.h"               /* falsch  */

Das ist P1204R0s wichtigste Einzelregel. Spitze Klammern durchsuchen nur die -I-Pfade, niemals das Verzeichnis der inkludierenden Datei — ein fehlender oder falsch installierter Header fällt damit sofort auf, statt dass zufällig ein gleichnamiger Nachbar gefunden wird.

Reihenfolge macht clang-format (IncludeBlocks: Regroup): eigener Header, Projekt-Header, Test-Framework, C-Standardbibliothek, C++-Standardbibliothek, Rest.

Extensions

Extension Bedeutung
.h / .c C
.hpp / .cpp C++
.test.c / .test.cpp Unit-Test neben dem getesteten Modul

Die Extension sagt die Sprache. In einem gemischten Projekt ist das der Punkt — ein .h ist aus C und C++ benutzbar (der extern "C"-Block im Header), ein .hpp nur aus C++.

Namen

Was Form Beispiel
Typen PascalCase Counter, Notebook
Funktionen, Variablen snake_case counter_tick, free_space_offset
Makros UPPER_CASE mit Projektpräfix PLAYGROUND_COUNTER_LIMIT
C-API Modulpräfix counter_create, counter_free, counter_tick
Out-Parameter out_-Präfix size_t* out_len
Member blank, kein m_, kein _ items, width
Namespace Projektname ohne lib namespace playground

Kommentare

Ausschließlich /* */, niemals // — auch nicht einzeilig. Abschnitte werden so getrennt:

/* ---- API ---- */

Kommentare sind auf Deutsch, Umlaute transliteriert (ae oe ue ss), damit die Quellen reines ASCII bleiben. README und öffentliche Header auf Englisch. Prüfen lässt sich das mit:

grep -rlP '[^\x00-\x7F]' playground tests

Header

#ifndef PLAYGROUND_COUNTER_H — Include-Guards, kein #pragma once. Öffentliche C-Header bekommen einen extern "C"-Block, damit die C++-Seite sie benutzen kann.

Structs, deren Layout niemanden angeht, sind opak: typedef struct Counter Counter; im Header, die Definition in der .c.

Tests

Unit-Test (<modul>.test.c / .test.cpp) liegt neben dem Modul, kennt dessen Interna und ist eine eigenständige Executable.

  • C → Unity, registriert als CTest unity.<modul> (hier: unity.counter)
  • C++ → doctest, doctest_discover_tests() legt einen CTest-Eintrag pro TEST_CASE an

Integrationstest (tests/<name>/driver.c oder .cpp) benutzt nur die öffentliche API, kein Framework, Ergebnis über den Exit-Code. Er würde genauso gegen eine installierte Bibliothek laufen.

TDD-Zyklus: RED (Test schlägt aus dem richtigen Grund fehl) → GREEN (kleinste Implementierung) → REFACTOR.

Build

Kein file(GLOB). Neue Dateien werden in CMakeLists.txt eingetragen — CMake merkt sonst nicht, dass eine Datei dazugekommen ist, der Build bleibt grün und die Datei fehlt einfach.

Preset Zweck
debug Alltag
release RelWithDebInfo
asan-ubsan AddressSanitizer + UBSan
apple-clang Portabilitätsprobe (macOS-System-Compiler)
homebrew-gcc Portabilitätsprobe (GCC-Frontend)
linux-gcc nur auf Linux sichtbar

build/<preset>/ — die Tiefe ist bindend: der neotest-CTest-Adapter sucht nur drei Ebenen tief, und der Neovim-Target-Picker leitet den File-API-Pfad daraus ab.

Commits

Conventional Commits: feat:, fix:, test:, refactor:, chore:, docs:. Aktuell ohne Hook — es gibt kein check_style.sh, das einen installieren könnte.

Zwei Dinge, die wie Fehler aussehen, aber keine sind

Nach einem frischen Clone zeigt clangd nichts. Es gibt noch kein compile_commands.json. Einmal <leader>oc (configure), dann ist alles da. Neovim weist beim Öffnen einer C/C++-Datei darauf hin.

Der Testbaum (<leader>ts) ist nach einem frischen Clone leer. doctest_discover_tests() ruft die Test-Executable beim Build auf, um die TEST_CASEs aufzulisten. Vor dem ersten Build kennt CTest sie also nicht. Einmal <leader>b, dann füllt sich der Baum.

Rückfallebene für die C-Tests

Der neotest-Adapter für Unity (nvim-local/neotest-unity) ist selbst geschrieben, weil neotest-ctest .c-Dateien grundsätzlich nicht sieht. Wenn er je zur Last wird: aus der adapters-Liste in nvim/lua/plugins/neotest.lua entfernen, neotest bleibt für C++ zuständig, und die C-Tests laufen über <leader>ot (overseer → ctest → Quickfix). Dann fehlt der Rot/Grün-Baum für C, aber ein Tastendruck und Sprung zur Fehlerstelle bleiben.