docs: update architecture

This commit is contained in:
2025-10-16 15:08:42 +08:00
parent 6b4c227d3c
commit 6c17e02731
15 changed files with 4889 additions and 18 deletions

View File

@@ -0,0 +1,428 @@
# Error Handling
本节定义控件库的错误处理策略,确保异常情况下的稳定性和良好的开发者体验。
---
### Error Handling Principles
**核心原则:**
1. **Fail Fast快速失败**:公开 API 在参数无效时立即抛出异常,不延迟到执行阶段
2. **Clear Error Messages清晰错误信息**:异常消息应明确指出问题和解决方案
3. **No Silent Failures不吞没异常**:禁止空 catch 块,必须记录或重新抛出异常
4. **Defensive Programming防御性编程**:内部方法使用 `Debug.Assert` 验证前置条件
---
### Exception Handling Patterns
**公开 API 参数验证:**
```csharp
namespace Penguin.AvaloniaUI.Themes;
public static class ThemeManager
{
/// <summary>
/// 应用指定的主题类型
/// </summary>
/// <param name="type">主题类型</param>
/// <exception cref="ArgumentException">当主题类型无效时抛出</exception>
public static void ApplyTheme(ThemeType type)
{
// 参数验证
if (!Enum.IsDefined(typeof(ThemeType), type))
{
throw new ArgumentException(
$"Invalid theme type: {type}. Valid values are: {string.Join(", ", Enum.GetNames(typeof(ThemeType)))}",
nameof(type));
}
// 核心逻辑
try
{
LoadThemeResources(type);
_currentTheme = type;
ThemeChanged?.Invoke(null, type);
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to apply theme '{type}'. Ensure theme resources are available.",
ex);
}
}
}
```
---
**PropertyGrid 错误处理:**
```csharp
namespace Penguin.AvaloniaUI.Controls.PropertyGrid;
public class PropertyGrid : TemplatedControl
{
public static readonly StyledProperty<object?> SelectedObjectProperty =
AvaloniaProperty.Register<PropertyGrid, object?>(nameof(SelectedObject));
public object? SelectedObject
{
get => GetValue(SelectedObjectProperty);
set => SetValue(SelectedObjectProperty, value);
}
private void RefreshProperties(object? obj)
{
Properties.Clear();
if (obj == null)
{
// 空对象是合法的,清空属性列表即可
return;
}
try
{
var properties = obj.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.CanRead);
foreach (var prop in properties)
{
try
{
var item = CreatePropertyItem(obj, prop);
Properties.Add(item);
}
catch (Exception ex)
{
// 单个属性解析失败不应影响其他属性
Debug.WriteLine($"[PropertyGrid] Failed to create property item for '{prop.Name}': {ex.Message}");
// 可选:添加错误占位符
Properties.Add(new PropertyItem
{
Name = prop.Name,
DisplayName = $"{prop.Name} (Error)",
IsReadOnly = true,
Value = $"Error: {ex.Message}"
});
}
}
}
catch (Exception ex)
{
throw new InvalidOperationException(
$"Failed to parse properties for object of type '{obj.GetType().Name}'.",
ex);
}
}
private PropertyItem CreatePropertyItem(object obj, PropertyInfo prop)
{
Debug.Assert(obj != null, "Object should not be null");
Debug.Assert(prop != null, "PropertyInfo should not be null");
try
{
var value = prop.GetValue(obj);
return new PropertyItem
{
Name = prop.Name,
DisplayName = GetDisplayName(prop),
Value = value,
PropertyType = prop.PropertyType,
IsReadOnly = !prop.CanWrite,
Category = GetCategory(prop),
PropertyInfo = prop
};
}
catch (TargetInvocationException ex)
{
throw new InvalidOperationException(
$"Property '{prop.Name}' getter threw an exception.",
ex.InnerException ?? ex);
}
}
}
```
---
**UserGuide 错误处理:**
```csharp
namespace Penguin.AvaloniaUI.Controls.UserGuide;
public class UserGuide : ContentControl
{
private void ShowCurrentStep()
{
if (CurrentStepIndex < 0 || CurrentStepIndex >= Steps.Count)
{
Debug.WriteLine($"[UserGuide] Invalid step index: {CurrentStepIndex}");
return;
}
var step = Steps[CurrentStepIndex];
// 验证目标控件是否可用
if (step.TargetControl == null)
{
Debug.WriteLine($"[UserGuide] Step '{step.Title}' has no target control, showing full-screen overlay.");
// 显示全屏提示
ShowFullScreenGuide(step);
return;
}
// 检查目标控件是否在可视化树中
if (!step.TargetControl.IsVisible || !IsControlInVisualTree(step.TargetControl))
{
Debug.WriteLine($"[UserGuide] Target control for step '{step.Title}' is not visible or not in visual tree. Skipping step.");
// 自动跳到下一步
if (CurrentStepIndex < Steps.Count - 1)
{
CurrentStepIndex++;
ShowCurrentStep();
}
else
{
Complete();
}
return;
}
// 正常显示引导
ShowGuideForControl(step);
}
private bool IsControlInVisualTree(Control control)
{
try
{
return control.GetVisualRoot() != null;
}
catch
{
return false;
}
}
}
```
---
### Logging Strategy
**使用 Debug.WriteLine 进行调试日志:**
```csharp
using System.Diagnostics;
namespace Penguin.AvaloniaUI.Utils;
internal static class Logger
{
[Conditional("DEBUG")]
public static void Debug(string message)
{
System.Diagnostics.Debug.WriteLine($"[Penguin.AvaloniaUI] {message}");
}
[Conditional("DEBUG")]
public static void Warning(string message)
{
System.Diagnostics.Debug.WriteLine($"[WARNING] {message}");
}
public static void Error(string message, Exception? ex = null)
{
System.Diagnostics.Debug.WriteLine($"[ERROR] {message}");
if (ex != null)
{
System.Diagnostics.Debug.WriteLine($"Exception: {ex}");
}
}
}
```
**使用示例:**
```csharp
private void RefreshProperties(object? obj)
{
Logger.Debug($"RefreshProperties called with object type: {obj?.GetType().Name ?? "null"}");
try
{
// 核心逻辑
}
catch (Exception ex)
{
Logger.Error("Failed to refresh properties", ex);
throw;
}
}
```
---
### Error Messages Guidelines
**好的错误消息示例:**
```csharp
// ❌ 不好的错误消息
throw new Exception("Error");
throw new ArgumentException("Invalid parameter");
// ✅ 好的错误消息
throw new ArgumentNullException(nameof(selectedObject),
"SelectedObject cannot be null. Please provide a valid object instance.");
throw new InvalidOperationException(
"Cannot apply theme: Application.Current is null. Ensure ThemeManager is called after App initialization.");
throw new NotSupportedException(
$"Property type '{propertyType.Name}' is not supported by PropertyGrid. " +
$"Supported types: string, int, double, bool, enum, DateTime.");
```
**错误消息模板:**
```
{What happened}: {Why it happened}. {How to fix it}.
```
---
### Exception Types
**使用合适的异常类型:**
| 场景 | 异常类型 | 示例 |
|------|---------|------|
| 参数为 null | `ArgumentNullException` | `throw new ArgumentNullException(nameof(obj))` |
| 参数值无效 | `ArgumentException` | `throw new ArgumentException("Invalid theme type", nameof(type))` |
| 参数超出范围 | `ArgumentOutOfRangeException` | `throw new ArgumentOutOfRangeException(nameof(index))` |
| 操作在当前状态无效 | `InvalidOperationException` | `throw new InvalidOperationException("Theme resources not loaded")` |
| 功能未实现 | `NotImplementedException` | `throw new NotImplementedException("Custom editors not supported in MVP")` |
| 功能不支持 | `NotSupportedException` | `throw new NotSupportedException($"Type {type} not supported")` |
---
### Try-Catch Guidelines
**何时使用 try-catch**
```csharp
// ✅ 捕获特定异常并提供上下文
try
{
var value = property.GetValue(obj);
}
catch (TargetInvocationException ex)
{
throw new InvalidOperationException(
$"Property '{property.Name}' getter threw an exception.",
ex.InnerException ?? ex);
}
// ✅ 记录异常并继续处理
try
{
LoadThemeResource(uri);
}
catch (Exception ex)
{
Logger.Error($"Failed to load theme resource: {uri}", ex);
// 使用默认主题
LoadDefaultTheme();
}
// ❌ 不要捕获所有异常并吞没
try
{
DoSomething();
}
catch
{
// 什么都不做 - 这是错误的!
}
// ❌ 不要捕获异常后重新抛出同一个异常
try
{
DoSomething();
}
catch (Exception ex)
{
throw ex; // 错误:会丢失调用栈
}
// ✅ 如果需要记录后重新抛出,使用 throw;
try
{
DoSomething();
}
catch (Exception ex)
{
Logger.Error("Operation failed", ex);
throw; // 正确:保留调用栈
}
```
---
### Validation Helpers
**创建参数验证辅助方法:**
```csharp
namespace Penguin.AvaloniaUI.Utils;
internal static class Guard
{
public static void NotNull<T>(T value, string paramName) where T : class
{
if (value == null)
{
throw new ArgumentNullException(paramName);
}
}
public static void NotNullOrEmpty(string value, string paramName)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException("Value cannot be null or empty.", paramName);
}
}
public static void InRange(int value, int min, int max, string paramName)
{
if (value < min || value > max)
{
throw new ArgumentOutOfRangeException(paramName,
$"Value must be between {min} and {max}, but was {value}.");
}
}
}
// 使用示例
public void SetProperty(string name, object value)
{
Guard.NotNullOrEmpty(name, nameof(name));
Guard.NotNull(value, nameof(value));
// 核心逻辑
}
```
---