From 8554f01423bce0e40646dc2242b6e368e105baf7 Mon Sep 17 00:00:00 2001 From: GeWuYou <95328647+GeWuYou@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:05:48 +0800 Subject: [PATCH] =?UTF-8?q?docs(guidelines):=20=E6=9B=B4=E6=96=B0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=A4=84=E7=90=86=E5=92=8C=E7=A7=BB=E5=8A=A8=E7=AB=AF?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9C=80=E4=BD=B3=E5=AE=9E=E8=B7=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加完整的错误处理最佳实践指南,涵盖Result和Option使用方式 - 补充移动端性能优化策略,包括纹理压缩、对象池、内存管理和电池优化 - 更新单元测试教程中的相关文档链接 - 完善错误处理层次结构和测试示例代码 - 增加移动端UI优化和平台适配最佳实践 --- docs/zh-CN/best-practices/error-handling.md | 382 ++++++++++++ .../best-practices/mobile-optimization.md | 546 ++++++++++++++++++ docs/zh-CN/tutorials/unit-testing.md | 4 +- 3 files changed, 930 insertions(+), 2 deletions(-) diff --git a/docs/zh-CN/best-practices/error-handling.md b/docs/zh-CN/best-practices/error-handling.md index b27bac0..2ec10bd 100644 --- a/docs/zh-CN/best-practices/error-handling.md +++ b/docs/zh-CN/best-practices/error-handling.md @@ -1301,3 +1301,385 @@ public class CircuitBreaker } ``` +## 最佳实践 + +### 1. 选择合适的错误处理方式 + +根据错误类型选择处理方式: + +| 错误类型 | 处理方式 | 示例 | +|-----------|-----------------|-------------| +| 可预期的业务错误 | Result<T> | 用户输入验证、权限检查 | +| 可能不存在的值 | Option<T> | 查找操作、配置读取 | +| 不可预期的系统错误 | Exception | 文件 IO、网络错误 | +| 不可恢复的错误 | Exception + 日志 | 初始化失败、资源耗尽 | + +### 2. 错误处理的层次结构 + +```csharp +// 底层:使用 Result 处理业务错误 +public class InventoryRepository +{ + public Result<Item> GetItem(string itemId) + { + var item = _items.FirstOrDefault(i => i.Id == itemId); + return item != null + ? Result<Item>.Success(item) + : Result<Item>.Failure("物品不存在"); + } +} + +// 中层:组合多个操作 +public class InventorySystem : AbstractSystem +{ + public Result<Item> UseItem(string itemId) + { + return _repository.GetItem(itemId) + .Bind(item => ValidateItemUsage(item)) + .Bind(item => ConsumeItem(item)) + .OnSuccess(item => this.SendEvent(new ItemUsedEvent { Item = item })) + .OnFailure(ex => _logger.Warn($"使用物品失败: {ex.Message}")); + } +} + +// 上层:处理用户交互 +public class InventoryController : IController +{ + public void OnUseItemButtonClicked(string itemId) + { + var result = _inventorySystem.UseItem(itemId); + + result.Match( + succ: item => ShowSuccessMessage($"使用了 {item.Name}"), + fail: ex => ShowErrorMessage(ex.Message) + ); + } +} +``` + +### 3. 避免过度使用异常 + +```csharp +// ❌ 避免:用异常处理正常流程 +public Player GetPlayerById(string playerId) +{ + var player = _players.FirstOrDefault(p => p.Id == playerId); + if (player == null) + throw new PlayerNotFoundException(playerId); // 不应该用异常 + + return player; +} + +// ✅ 好的做法:使用 Option +public Option<Player> GetPlayerById(string playerId) +{ + var player = _players.FirstOrDefault(p => p.Id == playerId); + return player != null + ? Option<Player>.Some(player) + : Option<Player>.None; +} +``` + +### 4. 提供有意义的错误消息 + +```csharp +// ❌ 避免:模糊的错误消息 +return Result<Item>.Failure("错误"); +return Result<Item>.Failure("操作失败"); + +// ✅ 好的做法:具体的错误消息 +return Result<Item>.Failure("物品不存在"); +return Result<Item>.Failure($"背包空间不足,需要 {required} 格,当前 {available} 格"); +return Result<Item>.Failure($"物品 {itemName} 不可交易"); +``` + +### 5. 记录错误上下文 + +```csharp +// ❌ 避免:缺少上下文 +_logger.Error("保存失败", ex); + +// ✅ 好的做法:包含完整上下文 +_logger.Log( + LogLevel.Error, + "保存玩家数据失败", + ex, + ("playerId", playerId), + ("saveSlot", saveSlot), + ("dataSize", data.Length), + ("timestamp", DateTime.UtcNow) +); +``` + +### 6. 优雅降级 + +```csharp +// ✅ 好的做法:提供降级方案 +public async Task<Texture> LoadTextureAsync(string path) +{ + // 1. 尝试加载指定纹理 + var result = await TryLoadTextureAsync(path); + if (result.IsSuccess) + return result.Match(succ: t => t, fail: _ => null); + + // 2. 尝试加载备用纹理 + _logger.Warn($"加载纹理失败: {path},使用备用纹理"); + var fallbackResult = await TryLoadTextureAsync(_fallbackTexturePath); + if (fallbackResult.IsSuccess) + return fallbackResult.Match(succ: t => t, fail: _ => null); + + // 3. 使用默认纹理 + _logger.Error("所有纹理加载失败,使用默认纹理"); + return _defaultTexture; +} +``` + +### 7. 测试错误处理 + +```csharp +[TestFixture] +public class InventorySystemTests +{ + [Test] + public void UseItem_ItemNotFound_ShouldReturnFailure() + { + // Arrange + var system = new InventorySystem(); + var invalidItemId = "invalid_item"; + + // Act + var result = system.UseItem(invalidItemId); + + // Assert + Assert.That(result.IsFaulted, Is.True); + Assert.That(result.Exception.Message, Contains.Substring("物品不存在")); + } + + [Test] + public void UseItem_ItemNotUsable_ShouldReturnFailure() + { + // Arrange + var system = new InventorySystem(); + var item = new Item { Id = "item1", IsUsable = false }; + system.AddItem(item); + + // Act + var result = system.UseItem("item1"); + + // Assert + Assert.That(result.IsFaulted, Is.True); + Assert.That(result.Exception.Message, Contains.Substring("不可使用")); + } + + [Test] + public void UseItem_ValidItem_ShouldReturnSuccess() + { + // Arrange + var system = new InventorySystem(); + var item = new Item { Id = "item1", IsUsable = true }; + system.AddItem(item); + + // Act + var result = system.UseItem("item1"); + + // Assert + Assert.That(result.IsSuccess, Is.True); + } +} +``` + +## 常见问题 + +### Q1: 何时使用 Result,何时使用 Option? + +**A:** + +- 使用 **Result<T>** 当操作可能失败,需要返回错误信息时 +- 使用 **Option<T>** 当值可能不存在,但不需要错误信息时 + +```csharp +// 使用 Result:需要知道为什么失败 +public Result<User> RegisterUser(string username, string password) +{ + if (string.IsNullOrEmpty(username)) + return Result<User>.Failure("用户名不能为空"); + + if (password.Length < 8) + return Result<User>.Failure("密码长度至少为 8 个字符"); + + // ... +} + +// 使用 Option:只需要知道是否存在 +public Option<User> FindUserById(string userId) +{ + var user = _users.FirstOrDefault(u => u.Id == userId); + return user != null ? Option<User>.Some(user) : Option<User>.None; +} +``` + +### Q2: 如何处理异步操作中的错误? + +**A:** 使用 Result 的异步扩展方法: + +```csharp +public async Task<Result<PlayerData>> LoadPlayerDataAsync(string playerId) +{ + return await ResultExtensions.TryAsync(async () => + { + var data = await _httpClient.GetStringAsync($"/api/players/{playerId}"); + return JsonSerializer.Deserialize<PlayerData>(data); + }); +} + +// 链式异步操作 +public async Task<Result<Player>> LoadAndValidatePlayerAsync(string playerId) +{ + var result = await LoadPlayerDataAsync(playerId); + return await result.BindAsync(async data => + { + var isValid = await ValidatePlayerDataAsync(data); + return isValid + ? Result<Player>.Success(CreatePlayer(data)) + : Result<Player>.Failure("玩家数据验证失败"); + }); +} +``` + +### Q3: 如何在 Command 和 Query 中处理错误? + +**A:** Command 和 Query 可以返回 Result: + +```csharp +// Command 返回 Result +public class SaveGameCommand : AbstractCommand<Result<SaveData>> +{ + protected override Result<SaveData> OnDo() + { + try + { + var data = CollectSaveData(); + var saveSystem = this.GetSystem<SaveSystem>(); + return saveSystem.SaveGame(data); + } + catch (Exception ex) + { + this.GetUtility<ILogger>().Error("保存游戏失败", ex); + return Result<SaveData>.Failure(ex); + } + } +} + +// Query 返回 Option +public class GetPlayerQuery : AbstractQuery<Option<Player>> +{ + public string PlayerId { get; set; } + + protected override Option<Player> OnDo() + { + var playerSystem = this.GetSystem<PlayerSystem>(); + return playerSystem.FindPlayerById(PlayerId); + } +} +``` + +### Q4: 如何处理多个可能失败的操作? + +**A:** 使用 Result 的链式操作: + +```csharp +public Result<Trade> ExecuteTrade(string sellerId, string buyerId, string itemId, int price) +{ + return ValidateSeller(sellerId) + .Bind(_ => ValidateBuyer(buyerId)) + .Bind(_ => ValidateItem(itemId)) + .Bind(_ => ValidatePrice(price)) + .Bind(_ => TransferItem(sellerId, buyerId, itemId)) + .Bind(_ => TransferCurrency(buyerId, sellerId, price)) + .Map(_ => CreateTradeRecord(sellerId, buyerId, itemId, price)); +} +``` + +### Q5: 如何避免 Result 嵌套过深? + +**A:** 使用 Bind 扁平化嵌套: + +```csharp +// ❌ 避免:嵌套过深 +public Result<string> ProcessData(string input) +{ + var result1 = Step1(input); + if (result1.IsSuccess) + { + var result2 = Step2(result1.Match(succ: v => v, fail: _ => "")); + if (result2.IsSuccess) + { + var result3 = Step3(result2.Match(succ: v => v, fail: _ => "")); + return result3; + } + return Result<string>.Failure(result2.Exception); + } + return Result<string>.Failure(result1.Exception); +} + +// ✅ 好的做法:使用 Bind 扁平化 +public Result<string> ProcessData(string input) +{ + return Step1(input) + .Bind(Step2) + .Bind(Step3); +} +``` + +### Q6: 如何在 UI 层处理错误? + +**A:** 将错误转换为用户友好的消息: + +```csharp +public class UIController : IController +{ + private readonly ErrorMessageService _errorMessageService; + + public void OnSaveButtonClicked() + { + var result = this.SendCommand(new SaveGameCommand()); + + result.Match( + succ: data => { + ShowSuccessToast("游戏保存成功"); + }, + fail: ex => { + var userMessage = _errorMessageService.GetUserFriendlyMessage(ex); + ShowErrorDialog("保存失败", userMessage); + } + ); + } +} +``` + +--- + +## 总结 + +良好的错误处理是构建健壮应用的基础。遵循本指南的最佳实践: + +- ✅ 使用 **Result<T>** 处理可预期的业务错误 +- ✅ 使用 **Option<T>** 处理可能不存在的值 +- ✅ 使用 **异常** 处理不可预期的系统错误 +- ✅ 记录详细的 **日志** 信息 +- ✅ 提供友好的 **用户反馈** +- ✅ 实现 **错误恢复** 和降级策略 +- ✅ 编写 **测试** 验证错误处理逻辑 + +通过这些实践,你将构建出更加稳定、可维护、用户友好的游戏应用。 + +--- + +**相关文档**: + +- [架构模式最佳实践](./architecture-patterns.md) +- [扩展方法使用指南](../core/extensions.md) +- [日志系统](../core/logging.md) + +**文档版本**: 1.0.0 +**更新日期**: 2026-03-07 diff --git a/docs/zh-CN/best-practices/mobile-optimization.md b/docs/zh-CN/best-practices/mobile-optimization.md index 452bbe1..c2795cf 100644 --- a/docs/zh-CN/best-practices/mobile-optimization.md +++ b/docs/zh-CN/best-practices/mobile-optimization.md @@ -382,3 +382,549 @@ public enum ResourcePriority ### 2. 纹理压缩和优化 +使用合适的纹理格式和压缩: + +```csharp +public class TextureOptimizer +{ + public static TextureSettings GetOptimalSettings(string platform) + { + return platform switch + { + "iOS" => new TextureSettings + { + Format = TextureFormat.PVRTC_RGB4, + MaxSize = 2048, + MipmapEnabled = true, + Compression = TextureCompression.High + }, + "Android" => new TextureSettings + { + Format = TextureFormat.ETC2_RGB, + MaxSize = 2048, + MipmapEnabled = true, + Compression = TextureCompression.High + }, + _ => new TextureSettings + { + Format = TextureFormat.RGB24, + MaxSize = 4096, + MipmapEnabled = true, + Compression = TextureCompression.Normal + } + }; + } +} +``` + +### 3. 对象池优化 + +针对移动平台优化对象池,限制池大小并定期清理: + +```csharp +public class MobileObjectPool<T> : AbstractObjectPoolSystem<string, T> + where T : IPoolableObject +{ + private const int MaxPoolSize = 50; + + public new void Release(string key, T obj) + { + if (Pools.TryGetValue(key, out var pool) && pool.Count >= MaxPoolSize) + { + obj.OnPoolDestroy(); + return; + } + base.Release(key, obj); + } + + protected override T Create(string key) + { + throw new NotImplementedException(); + } +} +``` + +### 4. 避免内存泄漏 + +确保正确释放资源和取消事件订阅: + +```csharp +public class LeakFreeSystem : AbstractSystem +{ + private IResourceHandle<Texture>? _textureHandle; + private IUnRegister? _eventUnregister; + + protected override void OnInit() + { + _eventUnregister = this.RegisterEvent<GameEvent>(OnGameEvent); + } + + protected override void OnDestroy() + { + // 释放资源句柄 + _textureHandle?.Dispose(); + _textureHandle = null; + + // 取消事件订阅 + _eventUnregister?.UnRegister(); + _eventUnregister = null; + + base.OnDestroy(); + } + + private void OnGameEvent(GameEvent e) + { + // 处理事件 + } +} +``` + +## 性能优化 + +### 1. CPU 优化 + +减少 CPU 计算负担: + +```csharp +public class CPUOptimizer : AbstractSystem +{ + private const int UpdateInterval = 5; // 每 5 帧更新一次 + private int _frameCount; + + protected override void OnInit() + { + this.RegisterEvent<GameUpdateEvent>(OnUpdate); + } + + private void OnUpdate(GameUpdateEvent e) + { + _frameCount++; + + // 降低更新频率 + if (_frameCount % UpdateInterval == 0) + { + UpdateNonCriticalSystems(); + } + + // 关键系统每帧更新 + UpdateCriticalSystems(); + } + + private void UpdateCriticalSystems() + { + // 玩家输入、物理等关键系统 + } + + private void UpdateNonCriticalSystems() + { + // AI、动画等非关键系统 + } +} +``` + +### 2. 批量处理 + +使用批量操作减少函数调用: + +```csharp +public class BatchProcessor : AbstractSystem +{ + private readonly List<Entity> _entitiesToUpdate = new(); + + public void ProcessEntities() + { + // 批量处理,减少函数调用开销 + for (int i = 0; i < _entitiesToUpdate.Count; i++) + { + var entity = _entitiesToUpdate[i]; + entity.Update(); + } + } +} +``` + +### 3. 缓存计算结果 + +避免重复计算: + +```csharp +public class CachedCalculator +{ + private readonly Dictionary<string, float> _cache = new(); + + public float GetDistance(Vector3 a, Vector3 b) + { + var key = $"{a}_{b}"; + + if (_cache.TryGetValue(key, out var distance)) + { + return distance; + } + + distance = Vector3.Distance(a, b); + _cache[key] = distance; + + return distance; + } + + public void ClearCache() + { + _cache.Clear(); + } +} +``` + +## 电池优化 + +### 1. 动态帧率调整 + +根据场景复杂度调整帧率: + +```csharp +public class DynamicFrameRateSystem : AbstractSystem +{ + private int _targetFrameRate = 60; + + public void AdjustFrameRate(SceneComplexity complexity) + { + _targetFrameRate = complexity switch + { + SceneComplexity.Low => 60, + SceneComplexity.Medium => 45, + SceneComplexity.High => 30, + _ => 60 + }; + + Application.targetFrameRate = _targetFrameRate; + } +} + +public enum SceneComplexity +{ + Low, + Medium, + High +} +``` + +### 2. 后台优化 + +应用进入后台时降低性能消耗: + +```csharp +public class BackgroundOptimizer : AbstractSystem +{ + protected override void OnInit() + { + Application.focusChanged += OnFocusChanged; + } + + private void OnFocusChanged(bool hasFocus) + { + if (hasFocus) + { + OnApplicationForeground(); + } + else + { + OnApplicationBackground(); + } + } + + private void OnApplicationBackground() + { + // 降低帧率 + Application.targetFrameRate = 10; + + // 暂停音频 + AudioListener.pause = true; + + // 暂停非关键系统 + PauseNonCriticalSystems(); + } + + private void OnApplicationForeground() + { + // 恢复帧率 + Application.targetFrameRate = 60; + + // 恢复音频 + AudioListener.pause = false; + + // 恢复系统 + ResumeNonCriticalSystems(); + } + + private void PauseNonCriticalSystems() + { + SendEvent(new PauseNonCriticalSystemsEvent()); + } + + private void ResumeNonCriticalSystems() + { + SendEvent(new ResumeNonCriticalSystemsEvent()); + } + + protected override void OnDestroy() + { + Application.focusChanged -= OnFocusChanged; + base.OnDestroy(); + } +} +``` + +## UI 优化 + +### 1. 触摸优化 + +优化触摸输入处理: + +```csharp +public class TouchInputSystem : AbstractSystem +{ + private const float TouchThreshold = 10f; // 最小移动距离 + private Vector2 _lastTouchPosition; + + protected override void OnInit() + { + this.RegisterEvent<TouchEvent>(OnTouch); + } + + private void OnTouch(TouchEvent e) + { + switch (e.Phase) + { + case TouchPhase.Began: + _lastTouchPosition = e.Position; + break; + + case TouchPhase.Moved: + var delta = e.Position - _lastTouchPosition; + if (delta.magnitude > TouchThreshold) + { + ProcessTouchMove(delta); + _lastTouchPosition = e.Position; + } + break; + + case TouchPhase.Ended: + ProcessTouchEnd(e.Position); + break; + } + } + + private void ProcessTouchMove(Vector2 delta) + { + SendEvent(new TouchMoveEvent { Delta = delta }); + } + + private void ProcessTouchEnd(Vector2 position) + { + SendEvent(new TouchEndEvent { Position = position }); + } +} +``` + +### 2. UI 元素池化 + +复用 UI 元素减少创建开销: + +```csharp +public class UIElementPool : AbstractObjectPoolSystem<string, UIElement> +{ + protected override UIElement Create(string key) + { + return new UIElement(key); + } + + public UIElement GetButton() + { + return Acquire("button"); + } + + public void ReturnButton(UIElement button) + { + Release("button", button); + } +} + +public class UIElement : IPoolableObject +{ + public string Type { get; } + + public UIElement(string type) + { + Type = type; + } + + public void OnAcquire() + { + // 激活 UI 元素 + } + + public void OnRelease() + { + // 重置状态 + } + + public void OnPoolDestroy() + { + // 清理资源 + } +} +``` + +## 平台适配 + +### iOS 优化 + +```csharp +public class iOSOptimizer +{ + public static void ApplyOptimizations() + { + // 使用 Metal 渲染 + SystemInfo.graphicsDeviceType = GraphicsDeviceType.Metal; + + // 优化纹理格式 + QualitySettings.masterTextureLimit = 0; + + // 启用多线程渲染 + PlayerSettings.MTRendering = true; + } +} +``` + +### Android 优化 + +```csharp +public class AndroidOptimizer +{ + public static void ApplyOptimizations() + { + // 使用 Vulkan 或 OpenGL ES 3 + SystemInfo.graphicsDeviceType = GraphicsDeviceType.Vulkan; + + // 优化纹理格式 + QualitySettings.masterTextureLimit = 0; + + // 启用多线程渲染 + PlayerSettings.MTRendering = true; + + // 优化 GC + GarbageCollector.GCMode = GarbageCollector.Mode.Disabled; + } +} +``` + +## 最佳实践 + +### 1. 资源管理 + +- 使用资源优先级系统 +- 及时卸载不用的资源 +- 使用资源压缩 +- 实现资源预加载 + +### 2. 内存管理 + +- 监控内存使用 +- 限制对象池大小 +- 避免内存泄漏 +- 定期执行 GC + +### 3. 性能优化 + +- 降低更新频率 +- 使用批量处理 +- 缓存计算结果 +- 优化算法复杂度 + +### 4. 电池优化 + +- 动态调整帧率 +- 后台降低性能 +- 减少渲染开销 +- 优化网络请求 + +### 5. UI 优化 + +- 优化触摸处理 +- 池化 UI 元素 +- 减少 UI 层级 +- 使用异步加载 + +## 常见问题 + +### 问题:如何监控移动设备的内存使用? + +**解答**: + +使用 `GC.GetTotalMemory()` 监控托管内存,结合平台 API 监控总内存: + +```csharp +var managedMemory = GC.GetTotalMemory(false); +Console.WriteLine($"托管内存: {managedMemory / 1024 / 1024}MB"); +``` + +### 问题:如何优化移动游戏的启动时间? + +**解答**: + +- 延迟加载非关键资源 +- 使用异步初始化 +- 减少启动时的计算 +- 优化资源包大小 + +### 问题:如何处理不同设备的性能差异? + +**解答**: + +实现设备性能分级系统: + +```csharp +public enum DevicePerformance +{ + Low, + Medium, + High +} + +public class DeviceProfiler +{ + public static DevicePerformance GetDevicePerformance() + { + var memory = SystemInfo.systemMemorySize; + var processorCount = SystemInfo.processorCount; + + if (memory < 2048 || processorCount < 4) + return DevicePerformance.Low; + else if (memory < 4096 || processorCount < 6) + return DevicePerformance.Medium; + else + return DevicePerformance.High; + } +} +``` + +### 问题:如何优化移动游戏的网络性能? + +**解答**: + +- 使用数据压缩 +- 批量发送请求 +- 实现请求队列 +- 处理网络中断 + +## 相关文档 + +- [资源管理系统](/zh-CN/core/resource) - 资源管理详细说明 +- [对象池系统](/zh-CN/core/pool) - 对象池优化 +- [协程系统](/zh-CN/core/coroutine) - 异步操作优化 +- [架构模式最佳实践](/zh-CN/best-practices/architecture-patterns) - 架构设计 + +--- + +**文档版本**: 1.0.0 +**更新日期**: 2026-03-07 + diff --git a/docs/zh-CN/tutorials/unit-testing.md b/docs/zh-CN/tutorials/unit-testing.md index 712819d..e2b9567 100644 --- a/docs/zh-CN/tutorials/unit-testing.md +++ b/docs/zh-CN/tutorials/unit-testing.md @@ -1143,5 +1143,5 @@ public void CalculateBonus_Should_Return_Double(int input, int expected) - [NUnit 官方文档](https://docs.nunit.org/) - [Moq 快速入门](https://github.com/moq/moq4/wiki/Quickstart) -- [测试驱动开发实践](/zh-CN/best-practices/tdd) -- [持续集成配置](/zh-CN/best-practices/ci-cd) +- [架构设计模式](/zh-CN/best-practices/architecture-patterns) +- [性能优化最佳实践](/zh-CN/best-practices/performance)