free-code-dotnet/docs/架构设计评估与改进建议.md
应文浩wenhao.ying@xiaobao100.com bce2612b64 feat: 完善具体实现
2026-04-06 15:25:34 +08:00

721 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 架构设计评估与改进建议
## 一、架构设计不足
### 1.1 模块划分过细,增加复杂度
**问题**: 当前 16 个项目模块划分过于细碎
```
当前结构 (16个项目):
FreeCode/ # 主入口
FreeCode.Core/ # 接口+模型
FreeCode.Engine/ # QueryEngine
FreeCode.State/ # 状态管理
FreeCode.Features/ # 特性开关
FreeCode.ApiProviders/ # API提供商
FreeCode.Tools/ # 工具
FreeCode.Commands/ # 命令
FreeCode.Services/ # 业务服务
FreeCode.Mcp/ # MCP
FreeCode.Lsp/ # LSP
FreeCode.Bridge/ # 桥接
FreeCode.Skills/ # 技能
FreeCode.Plugins/ # 插件
FreeCode.Tasks/ # 任务
FreeCode.TerminalUI/ # 终端UI
```
**问题分析**:
- 过度拆分导致项目间依赖复杂
- 每个项目都需要单独的 ServiceCollectionExtensions
- 增加构建时间和维护成本
- 违反 YAGNI 原则
**建议合并方案**:
```
优化后结构 (8个项目):
FreeCode/ # 主入口 + CLI
FreeCode.Core/ # 接口 + 模型 + 枚举
FreeCode.Engine/ # QueryEngine + PromptBuilder + Tools + Commands
FreeCode.Integrations/ # MCP + LSP + Bridge + ApiProviders
FreeCode.Services/ # 所有业务服务 (Auth/Memory/Voice/...)
FreeCode.Extensions/ # Skills + Plugins + Tasks
FreeCode.UI/ # TerminalUI + Components
FreeCode.Tests/ # 所有测试
```
### 1.2 缺少领域驱动设计 (DDD) 分层
**问题**: 当前架构按技术划分,而非按业务领域
```
当前: 按技术划分
├── Tools/ (技术: 工具执行)
├── Commands/ (技术: 命令处理)
├── Services/ (技术: 服务层)
└── Mcp/ (技术: 协议)
```
```
原始 TypeScript: 按功能划分
├── tools/ (功能: Agent 能力)
├── commands/ (功能: 用户交互)
├── services/ (功能: 支撑服务)
│ ├── api/ (API 调用)
│ ├── mcp/ (MCP 协议)
│ ├── oauth/ (认证)
│ └── SessionMemory/ (记忆)
```
**建议**: 采用垂直切片架构
```csharp
// 建议: src/FreeCode.Features/Conversation/
public class ConversationFeature
{
// 对话相关的一切: QueryEngine + Messages + Tools + Commands
}
// src/FreeCode.Features/Integration/
public class IntegrationFeature
{
// 集成相关: MCP + LSP + Bridge
}
// src/FreeCode.Features/Configuration/
public class ConfigurationFeature
{
// 配置相关: Settings + Plugins + Skills
}
```
### 1.3 状态管理设计问题
**问题**: `AppState` 是一个巨大的 record违反单一职责
```csharp
// src/FreeCode.State/AppState.cs - 当前设计
public sealed record AppState
{
public SettingsJson Settings { get; init; } // 配置
public PermissionMode PermissionMode { get; init; } // 权限
public IReadOnlyDictionary<string, BackgroundTask> Tasks { get; init; } // 任务
public McpState Mcp { get; init; } // MCP
public PluginState Plugins { get; init; } // 插件
public RemoteConnectionStatus RemoteConnectionStatus { get; init; } // 远程
public Companion? Companion { get; init; } // 同伴
public NotificationState Notifications { get; init; } // 通知
// ... 30+ 属性
}
```
**问题分析**:
1. 任何状态变更都会触发整个 AppState 的重新创建
2. 订阅者无法只订阅感兴趣的状态切片
3. 测试困难,需要构造完整的 AppState
**建议**: 采用分片状态设计
```csharp
// 建议: 分片状态管理
public interface IStateSlice<T>
{
T Value { get; }
IObservable<T> Observe();
void Update(Func<T, T> updater);
}
// 分片
public class ConversationSlice : IStateSlice<ConversationState> { }
public class McpSlice : IStateSlice<McpState> { }
public class TaskSlice : IStateSlice<TaskState> { }
public class UiSlice : IStateSlice<UiState> { }
// 组合
public class AppStateManager
{
public ConversationSlice Conversation { get; }
public McpSlice Mcp { get; }
public TaskSlice Tasks { get; }
public UiSlice Ui { get; }
}
```
### 1.4 依赖注入设计问题
**问题**: 服务注册过于分散,缺少模块化
```csharp
// src/FreeCode/Program.cs - 当前设计
services.AddCoreServices();
services.AddFeatures();
services.AddState();
services.AddEngine();
services.AddFreeCodeApiProviders();
services.AddFreeCodeTools();
services.AddCommands();
services.AddServices();
services.AddMcp();
services.AddLsp();
services.AddTasks();
services.AddBridge();
services.AddSkills();
services.AddPlugins();
services.AddTerminalUI();
// 16 个扩展方法!
```
**问题分析**:
1. 注册顺序敏感,隐式依赖
2. 难以单独测试某个模块
3. 无法按需加载模块
**建议**: 采用模块化 DI 设计
```csharp
// 建议: 模块化设计
public interface IFreeCodeModule
{
string Name { get; }
void ConfigureServices(IServiceCollection services);
void Configure(IApplicationBuilder app);
}
public class CoreModule : IFreeCodeModule
{
public string Name => "Core";
public void ConfigureServices(IServiceCollection services)
{
// Core 相关的所有服务
}
}
public class McpModule : IFreeCodeModule
{
public string Name => "Mcp";
public void ConfigureServices(IServiceCollection services)
{
// MCP 相关的所有服务
}
}
// Program.cs
var modules = new IFreeCodeModule[]
{
new CoreModule(),
new EngineModule(),
new McpModule(),
// 按需添加
};
foreach (var module in modules)
module.ConfigureServices(services);
```
---
## 二、设计模式问题
### 2.1 工具系统缺少责任链模式
**问题**: 当前工具执行是直接 switch-case
```csharp
// src/FreeCode.Tools/ToolRegistry.cs:162-214
return tool.Name switch
{
"Agent" => await ExecuteAsync<AgentToolInput, AgentToolOutput>(...),
"Bash" => await ExecuteAsync<BashToolInput, BashToolOutput>(...),
"Read" => await ExecuteAsync<FileReadToolInput, string>(...),
// ... 48 个 case
_ => ($"Unsupported tool execution for {tool.Name}", true, false)
};
```
**建议**: 使用责任链 + 策略模式
```csharp
// 建议: 工具执行管道
public interface IToolExecutor
{
bool CanExecute(ITool tool);
Task<(string Output, bool IsAllowed, bool ShouldContinue)> ExecuteAsync(
ITool tool, JsonElement input, ToolExecutionContext context, CancellationToken ct);
}
public class ToolExecutorPipeline
{
private readonly IEnumerable<IToolExecutor> _executors;
public async Task<(string, bool, bool)> ExecuteAsync(...)
{
foreach (var executor in _executors)
{
if (executor.CanExecute(tool))
return await executor.ExecuteAsync(tool, input, context, ct);
}
throw new NotSupportedException($"No executor for tool: {tool.Name}");
}
}
```
### 2.2 缺少事件溯源
**问题**: 消息历史直接存储在内存列表
```csharp
// src/FreeCode.Engine/QueryEngine.cs
private readonly List<Message> _messages = new();
private void AppendMessage(Message message)
{
lock (_gate)
{
_messages.Add(message); // 直接添加,无事件发布
}
}
```
**建议**: 采用事件溯源模式
```csharp
// 建议: 事件溯源
public abstract record MessageEvent
{
public sealed record MessageAdded(Message Message) : MessageEvent;
public sealed record MessageRemoved(string MessageId) : MessageEvent;
public sealed record MessagesCompacted(string Reason) : MessageEvent;
}
public class MessageStore
{
private readonly List<Message> _messages = new();
private readonly Subject<MessageEvent> _events = new();
public IObservable<MessageEvent> Events => _events.AsObservable();
public void Add(Message message)
{
_messages.Add(message);
_events.OnNext(new MessageEvent.MessageAdded(message));
}
}
```
### 2.3 缺少策略模式的动态切换
**问题**: API 提供商切换是硬编码的
```csharp
// src/FreeCode.ApiProviders/ApiProviderRouter.cs
public IApiProvider GetActiveProvider()
{
var providerType = GetProviderType(); // 从环境变量读取
return providerType switch
{
ApiProviderType.Anthropic => _anthropicProvider,
ApiProviderType.OpenAICodex => _codexProvider,
// ... 硬编码
};
}
```
**建议**: 使用策略模式 + 工厂
```csharp
// 建议: 动态策略注册
public interface IApiProviderFactory
{
IApiProvider Create(ApiProviderConfig config);
}
public class ApiProviderRegistry
{
private readonly Dictionary<string, IApiProviderFactory> _factories = new();
public void Register(string providerType, IApiProviderFactory factory)
=> _factories[providerType] = factory;
public IApiProvider Create(ApiProviderConfig config)
=> _factories.TryGetValue(config.Type, out var factory)
? factory.Create(config)
: throw new NotSupportedException(config.Type);
}
```
---
## 三、性能设计问题
### 3.1 JSON 处理效率低
**问题**: 大量使用 `JsonDocument.Parse``Clone()`
```csharp
// src/FreeCode.Mcp/McpClient.cs:170
private static JsonRpcMessage? ParseMessage(string line)
{
using var document = JsonDocument.Parse(line); // 分配
var root = document.RootElement;
// ...
return new JsonRpcResponse(responseId.ToString(), result.Clone(), error); // 再次分配
}
```
**建议**: 使用 `Utf8JsonReader``Span<T>`
```csharp
// 建议: 零分配 JSON 解析
public static JsonRpcMessage? ParseMessage(ReadOnlySpan<byte> utf8Json)
{
var reader = new Utf8JsonReader(utf8Json);
while (reader.Read())
{
if (reader.TokenType == JsonTokenType.PropertyName)
{
var propName = reader.GetString();
// 直接解析,无中间分配
}
}
}
```
### 3.2 消息渲染效率问题
**问题**: 每次消息更新都重绘整个列表
```csharp
// src/FreeCode.TerminalUI/REPLScreen.cs:227
private void RefreshMessages()
{
_messageList.SetSource(_messages); // 重置整个数据源
if (_messages.Count > 0)
_messageList.SelectedItem = _messages.Count - 1;
_messageList.SetNeedsDisplay(); // 触发完全重绘
}
```
**建议**: 增量更新
```csharp
// 建议: 增量渲染
private void OnNewMessage(Message message)
{
var index = _messages.Count - 1;
_messageList.AddSourceItem(message); // 只添加新项
_messageList.ScrollTo(index);
_messageList.SetNeedsDisplay(index); // 只重绘新行
}
```
### 3.3 缺少连接池
**问题**: MCP HTTP 连接每次都创建新的 HttpClient
```csharp
// src/FreeCode.Mcp/McpClientManager.cs:300
private static HttpClient CreateHttpClient(IReadOnlyDictionary<string, string>? headers = null)
{
var client = new HttpClient(); // 每次创建新实例
ApplyHeaders(client, headers);
return client;
}
```
**建议**: 使用 `IHttpClientFactory`
```csharp
// 建议: 连接池
services.AddHttpClient("Mcp")
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5)
});
public class McpClientManager
{
private readonly IHttpClientFactory _httpClientFactory;
private HttpClient CreateHttpClient(...)
=> _httpClientFactory.CreateClient("Mcp");
}
```
---
## 四、可扩展性问题
### 4.1 插件系统设计局限
**问题**: 当前插件系统只支持命令扩展
```csharp
// src/FreeCode.Plugins/PluginManager.cs:74-110
public IReadOnlyList<object> GetPluginCommands()
{
// 只加载 ICommand 类型
foreach (var type in assembly.GetTypes())
{
if (!typeof(ICommand).IsAssignableFrom(type))
continue;
// ...
}
}
```
**建议**: 支持多种扩展点
```csharp
// 建议: 多扩展点设计
public interface IPluginExtension
{
string ExtensionPoint { get; } // "tools", "commands", "services", "hooks"
}
public interface IToolExtension : IPluginExtension
{
IEnumerable<ITool> GetTools();
}
public interface IHookExtension : IPluginExtension
{
Task OnBeforeToolExecute(string toolName, object input);
Task OnAfterToolExecute(string toolName, object output);
}
// PluginManager
public class PluginManager
{
public IEnumerable<ITool> GetTools()
=> GetExtensions<IToolExtension>().SelectMany(e => e.GetTools());
public IEnumerable<ICommand> GetCommands()
=> GetExtensions<ICommandExtension>().SelectMany(e => e.GetCommands());
}
```
### 4.2 缺少中间件管道
**问题**: 工具执行没有预处理/后处理钩子
**原始 TypeScript 设计**:
```typescript
// 原始: hooks/ 目录下有完整的中间件系统
export async function executeWithHooks(toolName, input, context) {
await runPreHooks(toolName, input);
const result = await execute(toolName, input, context);
await runPostHooks(toolName, result);
return result;
}
```
**建议**: 添加中间件管道
```csharp
// 建议: 中间件设计
public interface IToolMiddleware
{
Task<ToolResult> ExecuteAsync(ToolContext context, ToolDelegate next);
}
public delegate Task<ToolResult> ToolDelegate(ToolContext context);
public class ToolPipeline
{
private readonly IList<IToolMiddleware> _middlewares = new List<IToolMiddleware>();
public void Use(IToolMiddleware middleware)
=> _middlewares.Add(middleware);
public async Task<ToolResult> ExecuteAsync(ToolContext context)
{
ToolDelegate pipeline = ctx => ctx.Tool.ExecuteAsync(ctx);
foreach (var middleware in _middlewares.Reverse())
{
var next = pipeline;
pipeline = ctx => middleware.ExecuteAsync(ctx, next);
}
return await pipeline(context);
}
}
```
---
## 五、测试性问题
### 5.1 缺少接口隔离
**问题**: `ITool` 接口职责过多
```csharp
// src/FreeCode.Core/Interfaces/ITool.cs
public interface ITool
{
string Name { get; }
string[]? Aliases { get; }
string? SearchHint { get; }
ToolCategory Category { get; }
bool IsEnabled();
JsonElement GetInputSchema();
Task<string> GetDescriptionAsync(object? input = null);
bool IsConcurrencySafe(object input);
bool IsReadOnly(object input);
// 9 个成员!
}
```
**建议**: 接口隔离
```csharp
// 建议: 接口隔离
public interface IToolInfo
{
string Name { get; }
string[]? Aliases { get; }
ToolCategory Category { get; }
}
public interface IToolSchema
{
JsonElement GetInputSchema();
Task<string> GetDescriptionAsync(object? input = null);
}
public interface IToolBehavior
{
bool IsEnabled();
bool IsConcurrencySafe(object input);
bool IsReadOnly(object input);
}
public interface IToolExecutor<TInput, TOutput>
{
Task<ToolResult<TOutput>> ExecuteAsync(TInput input, ToolExecutionContext context, CancellationToken ct);
}
// 组合接口
public interface ITool : IToolInfo, IToolSchema, IToolBehavior { }
```
### 5.2 缺少测试替身支持
**问题**: 难以为外部依赖创建测试替身
```csharp
// 当前: 直接依赖具体类型
public class BashTool : ToolBase<BashToolInput, BashToolOutput>
{
private readonly IBackgroundTaskManager _taskManager; // 接口,好
public override async Task<ToolResult<BashToolOutput>> ExecuteAsync(...)
{
using var process = new Process { StartInfo = psi }; // 具体类型,难测试
process.Start();
// ...
}
}
```
**建议**: 抽象进程执行
```csharp
// 建议: 进程执行抽象
public interface IProcessExecutor
{
Task<ProcessResult> ExecuteAsync(ProcessStartInfo info, CancellationToken ct);
}
public class BashTool : ToolBase<BashToolInput, BashToolOutput>
{
private readonly IProcessExecutor _processExecutor;
public override async Task<ToolResult<BashToolOutput>> ExecuteAsync(...)
{
var result = await _processExecutor.ExecuteAsync(psi, ct);
return new ToolResult<BashToolOutput>(new BashToolOutput
{
Stdout = result.Stdout,
Stderr = result.Stderr,
ExitCode = result.ExitCode
});
}
}
// 测试
public class FakeProcessExecutor : IProcessExecutor
{
public Task<ProcessResult> ExecuteAsync(...)
=> Task.FromResult(new ProcessResult { ExitCode = 0, Stdout = "fake output" });
}
```
---
## 六、总结与优先级
### 6.1 高优先级改进 (影响架构稳定性)
| # | 改进项 | 收益 | 工作量 |
|---|--------|------|--------|
| 1 | 状态分片管理 | 可测试性、性能 | 2天 |
| 2 | 工具执行管道 | 可扩展性、可测试性 | 2天 |
| 3 | 模块化 DI | 可维护性、可测试性 | 1天 |
| 4 | JSON 零分配 | 性能 | 2天 |
### 6.2 中优先级改进 (提升可维护性)
| # | 改进项 | 收益 | 工作量 |
|---|--------|------|--------|
| 5 | 项目合并 (16→8) | 构建速度、维护成本 | 1天 |
| 6 | 中间件管道 | 可扩展性 | 2天 |
| 7 | 接口隔离 | 可测试性 | 1天 |
| 8 | 进程执行抽象 | 可测试性 | 1天 |
### 6.3 低优先级改进 (锦上添花)
| # | 改进项 | 收益 | 工作量 |
|---|--------|------|--------|
| 9 | 事件溯源 | 可追溯性 | 2天 |
| 10 | 增量渲染 | 性能 | 1天 |
| 11 | HttpClient 池 | 性能 | 0.5天 |
| 12 | 多扩展点插件 | 可扩展性 | 2天 |
---
## 七、最终建议
### 当前架构评分: 6.5/10
### 主要优点
- 模块化思路正确
- 接口驱动设计
- 依赖注入完整
- 测试基础扎实
### 主要不足
- 模块划分过细
- 状态管理臃肿
- 缺少中间件机制
- 性能优化不足
### 建议路径
1. **短期** (1周): 状态分片 + 工具管道 + 模块化 DI
2. **中期** (2周): 项目合并 + 接口隔离 + 中间件
3. **长期** (持续): 性能优化 + 事件溯源
### 重构风险
**中等** - 建议在完成核心功能后再进行架构重构,避免过早优化。