ECC 编码风格指南:从不可变性到代码质量清单的通用规则体系
导读
本文基于 ECC(Agent Harness 性能优化系统)仓库中面向通用工程层的编码风格规范(英文原文及 西班牙语文档)展开。该规范是该仓库 rules/common/ 层的核心文件之一,为所有支持的语言规则集提供跨语言、跨框架的统一默认值。读完本文,你将掌握 ECC 项目对不可变性、KISS/DRY/YAGNI、文件组织、错误处理、输入校验、命名规范与代码坏味道的完整判定标准,并能结合仓库源码(Ajv 模式校验、错误 fail-fast 模式等)理解这些规则背后的实际落地方式,将其应用到自己的代码评审与日常开发中。
这份规范在 ECC 仓库中的定位
ECC 仓库将规则组织为"通用层 + 语言层"的双层结构(见 rules/README.md):
rules/
├── common/ # 跨语言通用原则(始终安装)
│ ├── coding-style.md
│ ├── git-workflow.md
│ ├── testing.md
│ ├── performance.md
│ ├── patterns.md
│ ├── hooks.md
│ ├── agents.md
│ └── security.md
├── typescript/ # TypeScript/JavaScript 专属
├── angular/ # Angular 专属
├── vue/ # Vue 3 专属
├── python/ # Python 专属
├── golang/ # Go 专属
└── ...
common/coding-style.md定义不针对任何语言的通用原则,因此规范本身不携带语言级代码示例(rules/README.md 明确说明 "common/ contains universal principles — no language-specific code examples")。- 各类语言目录(仓库现有 21 套语言级 coding-style 文件,如 rules/typescript/coding-style.md、rules/golang/coding-style.md、rules/python/coding-style.md 等)会以
> This file extends common/coding-style.md with ... specific content的方式扩展并覆盖通用默认值。 - 规则优先级遵循"特定覆盖通用"的分层配置原则(类似 CSS 特异性或
.gitignore优先级):当语言规则与通用规则冲突时,语言规则优先。例如通用规范默认推荐不可变性,而 Go 语言层可声明"符合 Go 惯用法的 struct 指针接收者变更在此优先"。
本文解析的 docs/es/rules/common/coding-style.md 是该通用规则的西班牙语本地化副本,内容与英文源文件逐条对应,是 ECC 多语言文档体系(docs/es、docs/ja-JP、docs/zh-CN 等)的一部分。
不可变性(CRITICAL,最高优先级)
该规范用 CRITICAL 标注的最高约束是:永远创建新对象,绝不原地变更已有对象。文档以伪代码给出正反示例:
// Pseudocódigo
INCORRECTO: modify(original, campo, valor) → 就地修改 original
CORRECTO: update(original, campo, valor) → 返回携带变更的新副本
规范给出的理由是:不可变数据能阻止隐藏的副作用、简化调试、支持安全并发。这三条理由直接服务于 ECC 这类"Agent harness"系统——在该仓库中,大量 CLI 脚本需要在同一进程内串行/并发处理多个会话、钩子与状态文件,若函数就地修改共享对象,极易产生难以复现的竞态与副作用。
落地到 JavaScript/TypeScript 语言层时,"返回新副本"通常意味着使用展开语法({ ...original, field: value })、structuredClone、不可变更新库或函数式更新模式,而不是给原对象属性重新赋值。值得注意的是,这一原则在仓库的 Rust 核心(ecc2)中几乎"零成本"成立——Rust 的绑定默认不可变,所有权与借用检查在编译期就阻止了大多数共享可变状态,这与通用规则的目标高度同构。
三大核心设计原则:KISS / DRY / YAGNI
KISS(Keep It Simple,保持简单)
- 优先选择真正可行且最简单的方案;
- 避免过早优化;
- 为清晰性而非机巧性进行优化。
KISS 是评审代码时最容易对照的第一条:如果一个方案需要读者反复揣摩才能理解,即使它"更聪明",也应当被否决。
DRY(Don't Repeat Yourself,不要重复自己)
- 将重复逻辑抽取到共享函数或工具中;
- 避免复制粘贴导致的实现漂移(同一逻辑在多处各自演化、最终行为不一致);
- 只有在重复是真实的时才引入抽象,而非出于猜测。
YAGNI(You Aren't Gonna Need It,你并不需要它)
- 不要在功能或抽象真正被需要之前就提前构建;
- 避免投机性通用(speculative generality);
- 先简单起步,当真实压力出现时再重构。
三条原则共同构成一个决策闭环:KISS 约束单次实现的范围,YAGNI 约束整体设计的前瞻尺度,DRY 则决定何时允许打破前两者引入抽象——三者相互制衡,防止任何一条被极端化执行。
文件组织:众多小文件优先于少量大文件
规范对源文件的体积与组织方式给出明确阈值:
- 高内聚、低耦合是组织目标;
- 单文件 200–400 行为典型区间,800 行是维护性软上限;
- 从大模块中抽取工具函数/工具模块;
- 按功能/领域组织,而非按类型组织(例如不要建立"所有工具放一个 util 目录、所有类型放一个 types 目录"的结构,而应让一个功能模块内聚自己的实现与类型)。
英文源文档 rules/common/coding-style.md 对"800 行软上限"补充了一个务实例外:测试文件、生成文件与 vendor 文件因其角色需要,可超过上限。这一点在评审依赖捆绑或 fixture 较多的仓库模块时尤其重要——硬性截断测试文件反而会伤害可读性。
错误处理:每个层级都要显式处理
规范对错误处理给出强制要求("ALWAYS handle errors comprehensively"):
- 在每一层显式处理错误;
- 面向 UI 的代码提供用户友好的错误消息;
- 服务端一侧记录详细的错误上下文日志;
- 永远不要静默吞掉错误。
这一原则在 ECC 的 CLI 脚本中有着非常一致的落地模式。以 scripts/lib/claude-plugin-setup.js 为例,其内部定义了集中的 fail(code, message, details) 函数,全文件数十处校验失败路径均以统一模式调用,例如 fail('INVALID_SCOPE', 'Invalid plugin scope: ...')。这种模式的价值在于:
- 错误携带机器可读的错误码(如
INVALID_SCOPE),便于上层与测试断言; - 消息对人友好,便于 CLI 用户理解;
- 通过统一的 fail 出口,避免散落各处的
console.error造成"吞错"或上下文丢失。
对照规范可看出:fail(code, message, details) 正是"显式处理 + 用户友好消息 + 服务端(CLI 进程)记录上下文"三者合一的工程化表达。
输入校验:在系统边界处决不信任外部数据
规范对输入校验同样使用强制语气("ALWAYS validate at system boundaries"):
- 在处理前校验所有用户输入;
- 在可用处使用基于 schema 的校验;
- 快速失败(fail fast)并给出清晰错误消息;
- 绝不信任外部数据——包括 API 响应、用户输入、文件内容。
"基于 schema 校验"与"快速失败"在 ECC 仓库中都有源码级印证。仓库提供了 schemas/ 目录(含 hooks.schema.json、install-state.schema.json、memory.schema.json 等十余个 JSON Schema),并基于 Ajv 在运行时编译这些 schema:
- scripts/lib/install/config.js 中:
const ajv = new Ajv({ allErrors: true }); cachedValidator = ajv.compile(schema);——allErrors: true表示一次报告全部校验错误,配合"快速失败"可在入口处一次性暴露所有非法字段; - scripts/lib/state-store/schema.js 中:单例化
getAjv()并compile(validatorSchema),将 schema 编译结果缓存复用,避免重复编译开销。
这两处实现共同说明:schema 驱动校验不是纸上谈兵——仓库把"安装配置""状态存储""内存快照""钩子清单"等所有进入系统的数据边界都用 JSON Schema 做了形式化约束,任何偏离 schema 的输入都会在边界处被拦截,而不是带着脏数据流入后续逻辑。
命名约定
规范给出跨语言的基础命名表(语言层可在其上扩展,例如 TypeScript 层还补充 interface/type/枚举的细分规则):
| 对象 | 约定 | 说明 |
|---|---|---|
| 变量与函数 | camelCase |
使用描述性名称 |
| 布尔值 | 前缀 is / has / should / can |
使条件语义一目了然 |
| 接口、类型、组件 | PascalCase |
类型与组件首字母大写 |
| 常量 | UPPER_SNAKE_CASE |
全大写加下划线 |
| 自定义 hooks | camelCase + 前缀 use |
符合 React 生态惯例 |
命名约定是代码"自文档化"的第一道防线。例如布尔值使用 isInstalled、hasPendingChanges、shouldRefresh 而非 installed、pending,能让 if (hasPendingChanges) 读起来本身就是一句自然语言。
需要规避的代码坏味道(Code Smells)
深层嵌套(Deep Nesting)
一旦条件逻辑开始层层叠加,优先使用**提前返回(early returns)**而非嵌套条件:
// 坏味道:多层 if 嵌套,缩进不断加深
if (user) {
if (user.active) {
if (user.plan === 'pro') {
// ...
}
}
}
// 更优:提前返回,拍平结构
if (!user) return;
if (!user.active) return;
if (user.plan !== 'pro') return;
// 主逻辑...
魔法数字(Magic Numbers)
对有意义的时间阈值、延迟、限额,使用具名常量,而非散落的裸数字。对照 ECC 仓库,文件体积 200–400/800、函数 50 行、嵌套 4 层这些规范中的阈值本身就应当以具名常量形式出现在配置或校验代码中,而不是散写在各处比较表达式里。
长函数(Long Functions)
将大函数拆分为职责清晰的聚焦片段。规范在检查清单中将函数长度红线定为 <50 行,与"单文件 <800 行"共同构成从函数到文件的两级粒度约束。
代码质量检查清单
规范要求在任何工作标记为"完成"之前逐项核对:
- [ ] 代码可读且命名良好
- [ ] 函数短小(<50 行)
- [ ] 文件聚焦(<800 行)
- [ ] 无深层嵌套(>4 层即超标)
- [ ] 恰当的错误处理
- [ ] 无硬编码值(使用常量或配置)
- [ ] 无变更操作(使用不可变模式)
这张清单可直接作为代码评审的模板使用:7 项中 6 项是"可机械检查"的客观指标(行数、嵌套深度、硬编码值、是否就地修改),只有"可读命名"与"错误处理"需要评审者经验判断。在 ECC 的评审流程中,类似的规则还可与语言层的审查 Agent(如 agents/python-reviewer.md、agents/typescript-reviewer.md)以及通用代码评审规则 rules/common/code-review.md 配合,让 AI 评审者与人工评审使用同一套判定语言。
规则如何落地到具体语言
通用规范本身不携带语言级代码,它的价值通过两件事放大:
- 语言层扩展:每种语言的 rules//coding-style.md 都以"extends common"开头,把抽象原则翻译为可执行的惯用法。以 TypeScript 层为例,它补充了"公共 API 必须显式标注参数与返回类型""用
unknown而非any处理不可信输入后安全收窄""字符串字面量联合优先于enum"等具体规则,并用 WRONG/CORRECT 双栏代码示例示范(见 rules/typescript/coding-style.md)。 - 分层覆盖:当语言惯用法与通用默认值冲突时,语言规则优先。例如通用层默认不可变,Go 层即可声明"struct 变更使用指针接收者更符合 Go 惯用法"来覆盖默认值(此机制在 rules/README.md 的 "Rule Priority" 一节有完整示例)。
ECC 仓库现有 21 套语言级 coding-style(TypeScript/JavaScript、Python、Go、Rust、Java、Kotlin、Swift、C++、C#、F#、PHP、Ruby、Perl、Dart、React、Vue、Nuxt、Angular、React Native、ArkTS、Web 等),且这些通用规则也被翻译为多种语言,例如本文依据的 西班牙语版本 与日语、简体中文等本地化目录。需要为某语言补充规则时,可参照 rules/README.md 的 "Adding a New Language" 一节:先建语言目录,再为每份 common 规则(coding-style/testing/patterns/hooks/security)编写以 "extends common" 开头的语言层文件,并链接到 skills/ 下已有的技能作为实操参考。
总结
ECC 的通用编码风格规范是一份"小而硬"的规则核心:一条最高约束(不可变)+ 三条设计原则(KISS/DRY/YAGNI)+ 两组体积红线(函数 50 行 / 文件 800 行)+ 四项强制工程纪律(错误处理、边界校验、显式命名、坏味道规避)+ 一张 7 项检查清单。它刻意保持语言无关,以换取对 TypeScript、Python、Go、Rust 等全部语言规则集的统一指导力,再通过"语言层覆盖"机制容忍各生态的惯用偏差。理解并执行这份清单,你就掌握了 ECC 在代码评审与 Agent 协作中衡量代码质量的共同标尺。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00