More cleanup

This commit is contained in:
2025-09-14 20:17:42 -04:00
parent f39149d516
commit 147edf5c93
16 changed files with 115 additions and 134 deletions

View File

@@ -21,7 +21,6 @@ ______________________________________________________________________
- **C++20** is the target standard
- Use modern C++ features: RAII, move semantics, constexpr, concepts where appropriate
- Prefer standard library containers and algorithms over custom implementations
### C Library Functions and Headers
@@ -65,16 +64,16 @@ signal(SIGTERM, handler);
- Interfacing with APIs that require unsigned types
- Where defined unsigned overflow behavior (wraparound) is intentional and desired
- **Almost always auto** - let the compiler deduce types except when:
- The type is not obvious from context (prefer explicit for clarity)
- The type is not obvious from context and the exact type is important (prefer explicit for clarity)
- Specific type requirements matter (numeric conversions, template parameters)
- Interface contracts need explicit types (public APIs, function signatures)
- **Prefer uninitialized memory to default initialization** when using before initializing would be an error
- Valgrind will catch uninitialized memory usage bugs
- Avoid hiding logic errors with unnecessary zero-initialization
- Avoid hiding logic errors that Valgrind would have caught with unnecessary zero-initialization
- Default initialization can mask bugs and hurt performance
- **Floating point is for metrics only** - avoid `float`/`double` in core data structures and algorithms
- Use for performance measurements, statistics, and monitoring data
- Never use for counts, sizes, or business logic
- Avoid branching on the values of floats
### Type Casting
@@ -106,7 +105,7 @@ auto addr = reinterpret_cast<uintptr_t>(ptr); // Pointer to integer conv
- **Strive for 0% CPU usage when idle** - avoid polling, busy waiting, or unnecessary background activity
- Use **inline functions** for performance-critical code (e.g., `allocate_raw`)
- **String views** with `std::string_view` to minimize unnecessary copying
- **Arena allocation** for efficient memory management (~1ns vs ~20-270ns for malloc)
- **Arena allocation** for efficient memory management, and to group related lifetimes together for simplicity
### String Formatting
@@ -131,6 +130,8 @@ std::string_view response = format(arena,
static_cast<int>(body.size()), body.data());
```
- Offer APIs that let you avoid concatenating strings if possible - e.g. if the bytes are going to get written to a file descriptor you can skip concatenating and use scatter/gather writev-type calls.
### Complexity Control
- **Encapsulation is the main tool for controlling complexity**
@@ -141,7 +142,7 @@ std::string_view response = format(arena,
- Thread safety guarantees
- Performance characteristics
- Ownership and lifetime semantics
- **Do not rely on undocumented interface properties** - if it's not in the header, don't depend on it
- **Do not rely on undocumented properties of an interface** - if it's not in the header, don't depend on it
______________________________________________________________________
@@ -165,17 +166,16 @@ int32_t initial_block_size_;
- **Public members first, private after** - puts the interface users care about at the top, implementation details below
- **Full encapsulation still applies** - use `private:` sections to hide implementation details and maintain deep, capable structs
- The struct keyword doesn't mean shallow design - it means interface-first organization for human readers
- Omit the `public` keyword when inheriting from a struct. It's public by default. E.g. `struct A : B {};` instead of `struct A : public B {};`
```cpp
struct Arena {
struct MyClass {
// Public interface first
explicit Arena(int64_t initial_size = 1024);
void* allocate_raw(int64_t size);
void do_thing();
private:
// Private members after
int32_t initial_block_size_;
Block* current_block_;
int thing_count_;
};
```
@@ -183,6 +183,7 @@ private:
- **PascalCase** for enum class names
- **PascalCase** for enum values (not SCREAMING_SNAKE_CASE)
- C-style enums are acceptable where implicit int conversion is desirable, like for bitflags
```cpp
enum class Type {
@@ -270,7 +271,7 @@ ______________________________________________________________________
- **Move-only semantics** for resource-owning types
- **Explicit constructors** to prevent implicit conversions
- **Delete copy operations** when inappropriate
- **Delete copy operations** when copying is inappropriate or should be discouraged
```cpp
struct Arena {
@@ -313,7 +314,7 @@ Arena(Arena &&source) noexcept;
### Factory Patterns & Ownership
- **Static factory methods** for complex construction requiring shared ownership
- **Static factory methods** for complex construction requirements like enforcing shared ownership
- **Friend-based factories** for access control when constructor should be private
- **Ownership guidelines:**
- **unique_ptr** for exclusive ownership (most common case)
@@ -329,7 +330,8 @@ auto connection = Connection::createForServer(addr, fd, connection_id, handler,
// Friend-based factory for access control
struct Connection {
void append_message(std::string_view message_data);
WeakRef<MessageSender> get_weak_ref() const;
private:
Connection(struct sockaddr_storage client_addr, int file_descriptor,
int64_t connection_id, ConnectionHandler* request_handler,
@@ -382,7 +384,7 @@ ______________________________________________________________________
### Ownership & Allocation
- **Arena allocators** for request-scoped memory with **STL allocator adapters** (see Performance Focus section for characteristics)
- **Arena** for request-scoped memory with **STL allocator adapters**
- **String views** pointing to arena-allocated memory to avoid unnecessary copying
- **STL containers with arena allocators require default construction after arena reset** - `clear()` is not sufficient
@@ -425,7 +427,7 @@ ______________________________________________________________________
- **Error codes are the API contract** - use enums for programmatic decisions
- **Error messages are human-readable only** - never parse message strings
- **Consistent error boundaries** - each component defines what it can/cannot recover from
- **Interface precondition violations are undefined behavior** - acceptable to skip checks for performance in hot paths
- **Interface precondition violations are undefined behavior** - it's acceptable to skip checks for performance in hot paths
- **Error code types must be nodiscard** - mark error code enums with `[[nodiscard]]` to prevent silent failures
```cpp
@@ -439,7 +441,7 @@ if (!memory) {
}
// ... use memory, eventually std::free(memory)
// Programming error - precondition violation (may be omitted for performance)
// Programming error - precondition violation (gets compiled out in release builds)
assert(ptr != nullptr && "Precondition violated: pointer must be non-null");
```
@@ -546,7 +548,7 @@ T *realloc(T *existing_ptr, int32_t current_size, int32_t requested_size);
### Code Comments
- **Explain why, not what** - code should be self-documenting
- **Explain why, not what** - *what* the code does should be clear without any comments
- **Performance notes** for optimization decisions
- **Thread safety** and ownership semantics
@@ -573,7 +575,7 @@ ______________________________________________________________________
### Test Structure
- **Descriptive test names** explaining the scenario
- **SUBCASE** for related test variations
- **SUBCASE** for related test variations that share setup/teardown code
- **Fresh instances** for each test to avoid state contamination
```cpp
@@ -600,25 +602,14 @@ TEST_CASE("Arena basic allocation") {
- **Prefer fakes to mocks** - use real implementations for internal components, fake external dependencies
- **Always enable assertions in tests** - use `-UNDEBUG` pattern to ensure assertions are checked (see Build Integration section)
TODO make a new example here using APIs that exist
```cpp
// Good: Testing through public API
TEST_CASE("Server accepts connections") {
auto config = Config::defaultConfig();
auto handler = std::make_unique<TestHandler>();
auto server = Server::create(config, std::move(handler));
// Test observable behavior - server can accept connections
auto result = connectToServer(server->getPort());
CHECK(result.connected);
}
// Avoid: Testing internal implementation details
// TEST_CASE("Server creates epoll instance") { /* implementation detail */ }
```
### What NOT to Test
**Avoid testing language features and plumbing:**
**Avoid testing language features:**
- Don't test that virtual functions dispatch correctly
- Don't test that standard library types work (unique_ptr, containers, etc.)
@@ -716,8 +707,9 @@ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
```cmake
# Test target with assertions always enabled
add_executable(test_example tests/test_example.cpp src/example.cpp)
target_link_libraries(test_example doctest::doctest)
target_link_libraries(test_example doctest_impl)
target_compile_options(test_example PRIVATE -UNDEBUG) # Always enable assertions
add_test(NAME test_example COMMAND test_example)
# Production target follows build type
add_executable(example src/example.cpp src/main.cpp)