Skip to content

refactor: Decentralize error code registry to improve system isolation #300

Description

@kcenon

Summary

Refactor the centralized error code registry to allow each system to define its own error codes locally while common_system provides only conversion utilities and base categories, improving system isolation and reducing coupling.

Background (Why)

  • Current design: centralized error code registry in common_system
  • All systems must register their error codes in common_system
  • Creates tight coupling between common_system and dependent systems
  • Adding new error codes requires modifying common_system
  • Kent Beck's "Loose Coupling": systems should be independently evolvable
  • Error codes should be defined where they're used

Current centralization problem:

// common_system/error/error_codes.h
namespace kcenon::common::error_codes {
    // Common errors
    constexpr int SUCCESS = 0;
    constexpr int UNKNOWN_ERROR = 1;
    
    // Logger system errors (why is this here?)
    constexpr int LOG_WRITE_FAILED = 1000;
    constexpr int LOG_QUEUE_FULL = 1001;
    
    // Network system errors (why is this here?)
    constexpr int NETWORK_CONNECTION_FAILED = 2000;
    constexpr int NETWORK_TIMEOUT = 2001;
    
    // Database system errors (why is this here?)
    constexpr int DB_CONNECTION_FAILED = 3000;
    // ... more system-specific codes
}

Scope (What)

Current State (Centralized Registry)

common_system/
└── error/
    └── error_codes.h  ← Contains ALL error codes for ALL systems

Proposed State (Decentralized with Common Base)

// common_system/error/error_category.h

namespace kcenon::common {

// Base error category interface
class error_category {
public:
    virtual ~error_category() = default;
    virtual std::string_view name() const noexcept = 0;
    virtual std::string message(int code) const = 0;
    virtual bool equivalent(int code, const error_category& other, int other_code) const;
};

// Common error category (shared across all systems)
class common_error_category : public error_category {
public:
    static const common_error_category& instance();
    
    std::string_view name() const noexcept override { return "common"; }
    std::string message(int code) const override;
    
    // Common error codes
    enum codes {
        success = 0,
        unknown_error = 1,
        invalid_argument = 2,
        out_of_range = 3,
        not_implemented = 4,
        operation_cancelled = 5,
        // Only truly common errors here
    };
};

// Error code that carries its category
class error_code {
public:
    error_code() = default;
    error_code(int code, const error_category& category);
    
    int value() const noexcept { return code_; }
    const error_category& category() const noexcept { return *category_; }
    std::string message() const { return category_->message(code_); }
    
    explicit operator bool() const noexcept { return code_ != 0; }
    
    bool operator==(const error_code& other) const;
    
private:
    int code_ = 0;
    const error_category* category_ = &common_error_category::instance();
};

// Helper to create error codes
template<typename Category>
error_code make_error_code(typename Category::codes code) {
    return error_code(static_cast<int>(code), Category::instance());
}

} // namespace kcenon::common

System-Specific Error Categories

// logger_system/error/logger_error_category.h

namespace kcenon::logger {

class logger_error_category : public common::error_category {
public:
    static const logger_error_category& instance();
    
    std::string_view name() const noexcept override { return "logger"; }
    std::string message(int code) const override;
    
    enum codes {
        write_failed = 1,
        queue_full = 2,
        writer_not_found = 3,
        invalid_format = 4,
        // Logger-specific errors defined HERE, not in common_system
    };
};

// Convenience function
inline common::error_code make_logger_error(logger_error_category::codes code) {
    return common::make_error_code<logger_error_category>(code);
}

} // namespace kcenon::logger
// network_system/error/network_error_category.h

namespace kcenon::network {

class network_error_category : public common::error_category {
public:
    static const network_error_category& instance();
    
    std::string_view name() const noexcept override { return "network"; }
    std::string message(int code) const override;
    
    enum codes {
        connection_failed = 1,
        timeout = 2,
        address_in_use = 3,
        connection_refused = 4,
        // Network-specific errors defined HERE
    };
};

} // namespace kcenon::network

Result Integration

// common_system/patterns/result.h

template<typename T>
class Result {
public:
    // Accept any error_code (from any category)
    static Result<T> error(common::error_code ec) {
        return Result<T>(ec);
    }
    
    // Error info includes category for debugging
    common::error_code error_code() const;
    std::string error_message() const {
        return error_code().message();
    }
    std::string_view error_category_name() const {
        return error_code().category().name();
    }
};

// Usage in logger_system
Result<void> logger::write(const log_entry& entry) {
    if (queue_full) {
        return Result<void>::error(
            make_logger_error(logger_error_category::queue_full)
        );
    }
    // ...
}

// Usage in network_system
Result<void> connection::connect() {
    if (failed) {
        return Result<void>::error(
            make_network_error(network_error_category::connection_failed)
        );
    }
    // ...
}

Impact Analysis (Where)

Before After Benefit
Error codes in common_system Error codes in each system Better isolation
Modify common for new errors Modify only relevant system Less coupling
Single namespace for all errors Category-based namespacing Clearer error origin
Error code collisions possible Category prevents collisions Type safety

Implementation Plan (How)

Phase 1: Infrastructure

  1. Create error_category base class
  2. Create common_error_category for shared errors
  3. Create error_code class with category support
  4. Update Result<T> to use new error_code

Phase 2: Migration - Common System

  1. Identify truly common error codes
  2. Move common errors to common_error_category
  3. Deprecate old error code constants

Phase 3: Migration - Dependent Systems

  1. Create logger_error_category in logger_system
  2. Create network_error_category in network_system
  3. Create database_error_category in database_system
  4. Create container_error_category in container_system

Phase 4: Cleanup

  1. Remove system-specific errors from common_system
  2. Update all error creation sites
  3. Update documentation

Acceptance Criteria

  • Each system defines its own error category
  • common_system only contains truly common errors
  • Error codes carry their category for debugging
  • No error code collisions between systems
  • Adding new errors doesn't require common_system changes
  • All existing tests passing
  • Migration guide provided

Breaking Change Assessment

Aspect Details
Breaking Change Yes - error code handling
Migration Path Wrapper functions + documentation
Deprecation Period 1 major version
Benefit Better system isolation, clearer error origin

Labels

  • refactor
  • architecture
  • breaking-change

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions