docs(guidelines): 更新错误处理和移动端优化最佳实践

- 添加完整的错误处理最佳实践指南,涵盖Result<T>和Option<T>使用方式
- 补充移动端性能优化策略,包括纹理压缩、对象池、内存管理和电池优化
- 更新单元测试教程中的相关文档链接
- 完善错误处理层次结构和测试示例代码
- 增加移动端UI优化和平台适配最佳实践
This commit is contained in:
GeWuYou 2026-03-07 22:05:48 +08:00
parent 519e3a480b
commit 8554f01423
3 changed files with 930 additions and 2 deletions

View File

@ -1301,3 +1301,385 @@ public class CircuitBreaker
} }
``` ```
## 最佳实践
### 1. 选择合适的错误处理方式
根据错误类型选择处理方式:
| 错误类型 | 处理方式 | 示例 |
|-----------|-----------------|-------------|
| 可预期的业务错误 | Result&lt;T&gt; | 用户输入验证、权限检查 |
| 可能不存在的值 | Option&lt;T&gt; | 查找操作、配置读取 |
| 不可预期的系统错误 | Exception | 文件 IO、网络错误 |
| 不可恢复的错误 | Exception + 日志 | 初始化失败、资源耗尽 |
### 2. 错误处理的层次结构
```csharp
// 底层:使用 Result 处理业务错误
public class InventoryRepository
{
public Result&lt;Item&gt; GetItem(string itemId)
{
var item = _items.FirstOrDefault(i => i.Id == itemId);
return item != null
? Result&lt;Item&gt;.Success(item)
: Result&lt;Item&gt;.Failure("物品不存在");
}
}
// 中层:组合多个操作
public class InventorySystem : AbstractSystem
{
public Result&lt;Item&gt; 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&lt;Player&gt; GetPlayerById(string playerId)
{
var player = _players.FirstOrDefault(p => p.Id == playerId);
return player != null
? Option&lt;Player&gt;.Some(player)
: Option&lt;Player&gt;.None;
}
```
### 4. 提供有意义的错误消息
```csharp
// ❌ 避免:模糊的错误消息
return Result&lt;Item&gt;.Failure("错误");
return Result&lt;Item&gt;.Failure("操作失败");
// ✅ 好的做法:具体的错误消息
return Result&lt;Item&gt;.Failure("物品不存在");
return Result&lt;Item&gt;.Failure($"背包空间不足,需要 {required} 格,当前 {available} 格");
return Result&lt;Item&gt;.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&lt;Texture&gt; 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&lt;T&gt;** 当操作可能失败,需要返回错误信息时
- 使用 **Option&lt;T&gt;** 当值可能不存在,但不需要错误信息时
```csharp
// 使用 Result需要知道为什么失败
public Result&lt;User&gt; RegisterUser(string username, string password)
{
if (string.IsNullOrEmpty(username))
return Result&lt;User&gt;.Failure("用户名不能为空");
if (password.Length &lt; 8)
return Result&lt;User&gt;.Failure("密码长度至少为 8 个字符");
// ...
}
// 使用 Option只需要知道是否存在
public Option&lt;User&gt; FindUserById(string userId)
{
var user = _users.FirstOrDefault(u => u.Id == userId);
return user != null ? Option&lt;User&gt;.Some(user) : Option&lt;User&gt;.None;
}
```
### Q2: 如何处理异步操作中的错误?
**A:** 使用 Result 的异步扩展方法:
```csharp
public async Task&lt;Result&lt;PlayerData&gt;&gt; LoadPlayerDataAsync(string playerId)
{
return await ResultExtensions.TryAsync(async () =>
{
var data = await _httpClient.GetStringAsync($"/api/players/{playerId}");
return JsonSerializer.Deserialize&lt;PlayerData&gt;(data);
});
}
// 链式异步操作
public async Task&lt;Result&lt;Player&gt;&gt; LoadAndValidatePlayerAsync(string playerId)
{
var result = await LoadPlayerDataAsync(playerId);
return await result.BindAsync(async data =>
{
var isValid = await ValidatePlayerDataAsync(data);
return isValid
? Result&lt;Player&gt;.Success(CreatePlayer(data))
: Result&lt;Player&gt;.Failure("玩家数据验证失败");
});
}
```
### Q3: 如何在 Command 和 Query 中处理错误?
**A:** Command 和 Query 可以返回 Result
```csharp
// Command 返回 Result
public class SaveGameCommand : AbstractCommand&lt;Result&lt;SaveData&gt;&gt;
{
protected override Result&lt;SaveData&gt; OnDo()
{
try
{
var data = CollectSaveData();
var saveSystem = this.GetSystem&lt;SaveSystem&gt;();
return saveSystem.SaveGame(data);
}
catch (Exception ex)
{
this.GetUtility&lt;ILogger&gt;().Error("保存游戏失败", ex);
return Result&lt;SaveData&gt;.Failure(ex);
}
}
}
// Query 返回 Option
public class GetPlayerQuery : AbstractQuery&lt;Option&lt;Player&gt;&gt;
{
public string PlayerId { get; set; }
protected override Option&lt;Player&gt; OnDo()
{
var playerSystem = this.GetSystem&lt;PlayerSystem&gt;();
return playerSystem.FindPlayerById(PlayerId);
}
}
```
### Q4: 如何处理多个可能失败的操作?
**A:** 使用 Result 的链式操作:
```csharp
public Result&lt;Trade&gt; 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&lt;string&gt; 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&lt;string&gt;.Failure(result2.Exception);
}
return Result&lt;string&gt;.Failure(result1.Exception);
}
// ✅ 好的做法:使用 Bind 扁平化
public Result&lt;string&gt; 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&lt;T&gt;** 处理可预期的业务错误
- ✅ 使用 **Option&lt;T&gt;** 处理可能不存在的值
- ✅ 使用 **异常** 处理不可预期的系统错误
- ✅ 记录详细的 **日志** 信息
- ✅ 提供友好的 **用户反馈**
- ✅ 实现 **错误恢复** 和降级策略
- ✅ 编写 **测试** 验证错误处理逻辑
通过这些实践,你将构建出更加稳定、可维护、用户友好的游戏应用。
---
**相关文档**
- [架构模式最佳实践](./architecture-patterns.md)
- [扩展方法使用指南](../core/extensions.md)
- [日志系统](../core/logging.md)
**文档版本**: 1.0.0
**更新日期**: 2026-03-07

View File

@ -382,3 +382,549 @@ public enum ResourcePriority
### 2. 纹理压缩和优化 ### 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&lt;T&gt; : AbstractObjectPoolSystem&lt;string, T&gt;
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&lt;Texture&gt;? _textureHandle;
private IUnRegister? _eventUnregister;
protected override void OnInit()
{
_eventUnregister = this.RegisterEvent&lt;GameEvent&gt;(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&lt;GameUpdateEvent&gt;(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&lt;Entity&gt; _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&lt;string, float&gt; _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&lt;TouchEvent&gt;(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&lt;string, UIElement&gt;
{
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

View File

@ -1143,5 +1143,5 @@ public void CalculateBonus_Should_Return_Double(int input, int expected)
- [NUnit 官方文档](https://docs.nunit.org/) - [NUnit 官方文档](https://docs.nunit.org/)
- [Moq 快速入门](https://github.com/moq/moq4/wiki/Quickstart) - [Moq 快速入门](https://github.com/moq/moq4/wiki/Quickstart)
- [测试驱动开发实践](/zh-CN/best-practices/tdd) - [架构设计模式](/zh-CN/best-practices/architecture-patterns)
- [持续集成配置](/zh-CN/best-practices/ci-cd) - [性能优化最佳实践](/zh-CN/best-practices/performance)