18 KiB
18 KiB
架构设计评估与改进建议
一、架构设计不足
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/ (记忆)
建议: 采用垂直切片架构
// 建议: 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,违反单一职责
// 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+ 属性
}
问题分析:
- 任何状态变更都会触发整个 AppState 的重新创建
- 订阅者无法只订阅感兴趣的状态切片
- 测试困难,需要构造完整的 AppState
建议: 采用分片状态设计
// 建议: 分片状态管理
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 依赖注入设计问题
问题: 服务注册过于分散,缺少模块化
// 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 个扩展方法!
问题分析:
- 注册顺序敏感,隐式依赖
- 难以单独测试某个模块
- 无法按需加载模块
建议: 采用模块化 DI 设计
// 建议: 模块化设计
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
// 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)
};
建议: 使用责任链 + 策略模式
// 建议: 工具执行管道
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 缺少事件溯源
问题: 消息历史直接存储在内存列表
// src/FreeCode.Engine/QueryEngine.cs
private readonly List<Message> _messages = new();
private void AppendMessage(Message message)
{
lock (_gate)
{
_messages.Add(message); // 直接添加,无事件发布
}
}
建议: 采用事件溯源模式
// 建议: 事件溯源
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 提供商切换是硬编码的
// src/FreeCode.ApiProviders/ApiProviderRouter.cs
public IApiProvider GetActiveProvider()
{
var providerType = GetProviderType(); // 从环境变量读取
return providerType switch
{
ApiProviderType.Anthropic => _anthropicProvider,
ApiProviderType.OpenAICodex => _codexProvider,
// ... 硬编码
};
}
建议: 使用策略模式 + 工厂
// 建议: 动态策略注册
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()
// 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>
// 建议: 零分配 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 消息渲染效率问题
问题: 每次消息更新都重绘整个列表
// src/FreeCode.TerminalUI/REPLScreen.cs:227
private void RefreshMessages()
{
_messageList.SetSource(_messages); // 重置整个数据源
if (_messages.Count > 0)
_messageList.SelectedItem = _messages.Count - 1;
_messageList.SetNeedsDisplay(); // 触发完全重绘
}
建议: 增量更新
// 建议: 增量渲染
private void OnNewMessage(Message message)
{
var index = _messages.Count - 1;
_messageList.AddSourceItem(message); // 只添加新项
_messageList.ScrollTo(index);
_messageList.SetNeedsDisplay(index); // 只重绘新行
}
3.3 缺少连接池
问题: MCP HTTP 连接每次都创建新的 HttpClient
// 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
// 建议: 连接池
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 插件系统设计局限
问题: 当前插件系统只支持命令扩展
// src/FreeCode.Plugins/PluginManager.cs:74-110
public IReadOnlyList<object> GetPluginCommands()
{
// 只加载 ICommand 类型
foreach (var type in assembly.GetTypes())
{
if (!typeof(ICommand).IsAssignableFrom(type))
continue;
// ...
}
}
建议: 支持多种扩展点
// 建议: 多扩展点设计
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 设计:
// 原始: 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;
}
建议: 添加中间件管道
// 建议: 中间件设计
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 接口职责过多
// 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 个成员!
}
建议: 接口隔离
// 建议: 接口隔离
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 缺少测试替身支持
问题: 难以为外部依赖创建测试替身
// 当前: 直接依赖具体类型
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();
// ...
}
}
建议: 抽象进程执行
// 建议: 进程执行抽象
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周): 状态分片 + 工具管道 + 模块化 DI
- 中期 (2周): 项目合并 + 接口隔离 + 中间件
- 长期 (持续): 性能优化 + 事件溯源
重构风险
中等 - 建议在完成核心功能后再进行架构重构,避免过早优化。