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

18 KiB
Raw Permalink Blame History

架构设计评估与改进建议

一、架构设计不足

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+ 属性
}

问题分析:

  1. 任何状态变更都会触发整个 AppState 的重新创建
  2. 订阅者无法只订阅感兴趣的状态切片
  3. 测试困难,需要构造完整的 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 个扩展方法!

问题分析:

  1. 注册顺序敏感,隐式依赖
  2. 难以单独测试某个模块
  3. 无法按需加载模块

建议: 采用模块化 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.ParseClone()

// 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); // 再次分配
}

建议: 使用 Utf8JsonReaderSpan<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. 短期 (1周): 状态分片 + 工具管道 + 模块化 DI
  2. 中期 (2周): 项目合并 + 接口隔离 + 中间件
  3. 长期 (持续): 性能优化 + 事件溯源

重构风险

中等 - 建议在完成核心功能后再进行架构重构,避免过早优化。