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&;
};
两层错误模型
- 业务错误层:预期的、可处理的错误(网络超时、权限拒绝等)
- 程序错误层:不应该发生的访问错误(忘记检查状态就调用value())
对上层调用的限制
强制性API契约
Expected通过以下机制强制上层代码遵循安全模式:
- 类型约束:函数必须返回
Expected<T, E>,不能返回裸类型 - 访问约束:调用
value()前必须确保hasValue() == true - 运行时检查:违反契约立即抛出
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
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());
}
}
核心设计理念
- 强制性:不能绕过错误检查
- 明确性:错误类型在类型系统中显式
- 安全性:正确使用永远不会意外崩溃
- 调试友好:错误使用立即暴露问题
设计哲学:"让正确的代码容易写,让错误的代码难写。通过类型系统和运行时检查,强制程序员思考错误处理。"
这种设计确保了程序员必须遵循检查-访问的模式,否则就承担程序崩溃的后果,从而快速培养正确的错误处理习惯。
浙公网安备 33010602011771号