mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-18 08:03:28 +08:00
Compare commits
22 Commits
4ff6831b2b
...
5cb9c1c2a5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cb9c1c2a5 | ||
|
|
595127954f | ||
|
|
bb084229aa | ||
|
|
849bb3b425 | ||
|
|
4db215f60d | ||
|
|
bb1486c404 | ||
|
|
9339d4c88c | ||
|
|
2497a9b6e5 | ||
|
|
e449471ed3 | ||
|
|
cad8db21b7 | ||
|
|
9d9258c7e1 | ||
|
|
40e80bcc61 | ||
|
|
eaf710847f | ||
|
|
b169a2e1dd | ||
|
|
8b4aac4e56 | ||
|
|
08f60355d4 | ||
|
|
1f74889dbf | ||
|
|
82d751556c | ||
|
|
3847cc0e0d | ||
|
|
94eaaad238 | ||
|
|
ab5be936e9 | ||
|
|
219bd1ff88 |
722
.cursor/skills/cpp-coding-standards/SKILL.md
Normal file
722
.cursor/skills/cpp-coding-standards/SKILL.md
Normal file
@@ -0,0 +1,722 @@
|
|||||||
|
---
|
||||||
|
name: cpp-coding-standards
|
||||||
|
description: C++ coding standards based on the C++ Core Guidelines (isocpp.github.io). Use when writing, reviewing, or refactoring C++ code to enforce modern, safe, and idiomatic practices.
|
||||||
|
---
|
||||||
|
|
||||||
|
# C++ Coding Standards (C++ Core Guidelines)
|
||||||
|
|
||||||
|
Comprehensive coding standards for modern C++ (C++17/20/23) derived from the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). Enforces type safety, resource safety, immutability, and clarity.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Writing new C++ code (classes, functions, templates)
|
||||||
|
- Reviewing or refactoring existing C++ code
|
||||||
|
- Making architectural decisions in C++ projects
|
||||||
|
- Enforcing consistent style across a C++ codebase
|
||||||
|
- Choosing between language features (e.g., `enum` vs `enum class`, raw pointer vs smart pointer)
|
||||||
|
|
||||||
|
### When NOT to Use
|
||||||
|
|
||||||
|
- Non-C++ projects
|
||||||
|
- Legacy C codebases that cannot adopt modern C++ features
|
||||||
|
- Embedded/bare-metal contexts where specific guidelines conflict with hardware constraints (adapt selectively)
|
||||||
|
|
||||||
|
## Cross-Cutting Principles
|
||||||
|
|
||||||
|
These themes recur across the entire guidelines and form the foundation:
|
||||||
|
|
||||||
|
1. **RAII everywhere** (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime
|
||||||
|
2. **Immutability by default** (P.10, Con.1-5, ES.25): Start with `const`/`constexpr`; mutability is the exception
|
||||||
|
3. **Type safety** (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time
|
||||||
|
4. **Express intent** (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose
|
||||||
|
5. **Minimize complexity** (F.2-3, ES.5, Per.4-5): Simple code is correct code
|
||||||
|
6. **Value semantics over pointer semantics** (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects
|
||||||
|
|
||||||
|
## Philosophy & Interfaces (P.*, I.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **P.1** | Express ideas directly in code |
|
||||||
|
| **P.3** | Express intent |
|
||||||
|
| **P.4** | Ideally, a program should be statically type safe |
|
||||||
|
| **P.5** | Prefer compile-time checking to run-time checking |
|
||||||
|
| **P.8** | Don't leak any resources |
|
||||||
|
| **P.10** | Prefer immutable data to mutable data |
|
||||||
|
| **I.1** | Make interfaces explicit |
|
||||||
|
| **I.2** | Avoid non-const global variables |
|
||||||
|
| **I.4** | Make interfaces precisely and strongly typed |
|
||||||
|
| **I.11** | Never transfer ownership by a raw pointer or reference |
|
||||||
|
| **I.23** | Keep the number of function arguments low |
|
||||||
|
|
||||||
|
### DO
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// P.10 + I.4: Immutable, strongly typed interface
|
||||||
|
struct Temperature {
|
||||||
|
double kelvin;
|
||||||
|
};
|
||||||
|
|
||||||
|
Temperature boil(const Temperature& water);
|
||||||
|
```
|
||||||
|
|
||||||
|
### DON'T
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Weak interface: unclear ownership, unclear units
|
||||||
|
double boil(double* temp);
|
||||||
|
|
||||||
|
// Non-const global variable
|
||||||
|
int g_counter = 0; // I.2 violation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions (F.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **F.1** | Package meaningful operations as carefully named functions |
|
||||||
|
| **F.2** | A function should perform a single logical operation |
|
||||||
|
| **F.3** | Keep functions short and simple |
|
||||||
|
| **F.4** | If a function might be evaluated at compile time, declare it `constexpr` |
|
||||||
|
| **F.6** | If your function must not throw, declare it `noexcept` |
|
||||||
|
| **F.8** | Prefer pure functions |
|
||||||
|
| **F.16** | For "in" parameters, pass cheaply-copied types by value and others by `const&` |
|
||||||
|
| **F.20** | For "out" values, prefer return values to output parameters |
|
||||||
|
| **F.21** | To return multiple "out" values, prefer returning a struct |
|
||||||
|
| **F.43** | Never return a pointer or reference to a local object |
|
||||||
|
|
||||||
|
### Parameter Passing
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// F.16: Cheap types by value, others by const&
|
||||||
|
void print(int x); // cheap: by value
|
||||||
|
void analyze(const std::string& data); // expensive: by const&
|
||||||
|
void transform(std::string s); // sink: by value (will move)
|
||||||
|
|
||||||
|
// F.20 + F.21: Return values, not output parameters
|
||||||
|
struct ParseResult {
|
||||||
|
std::string token;
|
||||||
|
int position;
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseResult parse(std::string_view input); // GOOD: return struct
|
||||||
|
|
||||||
|
// BAD: output parameters
|
||||||
|
void parse(std::string_view input,
|
||||||
|
std::string& token, int& pos); // avoid this
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pure Functions and constexpr
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// F.4 + F.8: Pure, constexpr where possible
|
||||||
|
constexpr int factorial(int n) noexcept {
|
||||||
|
return (n <= 1) ? 1 : n * factorial(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(factorial(5) == 120);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Returning `T&&` from functions (F.45)
|
||||||
|
- Using `va_arg` / C-style variadics (F.55)
|
||||||
|
- Capturing by reference in lambdas passed to other threads (F.53)
|
||||||
|
- Returning `const T` which inhibits move semantics (F.49)
|
||||||
|
|
||||||
|
## Classes & Class Hierarchies (C.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **C.2** | Use `class` if invariant exists; `struct` if data members vary independently |
|
||||||
|
| **C.9** | Minimize exposure of members |
|
||||||
|
| **C.20** | If you can avoid defining default operations, do (Rule of Zero) |
|
||||||
|
| **C.21** | If you define or `=delete` any copy/move/destructor, handle them all (Rule of Five) |
|
||||||
|
| **C.35** | Base class destructor: public virtual or protected non-virtual |
|
||||||
|
| **C.41** | A constructor should create a fully initialized object |
|
||||||
|
| **C.46** | Declare single-argument constructors `explicit` |
|
||||||
|
| **C.67** | A polymorphic class should suppress public copy/move |
|
||||||
|
| **C.128** | Virtual functions: specify exactly one of `virtual`, `override`, or `final` |
|
||||||
|
|
||||||
|
### Rule of Zero
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.20: Let the compiler generate special members
|
||||||
|
struct Employee {
|
||||||
|
std::string name;
|
||||||
|
std::string department;
|
||||||
|
int id;
|
||||||
|
// No destructor, copy/move constructors, or assignment operators needed
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule of Five
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.21: If you must manage a resource, define all five
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
explicit Buffer(std::size_t size)
|
||||||
|
: data_(std::make_unique<char[]>(size)), size_(size) {}
|
||||||
|
|
||||||
|
~Buffer() = default;
|
||||||
|
|
||||||
|
Buffer(const Buffer& other)
|
||||||
|
: data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
|
||||||
|
std::copy_n(other.data_.get(), size_, data_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer& operator=(const Buffer& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
auto new_data = std::make_unique<char[]>(other.size_);
|
||||||
|
std::copy_n(other.data_.get(), other.size_, new_data.get());
|
||||||
|
data_ = std::move(new_data);
|
||||||
|
size_ = other.size_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer(Buffer&&) noexcept = default;
|
||||||
|
Buffer& operator=(Buffer&&) noexcept = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<char[]> data_;
|
||||||
|
std::size_t size_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Hierarchy
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.35 + C.128: Virtual destructor, use override
|
||||||
|
class Shape {
|
||||||
|
public:
|
||||||
|
virtual ~Shape() = default;
|
||||||
|
virtual double area() const = 0; // C.121: pure interface
|
||||||
|
};
|
||||||
|
|
||||||
|
class Circle : public Shape {
|
||||||
|
public:
|
||||||
|
explicit Circle(double r) : radius_(r) {}
|
||||||
|
double area() const override { return 3.14159 * radius_ * radius_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
double radius_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Calling virtual functions in constructors/destructors (C.82)
|
||||||
|
- Using `memset`/`memcpy` on non-trivial types (C.90)
|
||||||
|
- Providing different default arguments for virtual function and overrider (C.140)
|
||||||
|
- Making data members `const` or references, which suppresses move/copy (C.12)
|
||||||
|
|
||||||
|
## Resource Management (R.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **R.1** | Manage resources automatically using RAII |
|
||||||
|
| **R.3** | A raw pointer (`T*`) is non-owning |
|
||||||
|
| **R.5** | Prefer scoped objects; don't heap-allocate unnecessarily |
|
||||||
|
| **R.10** | Avoid `malloc()`/`free()` |
|
||||||
|
| **R.11** | Avoid calling `new` and `delete` explicitly |
|
||||||
|
| **R.20** | Use `unique_ptr` or `shared_ptr` to represent ownership |
|
||||||
|
| **R.21** | Prefer `unique_ptr` over `shared_ptr` unless sharing ownership |
|
||||||
|
| **R.22** | Use `make_shared()` to make `shared_ptr`s |
|
||||||
|
|
||||||
|
### Smart Pointer Usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// R.11 + R.20 + R.21: RAII with smart pointers
|
||||||
|
auto widget = std::make_unique<Widget>("config"); // unique ownership
|
||||||
|
auto cache = std::make_shared<Cache>(1024); // shared ownership
|
||||||
|
|
||||||
|
// R.3: Raw pointer = non-owning observer
|
||||||
|
void render(const Widget* w) { // does NOT own w
|
||||||
|
if (w) w->draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(widget.get());
|
||||||
|
```
|
||||||
|
|
||||||
|
### RAII Pattern
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// R.1: Resource acquisition is initialization
|
||||||
|
class FileHandle {
|
||||||
|
public:
|
||||||
|
explicit FileHandle(const std::string& path)
|
||||||
|
: handle_(std::fopen(path.c_str(), "r")) {
|
||||||
|
if (!handle_) throw std::runtime_error("Failed to open: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FileHandle() {
|
||||||
|
if (handle_) std::fclose(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileHandle(const FileHandle&) = delete;
|
||||||
|
FileHandle& operator=(const FileHandle&) = delete;
|
||||||
|
FileHandle(FileHandle&& other) noexcept
|
||||||
|
: handle_(std::exchange(other.handle_, nullptr)) {}
|
||||||
|
FileHandle& operator=(FileHandle&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (handle_) std::fclose(handle_);
|
||||||
|
handle_ = std::exchange(other.handle_, nullptr);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::FILE* handle_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Naked `new`/`delete` (R.11)
|
||||||
|
- `malloc()`/`free()` in C++ code (R.10)
|
||||||
|
- Multiple resource allocations in a single expression (R.13 -- exception safety hazard)
|
||||||
|
- `shared_ptr` where `unique_ptr` suffices (R.21)
|
||||||
|
|
||||||
|
## Expressions & Statements (ES.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **ES.5** | Keep scopes small |
|
||||||
|
| **ES.20** | Always initialize an object |
|
||||||
|
| **ES.23** | Prefer `{}` initializer syntax |
|
||||||
|
| **ES.25** | Declare objects `const` or `constexpr` unless modification is intended |
|
||||||
|
| **ES.28** | Use lambdas for complex initialization of `const` variables |
|
||||||
|
| **ES.45** | Avoid magic constants; use symbolic constants |
|
||||||
|
| **ES.46** | Avoid narrowing/lossy arithmetic conversions |
|
||||||
|
| **ES.47** | Use `nullptr` rather than `0` or `NULL` |
|
||||||
|
| **ES.48** | Avoid casts |
|
||||||
|
| **ES.50** | Don't cast away `const` |
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ES.20 + ES.23 + ES.25: Always initialize, prefer {}, default to const
|
||||||
|
const int max_retries{3};
|
||||||
|
const std::string name{"widget"};
|
||||||
|
const std::vector<int> primes{2, 3, 5, 7, 11};
|
||||||
|
|
||||||
|
// ES.28: Lambda for complex const initialization
|
||||||
|
const auto config = [&] {
|
||||||
|
Config c;
|
||||||
|
c.timeout = std::chrono::seconds{30};
|
||||||
|
c.retries = max_retries;
|
||||||
|
c.verbose = debug_mode;
|
||||||
|
return c;
|
||||||
|
}();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Uninitialized variables (ES.20)
|
||||||
|
- Using `0` or `NULL` as pointer (ES.47 -- use `nullptr`)
|
||||||
|
- C-style casts (ES.48 -- use `static_cast`, `const_cast`, etc.)
|
||||||
|
- Casting away `const` (ES.50)
|
||||||
|
- Magic numbers without named constants (ES.45)
|
||||||
|
- Mixing signed and unsigned arithmetic (ES.100)
|
||||||
|
- Reusing names in nested scopes (ES.12)
|
||||||
|
|
||||||
|
## Error Handling (E.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **E.1** | Develop an error-handling strategy early in a design |
|
||||||
|
| **E.2** | Throw an exception to signal that a function can't perform its assigned task |
|
||||||
|
| **E.6** | Use RAII to prevent leaks |
|
||||||
|
| **E.12** | Use `noexcept` when throwing is impossible or unacceptable |
|
||||||
|
| **E.14** | Use purpose-designed user-defined types as exceptions |
|
||||||
|
| **E.15** | Throw by value, catch by reference |
|
||||||
|
| **E.16** | Destructors, deallocation, and swap must never fail |
|
||||||
|
| **E.17** | Don't try to catch every exception in every function |
|
||||||
|
|
||||||
|
### Exception Hierarchy
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// E.14 + E.15: Custom exception types, throw by value, catch by reference
|
||||||
|
class AppError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkError : public AppError {
|
||||||
|
public:
|
||||||
|
NetworkError(const std::string& msg, int code)
|
||||||
|
: AppError(msg), status_code(code) {}
|
||||||
|
int status_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetch_data(const std::string& url) {
|
||||||
|
// E.2: Throw to signal failure
|
||||||
|
throw NetworkError("connection refused", 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
try {
|
||||||
|
fetch_data("https://api.example.com");
|
||||||
|
} catch (const NetworkError& e) {
|
||||||
|
log_error(e.what(), e.status_code);
|
||||||
|
} catch (const AppError& e) {
|
||||||
|
log_error(e.what());
|
||||||
|
}
|
||||||
|
// E.17: Don't catch everything here -- let unexpected errors propagate
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Throwing built-in types like `int` or string literals (E.14)
|
||||||
|
- Catching by value (slicing risk) (E.15)
|
||||||
|
- Empty catch blocks that silently swallow errors
|
||||||
|
- Using exceptions for flow control (E.3)
|
||||||
|
- Error handling based on global state like `errno` (E.28)
|
||||||
|
|
||||||
|
## Constants & Immutability (Con.*)
|
||||||
|
|
||||||
|
### All Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Con.1** | By default, make objects immutable |
|
||||||
|
| **Con.2** | By default, make member functions `const` |
|
||||||
|
| **Con.3** | By default, pass pointers and references to `const` |
|
||||||
|
| **Con.4** | Use `const` for values that don't change after construction |
|
||||||
|
| **Con.5** | Use `constexpr` for values computable at compile time |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Con.1 through Con.5: Immutability by default
|
||||||
|
class Sensor {
|
||||||
|
public:
|
||||||
|
explicit Sensor(std::string id) : id_(std::move(id)) {}
|
||||||
|
|
||||||
|
// Con.2: const member functions by default
|
||||||
|
const std::string& id() const { return id_; }
|
||||||
|
double last_reading() const { return reading_; }
|
||||||
|
|
||||||
|
// Only non-const when mutation is required
|
||||||
|
void record(double value) { reading_ = value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string id_; // Con.4: never changes after construction
|
||||||
|
double reading_{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Con.3: Pass by const reference
|
||||||
|
void display(const Sensor& s) {
|
||||||
|
std::cout << s.id() << ": " << s.last_reading() << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Con.5: Compile-time constants
|
||||||
|
constexpr double PI = 3.14159265358979;
|
||||||
|
constexpr int MAX_SENSORS = 256;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concurrency & Parallelism (CP.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **CP.2** | Avoid data races |
|
||||||
|
| **CP.3** | Minimize explicit sharing of writable data |
|
||||||
|
| **CP.4** | Think in terms of tasks, rather than threads |
|
||||||
|
| **CP.8** | Don't use `volatile` for synchronization |
|
||||||
|
| **CP.20** | Use RAII, never plain `lock()`/`unlock()` |
|
||||||
|
| **CP.21** | Use `std::scoped_lock` to acquire multiple mutexes |
|
||||||
|
| **CP.22** | Never call unknown code while holding a lock |
|
||||||
|
| **CP.42** | Don't wait without a condition |
|
||||||
|
| **CP.44** | Remember to name your `lock_guard`s and `unique_lock`s |
|
||||||
|
| **CP.100** | Don't use lock-free programming unless you absolutely have to |
|
||||||
|
|
||||||
|
### Safe Locking
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// CP.20 + CP.44: RAII locks, always named
|
||||||
|
class ThreadSafeQueue {
|
||||||
|
public:
|
||||||
|
void push(int value) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_); // CP.44: named!
|
||||||
|
queue_.push(value);
|
||||||
|
cv_.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
int pop() {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
// CP.42: Always wait with a condition
|
||||||
|
cv_.wait(lock, [this] { return !queue_.empty(); });
|
||||||
|
const int value = queue_.front();
|
||||||
|
queue_.pop();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex_; // CP.50: mutex with its data
|
||||||
|
std::condition_variable cv_;
|
||||||
|
std::queue<int> queue_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Mutexes
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// CP.21: std::scoped_lock for multiple mutexes (deadlock-free)
|
||||||
|
void transfer(Account& from, Account& to, double amount) {
|
||||||
|
std::scoped_lock lock(from.mutex_, to.mutex_);
|
||||||
|
from.balance_ -= amount;
|
||||||
|
to.balance_ += amount;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- `volatile` for synchronization (CP.8 -- it's for hardware I/O only)
|
||||||
|
- Detaching threads (CP.26 -- lifetime management becomes nearly impossible)
|
||||||
|
- Unnamed lock guards: `std::lock_guard<std::mutex>(m);` destroys immediately (CP.44)
|
||||||
|
- Holding locks while calling callbacks (CP.22 -- deadlock risk)
|
||||||
|
- Lock-free programming without deep expertise (CP.100)
|
||||||
|
|
||||||
|
## Templates & Generic Programming (T.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **T.1** | Use templates to raise the level of abstraction |
|
||||||
|
| **T.2** | Use templates to express algorithms for many argument types |
|
||||||
|
| **T.10** | Specify concepts for all template arguments |
|
||||||
|
| **T.11** | Use standard concepts whenever possible |
|
||||||
|
| **T.13** | Prefer shorthand notation for simple concepts |
|
||||||
|
| **T.43** | Prefer `using` over `typedef` |
|
||||||
|
| **T.120** | Use template metaprogramming only when you really need to |
|
||||||
|
| **T.144** | Don't specialize function templates (overload instead) |
|
||||||
|
|
||||||
|
### Concepts (C++20)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
// T.10 + T.11: Constrain templates with standard concepts
|
||||||
|
template<std::integral T>
|
||||||
|
T gcd(T a, T b) {
|
||||||
|
while (b != 0) {
|
||||||
|
a = std::exchange(b, a % b);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// T.13: Shorthand concept syntax
|
||||||
|
void sort(std::ranges::random_access_range auto& range) {
|
||||||
|
std::ranges::sort(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom concept for domain-specific constraints
|
||||||
|
template<typename T>
|
||||||
|
concept Serializable = requires(const T& t) {
|
||||||
|
{ t.serialize() } -> std::convertible_to<std::string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<Serializable T>
|
||||||
|
void save(const T& obj, const std::string& path);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Unconstrained templates in visible namespaces (T.47)
|
||||||
|
- Specializing function templates instead of overloading (T.144)
|
||||||
|
- Template metaprogramming where `constexpr` suffices (T.120)
|
||||||
|
- `typedef` instead of `using` (T.43)
|
||||||
|
|
||||||
|
## Standard Library (SL.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **SL.1** | Use libraries wherever possible |
|
||||||
|
| **SL.2** | Prefer the standard library to other libraries |
|
||||||
|
| **SL.con.1** | Prefer `std::array` or `std::vector` over C arrays |
|
||||||
|
| **SL.con.2** | Prefer `std::vector` by default |
|
||||||
|
| **SL.str.1** | Use `std::string` to own character sequences |
|
||||||
|
| **SL.str.2** | Use `std::string_view` to refer to character sequences |
|
||||||
|
| **SL.io.50** | Avoid `endl` (use `'\n'` -- `endl` forces a flush) |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// SL.con.1 + SL.con.2: Prefer vector/array over C arrays
|
||||||
|
const std::array<int, 4> fixed_data{1, 2, 3, 4};
|
||||||
|
std::vector<std::string> dynamic_data;
|
||||||
|
|
||||||
|
// SL.str.1 + SL.str.2: string owns, string_view observes
|
||||||
|
std::string build_greeting(std::string_view name) {
|
||||||
|
return "Hello, " + std::string(name) + "!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// SL.io.50: Use '\n' not endl
|
||||||
|
std::cout << "result: " << value << '\n';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enumerations (Enum.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Enum.1** | Prefer enumerations over macros |
|
||||||
|
| **Enum.3** | Prefer `enum class` over plain `enum` |
|
||||||
|
| **Enum.5** | Don't use ALL_CAPS for enumerators |
|
||||||
|
| **Enum.6** | Avoid unnamed enumerations |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Enum.3 + Enum.5: Scoped enum, no ALL_CAPS
|
||||||
|
enum class Color { red, green, blue };
|
||||||
|
enum class LogLevel { debug, info, warning, error };
|
||||||
|
|
||||||
|
// BAD: plain enum leaks names, ALL_CAPS clashes with macros
|
||||||
|
enum { RED, GREEN, BLUE }; // Enum.3 + Enum.5 + Enum.6 violation
|
||||||
|
#define MAX_SIZE 100 // Enum.1 violation -- use constexpr
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Files & Naming (SF.*, NL.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **SF.1** | Use `.cpp` for code files and `.h` for interface files |
|
||||||
|
| **SF.7** | Don't write `using namespace` at global scope in a header |
|
||||||
|
| **SF.8** | Use `#include` guards for all `.h` files |
|
||||||
|
| **SF.11** | Header files should be self-contained |
|
||||||
|
| **NL.5** | Avoid encoding type information in names (no Hungarian notation) |
|
||||||
|
| **NL.8** | Use a consistent naming style |
|
||||||
|
| **NL.9** | Use ALL_CAPS for macro names only |
|
||||||
|
| **NL.10** | Prefer `underscore_style` names |
|
||||||
|
|
||||||
|
### Header Guard
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// SF.8: Include guard (or #pragma once)
|
||||||
|
#ifndef PROJECT_MODULE_WIDGET_H
|
||||||
|
#define PROJECT_MODULE_WIDGET_H
|
||||||
|
|
||||||
|
// SF.11: Self-contained -- include everything this header needs
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace project::module {
|
||||||
|
|
||||||
|
class Widget {
|
||||||
|
public:
|
||||||
|
explicit Widget(std::string name);
|
||||||
|
const std::string& name() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace project::module
|
||||||
|
|
||||||
|
#endif // PROJECT_MODULE_WIDGET_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// NL.8 + NL.10: Consistent underscore_style
|
||||||
|
namespace my_project {
|
||||||
|
|
||||||
|
constexpr int max_buffer_size = 4096; // NL.9: not ALL_CAPS (it's not a macro)
|
||||||
|
|
||||||
|
class tcp_connection { // underscore_style class
|
||||||
|
public:
|
||||||
|
void send_message(std::string_view msg);
|
||||||
|
bool is_connected() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host_; // trailing underscore for members
|
||||||
|
int port_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace my_project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- `using namespace std;` in a header at global scope (SF.7)
|
||||||
|
- Headers that depend on inclusion order (SF.10, SF.11)
|
||||||
|
- Hungarian notation like `strName`, `iCount` (NL.5)
|
||||||
|
- ALL_CAPS for anything other than macros (NL.9)
|
||||||
|
|
||||||
|
## Performance (Per.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Per.1** | Don't optimize without reason |
|
||||||
|
| **Per.2** | Don't optimize prematurely |
|
||||||
|
| **Per.6** | Don't make claims about performance without measurements |
|
||||||
|
| **Per.7** | Design to enable optimization |
|
||||||
|
| **Per.10** | Rely on the static type system |
|
||||||
|
| **Per.11** | Move computation from run time to compile time |
|
||||||
|
| **Per.19** | Access memory predictably |
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Per.11: Compile-time computation where possible
|
||||||
|
constexpr auto lookup_table = [] {
|
||||||
|
std::array<int, 256> table{};
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
table[i] = i * i;
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Per.19: Prefer contiguous data for cache-friendliness
|
||||||
|
std::vector<Point> points; // GOOD: contiguous
|
||||||
|
std::vector<std::unique_ptr<Point>> indirect_points; // BAD: pointer chasing
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Optimizing without profiling data (Per.1, Per.6)
|
||||||
|
- Choosing "clever" low-level code over clear abstractions (Per.4, Per.5)
|
||||||
|
- Ignoring data layout and cache behavior (Per.19)
|
||||||
|
|
||||||
|
## Quick Reference Checklist
|
||||||
|
|
||||||
|
Before marking C++ work complete:
|
||||||
|
|
||||||
|
- [ ] No raw `new`/`delete` -- use smart pointers or RAII (R.11)
|
||||||
|
- [ ] Objects initialized at declaration (ES.20)
|
||||||
|
- [ ] Variables are `const`/`constexpr` by default (Con.1, ES.25)
|
||||||
|
- [ ] Member functions are `const` where possible (Con.2)
|
||||||
|
- [ ] `enum class` instead of plain `enum` (Enum.3)
|
||||||
|
- [ ] `nullptr` instead of `0`/`NULL` (ES.47)
|
||||||
|
- [ ] No narrowing conversions (ES.46)
|
||||||
|
- [ ] No C-style casts (ES.48)
|
||||||
|
- [ ] Single-argument constructors are `explicit` (C.46)
|
||||||
|
- [ ] Rule of Zero or Rule of Five applied (C.20, C.21)
|
||||||
|
- [ ] Base class destructors are public virtual or protected non-virtual (C.35)
|
||||||
|
- [ ] Templates are constrained with concepts (T.10)
|
||||||
|
- [ ] No `using namespace` in headers at global scope (SF.7)
|
||||||
|
- [ ] Headers have include guards and are self-contained (SF.8, SF.11)
|
||||||
|
- [ ] Locks use RAII (`scoped_lock`/`lock_guard`) (CP.20)
|
||||||
|
- [ ] Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)
|
||||||
|
- [ ] `'\n'` instead of `std::endl` (SL.io.50)
|
||||||
|
- [ ] No magic numbers (ES.45)
|
||||||
@@ -222,6 +222,7 @@ everything-claude-code/
|
|||||||
| |-- verification-loop/ # Continuous verification (Longform Guide)
|
| |-- verification-loop/ # Continuous verification (Longform Guide)
|
||||||
| |-- golang-patterns/ # Go idioms and best practices
|
| |-- golang-patterns/ # Go idioms and best practices
|
||||||
| |-- golang-testing/ # Go testing patterns, TDD, benchmarks
|
| |-- golang-testing/ # Go testing patterns, TDD, benchmarks
|
||||||
|
| |-- cpp-coding-standards/ # C++ coding standards from C++ Core Guidelines (NEW)
|
||||||
| |-- cpp-testing/ # C++ testing with GoogleTest, CMake/CTest (NEW)
|
| |-- cpp-testing/ # C++ testing with GoogleTest, CMake/CTest (NEW)
|
||||||
| |-- django-patterns/ # Django patterns, models, views (NEW)
|
| |-- django-patterns/ # Django patterns, models, views (NEW)
|
||||||
| |-- django-security/ # Django security best practices (NEW)
|
| |-- django-security/ # Django security best practices (NEW)
|
||||||
@@ -486,6 +487,7 @@ This gives you instant access to all commands, agents, skills, and hooks.
|
|||||||
> git clone https://github.com/affaan-m/everything-claude-code.git
|
> git clone https://github.com/affaan-m/everything-claude-code.git
|
||||||
>
|
>
|
||||||
> # Option A: User-level rules (applies to all projects)
|
> # Option A: User-level rules (applies to all projects)
|
||||||
|
> mkdir -p ~/.claude/rules
|
||||||
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
||||||
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # pick your stack
|
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # pick your stack
|
||||||
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
||||||
|
|||||||
@@ -454,6 +454,7 @@ Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded fil
|
|||||||
> git clone https://github.com/affaan-m/everything-claude-code.git
|
> git clone https://github.com/affaan-m/everything-claude-code.git
|
||||||
>
|
>
|
||||||
> # オプション A:ユーザーレベルルール(すべてのプロジェクトに適用)
|
> # オプション A:ユーザーレベルルール(すべてのプロジェクトに適用)
|
||||||
|
> mkdir -p ~/.claude/rules
|
||||||
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
||||||
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # スタックを選択
|
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # スタックを選択
|
||||||
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
||||||
|
|||||||
@@ -456,6 +456,7 @@ Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded fil
|
|||||||
> git clone https://github.com/affaan-m/everything-claude-code.git
|
> git clone https://github.com/affaan-m/everything-claude-code.git
|
||||||
>
|
>
|
||||||
> # 选项 A:用户级规则(适用于所有项目)
|
> # 选项 A:用户级规则(适用于所有项目)
|
||||||
|
> mkdir -p ~/.claude/rules
|
||||||
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/common/* ~/.claude/rules/
|
||||||
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # 选择您的技术栈
|
> cp -r everything-claude-code/rules/typescript/* ~/.claude/rules/ # 选择您的技术栈
|
||||||
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
> cp -r everything-claude-code/rules/python/* ~/.claude/rules/
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
"hooks": [
|
"hooks": [
|
||||||
{
|
{
|
||||||
"type": "command",
|
"type": "command",
|
||||||
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.(md|txt)$/.test(p)&&!/(README|CLAUDE|AGENTS|CONTRIBUTING)\\.md$/.test(p)){console.error('[Hook] BLOCKED: Unnecessary documentation file creation');console.error('[Hook] File: '+p);console.error('[Hook] Use README.md for documentation instead');process.exit(2)}}catch{}console.log(d)})\""
|
"command": "node -e \"const fs=require('fs');let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.(md|txt)$/.test(p)&&!/(README|CLAUDE|AGENTS|CONTRIBUTING)\\.md$/.test(p)&&!/\\.claude\\/plans\\//.test(p)){console.error('[Hook] BLOCKED: Unnecessary documentation file creation');console.error('[Hook] File: '+p);console.error('[Hook] Use README.md for documentation instead');process.exit(2)}}catch{}console.log(d)})\""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "Block creation of random .md files - keeps docs consolidated"
|
"description": "Block creation of random .md files - keeps docs consolidated"
|
||||||
|
|||||||
160
skills/content-hash-cache-pattern/SKILL.md
Normal file
160
skills/content-hash-cache-pattern/SKILL.md
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
name: content-hash-cache-pattern
|
||||||
|
description: Cache expensive file processing results using SHA-256 content hashes — path-independent, auto-invalidating, with service layer separation.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Content-Hash File Cache Pattern
|
||||||
|
|
||||||
|
Cache expensive file processing results (PDF parsing, text extraction, image analysis) using SHA-256 content hashes as cache keys. Unlike path-based caching, this approach survives file moves/renames and auto-invalidates when content changes.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building file processing pipelines (PDF, images, text extraction)
|
||||||
|
- Processing cost is high and same files are processed repeatedly
|
||||||
|
- Need a `--cache/--no-cache` CLI option
|
||||||
|
- Want to add caching to existing pure functions without modifying them
|
||||||
|
|
||||||
|
## Core Pattern
|
||||||
|
|
||||||
|
### 1. Content-Hash Based Cache Key
|
||||||
|
|
||||||
|
Use file content (not path) as the cache key:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_HASH_CHUNK_SIZE = 65536 # 64KB chunks for large files
|
||||||
|
|
||||||
|
def compute_file_hash(path: Path) -> str:
|
||||||
|
"""SHA-256 of file contents (chunked for large files)."""
|
||||||
|
if not path.is_file():
|
||||||
|
raise FileNotFoundError(f"File not found: {path}")
|
||||||
|
sha256 = hashlib.sha256()
|
||||||
|
with open(path, "rb") as f:
|
||||||
|
while True:
|
||||||
|
chunk = f.read(_HASH_CHUNK_SIZE)
|
||||||
|
if not chunk:
|
||||||
|
break
|
||||||
|
sha256.update(chunk)
|
||||||
|
return sha256.hexdigest()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why content hash?** File rename/move = cache hit. Content change = automatic invalidation. No index file needed.
|
||||||
|
|
||||||
|
### 2. Frozen Dataclass for Cache Entry
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CacheEntry:
|
||||||
|
file_hash: str
|
||||||
|
source_path: str
|
||||||
|
document: ExtractedDocument # The cached result
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. File-Based Cache Storage
|
||||||
|
|
||||||
|
Each cache entry is stored as `{hash}.json` — O(1) lookup by hash, no index file required.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import json
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
def write_cache(cache_dir: Path, entry: CacheEntry) -> None:
|
||||||
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
cache_file = cache_dir / f"{entry.file_hash}.json"
|
||||||
|
data = serialize_entry(entry)
|
||||||
|
cache_file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
|
||||||
|
|
||||||
|
def read_cache(cache_dir: Path, file_hash: str) -> CacheEntry | None:
|
||||||
|
cache_file = cache_dir / f"{file_hash}.json"
|
||||||
|
if not cache_file.is_file():
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
raw = cache_file.read_text(encoding="utf-8")
|
||||||
|
data = json.loads(raw)
|
||||||
|
return deserialize_entry(data)
|
||||||
|
except (json.JSONDecodeError, ValueError, KeyError):
|
||||||
|
return None # Treat corruption as cache miss
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Service Layer Wrapper (SRP)
|
||||||
|
|
||||||
|
Keep the processing function pure. Add caching as a separate service layer.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def extract_with_cache(
|
||||||
|
file_path: Path,
|
||||||
|
*,
|
||||||
|
cache_enabled: bool = True,
|
||||||
|
cache_dir: Path = Path(".cache"),
|
||||||
|
) -> ExtractedDocument:
|
||||||
|
"""Service layer: cache check -> extraction -> cache write."""
|
||||||
|
if not cache_enabled:
|
||||||
|
return extract_text(file_path) # Pure function, no cache knowledge
|
||||||
|
|
||||||
|
file_hash = compute_file_hash(file_path)
|
||||||
|
|
||||||
|
# Check cache
|
||||||
|
cached = read_cache(cache_dir, file_hash)
|
||||||
|
if cached is not None:
|
||||||
|
logger.info("Cache hit: %s (hash=%s)", file_path.name, file_hash[:12])
|
||||||
|
return cached.document
|
||||||
|
|
||||||
|
# Cache miss -> extract -> store
|
||||||
|
logger.info("Cache miss: %s (hash=%s)", file_path.name, file_hash[:12])
|
||||||
|
doc = extract_text(file_path)
|
||||||
|
entry = CacheEntry(file_hash=file_hash, source_path=str(file_path), document=doc)
|
||||||
|
write_cache(cache_dir, entry)
|
||||||
|
return doc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| SHA-256 content hash | Path-independent, auto-invalidates on content change |
|
||||||
|
| `{hash}.json` file naming | O(1) lookup, no index file needed |
|
||||||
|
| Service layer wrapper | SRP: extraction stays pure, cache is a separate concern |
|
||||||
|
| Manual JSON serialization | Full control over frozen dataclass serialization |
|
||||||
|
| Corruption returns `None` | Graceful degradation, re-processes on next run |
|
||||||
|
| `cache_dir.mkdir(parents=True)` | Lazy directory creation on first write |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Hash content, not paths** — paths change, content identity doesn't
|
||||||
|
- **Chunk large files** when hashing — avoid loading entire files into memory
|
||||||
|
- **Keep processing functions pure** — they should know nothing about caching
|
||||||
|
- **Log cache hit/miss** with truncated hashes for debugging
|
||||||
|
- **Handle corruption gracefully** — treat invalid cache entries as misses, never crash
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
```python
|
||||||
|
# BAD: Path-based caching (breaks on file move/rename)
|
||||||
|
cache = {"/path/to/file.pdf": result}
|
||||||
|
|
||||||
|
# BAD: Adding cache logic inside the processing function (SRP violation)
|
||||||
|
def extract_text(path, *, cache_enabled=False, cache_dir=None):
|
||||||
|
if cache_enabled: # Now this function has two responsibilities
|
||||||
|
...
|
||||||
|
|
||||||
|
# BAD: Using dataclasses.asdict() with nested frozen dataclasses
|
||||||
|
# (can cause issues with complex nested types)
|
||||||
|
data = dataclasses.asdict(entry) # Use manual serialization instead
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- File processing pipelines (PDF parsing, OCR, text extraction, image analysis)
|
||||||
|
- CLI tools that benefit from `--cache/--no-cache` options
|
||||||
|
- Batch processing where the same files appear across runs
|
||||||
|
- Adding caching to existing pure functions without modifying them
|
||||||
|
|
||||||
|
## When NOT to Use
|
||||||
|
|
||||||
|
- Data that must always be fresh (real-time feeds)
|
||||||
|
- Cache entries that would be extremely large (consider streaming instead)
|
||||||
|
- Results that depend on parameters beyond file content (e.g., different extraction configs)
|
||||||
@@ -103,7 +103,8 @@ PARSED_OK=$(echo "$PARSED" | python3 -c "import json,sys; print(json.load(sys.st
|
|||||||
if [ "$PARSED_OK" != "True" ]; then
|
if [ "$PARSED_OK" != "True" ]; then
|
||||||
# Fallback: log raw input for debugging
|
# Fallback: log raw input for debugging
|
||||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
TIMESTAMP="$timestamp" echo "$INPUT_JSON" | python3 -c "
|
export TIMESTAMP="$timestamp"
|
||||||
|
echo "$INPUT_JSON" | python3 -c "
|
||||||
import json, sys, os
|
import json, sys, os
|
||||||
raw = sys.stdin.read()[:2000]
|
raw = sys.stdin.read()[:2000]
|
||||||
print(json.dumps({'timestamp': os.environ['TIMESTAMP'], 'event': 'parse_error', 'raw': raw}))
|
print(json.dumps({'timestamp': os.environ['TIMESTAMP'], 'event': 'parse_error', 'raw': raw}))
|
||||||
@@ -124,7 +125,8 @@ fi
|
|||||||
# Build and write observation
|
# Build and write observation
|
||||||
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
TIMESTAMP="$timestamp" echo "$PARSED" | python3 -c "
|
export TIMESTAMP="$timestamp"
|
||||||
|
echo "$PARSED" | python3 -c "
|
||||||
import json, sys, os
|
import json, sys, os
|
||||||
|
|
||||||
parsed = json.load(sys.stdin)
|
parsed = json.load(sys.stdin)
|
||||||
|
|||||||
182
skills/cost-aware-llm-pipeline/SKILL.md
Normal file
182
skills/cost-aware-llm-pipeline/SKILL.md
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
---
|
||||||
|
name: cost-aware-llm-pipeline
|
||||||
|
description: Cost optimization patterns for LLM API usage — model routing by task complexity, budget tracking, retry logic, and prompt caching.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cost-Aware LLM Pipeline
|
||||||
|
|
||||||
|
Patterns for controlling LLM API costs while maintaining quality. Combines model routing, budget tracking, retry logic, and prompt caching into a composable pipeline.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building applications that call LLM APIs (Claude, GPT, etc.)
|
||||||
|
- Processing batches of items with varying complexity
|
||||||
|
- Need to stay within a budget for API spend
|
||||||
|
- Optimizing cost without sacrificing quality on complex tasks
|
||||||
|
|
||||||
|
## Core Concepts
|
||||||
|
|
||||||
|
### 1. Model Routing by Task Complexity
|
||||||
|
|
||||||
|
Automatically select cheaper models for simple tasks, reserving expensive models for complex ones.
|
||||||
|
|
||||||
|
```python
|
||||||
|
MODEL_SONNET = "claude-sonnet-4-5-20250929"
|
||||||
|
MODEL_HAIKU = "claude-haiku-4-5-20251001"
|
||||||
|
|
||||||
|
_SONNET_TEXT_THRESHOLD = 10_000 # chars
|
||||||
|
_SONNET_ITEM_THRESHOLD = 30 # items
|
||||||
|
|
||||||
|
def select_model(
|
||||||
|
text_length: int,
|
||||||
|
item_count: int,
|
||||||
|
force_model: str | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Select model based on task complexity."""
|
||||||
|
if force_model is not None:
|
||||||
|
return force_model
|
||||||
|
if text_length >= _SONNET_TEXT_THRESHOLD or item_count >= _SONNET_ITEM_THRESHOLD:
|
||||||
|
return MODEL_SONNET # Complex task
|
||||||
|
return MODEL_HAIKU # Simple task (3-4x cheaper)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Immutable Cost Tracking
|
||||||
|
|
||||||
|
Track cumulative spend with frozen dataclasses. Each API call returns a new tracker — never mutates state.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CostRecord:
|
||||||
|
model: str
|
||||||
|
input_tokens: int
|
||||||
|
output_tokens: int
|
||||||
|
cost_usd: float
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class CostTracker:
|
||||||
|
budget_limit: float = 1.00
|
||||||
|
records: tuple[CostRecord, ...] = ()
|
||||||
|
|
||||||
|
def add(self, record: CostRecord) -> "CostTracker":
|
||||||
|
"""Return new tracker with added record (never mutates self)."""
|
||||||
|
return CostTracker(
|
||||||
|
budget_limit=self.budget_limit,
|
||||||
|
records=(*self.records, record),
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_cost(self) -> float:
|
||||||
|
return sum(r.cost_usd for r in self.records)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def over_budget(self) -> bool:
|
||||||
|
return self.total_cost > self.budget_limit
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Narrow Retry Logic
|
||||||
|
|
||||||
|
Retry only on transient errors. Fail fast on authentication or bad request errors.
|
||||||
|
|
||||||
|
```python
|
||||||
|
from anthropic import (
|
||||||
|
APIConnectionError,
|
||||||
|
InternalServerError,
|
||||||
|
RateLimitError,
|
||||||
|
)
|
||||||
|
|
||||||
|
_RETRYABLE_ERRORS = (APIConnectionError, RateLimitError, InternalServerError)
|
||||||
|
_MAX_RETRIES = 3
|
||||||
|
|
||||||
|
def call_with_retry(func, *, max_retries: int = _MAX_RETRIES):
|
||||||
|
"""Retry only on transient errors, fail fast on others."""
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
return func()
|
||||||
|
except _RETRYABLE_ERRORS:
|
||||||
|
if attempt == max_retries - 1:
|
||||||
|
raise
|
||||||
|
time.sleep(2 ** attempt) # Exponential backoff
|
||||||
|
# AuthenticationError, BadRequestError etc. → raise immediately
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Prompt Caching
|
||||||
|
|
||||||
|
Cache long system prompts to avoid resending them on every request.
|
||||||
|
|
||||||
|
```python
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": system_prompt,
|
||||||
|
"cache_control": {"type": "ephemeral"}, # Cache this
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": user_input, # Variable part
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Composition
|
||||||
|
|
||||||
|
Combine all four techniques in a single pipeline function:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def process(text: str, config: Config, tracker: CostTracker) -> tuple[Result, CostTracker]:
|
||||||
|
# 1. Route model
|
||||||
|
model = select_model(len(text), estimated_items, config.force_model)
|
||||||
|
|
||||||
|
# 2. Check budget
|
||||||
|
if tracker.over_budget:
|
||||||
|
raise BudgetExceededError(tracker.total_cost, tracker.budget_limit)
|
||||||
|
|
||||||
|
# 3. Call with retry + caching
|
||||||
|
response = call_with_retry(lambda: client.messages.create(
|
||||||
|
model=model,
|
||||||
|
messages=build_cached_messages(system_prompt, text),
|
||||||
|
))
|
||||||
|
|
||||||
|
# 4. Track cost (immutable)
|
||||||
|
record = CostRecord(model=model, input_tokens=..., output_tokens=..., cost_usd=...)
|
||||||
|
tracker = tracker.add(record)
|
||||||
|
|
||||||
|
return parse_result(response), tracker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pricing Reference (2025-2026)
|
||||||
|
|
||||||
|
| Model | Input ($/1M tokens) | Output ($/1M tokens) | Relative Cost |
|
||||||
|
|-------|---------------------|----------------------|---------------|
|
||||||
|
| Haiku 4.5 | $0.80 | $4.00 | 1x |
|
||||||
|
| Sonnet 4.5 | $3.00 | $15.00 | ~4x |
|
||||||
|
| Opus 4.5 | $15.00 | $75.00 | ~19x |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Start with the cheapest model** and only route to expensive models when complexity thresholds are met
|
||||||
|
- **Set explicit budget limits** before processing batches — fail early rather than overspend
|
||||||
|
- **Log model selection decisions** so you can tune thresholds based on real data
|
||||||
|
- **Use prompt caching** for system prompts over 1024 tokens — saves both cost and latency
|
||||||
|
- **Never retry on authentication or validation errors** — only transient failures (network, rate limit, server error)
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Using the most expensive model for all requests regardless of complexity
|
||||||
|
- Retrying on all errors (wastes budget on permanent failures)
|
||||||
|
- Mutating cost tracking state (makes debugging and auditing difficult)
|
||||||
|
- Hardcoding model names throughout the codebase (use constants or config)
|
||||||
|
- Ignoring prompt caching for repetitive system prompts
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Any application calling Claude, OpenAI, or similar LLM APIs
|
||||||
|
- Batch processing pipelines where cost adds up quickly
|
||||||
|
- Multi-model architectures that need intelligent routing
|
||||||
|
- Production systems that need budget guardrails
|
||||||
722
skills/cpp-coding-standards/SKILL.md
Normal file
722
skills/cpp-coding-standards/SKILL.md
Normal file
@@ -0,0 +1,722 @@
|
|||||||
|
---
|
||||||
|
name: cpp-coding-standards
|
||||||
|
description: C++ coding standards based on the C++ Core Guidelines (isocpp.github.io). Use when writing, reviewing, or refactoring C++ code to enforce modern, safe, and idiomatic practices.
|
||||||
|
---
|
||||||
|
|
||||||
|
# C++ Coding Standards (C++ Core Guidelines)
|
||||||
|
|
||||||
|
Comprehensive coding standards for modern C++ (C++17/20/23) derived from the [C++ Core Guidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines). Enforces type safety, resource safety, immutability, and clarity.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Writing new C++ code (classes, functions, templates)
|
||||||
|
- Reviewing or refactoring existing C++ code
|
||||||
|
- Making architectural decisions in C++ projects
|
||||||
|
- Enforcing consistent style across a C++ codebase
|
||||||
|
- Choosing between language features (e.g., `enum` vs `enum class`, raw pointer vs smart pointer)
|
||||||
|
|
||||||
|
### When NOT to Use
|
||||||
|
|
||||||
|
- Non-C++ projects
|
||||||
|
- Legacy C codebases that cannot adopt modern C++ features
|
||||||
|
- Embedded/bare-metal contexts where specific guidelines conflict with hardware constraints (adapt selectively)
|
||||||
|
|
||||||
|
## Cross-Cutting Principles
|
||||||
|
|
||||||
|
These themes recur across the entire guidelines and form the foundation:
|
||||||
|
|
||||||
|
1. **RAII everywhere** (P.8, R.1, E.6, CP.20): Bind resource lifetime to object lifetime
|
||||||
|
2. **Immutability by default** (P.10, Con.1-5, ES.25): Start with `const`/`constexpr`; mutability is the exception
|
||||||
|
3. **Type safety** (P.4, I.4, ES.46-49, Enum.3): Use the type system to prevent errors at compile time
|
||||||
|
4. **Express intent** (P.3, F.1, NL.1-2, T.10): Names, types, and concepts should communicate purpose
|
||||||
|
5. **Minimize complexity** (F.2-3, ES.5, Per.4-5): Simple code is correct code
|
||||||
|
6. **Value semantics over pointer semantics** (C.10, R.3-5, F.20, CP.31): Prefer returning by value and scoped objects
|
||||||
|
|
||||||
|
## Philosophy & Interfaces (P.*, I.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **P.1** | Express ideas directly in code |
|
||||||
|
| **P.3** | Express intent |
|
||||||
|
| **P.4** | Ideally, a program should be statically type safe |
|
||||||
|
| **P.5** | Prefer compile-time checking to run-time checking |
|
||||||
|
| **P.8** | Don't leak any resources |
|
||||||
|
| **P.10** | Prefer immutable data to mutable data |
|
||||||
|
| **I.1** | Make interfaces explicit |
|
||||||
|
| **I.2** | Avoid non-const global variables |
|
||||||
|
| **I.4** | Make interfaces precisely and strongly typed |
|
||||||
|
| **I.11** | Never transfer ownership by a raw pointer or reference |
|
||||||
|
| **I.23** | Keep the number of function arguments low |
|
||||||
|
|
||||||
|
### DO
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// P.10 + I.4: Immutable, strongly typed interface
|
||||||
|
struct Temperature {
|
||||||
|
double kelvin;
|
||||||
|
};
|
||||||
|
|
||||||
|
Temperature boil(const Temperature& water);
|
||||||
|
```
|
||||||
|
|
||||||
|
### DON'T
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Weak interface: unclear ownership, unclear units
|
||||||
|
double boil(double* temp);
|
||||||
|
|
||||||
|
// Non-const global variable
|
||||||
|
int g_counter = 0; // I.2 violation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions (F.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **F.1** | Package meaningful operations as carefully named functions |
|
||||||
|
| **F.2** | A function should perform a single logical operation |
|
||||||
|
| **F.3** | Keep functions short and simple |
|
||||||
|
| **F.4** | If a function might be evaluated at compile time, declare it `constexpr` |
|
||||||
|
| **F.6** | If your function must not throw, declare it `noexcept` |
|
||||||
|
| **F.8** | Prefer pure functions |
|
||||||
|
| **F.16** | For "in" parameters, pass cheaply-copied types by value and others by `const&` |
|
||||||
|
| **F.20** | For "out" values, prefer return values to output parameters |
|
||||||
|
| **F.21** | To return multiple "out" values, prefer returning a struct |
|
||||||
|
| **F.43** | Never return a pointer or reference to a local object |
|
||||||
|
|
||||||
|
### Parameter Passing
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// F.16: Cheap types by value, others by const&
|
||||||
|
void print(int x); // cheap: by value
|
||||||
|
void analyze(const std::string& data); // expensive: by const&
|
||||||
|
void transform(std::string s); // sink: by value (will move)
|
||||||
|
|
||||||
|
// F.20 + F.21: Return values, not output parameters
|
||||||
|
struct ParseResult {
|
||||||
|
std::string token;
|
||||||
|
int position;
|
||||||
|
};
|
||||||
|
|
||||||
|
ParseResult parse(std::string_view input); // GOOD: return struct
|
||||||
|
|
||||||
|
// BAD: output parameters
|
||||||
|
void parse(std::string_view input,
|
||||||
|
std::string& token, int& pos); // avoid this
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pure Functions and constexpr
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// F.4 + F.8: Pure, constexpr where possible
|
||||||
|
constexpr int factorial(int n) noexcept {
|
||||||
|
return (n <= 1) ? 1 : n * factorial(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(factorial(5) == 120);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Returning `T&&` from functions (F.45)
|
||||||
|
- Using `va_arg` / C-style variadics (F.55)
|
||||||
|
- Capturing by reference in lambdas passed to other threads (F.53)
|
||||||
|
- Returning `const T` which inhibits move semantics (F.49)
|
||||||
|
|
||||||
|
## Classes & Class Hierarchies (C.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **C.2** | Use `class` if invariant exists; `struct` if data members vary independently |
|
||||||
|
| **C.9** | Minimize exposure of members |
|
||||||
|
| **C.20** | If you can avoid defining default operations, do (Rule of Zero) |
|
||||||
|
| **C.21** | If you define or `=delete` any copy/move/destructor, handle them all (Rule of Five) |
|
||||||
|
| **C.35** | Base class destructor: public virtual or protected non-virtual |
|
||||||
|
| **C.41** | A constructor should create a fully initialized object |
|
||||||
|
| **C.46** | Declare single-argument constructors `explicit` |
|
||||||
|
| **C.67** | A polymorphic class should suppress public copy/move |
|
||||||
|
| **C.128** | Virtual functions: specify exactly one of `virtual`, `override`, or `final` |
|
||||||
|
|
||||||
|
### Rule of Zero
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.20: Let the compiler generate special members
|
||||||
|
struct Employee {
|
||||||
|
std::string name;
|
||||||
|
std::string department;
|
||||||
|
int id;
|
||||||
|
// No destructor, copy/move constructors, or assignment operators needed
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rule of Five
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.21: If you must manage a resource, define all five
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
explicit Buffer(std::size_t size)
|
||||||
|
: data_(std::make_unique<char[]>(size)), size_(size) {}
|
||||||
|
|
||||||
|
~Buffer() = default;
|
||||||
|
|
||||||
|
Buffer(const Buffer& other)
|
||||||
|
: data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
|
||||||
|
std::copy_n(other.data_.get(), size_, data_.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer& operator=(const Buffer& other) {
|
||||||
|
if (this != &other) {
|
||||||
|
auto new_data = std::make_unique<char[]>(other.size_);
|
||||||
|
std::copy_n(other.data_.get(), other.size_, new_data.get());
|
||||||
|
data_ = std::move(new_data);
|
||||||
|
size_ = other.size_;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer(Buffer&&) noexcept = default;
|
||||||
|
Buffer& operator=(Buffer&&) noexcept = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<char[]> data_;
|
||||||
|
std::size_t size_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Class Hierarchy
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// C.35 + C.128: Virtual destructor, use override
|
||||||
|
class Shape {
|
||||||
|
public:
|
||||||
|
virtual ~Shape() = default;
|
||||||
|
virtual double area() const = 0; // C.121: pure interface
|
||||||
|
};
|
||||||
|
|
||||||
|
class Circle : public Shape {
|
||||||
|
public:
|
||||||
|
explicit Circle(double r) : radius_(r) {}
|
||||||
|
double area() const override { return 3.14159 * radius_ * radius_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
double radius_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Calling virtual functions in constructors/destructors (C.82)
|
||||||
|
- Using `memset`/`memcpy` on non-trivial types (C.90)
|
||||||
|
- Providing different default arguments for virtual function and overrider (C.140)
|
||||||
|
- Making data members `const` or references, which suppresses move/copy (C.12)
|
||||||
|
|
||||||
|
## Resource Management (R.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **R.1** | Manage resources automatically using RAII |
|
||||||
|
| **R.3** | A raw pointer (`T*`) is non-owning |
|
||||||
|
| **R.5** | Prefer scoped objects; don't heap-allocate unnecessarily |
|
||||||
|
| **R.10** | Avoid `malloc()`/`free()` |
|
||||||
|
| **R.11** | Avoid calling `new` and `delete` explicitly |
|
||||||
|
| **R.20** | Use `unique_ptr` or `shared_ptr` to represent ownership |
|
||||||
|
| **R.21** | Prefer `unique_ptr` over `shared_ptr` unless sharing ownership |
|
||||||
|
| **R.22** | Use `make_shared()` to make `shared_ptr`s |
|
||||||
|
|
||||||
|
### Smart Pointer Usage
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// R.11 + R.20 + R.21: RAII with smart pointers
|
||||||
|
auto widget = std::make_unique<Widget>("config"); // unique ownership
|
||||||
|
auto cache = std::make_shared<Cache>(1024); // shared ownership
|
||||||
|
|
||||||
|
// R.3: Raw pointer = non-owning observer
|
||||||
|
void render(const Widget* w) { // does NOT own w
|
||||||
|
if (w) w->draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(widget.get());
|
||||||
|
```
|
||||||
|
|
||||||
|
### RAII Pattern
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// R.1: Resource acquisition is initialization
|
||||||
|
class FileHandle {
|
||||||
|
public:
|
||||||
|
explicit FileHandle(const std::string& path)
|
||||||
|
: handle_(std::fopen(path.c_str(), "r")) {
|
||||||
|
if (!handle_) throw std::runtime_error("Failed to open: " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FileHandle() {
|
||||||
|
if (handle_) std::fclose(handle_);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileHandle(const FileHandle&) = delete;
|
||||||
|
FileHandle& operator=(const FileHandle&) = delete;
|
||||||
|
FileHandle(FileHandle&& other) noexcept
|
||||||
|
: handle_(std::exchange(other.handle_, nullptr)) {}
|
||||||
|
FileHandle& operator=(FileHandle&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
if (handle_) std::fclose(handle_);
|
||||||
|
handle_ = std::exchange(other.handle_, nullptr);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::FILE* handle_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Naked `new`/`delete` (R.11)
|
||||||
|
- `malloc()`/`free()` in C++ code (R.10)
|
||||||
|
- Multiple resource allocations in a single expression (R.13 -- exception safety hazard)
|
||||||
|
- `shared_ptr` where `unique_ptr` suffices (R.21)
|
||||||
|
|
||||||
|
## Expressions & Statements (ES.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **ES.5** | Keep scopes small |
|
||||||
|
| **ES.20** | Always initialize an object |
|
||||||
|
| **ES.23** | Prefer `{}` initializer syntax |
|
||||||
|
| **ES.25** | Declare objects `const` or `constexpr` unless modification is intended |
|
||||||
|
| **ES.28** | Use lambdas for complex initialization of `const` variables |
|
||||||
|
| **ES.45** | Avoid magic constants; use symbolic constants |
|
||||||
|
| **ES.46** | Avoid narrowing/lossy arithmetic conversions |
|
||||||
|
| **ES.47** | Use `nullptr` rather than `0` or `NULL` |
|
||||||
|
| **ES.48** | Avoid casts |
|
||||||
|
| **ES.50** | Don't cast away `const` |
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// ES.20 + ES.23 + ES.25: Always initialize, prefer {}, default to const
|
||||||
|
const int max_retries{3};
|
||||||
|
const std::string name{"widget"};
|
||||||
|
const std::vector<int> primes{2, 3, 5, 7, 11};
|
||||||
|
|
||||||
|
// ES.28: Lambda for complex const initialization
|
||||||
|
const auto config = [&] {
|
||||||
|
Config c;
|
||||||
|
c.timeout = std::chrono::seconds{30};
|
||||||
|
c.retries = max_retries;
|
||||||
|
c.verbose = debug_mode;
|
||||||
|
return c;
|
||||||
|
}();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Uninitialized variables (ES.20)
|
||||||
|
- Using `0` or `NULL` as pointer (ES.47 -- use `nullptr`)
|
||||||
|
- C-style casts (ES.48 -- use `static_cast`, `const_cast`, etc.)
|
||||||
|
- Casting away `const` (ES.50)
|
||||||
|
- Magic numbers without named constants (ES.45)
|
||||||
|
- Mixing signed and unsigned arithmetic (ES.100)
|
||||||
|
- Reusing names in nested scopes (ES.12)
|
||||||
|
|
||||||
|
## Error Handling (E.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **E.1** | Develop an error-handling strategy early in a design |
|
||||||
|
| **E.2** | Throw an exception to signal that a function can't perform its assigned task |
|
||||||
|
| **E.6** | Use RAII to prevent leaks |
|
||||||
|
| **E.12** | Use `noexcept` when throwing is impossible or unacceptable |
|
||||||
|
| **E.14** | Use purpose-designed user-defined types as exceptions |
|
||||||
|
| **E.15** | Throw by value, catch by reference |
|
||||||
|
| **E.16** | Destructors, deallocation, and swap must never fail |
|
||||||
|
| **E.17** | Don't try to catch every exception in every function |
|
||||||
|
|
||||||
|
### Exception Hierarchy
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// E.14 + E.15: Custom exception types, throw by value, catch by reference
|
||||||
|
class AppError : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
using std::runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NetworkError : public AppError {
|
||||||
|
public:
|
||||||
|
NetworkError(const std::string& msg, int code)
|
||||||
|
: AppError(msg), status_code(code) {}
|
||||||
|
int status_code;
|
||||||
|
};
|
||||||
|
|
||||||
|
void fetch_data(const std::string& url) {
|
||||||
|
// E.2: Throw to signal failure
|
||||||
|
throw NetworkError("connection refused", 503);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
try {
|
||||||
|
fetch_data("https://api.example.com");
|
||||||
|
} catch (const NetworkError& e) {
|
||||||
|
log_error(e.what(), e.status_code);
|
||||||
|
} catch (const AppError& e) {
|
||||||
|
log_error(e.what());
|
||||||
|
}
|
||||||
|
// E.17: Don't catch everything here -- let unexpected errors propagate
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Throwing built-in types like `int` or string literals (E.14)
|
||||||
|
- Catching by value (slicing risk) (E.15)
|
||||||
|
- Empty catch blocks that silently swallow errors
|
||||||
|
- Using exceptions for flow control (E.3)
|
||||||
|
- Error handling based on global state like `errno` (E.28)
|
||||||
|
|
||||||
|
## Constants & Immutability (Con.*)
|
||||||
|
|
||||||
|
### All Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Con.1** | By default, make objects immutable |
|
||||||
|
| **Con.2** | By default, make member functions `const` |
|
||||||
|
| **Con.3** | By default, pass pointers and references to `const` |
|
||||||
|
| **Con.4** | Use `const` for values that don't change after construction |
|
||||||
|
| **Con.5** | Use `constexpr` for values computable at compile time |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Con.1 through Con.5: Immutability by default
|
||||||
|
class Sensor {
|
||||||
|
public:
|
||||||
|
explicit Sensor(std::string id) : id_(std::move(id)) {}
|
||||||
|
|
||||||
|
// Con.2: const member functions by default
|
||||||
|
const std::string& id() const { return id_; }
|
||||||
|
double last_reading() const { return reading_; }
|
||||||
|
|
||||||
|
// Only non-const when mutation is required
|
||||||
|
void record(double value) { reading_ = value; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string id_; // Con.4: never changes after construction
|
||||||
|
double reading_{0.0};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Con.3: Pass by const reference
|
||||||
|
void display(const Sensor& s) {
|
||||||
|
std::cout << s.id() << ": " << s.last_reading() << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Con.5: Compile-time constants
|
||||||
|
constexpr double PI = 3.14159265358979;
|
||||||
|
constexpr int MAX_SENSORS = 256;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Concurrency & Parallelism (CP.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **CP.2** | Avoid data races |
|
||||||
|
| **CP.3** | Minimize explicit sharing of writable data |
|
||||||
|
| **CP.4** | Think in terms of tasks, rather than threads |
|
||||||
|
| **CP.8** | Don't use `volatile` for synchronization |
|
||||||
|
| **CP.20** | Use RAII, never plain `lock()`/`unlock()` |
|
||||||
|
| **CP.21** | Use `std::scoped_lock` to acquire multiple mutexes |
|
||||||
|
| **CP.22** | Never call unknown code while holding a lock |
|
||||||
|
| **CP.42** | Don't wait without a condition |
|
||||||
|
| **CP.44** | Remember to name your `lock_guard`s and `unique_lock`s |
|
||||||
|
| **CP.100** | Don't use lock-free programming unless you absolutely have to |
|
||||||
|
|
||||||
|
### Safe Locking
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// CP.20 + CP.44: RAII locks, always named
|
||||||
|
class ThreadSafeQueue {
|
||||||
|
public:
|
||||||
|
void push(int value) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_); // CP.44: named!
|
||||||
|
queue_.push(value);
|
||||||
|
cv_.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
int pop() {
|
||||||
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
|
// CP.42: Always wait with a condition
|
||||||
|
cv_.wait(lock, [this] { return !queue_.empty(); });
|
||||||
|
const int value = queue_.front();
|
||||||
|
queue_.pop();
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex mutex_; // CP.50: mutex with its data
|
||||||
|
std::condition_variable cv_;
|
||||||
|
std::queue<int> queue_;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Mutexes
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// CP.21: std::scoped_lock for multiple mutexes (deadlock-free)
|
||||||
|
void transfer(Account& from, Account& to, double amount) {
|
||||||
|
std::scoped_lock lock(from.mutex_, to.mutex_);
|
||||||
|
from.balance_ -= amount;
|
||||||
|
to.balance_ += amount;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- `volatile` for synchronization (CP.8 -- it's for hardware I/O only)
|
||||||
|
- Detaching threads (CP.26 -- lifetime management becomes nearly impossible)
|
||||||
|
- Unnamed lock guards: `std::lock_guard<std::mutex>(m);` destroys immediately (CP.44)
|
||||||
|
- Holding locks while calling callbacks (CP.22 -- deadlock risk)
|
||||||
|
- Lock-free programming without deep expertise (CP.100)
|
||||||
|
|
||||||
|
## Templates & Generic Programming (T.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **T.1** | Use templates to raise the level of abstraction |
|
||||||
|
| **T.2** | Use templates to express algorithms for many argument types |
|
||||||
|
| **T.10** | Specify concepts for all template arguments |
|
||||||
|
| **T.11** | Use standard concepts whenever possible |
|
||||||
|
| **T.13** | Prefer shorthand notation for simple concepts |
|
||||||
|
| **T.43** | Prefer `using` over `typedef` |
|
||||||
|
| **T.120** | Use template metaprogramming only when you really need to |
|
||||||
|
| **T.144** | Don't specialize function templates (overload instead) |
|
||||||
|
|
||||||
|
### Concepts (C++20)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
// T.10 + T.11: Constrain templates with standard concepts
|
||||||
|
template<std::integral T>
|
||||||
|
T gcd(T a, T b) {
|
||||||
|
while (b != 0) {
|
||||||
|
a = std::exchange(b, a % b);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// T.13: Shorthand concept syntax
|
||||||
|
void sort(std::ranges::random_access_range auto& range) {
|
||||||
|
std::ranges::sort(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom concept for domain-specific constraints
|
||||||
|
template<typename T>
|
||||||
|
concept Serializable = requires(const T& t) {
|
||||||
|
{ t.serialize() } -> std::convertible_to<std::string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<Serializable T>
|
||||||
|
void save(const T& obj, const std::string& path);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Unconstrained templates in visible namespaces (T.47)
|
||||||
|
- Specializing function templates instead of overloading (T.144)
|
||||||
|
- Template metaprogramming where `constexpr` suffices (T.120)
|
||||||
|
- `typedef` instead of `using` (T.43)
|
||||||
|
|
||||||
|
## Standard Library (SL.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **SL.1** | Use libraries wherever possible |
|
||||||
|
| **SL.2** | Prefer the standard library to other libraries |
|
||||||
|
| **SL.con.1** | Prefer `std::array` or `std::vector` over C arrays |
|
||||||
|
| **SL.con.2** | Prefer `std::vector` by default |
|
||||||
|
| **SL.str.1** | Use `std::string` to own character sequences |
|
||||||
|
| **SL.str.2** | Use `std::string_view` to refer to character sequences |
|
||||||
|
| **SL.io.50** | Avoid `endl` (use `'\n'` -- `endl` forces a flush) |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// SL.con.1 + SL.con.2: Prefer vector/array over C arrays
|
||||||
|
const std::array<int, 4> fixed_data{1, 2, 3, 4};
|
||||||
|
std::vector<std::string> dynamic_data;
|
||||||
|
|
||||||
|
// SL.str.1 + SL.str.2: string owns, string_view observes
|
||||||
|
std::string build_greeting(std::string_view name) {
|
||||||
|
return "Hello, " + std::string(name) + "!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// SL.io.50: Use '\n' not endl
|
||||||
|
std::cout << "result: " << value << '\n';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enumerations (Enum.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Enum.1** | Prefer enumerations over macros |
|
||||||
|
| **Enum.3** | Prefer `enum class` over plain `enum` |
|
||||||
|
| **Enum.5** | Don't use ALL_CAPS for enumerators |
|
||||||
|
| **Enum.6** | Avoid unnamed enumerations |
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Enum.3 + Enum.5: Scoped enum, no ALL_CAPS
|
||||||
|
enum class Color { red, green, blue };
|
||||||
|
enum class LogLevel { debug, info, warning, error };
|
||||||
|
|
||||||
|
// BAD: plain enum leaks names, ALL_CAPS clashes with macros
|
||||||
|
enum { RED, GREEN, BLUE }; // Enum.3 + Enum.5 + Enum.6 violation
|
||||||
|
#define MAX_SIZE 100 // Enum.1 violation -- use constexpr
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Files & Naming (SF.*, NL.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **SF.1** | Use `.cpp` for code files and `.h` for interface files |
|
||||||
|
| **SF.7** | Don't write `using namespace` at global scope in a header |
|
||||||
|
| **SF.8** | Use `#include` guards for all `.h` files |
|
||||||
|
| **SF.11** | Header files should be self-contained |
|
||||||
|
| **NL.5** | Avoid encoding type information in names (no Hungarian notation) |
|
||||||
|
| **NL.8** | Use a consistent naming style |
|
||||||
|
| **NL.9** | Use ALL_CAPS for macro names only |
|
||||||
|
| **NL.10** | Prefer `underscore_style` names |
|
||||||
|
|
||||||
|
### Header Guard
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// SF.8: Include guard (or #pragma once)
|
||||||
|
#ifndef PROJECT_MODULE_WIDGET_H
|
||||||
|
#define PROJECT_MODULE_WIDGET_H
|
||||||
|
|
||||||
|
// SF.11: Self-contained -- include everything this header needs
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace project::module {
|
||||||
|
|
||||||
|
class Widget {
|
||||||
|
public:
|
||||||
|
explicit Widget(std::string name);
|
||||||
|
const std::string& name() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace project::module
|
||||||
|
|
||||||
|
#endif // PROJECT_MODULE_WIDGET_H
|
||||||
|
```
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// NL.8 + NL.10: Consistent underscore_style
|
||||||
|
namespace my_project {
|
||||||
|
|
||||||
|
constexpr int max_buffer_size = 4096; // NL.9: not ALL_CAPS (it's not a macro)
|
||||||
|
|
||||||
|
class tcp_connection { // underscore_style class
|
||||||
|
public:
|
||||||
|
void send_message(std::string_view msg);
|
||||||
|
bool is_connected() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host_; // trailing underscore for members
|
||||||
|
int port_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace my_project
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- `using namespace std;` in a header at global scope (SF.7)
|
||||||
|
- Headers that depend on inclusion order (SF.10, SF.11)
|
||||||
|
- Hungarian notation like `strName`, `iCount` (NL.5)
|
||||||
|
- ALL_CAPS for anything other than macros (NL.9)
|
||||||
|
|
||||||
|
## Performance (Per.*)
|
||||||
|
|
||||||
|
### Key Rules
|
||||||
|
|
||||||
|
| Rule | Summary |
|
||||||
|
|------|---------|
|
||||||
|
| **Per.1** | Don't optimize without reason |
|
||||||
|
| **Per.2** | Don't optimize prematurely |
|
||||||
|
| **Per.6** | Don't make claims about performance without measurements |
|
||||||
|
| **Per.7** | Design to enable optimization |
|
||||||
|
| **Per.10** | Rely on the static type system |
|
||||||
|
| **Per.11** | Move computation from run time to compile time |
|
||||||
|
| **Per.19** | Access memory predictably |
|
||||||
|
|
||||||
|
### Guidelines
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Per.11: Compile-time computation where possible
|
||||||
|
constexpr auto lookup_table = [] {
|
||||||
|
std::array<int, 256> table{};
|
||||||
|
for (int i = 0; i < 256; ++i) {
|
||||||
|
table[i] = i * i;
|
||||||
|
}
|
||||||
|
return table;
|
||||||
|
}();
|
||||||
|
|
||||||
|
// Per.19: Prefer contiguous data for cache-friendliness
|
||||||
|
std::vector<Point> points; // GOOD: contiguous
|
||||||
|
std::vector<std::unique_ptr<Point>> indirect_points; // BAD: pointer chasing
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- Optimizing without profiling data (Per.1, Per.6)
|
||||||
|
- Choosing "clever" low-level code over clear abstractions (Per.4, Per.5)
|
||||||
|
- Ignoring data layout and cache behavior (Per.19)
|
||||||
|
|
||||||
|
## Quick Reference Checklist
|
||||||
|
|
||||||
|
Before marking C++ work complete:
|
||||||
|
|
||||||
|
- [ ] No raw `new`/`delete` -- use smart pointers or RAII (R.11)
|
||||||
|
- [ ] Objects initialized at declaration (ES.20)
|
||||||
|
- [ ] Variables are `const`/`constexpr` by default (Con.1, ES.25)
|
||||||
|
- [ ] Member functions are `const` where possible (Con.2)
|
||||||
|
- [ ] `enum class` instead of plain `enum` (Enum.3)
|
||||||
|
- [ ] `nullptr` instead of `0`/`NULL` (ES.47)
|
||||||
|
- [ ] No narrowing conversions (ES.46)
|
||||||
|
- [ ] No C-style casts (ES.48)
|
||||||
|
- [ ] Single-argument constructors are `explicit` (C.46)
|
||||||
|
- [ ] Rule of Zero or Rule of Five applied (C.20, C.21)
|
||||||
|
- [ ] Base class destructors are public virtual or protected non-virtual (C.35)
|
||||||
|
- [ ] Templates are constrained with concepts (T.10)
|
||||||
|
- [ ] No `using namespace` in headers at global scope (SF.7)
|
||||||
|
- [ ] Headers have include guards and are self-contained (SF.8, SF.11)
|
||||||
|
- [ ] Locks use RAII (`scoped_lock`/`lock_guard`) (CP.20)
|
||||||
|
- [ ] Exceptions are custom types, thrown by value, caught by reference (E.14, E.15)
|
||||||
|
- [ ] `'\n'` instead of `std::endl` (SL.io.50)
|
||||||
|
- [ ] No magic numbers (ES.45)
|
||||||
219
skills/regex-vs-llm-structured-text/SKILL.md
Normal file
219
skills/regex-vs-llm-structured-text/SKILL.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
---
|
||||||
|
name: regex-vs-llm-structured-text
|
||||||
|
description: Decision framework for choosing between regex and LLM when parsing structured text — start with regex, add LLM only for low-confidence edge cases.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Regex vs LLM for Structured Text Parsing
|
||||||
|
|
||||||
|
A practical decision framework for parsing structured text (quizzes, forms, invoices, documents). The key insight: regex handles 95-98% of cases cheaply and deterministically. Reserve expensive LLM calls for the remaining edge cases.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Parsing structured text with repeating patterns (questions, forms, tables)
|
||||||
|
- Deciding between regex and LLM for text extraction
|
||||||
|
- Building hybrid pipelines that combine both approaches
|
||||||
|
- Optimizing cost/accuracy tradeoffs in text processing
|
||||||
|
|
||||||
|
## Decision Framework
|
||||||
|
|
||||||
|
```
|
||||||
|
Is the text format consistent and repeating?
|
||||||
|
├── Yes (>90% follows a pattern) → Start with Regex
|
||||||
|
│ ├── Regex handles 95%+ → Done, no LLM needed
|
||||||
|
│ └── Regex handles <95% → Add LLM for edge cases only
|
||||||
|
└── No (free-form, highly variable) → Use LLM directly
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Pattern
|
||||||
|
|
||||||
|
```
|
||||||
|
Source Text
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[Regex Parser] ─── Extracts structure (95-98% accuracy)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[Text Cleaner] ─── Removes noise (markers, page numbers, artifacts)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
[Confidence Scorer] ─── Flags low-confidence extractions
|
||||||
|
│
|
||||||
|
├── High confidence (≥0.95) → Direct output
|
||||||
|
│
|
||||||
|
└── Low confidence (<0.95) → [LLM Validator] → Output
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### 1. Regex Parser (Handles the Majority)
|
||||||
|
|
||||||
|
```python
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ParsedItem:
|
||||||
|
id: str
|
||||||
|
text: str
|
||||||
|
choices: tuple[str, ...]
|
||||||
|
answer: str
|
||||||
|
confidence: float = 1.0
|
||||||
|
|
||||||
|
def parse_structured_text(content: str) -> list[ParsedItem]:
|
||||||
|
"""Parse structured text using regex patterns."""
|
||||||
|
pattern = re.compile(
|
||||||
|
r"(?P<id>\d+)\.\s*(?P<text>.+?)\n"
|
||||||
|
r"(?P<choices>(?:[A-D]\..+?\n)+)"
|
||||||
|
r"Answer:\s*(?P<answer>[A-D])",
|
||||||
|
re.MULTILINE | re.DOTALL,
|
||||||
|
)
|
||||||
|
items = []
|
||||||
|
for match in pattern.finditer(content):
|
||||||
|
choices = tuple(
|
||||||
|
c.strip() for c in re.findall(r"[A-D]\.\s*(.+)", match.group("choices"))
|
||||||
|
)
|
||||||
|
items.append(ParsedItem(
|
||||||
|
id=match.group("id"),
|
||||||
|
text=match.group("text").strip(),
|
||||||
|
choices=choices,
|
||||||
|
answer=match.group("answer"),
|
||||||
|
))
|
||||||
|
return items
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Confidence Scoring
|
||||||
|
|
||||||
|
Flag items that may need LLM review:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ConfidenceFlag:
|
||||||
|
item_id: str
|
||||||
|
score: float
|
||||||
|
reasons: tuple[str, ...]
|
||||||
|
|
||||||
|
def score_confidence(item: ParsedItem) -> ConfidenceFlag:
|
||||||
|
"""Score extraction confidence and flag issues."""
|
||||||
|
reasons = []
|
||||||
|
score = 1.0
|
||||||
|
|
||||||
|
if len(item.choices) < 3:
|
||||||
|
reasons.append("few_choices")
|
||||||
|
score -= 0.3
|
||||||
|
|
||||||
|
if not item.answer:
|
||||||
|
reasons.append("missing_answer")
|
||||||
|
score -= 0.5
|
||||||
|
|
||||||
|
if len(item.text) < 10:
|
||||||
|
reasons.append("short_text")
|
||||||
|
score -= 0.2
|
||||||
|
|
||||||
|
return ConfidenceFlag(
|
||||||
|
item_id=item.id,
|
||||||
|
score=max(0.0, score),
|
||||||
|
reasons=tuple(reasons),
|
||||||
|
)
|
||||||
|
|
||||||
|
def identify_low_confidence(
|
||||||
|
items: list[ParsedItem],
|
||||||
|
threshold: float = 0.95,
|
||||||
|
) -> list[ConfidenceFlag]:
|
||||||
|
"""Return items below confidence threshold."""
|
||||||
|
flags = [score_confidence(item) for item in items]
|
||||||
|
return [f for f in flags if f.score < threshold]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. LLM Validator (Edge Cases Only)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def validate_with_llm(
|
||||||
|
item: ParsedItem,
|
||||||
|
original_text: str,
|
||||||
|
client,
|
||||||
|
) -> ParsedItem:
|
||||||
|
"""Use LLM to fix low-confidence extractions."""
|
||||||
|
response = client.messages.create(
|
||||||
|
model="claude-haiku-4-5-20251001", # Cheapest model for validation
|
||||||
|
max_tokens=500,
|
||||||
|
messages=[{
|
||||||
|
"role": "user",
|
||||||
|
"content": (
|
||||||
|
f"Extract the question, choices, and answer from this text.\n\n"
|
||||||
|
f"Text: {original_text}\n\n"
|
||||||
|
f"Current extraction: {item}\n\n"
|
||||||
|
f"Return corrected JSON if needed, or 'CORRECT' if accurate."
|
||||||
|
),
|
||||||
|
}],
|
||||||
|
)
|
||||||
|
# Parse LLM response and return corrected item...
|
||||||
|
return corrected_item
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Hybrid Pipeline
|
||||||
|
|
||||||
|
```python
|
||||||
|
def process_document(
|
||||||
|
content: str,
|
||||||
|
*,
|
||||||
|
llm_client=None,
|
||||||
|
confidence_threshold: float = 0.95,
|
||||||
|
) -> list[ParsedItem]:
|
||||||
|
"""Full pipeline: regex -> confidence check -> LLM for edge cases."""
|
||||||
|
# Step 1: Regex extraction (handles 95-98%)
|
||||||
|
items = parse_structured_text(content)
|
||||||
|
|
||||||
|
# Step 2: Confidence scoring
|
||||||
|
low_confidence = identify_low_confidence(items, confidence_threshold)
|
||||||
|
|
||||||
|
if not low_confidence or llm_client is None:
|
||||||
|
return items
|
||||||
|
|
||||||
|
# Step 3: LLM validation (only for flagged items)
|
||||||
|
low_conf_ids = {f.item_id for f in low_confidence}
|
||||||
|
result = []
|
||||||
|
for item in items:
|
||||||
|
if item.id in low_conf_ids:
|
||||||
|
result.append(validate_with_llm(item, content, llm_client))
|
||||||
|
else:
|
||||||
|
result.append(item)
|
||||||
|
|
||||||
|
return result
|
||||||
|
```
|
||||||
|
|
||||||
|
## Real-World Metrics
|
||||||
|
|
||||||
|
From a production quiz parsing pipeline (410 items):
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|-------|
|
||||||
|
| Regex success rate | 98.0% |
|
||||||
|
| Low confidence items | 8 (2.0%) |
|
||||||
|
| LLM calls needed | ~5 |
|
||||||
|
| Cost savings vs all-LLM | ~95% |
|
||||||
|
| Test coverage | 93% |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Start with regex** — even imperfect regex gives you a baseline to improve
|
||||||
|
- **Use confidence scoring** to programmatically identify what needs LLM help
|
||||||
|
- **Use the cheapest LLM** for validation (Haiku-class models are sufficient)
|
||||||
|
- **Never mutate** parsed items — return new instances from cleaning/validation steps
|
||||||
|
- **TDD works well** for parsers — write tests for known patterns first, then edge cases
|
||||||
|
- **Log metrics** (regex success rate, LLM call count) to track pipeline health
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Sending all text to an LLM when regex handles 95%+ of cases (expensive and slow)
|
||||||
|
- Using regex for free-form, highly variable text (LLM is better here)
|
||||||
|
- Skipping confidence scoring and hoping regex "just works"
|
||||||
|
- Mutating parsed objects during cleaning/validation steps
|
||||||
|
- Not testing edge cases (malformed input, missing fields, encoding issues)
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Quiz/exam question parsing
|
||||||
|
- Form data extraction
|
||||||
|
- Invoice/receipt processing
|
||||||
|
- Document structure parsing (headers, sections, tables)
|
||||||
|
- Any structured text with repeating patterns where cost matters
|
||||||
142
skills/swift-actor-persistence/SKILL.md
Normal file
142
skills/swift-actor-persistence/SKILL.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
---
|
||||||
|
name: swift-actor-persistence
|
||||||
|
description: Thread-safe data persistence in Swift using actors — in-memory cache with file-backed storage, eliminating data races by design.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Swift Actors for Thread-Safe Persistence
|
||||||
|
|
||||||
|
Patterns for building thread-safe data persistence layers using Swift actors. Combines in-memory caching with file-backed storage, leveraging the actor model to eliminate data races at compile time.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Building a data persistence layer in Swift 5.5+
|
||||||
|
- Need thread-safe access to shared mutable state
|
||||||
|
- Want to eliminate manual synchronization (locks, DispatchQueues)
|
||||||
|
- Building offline-first apps with local storage
|
||||||
|
|
||||||
|
## Core Pattern
|
||||||
|
|
||||||
|
### Actor-Based Repository
|
||||||
|
|
||||||
|
The actor model guarantees serialized access — no data races, enforced by the compiler.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public actor LocalRepository<T: Codable & Identifiable> where T.ID == String {
|
||||||
|
private var cache: [String: T] = [:]
|
||||||
|
private let fileURL: URL
|
||||||
|
|
||||||
|
public init(directory: URL = .documentsDirectory, filename: String = "data.json") {
|
||||||
|
self.fileURL = directory.appendingPathComponent(filename)
|
||||||
|
// Synchronous load during init (actor isolation not yet active)
|
||||||
|
self.cache = Self.loadSynchronously(from: fileURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public API
|
||||||
|
|
||||||
|
public func save(_ item: T) throws {
|
||||||
|
cache[item.id] = item
|
||||||
|
try persistToFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func delete(_ id: String) throws {
|
||||||
|
cache[id] = nil
|
||||||
|
try persistToFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func find(by id: String) -> T? {
|
||||||
|
cache[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
public func loadAll() -> [T] {
|
||||||
|
Array(cache.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
private func persistToFile() throws {
|
||||||
|
let data = try JSONEncoder().encode(Array(cache.values))
|
||||||
|
try data.write(to: fileURL, options: .atomic)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func loadSynchronously(from url: URL) -> [String: T] {
|
||||||
|
guard let data = try? Data(contentsOf: url),
|
||||||
|
let items = try? JSONDecoder().decode([T].self, from: data) else {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
return Dictionary(uniqueKeysWithValues: items.map { ($0.id, $0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
All calls are automatically async due to actor isolation:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
let repository = LocalRepository<Question>()
|
||||||
|
|
||||||
|
// Read — fast O(1) lookup from in-memory cache
|
||||||
|
let question = await repository.find(by: "q-001")
|
||||||
|
let allQuestions = await repository.loadAll()
|
||||||
|
|
||||||
|
// Write — updates cache and persists to file atomically
|
||||||
|
try await repository.save(newQuestion)
|
||||||
|
try await repository.delete("q-001")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Combining with @Observable ViewModel
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@Observable
|
||||||
|
final class QuestionListViewModel {
|
||||||
|
private(set) var questions: [Question] = []
|
||||||
|
private let repository: LocalRepository<Question>
|
||||||
|
|
||||||
|
init(repository: LocalRepository<Question> = LocalRepository()) {
|
||||||
|
self.repository = repository
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() async {
|
||||||
|
questions = await repository.loadAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(_ question: Question) async throws {
|
||||||
|
try await repository.save(question)
|
||||||
|
questions = await repository.loadAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
| Decision | Rationale |
|
||||||
|
|----------|-----------|
|
||||||
|
| Actor (not class + lock) | Compiler-enforced thread safety, no manual synchronization |
|
||||||
|
| In-memory cache + file persistence | Fast reads from cache, durable writes to disk |
|
||||||
|
| Synchronous init loading | Avoids async initialization complexity |
|
||||||
|
| Dictionary keyed by ID | O(1) lookups by identifier |
|
||||||
|
| Generic over `Codable & Identifiable` | Reusable across any model type |
|
||||||
|
| Atomic file writes (`.atomic`) | Prevents partial writes on crash |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Use `Sendable` types** for all data crossing actor boundaries
|
||||||
|
- **Keep the actor's public API minimal** — only expose domain operations, not persistence details
|
||||||
|
- **Use `.atomic` writes** to prevent data corruption if the app crashes mid-write
|
||||||
|
- **Load synchronously in `init`** — async initializers add complexity with minimal benefit for local files
|
||||||
|
- **Combine with `@Observable`** ViewModels for reactive UI updates
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Using `DispatchQueue` or `NSLock` instead of actors for new Swift concurrency code
|
||||||
|
- Exposing the internal cache dictionary to external callers
|
||||||
|
- Making the file URL configurable without validation
|
||||||
|
- Forgetting that all actor method calls are `await` — callers must handle async context
|
||||||
|
- Using `nonisolated` to bypass actor isolation (defeats the purpose)
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Local data storage in iOS/macOS apps (user data, settings, cached content)
|
||||||
|
- Offline-first architectures that sync to a server later
|
||||||
|
- Any shared mutable state that multiple parts of the app access concurrently
|
||||||
|
- Replacing legacy `DispatchQueue`-based thread safety with modern Swift concurrency
|
||||||
189
skills/swift-protocol-di-testing/SKILL.md
Normal file
189
skills/swift-protocol-di-testing/SKILL.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
---
|
||||||
|
name: swift-protocol-di-testing
|
||||||
|
description: Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Swift Protocol-Based Dependency Injection for Testing
|
||||||
|
|
||||||
|
Patterns for making Swift code testable by abstracting external dependencies (file system, network, iCloud) behind small, focused protocols. Enables deterministic tests without I/O.
|
||||||
|
|
||||||
|
## When to Activate
|
||||||
|
|
||||||
|
- Writing Swift code that accesses file system, network, or external APIs
|
||||||
|
- Need to test error handling paths without triggering real failures
|
||||||
|
- Building modules that work across environments (app, test, SwiftUI preview)
|
||||||
|
- Designing testable architecture with Swift concurrency (actors, Sendable)
|
||||||
|
|
||||||
|
## Core Pattern
|
||||||
|
|
||||||
|
### 1. Define Small, Focused Protocols
|
||||||
|
|
||||||
|
Each protocol handles exactly one external concern.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// File system access
|
||||||
|
public protocol FileSystemProviding: Sendable {
|
||||||
|
func containerURL(for purpose: Purpose) -> URL?
|
||||||
|
}
|
||||||
|
|
||||||
|
// File read/write operations
|
||||||
|
public protocol FileAccessorProviding: Sendable {
|
||||||
|
func read(from url: URL) throws -> Data
|
||||||
|
func write(_ data: Data, to url: URL) throws
|
||||||
|
func fileExists(at url: URL) -> Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bookmark storage (e.g., for sandboxed apps)
|
||||||
|
public protocol BookmarkStorageProviding: Sendable {
|
||||||
|
func saveBookmark(_ data: Data, for key: String) throws
|
||||||
|
func loadBookmark(for key: String) throws -> Data?
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Default (Production) Implementations
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public struct DefaultFileSystemProvider: FileSystemProviding {
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func containerURL(for purpose: Purpose) -> URL? {
|
||||||
|
FileManager.default.url(forUbiquityContainerIdentifier: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DefaultFileAccessor: FileAccessorProviding {
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func read(from url: URL) throws -> Data {
|
||||||
|
try Data(contentsOf: url)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func write(_ data: Data, to url: URL) throws {
|
||||||
|
try data.write(to: url, options: .atomic)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func fileExists(at url: URL) -> Bool {
|
||||||
|
FileManager.default.fileExists(atPath: url.path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Create Mock Implementations for Testing
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public final class MockFileAccessor: FileAccessorProviding, @unchecked Sendable {
|
||||||
|
public var files: [URL: Data] = [:]
|
||||||
|
public var readError: Error?
|
||||||
|
public var writeError: Error?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
public func read(from url: URL) throws -> Data {
|
||||||
|
if let error = readError { throw error }
|
||||||
|
guard let data = files[url] else {
|
||||||
|
throw CocoaError(.fileReadNoSuchFile)
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
public func write(_ data: Data, to url: URL) throws {
|
||||||
|
if let error = writeError { throw error }
|
||||||
|
files[url] = data
|
||||||
|
}
|
||||||
|
|
||||||
|
public func fileExists(at url: URL) -> Bool {
|
||||||
|
files[url] != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Inject Dependencies with Default Parameters
|
||||||
|
|
||||||
|
Production code uses defaults; tests inject mocks.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public actor SyncManager {
|
||||||
|
private let fileSystem: FileSystemProviding
|
||||||
|
private let fileAccessor: FileAccessorProviding
|
||||||
|
|
||||||
|
public init(
|
||||||
|
fileSystem: FileSystemProviding = DefaultFileSystemProvider(),
|
||||||
|
fileAccessor: FileAccessorProviding = DefaultFileAccessor()
|
||||||
|
) {
|
||||||
|
self.fileSystem = fileSystem
|
||||||
|
self.fileAccessor = fileAccessor
|
||||||
|
}
|
||||||
|
|
||||||
|
public func sync() async throws {
|
||||||
|
guard let containerURL = fileSystem.containerURL(for: .sync) else {
|
||||||
|
throw SyncError.containerNotAvailable
|
||||||
|
}
|
||||||
|
let data = try fileAccessor.read(
|
||||||
|
from: containerURL.appendingPathComponent("data.json")
|
||||||
|
)
|
||||||
|
// Process data...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Write Tests with Swift Testing
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@Test("Sync manager handles missing container")
|
||||||
|
func testMissingContainer() async {
|
||||||
|
let mockFileSystem = MockFileSystemProvider(containerURL: nil)
|
||||||
|
let manager = SyncManager(fileSystem: mockFileSystem)
|
||||||
|
|
||||||
|
await #expect(throws: SyncError.containerNotAvailable) {
|
||||||
|
try await manager.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Sync manager reads data correctly")
|
||||||
|
func testReadData() async throws {
|
||||||
|
let mockFileAccessor = MockFileAccessor()
|
||||||
|
mockFileAccessor.files[testURL] = testData
|
||||||
|
|
||||||
|
let manager = SyncManager(fileAccessor: mockFileAccessor)
|
||||||
|
let result = try await manager.loadData()
|
||||||
|
|
||||||
|
#expect(result == expectedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test("Sync manager handles read errors gracefully")
|
||||||
|
func testReadError() async {
|
||||||
|
let mockFileAccessor = MockFileAccessor()
|
||||||
|
mockFileAccessor.readError = CocoaError(.fileReadCorruptFile)
|
||||||
|
|
||||||
|
let manager = SyncManager(fileAccessor: mockFileAccessor)
|
||||||
|
|
||||||
|
await #expect(throws: SyncError.self) {
|
||||||
|
try await manager.sync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
- **Single Responsibility**: Each protocol should handle one concern — don't create "god protocols" with many methods
|
||||||
|
- **Sendable conformance**: Required when protocols are used across actor boundaries
|
||||||
|
- **Default parameters**: Let production code use real implementations by default; only tests need to specify mocks
|
||||||
|
- **Error simulation**: Design mocks with configurable error properties for testing failure paths
|
||||||
|
- **Only mock boundaries**: Mock external dependencies (file system, network, APIs), not internal types
|
||||||
|
|
||||||
|
## Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- Creating a single large protocol that covers all external access
|
||||||
|
- Mocking internal types that have no external dependencies
|
||||||
|
- Using `#if DEBUG` conditionals instead of proper dependency injection
|
||||||
|
- Forgetting `Sendable` conformance when used with actors
|
||||||
|
- Over-engineering: if a type has no external dependencies, it doesn't need a protocol
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- Any Swift code that touches file system, network, or external APIs
|
||||||
|
- Testing error handling paths that are hard to trigger in real environments
|
||||||
|
- Building modules that need to work in app, test, and SwiftUI preview contexts
|
||||||
|
- Apps using Swift concurrency (actors, structured concurrency) that need testable architecture
|
||||||
Reference in New Issue
Block a user