tortelee

导航

 

Folly Expected 错误处理设计指南

概述

Folly的Expected<Value, Error>采用了一种强制性的错误处理设计,通过类型系统和运行时检查来确保程序员正确处理错误情况。这种设计将业务错误程序逻辑错误明确区分,强制上层调用者遵循安全的访问模式。

底层设计机制

核心设计原理

template<class Value, class Error>
class Expected {
private:
    // 强制性契约检查
    void requireValue() const {
        if (FOLLY_UNLIKELY(!hasValue())) {
            if (FOLLY_LIKELY(hasError())) {
                // 抛出包含原始错误信息的异常
                throw_exception<BadExpectedAccess<Error>>(this->error_);
            }
            throw_exception<BadExpectedAccess<void>>();
        }
    }

public:
    // 断言式访问 - 要求调用者确信有值
    const Value& value() const& {
        requireValue();  // 契约验证点
        return this->Base::value();
    }
    
    // 安全访问方法
    bool hasValue() const noexcept;
    bool hasError() const noexcept;
    const Error& error() const&;
    Value value_or(U&& defaultValue) const&;
};

两层错误模型

  1. 业务错误层:预期的、可处理的错误(网络超时、权限拒绝等)
  2. 程序错误层:不应该发生的访问错误(忘记检查状态就调用value())

对上层调用的限制

强制性API契约

Expected通过以下机制强制上层代码遵循安全模式:

  1. 类型约束:函数必须返回Expected<T, E>,不能返回裸类型
  2. 访问约束:调用value()前必须确保hasValue() == true
  3. 运行时检查:违反契约立即抛出BadExpectedAccess异常

被强制的编程模式

// ✅ 允许的安全模式
Expected<Data, NetworkError> result = fetchData();

// 模式1:明确检查
if (result.hasValue()) {
    process(result.value());  // 安全访问
} else {
    handle(result.error());   // 处理业务错误
}

// 模式2:提供默认值
Data data = result.value_or(getDefaultData());

// 模式3:函数式处理
auto final = result
    .then([](Data d) { return transform(d); })
    .orElse([](Error e) { return handleError(e); });

// ❌ 被禁止的危险模式
Data data = result.value();  // 💥 未检查状态,可能抛异常

上层错误处理最佳实践

1. 业务逻辑层 - 明确检查模式

// 处理用户输入等不可信数据
void handleUserRequest(const string& ipAddress) {
    auto result = IPAddress::tryFromString(ipAddress);
    
    if (result.hasValue()) {
        IPAddress addr = result.value();  // 安全访问
        processValidAddress(addr);
    } else {
        // 处理业务错误
        auto error = result.error();
        switch (error) {
            case IPAddressFormatError::INVALID_FORMAT:
                showError("IP地址格式错误");
                break;
            case IPAddressFormatError::INVALID_RANGE:
                showError("IP地址超出有效范围");
                break;
        }
    }
}

2. 配置/内部逻辑 - 断言式访问

// 处理应该可信的内部数据
void loadConfiguration() {
    // 配置文件中的IP应该是正确的
    // 如果错误说明配置有问题,程序应该立即失败
    auto serverIP = IPAddress::tryFromString(config.serverAddress).value();
    //                                                               ↑
    //                               如果配置错误,抛异常暴露问题
    
    connectToServer(serverIP);
}

3. 防御式编程 - 默认值模式

// 提供合理的默认值
void initializeNetwork() {
    auto result = IPAddress::tryCreateNetwork(userConfig.networkCIDR);
    
    // 使用默认网络配置作为后备
    auto network = result.value_or(getDefaultNetwork());
    setupNetworking(network);
}

4. 函数式错误处理

// 链式处理复杂的错误传播
Expected<ProcessedData, Error> processNetworkData(const string& input) {
    return IPAddress::tryFromString(input)
        .then([](IPAddress addr) {
            return fetchDataFromAddress(addr);
        })
        .then([](RawData data) {
            return validateAndProcess(data);
        })
        .orElse([](auto error) {
            logError("Processing failed", error);
            return getDefaultProcessedData();
        });
}

异常处理策略

谁应该捕获 BadExpectedAccess?

层级 是否捕获 目的
业务逻辑层 ❌ 不应该 掩盖程序错误
测试代码 ✅ 应该 验证错误行为
主程序/框架 ✅ 应该 优雅终止程序
监控系统 ✅ 应该 记录程序bug

正确的异常处理示例

首先看看getData()的典型实现:

// 业务错误类型定义
enum class DataError {
    NETWORK_TIMEOUT,
    PERMISSION_DENIED,
    DATA_CORRUPTED,
    SERVICE_UNAVAILABLE
};

// getData() 的实现 - 返回Expected处理业务错误
Expected<Data, DataError> getData() {
    try {
        // 尝试网络请求
        auto response = networkClient.request("/api/data");
        
        if (response.statusCode == 200) {
            return Data{response.body};  // 成功返回数据
        } else if (response.statusCode == 403) {
            return makeUnexpected(DataError::PERMISSION_DENIED);
        } else if (response.statusCode == 408) {
            return makeUnexpected(DataError::NETWORK_TIMEOUT);
        } else {
            return makeUnexpected(DataError::SERVICE_UNAVAILABLE);
        }
        
    } catch (const NetworkException& e) {
        // 网络层异常转换为业务错误
        return makeUnexpected(DataError::NETWORK_TIMEOUT);
    } catch (const ParseException& e) {
        // 数据解析异常转换为业务错误
        return makeUnexpected(DataError::DATA_CORRUPTED);
    }
    
    // 注意:getData()本身不会抛BadExpectedAccess异常
    // BadExpectedAccess只在错误使用Expected.value()时抛出
}

现在看错误和正确的使用方式:

// ❌ 错误:业务代码试图"修复"程序错误
void badErrorHandling() {
    try {
        auto result = getData();  // 可能返回DataError状态
        auto value = result.value();  // 💥 忘记检查状态,可能抛BadExpectedAccess
        process(value);
    } catch (const BadExpectedAccess<DataError>& e) {
        // 💀 这掩盖了程序员忘记检查hasValue()的错误
        // 通过异常"意外地"获得了原始的DataError
        DataError originalError = e.error();
        handleDataError(originalError);
    }
}

// ✅ 正确:明确区分业务错误和程序错误
void goodErrorHandling() {
    auto result = getData();  // Expected<Data, DataError>
    
    if (result.hasValue()) {
        process(result.value());  // 永远不会抛BadExpectedAccess,因为已确认有值
    } else {
        // 直接处理业务错误,不通过异常机制
        DataError error = result.error();
        handleDataError(error);  // 明确处理各种DataError情况
    }
}

// 对应的handleDataError实现
void handleDataError(DataError error) {
    switch (error) {
        case DataError::NETWORK_TIMEOUT:
            LOG(WARNING) << "网络超时,将重试";
            scheduleRetry();
            break;
        case DataError::PERMISSION_DENIED:
            LOG(ERROR) << "权限被拒绝,请检查认证";
            showAuthenticationDialog();
            break;
        case DataError::DATA_CORRUPTED:
            LOG(ERROR) << "数据损坏,使用缓存数据";
            useCachedData();
            break;
        case DataError::SERVICE_UNAVAILABLE:
            LOG(ERROR) << "服务不可用,切换到备用服务";
            switchToBackupService();
            break;
    }
}
}

}

// ✅ 正确:顶层捕获程序错误
int main() {
try {
runApplication();
} catch (const BadExpectedAccess& e) {
LOG(FATAL) << "程序逻辑错误: " << e.what();
return -1; // 程序有bug,需要修复
}
}


## 实际应用案例

### 网络服务示例

```cpp
class NetworkService {
public:
    // 返回Expected,强制调用者处理连接错误
    Expected<Connection, ConnectionError> connect(const string& address) {
        auto ipResult = IPAddress::tryFromString(address);
        if (ipResult.hasError()) {
            return makeUnexpected(ConnectionError::INVALID_ADDRESS);
        }
        
        // 内部确信IP有效,安全使用value()
        return establishConnection(ipResult.value());
    }
    
private:
    Expected<Connection, ConnectionError> establishConnection(IPAddress ip) {
        // 具体连接逻辑...
    }
};

// 客户端使用
void clientCode() {
    NetworkService service;
    
    // 处理用户输入 - 明确检查模式
    auto conn = service.connect(userInputAddress);
    if (conn.hasValue()) {
        useConnection(conn.value());
    } else {
        showConnectionError(conn.error());
    }
}

核心设计理念

  1. 强制性:不能绕过错误检查
  2. 明确性:错误类型在类型系统中显式
  3. 安全性:正确使用永远不会意外崩溃
  4. 调试友好:错误使用立即暴露问题

设计哲学:"让正确的代码容易写,让错误的代码难写。通过类型系统和运行时检查,强制程序员思考错误处理。"

这种设计确保了程序员必须遵循检查-访问的模式,否则就承担程序崩溃的后果,从而快速培养正确的错误处理习惯。

posted on 2025-09-07 12:13  tortelee  阅读(23)  评论(0)    收藏  举报