# 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 { /// /// 应用指定的主题类型 /// /// 主题类型 /// 当主题类型无效时抛出 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 SelectedObjectProperty = AvaloniaProperty.Register(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 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)); // 核心逻辑 } ``` ---