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>
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 proTEST_CASEan
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.