mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-24 20:34:29 +08:00
- 将标题从"架构模式最佳实践"改为"架构设计模式指南" - 添加全面的架构设计模式介绍和概述 - 新增MVC模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增MVVM模式详细说明,包括概念、GFramework实现示例和最佳实践 - 新增命令模式详细说明,包括概念、实现示例和撤销功能支持 - 新增查询模式详细说明,包括CQRS概念和复杂查询示例 - 新增事件驱动模式详细说明,包括事件定义和监听实现 - 新增依赖注入模式详细说明,包括构造函数注入示例 - 新增服务定位器模式详细说明,包括与依赖注入对比 - 新增对象池模式详细说明,包括通用对象池实现 - 新增状态模式详细说明,包括异步状态和状态机系统 - 补充模式选择与组合建议,针对小型、中型、大型项目提供不同方案 - 更新代码示例中的泛型语法格式,统一使用尖括号表示法
35 KiB
35 KiB
错误处理最佳实践
本指南介绍 GFramework 中的错误处理模式和最佳实践,帮助你构建健壮、可维护的游戏应用。
📋 目录
概述
为什么错误处理很重要
良好的错误处理是构建健壮应用的基础:
- 提高稳定性:优雅地处理异常情况,避免崩溃
- 改善用户体验:提供清晰的错误提示和恢复选项
- 简化调试:记录详细的错误信息,快速定位问题
- 增强可维护性:使用类型安全的错误处理模式
GFramework 的错误处理策略
GFramework 提供多种错误处理机制:
- Result<T> 模式:函数式错误处理,类型安全
- Option<T> 模式:处理可空值,避免 null 引用
- 异常处理:处理不可恢复的错误
- 日志系统:记录错误信息和调试数据
核心概念
错误类型
根据错误的性质和处理方式,可以分为以下几类:
1. 可预期的错误
这类错误是业务逻辑的一部分,应该使用 Result 或 Option 模式处理:
// 示例:用户输入验证
public Result<PlayerData> ValidatePlayerName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return Result<PlayerData>.Failure("玩家名称不能为空");
if (name.Length < 3)
return Result<PlayerData>.Failure("玩家名称至少需要 3 个字符");
if (name.Length > 20)
return Result<PlayerData>.Failure("玩家名称不能超过 20 个字符");
return Result<PlayerData>.Success(new PlayerData { Name = name });
}
2. 不可预期的错误
这类错误通常是程序错误或系统故障,应该使用异常处理:
// 示例:文件系统错误
public async Task<SaveData> LoadSaveFileAsync(string path)
{
try
{
var json = await File.ReadAllTextAsync(path);
return JsonSerializer.Deserialize<SaveData>(json);
}
catch (FileNotFoundException ex)
{
_logger.Error($"存档文件不存在: {path}", ex);
throw new SaveSystemException("无法加载存档文件", ex);
}
catch (JsonException ex)
{
_logger.Error($"存档文件格式错误: {path}", ex);
throw new SaveSystemException("存档文件已损坏", ex);
}
}
3. 可空值
使用 Option 模式处理可能不存在的值:
// 示例:查找玩家
public Option<Player> FindPlayerById(string playerId)
{
var player = _players.FirstOrDefault(p => p.Id == playerId);
return player != null ? Option<Player>.Some(player) : Option<Player>.None;
}
错误传播
错误应该在合适的层级处理,不要过早捕获:
// ✅ 好的做法:让错误向上传播
public class InventorySystem : AbstractSystem
{
public Result<Item> AddItem(string itemId, int quantity)
{
// 验证参数
var validationResult = ValidateAddItem(itemId, quantity);
if (validationResult.IsFaulted)
return validationResult;
// 检查空间
var spaceResult = CheckInventorySpace(quantity);
if (spaceResult.IsFaulted)
return Result<Item>.Failure(spaceResult.Exception);
// 添加物品
var item = CreateItem(itemId, quantity);
_items.Add(item);
return Result<Item>.Success(item);
}
}
// ❌ 避免:过早捕获错误
public class InventorySystem : AbstractSystem
{
public Result<Item> AddItem(string itemId, int quantity)
{
try
{
// 所有操作都在 try 块中
ValidateAddItem(itemId, quantity);
CheckInventorySpace(quantity);
var item = CreateItem(itemId, quantity);
_items.Add(item);
return Result<Item>.Success(item);
}
catch (Exception ex)
{
// 捕获所有异常,丢失了错误的具体信息
return Result<Item>.Failure("添加物品失败");
}
}
}
错误恢复
设计错误恢复策略,提供降级方案:
// 示例:多级降级策略
public async Task<GameConfig> LoadConfigAsync()
{
// 1. 尝试从云端加载
var cloudResult = await TryLoadFromCloudAsync();
if (cloudResult.IsSuccess)
{
_logger.Info("从云端加载配置成功");
return cloudResult.Match(succ: c => c, fail: _ => null);
}
// 2. 尝试从本地缓存加载
var cacheResult = await TryLoadFromCacheAsync();
if (cacheResult.IsSuccess)
{
_logger.Warn("云端加载失败,使用本地缓存");
return cacheResult.Match(succ: c => c, fail: _ => null);
}
// 3. 使用默认配置
_logger.Error("所有配置源加载失败,使用默认配置");
return GetDefaultConfig();
}
Result 模式
基本用法
Result<T> 是一个函数式类型,表示操作可能成功(包含值)或失败(包含异常):
// 创建成功结果
var successResult = Result<int>.Success(42);
// 创建失败结果
var failureResult = Result<int>.Failure(new Exception("操作失败"));
var failureResult2 = Result<int>.Failure("操作失败"); // 使用字符串
// 检查状态
if (result.IsSuccess)
{
var value = result.Match(succ: v => v, fail: _ => 0);
}
if (result.IsFaulted)
{
var error = result.Exception;
}
链式操作
Result 支持函数式编程的链式操作:
// Map:转换成功值
var result = Result<int>.Success(42)
.Map(x => x * 2) // Result<int>(84)
.Map(x => x.ToString()); // Result<string>("84")
// Bind:链式转换,可能失败
var result = Result<string>.Success("42")
.Bind(s => int.TryParse(s, out var i)
? Result<int>.Success(i)
: Result<int>.Failure("解析失败"))
.Map(i => i * 2);
// Ensure:验证条件
var result = Result<int>.Success(42)
.Ensure(x => x > 0, "值必须为正数")
.Ensure(x => x < 100, "值必须小于 100");
// OnSuccess/OnFailure:执行副作用
result
.OnSuccess(value => _logger.Info($"成功: {value}"))
.OnFailure(ex => _logger.Error($"失败: {ex.Message}"));
实际应用示例
示例 1:用户注册
public class UserRegistrationSystem : AbstractSystem
{
public Result<User> RegisterUser(string username, string email, string password)
{
return ValidateUsername(username)
.Bind(_ => ValidateEmail(email))
.Bind(_ => ValidatePassword(password))
.Bind(_ => CheckUsernameAvailability(username))
.Bind(_ => CheckEmailAvailability(email))
.Map(_ => CreateUser(username, email, password))
.OnSuccess(user => {
_logger.Info($"用户注册成功: {username}");
this.SendEvent(new UserRegisteredEvent { User = user });
})
.OnFailure(ex => _logger.Warn($"用户注册失败: {ex.Message}"));
}
private Result<string> ValidateUsername(string username)
{
if (string.IsNullOrWhiteSpace(username))
return Result<string>.Failure("用户名不能为空");
if (username.Length < 3 || username.Length > 20)
return Result<string>.Failure("用户名长度必须在 3-20 个字符之间");
if (!Regex.IsMatch(username, @"^[a-zA-Z0-9_]+$"))
return Result<string>.Failure("用户名只能包含字母、数字和下划线");
return Result<string>.Success(username);
}
private Result<string> ValidateEmail(string email)
{
if (string.IsNullOrWhiteSpace(email))
return Result<string>.Failure("邮箱不能为空");
if (!Regex.IsMatch(email, @"^[^@\s]+@[^@\s]+\.[^@\s]+$"))
return Result<string>.Failure("邮箱格式不正确");
return Result<string>.Success(email);
}
private Result<string> ValidatePassword(string password)
{
if (string.IsNullOrWhiteSpace(password))
return Result<string>.Failure("密码不能为空");
if (password.Length < 8)
return Result<string>.Failure("密码长度至少为 8 个字符");
if (!passwordchar.IsUpper))
return Result<string>.Failure("密码必须包含至少一个大写字母");
if (!password.Any(char.IsDigit))
return Result<string>.Failure("密码必须包含至少一个数字");
return Result<string>.Success(password);
}
private Result<bool> CheckUsernameAvailability(string username)
{
var exists = _userRepository.ExistsByUsername(username);
return exists
? Result<bool>.Failure("用户名已被使用")
: Result<bool>.Success(true);
}
private Result<bool> CheckEmailAvailability(string email)
{
var exists = _userRepository.ExistsByEmail(email);
return exists
? Result<bool>.Failure("邮箱已被注册")
: Result<bool>.Success(true);
}
private User CreateUser(string username, string email, string password)
{
var user = new User
{
Id = Guid.NewGuid().ToString(),
Username = username,
Email = email,
PasswordHash = HashPassword(password),
CreatedAt = DateTime.UtcNow
};
_userRepository.Add(user);
return user;
}
}
示例 2:物品交易
public class TradingSystem : AbstractSystem
{
public Result<Trade> ExecuteTrade(string sellerId, string buyerId, string itemId, int price)
{
return ValidateTradeParticipants(sellerId, buyerId)
.Bind(_ => ValidateItem(sellerId, itemId))
.Bind(_ => ValidateBuyerFunds(buyerId, price))
.Bind(_ => TransferItem(sellerId, buyerId, itemId))
.Bind(_ =&gerCurrency(buyerId, sellerId, price))
.Map(_ => CreateTradeRecord(sellerId, buyerId, itemId, price))
.OnSuccess(trade => {
_logger.Info($"交易完成: {trade.Id}");
this.SendEvent(new TradeCompletedEvent { Trade = trade });
})
.OnFailure(ex => {
_logger.Error($"交易失败: {ex.Message}");
RollbackTrade(sellerId, buyerId, itemId, price);
});
}
private Result<bool> ValidateTradeParticipants(string sellerId, string buyerId)
{
if (sellerId == buyerId)
return Result<bool>.Failure("不能与自己交易");
var seller = _playerRepository.GetById(sellerId);
if (seller == null)
return Result<bool>.Failure("卖家不存在");
var buyer = _playerRepository.GetById(buyerId);
if (buyer == null)
return Result<bool>.Failure("买家不存在");
return Result<bool>.Success(true);
}
private Result<bool> ValidateItem(string sellerId, string itemId)
{
var inventory = _inventoryRepository.GetByPlayerId(sellerId);
var item = inventory.Items.FirstOrDefault(i => i.Id == itemId);
if (item == null)
return Result<bool>.Failure("物品不存在");
if (!item.IsTradeable)
return Result<bool>.Failure("该物品不可交易");
return Result<bool>.Success(true);
}
private Result<bool> ValidateBuyerFunds(string buyerId, int price)
{
var buyer = _playerRepository.GetById(buyerId);
if (buyer.Currency < price)
return Result<bool>.Failure($"金币不足,需要 {price},当前 {buyer.Currency}");
return Result<bool>.Success(true);
}
}
异步操作
Result 支持异步操作:
// 异步 Map
var result = await Result<int>.Success(42)
.MapAsync(async x => await GetUserDataAsync(x));
// 异步 Bind
var result = await Result<string>.Success("user123")
.BindAsync(async userId => await LoadUserAsync(userId));
// TryAsync:安全执行异步操作
var result = await ResultExtensions.TryAsync(async () =>
{
var data = await LoadDataAsync();
return ProcessData(data);
});
组合多个 Result
// 组合多个结果,全部成功才返回成功
var results = new[]
{
ValidateUsername("player1"),
ValidateEmail("player1@example.com"),
ValidatePassword("Password123")
};
var combinedResult = results.Combine(); // Result<List<string>>
if (combinedResult.IsSuccess)
{
_logger.Info("所有验证通过");
}
else
{
_logger.Error($"验证失败: {combinedResult.Exception.Message}");
}
Option 模式
基本用法
Option<T> 用于表示可能存在或不存在的值,避免 null 引用异常:
// 创建 Some(有值)
var someOption = Option<int>.Some(42);
// 创建 None(无值)
var noneOption = Option<int>.None;
// 检查状态
if (option.IsSome)
{
var value = option.GetOrElse(0);
}
if (option.IsNone)
{
_logger.Warn("值不存在");
}
常用操作
// GetOrElse:获取值或返回默认值
var value = option.GetOrElse(0);
var value2 = option.GetOrElse(() => GetDefaultValue());
// Map:转换值
var mapped = option.Map(x => x * 2);
// Bind:链式转换
var bound = option.Bind(x => x > 0
? Option<string>.Some(x.ToString())
: Option<string>.None);
// Filter:过滤值
var filtered = option.Filter(x => x > 0);
// Match:模式匹配
var result = option.Match(
some: value => $"值: {value}",
none: () => "无值"
);
实际应用示例
示例 1:查找玩家
public class PlayerSystem : AbstractSystem
{
public Option<Player> FindPlayerById(string playerId)
{
var player = _players.FirstOrDefault(p => p.Id == playerId);
return player != null ? Option<Player>.Some(player) : Option<Player>.None;
}
public Option<Player> FindPlayerByName(string playerName)
{
var player = _players.FirstOrDefault(p => p.Name == playerName);
return player != null ? Option<Player>.Some(player) : Option<Player>.None;
}
public void UpdatePlayerScore(string playerId, int score)
{
FindPlayerById(playerId).Match(
some: player => {
player.Score += score;
_logger.Info($"玩家 {player.Name} 得分更新: {player.Score}");
this.SendEvent(new PlayerScoreUpdatedEvent { Player = player });
},
none: () => _logger.Warn($"玩家不存在: {playerId}")
);
}
}
示例 2:配置管理
public class ConfigurationSystem : AbstractSystem
{
private readonly Dictionary<string, string> _config = new();
public Option<string> GetConfig(string key)
{
return _config.TryGetValue(key, out var value)
? Option<string>.Some(value)
: Option<string>.None;
}
public Option<int> GetIntConfig(string key)
{
return GetConfig(key)
.Bind(value => int.TryParse(value, out var result)
? Option<int>.Some(result)
: Option<int>.None);
}
public Option<bool> GetBoolConfig(string key)
{
return GetConfig(key)
.Bind(value => bool.TryParse(value, out var result)
? Option<bool>.Some(result)
: Option<bool>.None);
}
public void ApplyGraphicsSettings()
{
var width = GetIntConfig("screen_width").GetOrElse(1920);
var height = GetIntConfig("screen_height").GetOrElse(1080);
var fullscreen = GetBoolConfig("fullscreen").GetOrElse(false);
var vsync = GetBoolConfig("vsync").GetOrElse(true);
ApplyScreenSettings(width, height, fullscreen, vsync);
}
}
示例 3:物品查找
public class InventorySystem : AbstractSystem
{
public Option<Item> FindItemById(string itemId)
{
var item = _items.FirstOrDefault(i => i.Id == itemId);
return item != null ? Option<Item>.Some(item) : Option<Item>.None;
}
public Option<Item> FindItemByType(ItemType type)
{
var item = _items.FirstOrDefault(i => i.Type == type);
return item != null ? Option<Item>.Some(item) : Option<Item>.None;
}
public Result<Item> UseItem(string itemId)
{
return FindItemById(itemId)
.ToResult("物品不存在")
.Bind(item => item.IsUsable
? Result<Item>.Success(item)
: Result<Item>.Failure("物品不可使用"))
.OnSuccess(item => {
item.Use();
_logger.Info($"使用物品: {item.Name}");
this.SendEvent(new ItemUsedEvent { Item = item });
});
}
}
Option 与 Result 的转换
// Option 转 Result
var option = Option<int>.Some(42);
var result = option.ToResult("值不存在"); // Result<int>
// Result 转 Option(需要自定义扩展)
var result = Result<int>.Success(42);
var option = result.IsSuccess
? Option<int>.Some(result.Match(succ: v => v, fail: _ => 0))
: Option<int>.None;
异常处理
何时使用异常
异常应该用于处理不可预期的错误和不可恢复的错误:
// ✅ 适合使用异常的场景
public class SaveSystem : AbstractSystem
{
public async Task SaveGameAsync(SaveData data)
{
try
{
var json = JsonSerializer.Serialize(data);
await File.WriteAllTextAsync(_savePath, json);
}
catch (UnauthorizedAccessException ex)
{
_logger.Fatal("没有写入权限", ex);
throw new SaveSystemException("无法保存游戏:权限不足", ex);
}
catch (IOException ex)
{
_logger.Error("IO 错误", ex);
throw new SaveSystemException("无法保存游戏:磁盘错误", ex);
}
}
}
// ❌ 不适合使用异常的场景
public class PlayerSystem : AbstractSystem
{
// 不要用异常处理业务逻辑
public void UpdatePlayerHealth(string playerId, int damage)
{
var player = FindPlayerById(playerId);
if (player == null)
throw new PlayerNotFoundException(playerId); // ❌ 应该用 Option 或 Result
player.Health -= damage;
}
}
自定义异常
创建有意义的异常类型:
// 基础游戏异常
public class GameException : Exception
{
public string ErrorCode { get; }
public Dictionary<string, object> Context { get; }
public GameException(
string message,
string errorCode,
Dictionary<string, object> context = null,
Exception innerException = null)
: base(message, innerException)
{
ErrorCode = errorCode;
Context = context ?? new Dictionary<string, object>();
}
}
// 存档系统异常
public class SaveSystemException : GameException
{
public SaveSystemException(
string message,
Exception innerException = null)
: base(message, "SAVE_ERROR", null, innerException)
{
}
}
// 网络异常
public class NetworkException : GameException
{
public int StatusCode { get; }
public NetworkException(
string message,
int statusCode,
Exception innerException = null)
: base(
message,
"NETWORK_ERROR",
new Dictionary<string, object> { ["statusCode"] = statusCode },
innerException)
{
StatusCode = statusCode;
}
}
// 使用示例
public class NetworkSystem : AbstractSystem
{
public async Task<PlayerData> FetchPlayerDataAsync(string playerId)
{
try
{
var response = await _httpClient.GetAsync($"/api/players/{playerId}");
if (!response.IsSuccessStatusCode)
{
throw new NetworkException(
$"获取玩家数据失败: {playerId}",
(int)response.StatusCode
);
}
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<PlayerData>(json);
}
catch (HttpRequestException ex)
{
_logger.Error($"网络请求失败: {playerId}", ex);
throw new NetworkException("网络连接失败", 0, ex);
}
}
}
异常处理最佳实践
1. 捕获具体的异常类型
// ✅ 好的做法
try
{
var data = await LoadDataAsync();
}
catch (FileNotFoundException ex)
{
_logger.Warn("文件不存在", ex);
return GetDefaultData();
}
catch (JsonException ex)
{
_logger.Error("JSON 解析失败", ex);
throw new DataException("数据格式错误", ex);
}
catch (IOException ex)
{
_logger.Error("IO 错误", ex);
throw new DataException("读取数据失败", ex);
}
// ❌ 避免:捕获所有异常
try
{
var data = await LoadDataAsync();
}
catch (Exception ex) // 太宽泛
{
_logger.Error("加载失败", ex);
return null;
}
2. 不要吞掉异常
// ❌ 避免:吞掉异常
try
{
RiskyOperation();
}
catch (Exception ex)
{
// 什么都不做,异常被吞掉了
}
// ✅ 好的做法:记录并重新抛出或处理
try
{
RiskyOperation();
}
catch (Exception ex)
{
_logger.Error("操作失败", ex);
throw; // 重新抛出原始异常
}
3. 使用 finally 清理资源
// ✅ 好的做法
FileStream stream = null;
try
{
stream = File.OpenRead(path);
// 处理文件
}
catch (IOException ex)
{
_logger.Error("文件读取失败", ex);
throw;
}
finally
{
stream?.Dispose(); // 确保资源被释放
}
// 更好的做法:使用 using 语句
using (var stream = File.OpenRead(path))
{
// 处理文件
} // 自动释放资源
日志记录
日志级别
GFramework 提供多个日志级别,根据错误严重程度选择合适的级别:
// Trace:详细的调试信息
_logger.Trace("进入 ProcessInput 方法");
// Debug:调试信息
_logger.Debug($"玩家位置: {player.Position}");
// Info:一般信息
_logger.Info("游戏启动成功");
// Warning:警告信息
_logger.Warn("配置文件不存在,使用默认配置");
// Error:错误信息
_logger.Error("保存游戏失败", exception);
// Fatal:致命错误
_logger.Fatal("无法初始化渲染系统", exception);
结构化日志
使用结构化日志记录上下文信息:
// 记录带上下文的日志
_logger.Log(
LogLevel.Error,
"玩家操作失败",
("playerId", playerId),
("action", "attack"),
("targetId", targetId),
("timestamp", DateTime.UtcNow)
);
// 记录带异常的结构化日志
_logger.Log(
LogLevel.Error,
"数据库操作失败",
exception,
("operation", "insert"),
("table", "players"),
("retryCount", retryCount)
);
日志最佳实践
1. 记录足够的上下文
// ❌ 避免:信息不足
_logger.Error("操作失败");
// ✅ 好的做法:包含上下文
_logger.Error(
$"保存玩家数据失败: PlayerId={playerId}, Reason={ex.Message}",
ex
);
2. 不要记录敏感信息
// ❌ 避免:记录密码等敏感信息
_logger.Info($"用户登录: {username}, 密码: {password}");
// ✅ 好的做法:不记录敏感信息
_logger.Info($"用户登录: {username}");
3. 使用合适的日志级别
public class SaveSystem : AbstractSystem
{
public async Task<Result<SaveData>> LoadSaveAsync(string saveId)
{
_logger.Debug($"开始加载存档: {saveId}");
try
{
var data = await _storage.LoadAsync<SaveData>(saveId);
if (data == null)
{
_logger.Warn($"存档不存在: {saveId}");
return Result<SaveData>.Failure("存档不存在");
}
_logger.Info($"存档加载成功: {saveId}");
return Result<SaveData>.Success(data);
}
catch (Exception ex)
{
_logger.Error($"加载存档失败: {saveId}", ex);
return Result<SaveData>.Failure(ex);
}
}
}
用户反馈
友好的错误提示
将技术错误转换为用户友好的消息:
public class ErrorMessageService
{
private readonly Dictionary<string, string> _errorMessages = new()
{
["NETWORK_ERROR"] = "网络连接失败,请检查网络设置",
["SAVE_ERROR"] = "保存失败,请确保有足够的磁盘空间",
["INVALID_INPUT"] = "输入无效,请检查后重试",
["PERMISSION_DENIED"] = "权限不足,无法执行此操作",
["RESOURCE_NOT_FOUND"] = "资源不存在",
["TIMEOUT"] = "操作超时,请稍后重试"
};
public string GetUserFriendlyMessage(string errorCode)
{
return _errorMessages.TryGetValue(errorCode, out var message)
? message
: "发生未知错误,请联系技术支持";
}
public string GetUserFriendlyMessage(Exception ex)
{
return ex switch
{
GameException gameEx => GetUserFriendlyMessage(gameEx.ErrorCode),
FileNotFoundException => "文件不存在",
UnauthorizedAccessException => "权限不足",
TimeoutException => "操作超时",
_ => "发生未知错误"
};
}
}
错误提示 UI
public class ErrorNotificationSystem : AbstractSystem
{
private readonly ErrorMessageService _messageService;
protected override void OnInit()
{
this.RegisterEvent<ErrorOccurredEvent>(OnErrorOccurred);
}
private void OnErrorOccurred(ErrorOccurredEvent e)
{
var userMessage = _messageService.GetUserFriendlyMessage(e.Exception);
// 根据错误严重程度显示不同的 UI
if (e.Exception is GameException gameEx)
{
switch (gameEx.ErrorCode)
{
case "NETWORK_ERROR":
ShowNetworkErrorDialog(userMessage);
break;
case "SAVE_ERROR":
ShowSaveErrorDialog(userMessage);
break;
default:
ShowGenericErrorToast(userMessage);
break;
}
}
else
{
ShowGenericErrorDialog(userMessage);
}
}
private void ShowNetworkErrorDialog(string message)
{
this.SendEvent(new ShowDialogEvent
{
Title = "网络错误",
Message = message,
Buttons = new[] { "重试", "取消" },
OnButtonClicked = buttonIndex =>
{
if (buttonIndex == 0) // 重试
{
this.SendEvent(new RetryNetworkOperationEvent());
}
}
});
}
private void ShowSaveErrorDialog(string message)
{
this.SendEvent(new ShowDialogEvent
{
Title = "保存失败",
Message = message,
Buttons = new[] { "确定" }
});
}
private void ShowGenericErrorToast(string message)
{
this.SendEvent(new ShowToastEvent
{
Message = message,
Duration = 3000
});
}
private void ShowGenericErrorDialog(string message)
{
this.SendEvent(new ShowDialogEvent
{
Title = "错误",
Message = message,
Buttons = new[] { "确定" }
});
}
}
错误恢复
重试机制
实现自动重试策略:
public class RetryPolicy
{
public int MaxRetries { get; set; } = 3;
public TimeSpan InitialDelay { get; set; } = TimeSpan.FromSeconds(1);
public double BackoffMultiplier { get; set; } = 2.0;
public async Task<Result<T>> ExecuteAsync<T>(
Func<Task<T>> operation,
Func<Exception, bool> shouldRetry = null)
{
var delay = InitialDelay;
Exception lastException = null;
for (int attempt = 0; attempt <= MaxRetries; attempt++)
{
try
{
var result = await operation();
return Result<T>.Success(result);
}
catch (Exception ex)
{
lastException = ex;
// 检查是否应该重试
if (shouldRetry != null && !shouldRetry(ex))
{
break;
}
// 最后一次尝试失败
if (attempt == MaxRetries)
{
break;
}
// 等待后重试
await Task.Delay(delay);
delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * BackoffMultiplier);
}
}
return Result<T>.Failure(lastException);
}
}
// 使用示例
public class NetworkSystem : AbstractSystem
{
private readonly RetryPolicy _retryPolicy = new()
{
MaxRetries = 3,
InitialDelay = TimeSpan.FromSeconds(1),
BackoffMultiplier = 2.0
};
public async Task<Result<PlayerData>> FetchPlayerDataAsync(string playerId)
{
return await _retryPolicy.ExecuteAsync(
operation: async () =>
{
var response = await _httpClient.GetAsync($"/api/players/{playerId}");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<PlayerData>(json);
},
shouldRetry: ex => ex is HttpRequestException || ex is TimeoutException
);
}
}
降级策略
提供多级降级方案:
public class ConfigurationSystem : AbstractSystem
{
public async Task<GameConfig> LoadConfigAsync()
{
// 1. 尝试从远程服务器加载
var remoteResult = await TryLoadFromRemoteAsync();
if (remoteResult.IsSuccess)
{
_logger.Info("从远程服务器加载配置成功");
await CacheConfigAsync(remoteResult.Match(succ: c => c, fail: _ => null));
return remoteResult.Match(succ: c => c, fail: _ => null);
}
_logger.Warn($"远程加载失败: {remoteResult.Exception.Message}");
// 2. 尝试从本地缓存加载
var cacheResult = await TryLoadFromCacheAsync();
if (cacheResult.IsSuccess)
{
_logger.Info("从本地缓存加载配置成功");
return cacheResult.Match(succ: c => c, fail: _ => null);
}
_logger.Warn($"缓存加载失败: {cacheResult.Exception.Message}");
// 3. 使用内置默认配置
_logger.Error("所有配置源加载失败,使用默认配置");
return GetDefaultConfig();
}
private async Task<Result<GameConfig>> TryLoadFromRemoteAsync()
{
try
{
var response = await _httpClient.GetAsync("/api/config");
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
var config = JsonSerializer.Deserialize<GameConfig>(json);
return Result<GameConfig>.Success(config);
}
catch (Exception ex)
{
return Result<GameConfig>.Failure(ex);
}
}
private async Task<Result<GameConfig>> TryLoadFromCacheAsync()
{
try
{
var json = await File.ReadAllTextAsync(_cachePath);
var config = JsonSerializer.Deserialize<GameConfig>(json);
return Result<GameConfig>.Success(config);
}
catch (Exception ex)
{
return Result<GameConfig>.Failure(ex);
}
}
private async Task CacheConfigAsync(GameConfig config)
{
try
{
var json = JsonSerializer.Serialize(config);
await File.WriteAllTextAsync(_cachePath, json);
}
catch (Exception ex)
{
_logger.Warn("缓存配置失败", ex);
}
}
private GameConfig GetDefaultConfig()
{
return new GameConfig
{
ServerUrl = "https://default.example.com",
Timeout = 30,
MaxRetries = 3
};
}
}
断路器模式
防止级联失败:
public class CircuitBreaker
{
private int _failureCount = 0;
private DateTime _lastFailureTime = DateTime.MinValue;
private CircuitState _state = CircuitState.Closed;
public int FailureThreshold { get; set; } = 5;
public TimeSpan OpenDuration { get; set; } = TimeSpan.FromMinutes(1);
public async Task<Result<T>> ExecuteAsync<T>(Func<Task<T>> operation)
{
// 检查断路器状态
if (_state == CircuitState.Open)
{
if (DateTime.UtcNow - _lastFailureTime > OpenDuration)
{
_state = CircuitState.HalfOpen;
}
else
{
return Result<T>.Failure("断路器已打开,操作被拒绝");
}
}
try
{
var result = await operation();
// 成功,重置计数器
if (_state == CircuitState.HalfOpen)
{
_state = CircuitState.Closed;
}
_failureCount = 0;
return Result<T>.Success(result);
}
catch (Exception ex)
{
_failureCount++;
_lastFailureTime = DateTime.UtcNow;
// 达到阈值,打开断路器
if (_failureCount >= FailureThreshold)
{
_state = CircuitState.Open;
}
return Result<T>.Failure(ex);
}
}
private enum CircuitState
{
Closed, // 正常状态
Open, // 断路器打开,拒绝请求
HalfOpen // 半开状态,尝试恢复
}
}