# Recipe 模块代码审阅报告

## 一、架构概览

配方模块采用 MVVM 架构，分为四层配方体系：Carrier → Substrate → Wafer → Process，形成级联关系。使用 Stylet 框架 + JSON 文件持久化。

```mermaid
graph TD
    A[CarrierRecipe 载具配方] --> B[SubstrateRecipe 基板配方]
    B --> C[WaferRecipe 芯片配方]
    C --> D[ProcessRecipe 工艺配方]
    
    E[RecipeManager] --> A
    E --> B
    E --> C
    E --> D
    
    F[RecipeWrapManager] --> G[RecipeWrap 列表]
    
    H[RecipeViewModel] --> I[CarrierRecipeViewModel]
    H --> J[SubstrateRecipeViewModel]
    H --> K[WaferRecipeViewModel]
    H --> L[ProcessRecipeViewModel]
```

---

## 二、发现的问题

### 问题 1：ViewModel 层存在严重的代码重复（高优先级）

`CarrierRecipeViewModel`、`SubstrateRecipeViewModel`、`WaferRecipeViewModel`、`ProcessRecipeViewModel` 四个类中，以下逻辑几乎完全相同：

- `SelectedRecipeWrap` 属性的 setter 逻辑（离开检查 + 加载配方 + 快照）
- `RegisterButtonGroupEvents()` 方法（18 行事件注册代码完全一致）
- `TryLeaveCurrentRecipeContext()` 方法
- `SaveCurrentRecipe()` / `CaptureSavedSnapshot()` / `IsRecipeDirty()` / `SelectRecipeWrapInternal()` 方法
- `OnCreateNewRecipe` / `OnDeleteRecipe` / `OnOpenRecipe` / `OnSaveRecipe` / `OnClearRecipe` 等事件处理

**改进建议**：提取一个泛型基类 `RecipeViewModelBase<TRecipe> where TRecipe : RecipeBase`，将公共逻辑下沉，子类只需覆写差异部分（如加载配方的具体类型、级联逻辑等）。

---

### 问题 2：RecipeButtonGroupViewModel 在 ViewModel 中直接创建 View（中优先级）

`RecipeButtonGroupViewModel.ShowRecipeWindow<TWindow, TViewModel>()` 方法直接 `new TWindow()`，违反了 MVVM 的核心原则——ViewModel 不应直接创建和管理 View。

**改进建议**：使用 Stylet 的 `IWindowManager.ShowDialog()` 来显示对话框窗口，或引入一个 `IDialogService` 接口来解耦。

---

### 问题 3：RecipeNameWindowModel 与 RecipeReNameWindowModel 代码重复（中优先级）

这两个类的验证逻辑、`OkCommand`/`CancelCommand`、`UpdateValidationMessage()` 方法几乎完全一致，`RecipeReNameWindowModel` 仅多了一个 `OldRecipeName` 属性。

**改进建议**：让 `RecipeReNameWindowModel` 继承 `RecipeNameWindowModel`，或提取公共基类。

---

### 问题 4：RecipeBase 同时实现 INotifyPropertyChanged 和继承 JsonFileWritableBase（中优先级）

`RecipeBase` 手动实现了 `INotifyPropertyChanged`（包含 `SetProperty<T>` 辅助方法），但其子类的子属性模型（如 `SubstrateInfo`、`MarkData` 等）继承的是 Stylet 的 `PropertyChangedBase`，使用 `SetAndNotify`。这导致项目中存在两套属性变更通知机制。

**改进建议**：统一通知机制。如果 `JsonFileWritableBase` 不能继承 `PropertyChangedBase`，考虑用组合代替继承，或让 `RecipeBase` 也使用 Stylet 的通知基础设施。

---

### 问题 5：CameraBaseViewModel 放在 Models 目录下（低优先级）

`CameraBaseViewModel` 是一个 ViewModel 基类，但放在了 `Recipe/Models/` 目录下，违反了项目的分层约定。

**改进建议**：移动到 `Recipe/ViewModel/` 目录。

---

### 问题 6：命名拼写错误（低优先级）

- `SubtrateMarkPars` → 应为 `SubstrateMarkPars`（缺少 `s`）
- `SubtrateMarkParameterInfo` → 应为 `SubstrateMarkParameterInfo`
- `ThickNess` → 应为 `Thickness`（大小写不一致）
- `PIDOperater` → 应为 `PIDOperator`

**改进建议**：统一修正拼写，使用 IDE 的重命名功能确保所有引用同步更新。

---

### 问题 7：PIDOperater 使用反射遍历属性，且通过 IoC.Get 静态调用获取硬件（中优先级）

`PIDOperater.SendPIDParameters()` 和 `ReadPIDParametersFromDevice()` 使用反射遍历所有属性来读写 PID 参数，性能不佳且不易维护。同时 `GetControler()` 通过 `IoC.Get<HardwareManager>()` 静态调用获取依赖，违反了依赖注入原则。

**改进建议**：
- 将 `IMotionController` 或 `HardwareManager` 通过构造函数注入
- 考虑用显式的参数列表或字典代替反射遍历

---

### 问题 8：RecipeManager 的删除操作缺少异常处理（中优先级）

`DeleteCarrierRecipe` / `DeleteSubstrateRecipe` / `DeleteWaferRecipe` / `DeleteProcessRecipe` 直接调用 `Directory.Delete(path, true)` 而没有任何异常处理。如果目录不存在或被占用，会直接抛出异常。

**改进建议**：添加 try-catch 和存在性检查，返回操作结果或错误信息。

---

### 问题 9：OnCopyRecipe / OnImportRecipe / OnExportRecipe 为空实现（低优先级）

多个 ViewModel 中的 `OnCopyRecipe`、`OnImportRecipe`、`OnExportRecipe` 方法体为空，功能未实现。

**改进建议**：要么实现这些功能，要么在 UI 上禁用对应按钮（通过 `CanExecute` 返回 false），避免用户点击后无反应。

---

### 问题 10：CarrierRecipeViewModel.OnViewLoaded 中直接访问 RecipeWraps[0] 可能越界（高优先级）

```csharp
// CarrierRecipeViewModel.cs:248
RecipeWraps[0].IsInUse = true;
```

如果 `RecipeWraps` 为空集合，这行代码会抛出 `ArgumentOutOfRangeException`。

**改进建议**：添加空集合检查。

---

### 问题 11：SubstrateRecipeViewModel.OnViewLoaded 中 SelectedRecipeWrap 可能为 null（高优先级）

```csharp
// SubstrateRecipeViewModel.cs:498-499
SelectedRecipeWrap = RecipeWraps.Where(...).FirstOrDefault();
SelectedRecipeWrap.IsInUse = true;  // 可能 NullReferenceException
```

`FirstOrDefault()` 可能返回 null，直接访问 `.IsInUse` 会崩溃。

**改进建议**：添加 null 检查。

---

### 问题 12：RecipeWrapManager.Handle 方法中复用同一个 CarrierRecipe 实例（中优先级）

```csharp
// RecipeWrapManager.cs:74
CarrierRecipe carrierRecipe = new CarrierRecipe();
foreach (var carrierWrap in CarrierRecipeWraps)
{
    carrierRecipe.RecipeName = carrierWrap.RecipeName;
    carrierRecipe.Read();
    ...
}
```

在循环中复用同一个 `CarrierRecipe` 实例，每次 `Read()` 会覆盖上一次的数据。虽然功能上可能没问题，但语义不清晰，且如果 `Read()` 失败可能导致残留旧数据。

**改进建议**：每次循环创建新实例。

---

### 问题 13：MarkTeachViewModel 直接持有 Window 引用（中优先级）

```csharp
private MarkCoordinatePreviewWindow _coordinatePreviewWindow;
```

ViewModel 直接持有 `Window` 实例引用，违反 MVVM 原则。

**改进建议**：使用 `IWindowManager` 管理窗口生命周期，或通过 Messenger/EventAggregator 通知 View 层打开窗口。

---

### 问题 14：事件参数类散落在不同位置（低优先级）

- `RecipeEventArgs` / `RecipeRenameEventArgs` 在 `Recipe/Models/RecipeEventArgs.cs` 中
- `SubstrateNameChangedEventArgs` 等在 `EventArgsFolder/RecipeNameChangedEventArgs.cs` 中，且命名空间为根命名空间 `MainShell`

**改进建议**：统一事件参数的命名空间和存放位置，建议放在 `Recipe/Models/` 或专门的 `Recipe/Events/` 目录下。

---

### 问题 15：SubstrateHeightMeasureSetting 和 SubstrateHeightMeasurePoint 未实现 IParameterItem（低优先级）

其他模型类（如 `SubstrateInfo`、`CarrierInfo`）都实现了 `IParameterItem` 接口以支持 `Clone()`，但 `SubstrateHeightMeasureSetting` 和 `SubstrateHeightMeasurePoint` 没有实现，可能导致序列化/克隆时行为不一致。

**改进建议**：统一实现 `IParameterItem` 接口。

---

### 问题 16：ActiveRecipeService 未被使用（低优先级）

`IActiveRecipeService<T>` 和 `ActiveRecipeService<T>` 定义了但在 Recipe 模块中未见使用。

**改进建议**：确认是否为未来预留，如果不需要则移除以减少代码噪音。

---

## 三、改进优先级汇总

| 优先级 | 问题编号 | 简述 |
|--------|---------|------|
| 高 | 1 | 四个 RecipeViewModel 严重代码重复，提取泛型基类 |
| 高 | 10 | CarrierRecipeViewModel.OnViewLoaded 数组越界风险 |
| 高 | 11 | SubstrateRecipeViewModel.OnViewLoaded 空引用风险 |
| 中 | 2 | ButtonGroupViewModel 直接创建 View |
| 中 | 3 | 两个命名窗口 Model 代码重复 |
| 中 | 4 | 两套属性变更通知机制并存 |
| 中 | 7 | PIDOperater 反射 + 静态 IoC 调用 |
| 中 | 8 | 删除操作缺少异常处理 |
| 中 | 12 | RecipeWrapManager 循环中复用实例 |
| 中 | 13 | MarkTeachViewModel 持有 Window 引用 |
| 低 | 5 | CameraBaseViewModel 放错目录 |
| 低 | 6 | 命名拼写错误 |
| 低 | 9 | 空实现的操作方法 |
| 低 | 14 | 事件参数类位置分散 |
| 低 | 15 | 部分模型未实现 IParameterItem |
| 低 | 16 | ActiveRecipeService 未使用 |
