首页
/ ECC coding-standards 技能:跨项目编码规范、不可变模式与代码坏味道审查的完整实战指南

ECC coding-standards 技能:跨项目编码规范、不可变模式与代码坏味道审查的完整实战指南

2026-09-05 16:11:40作者:郜逊炳

ECC(Everything Claude Code)将「编码规范」沉淀为可被 Agent 直接调用的技能(Skill),让命名、不可变性、可读性、代码坏味道审查等跨项目公约数不再依赖团队口头约定,而是变成可复用、可审计的标准化流程。本文以 ECC 仓库中的 coding-standards 技能 为主体,完整解析其激活条件、作用边界、代码质量四大原则、TypeScript/JavaScript 与 React 实战规范、API 设计标准、文件组织约定、注释文档规范、性能与测试标准以及代码坏味道检测清单,并结合仓库中的规则层(coding-style.mdcode-review.md)与安装清单(install-components.json)补充其落地机制。读完后,你可以在任何 TypeScript/JavaScript/React 项目中直接套用这套规范,并理解 ECC 如何在 Agent 工作流中强制执行这些约定。

一、技能定位:共享底线(shared floor),而非框架手册

coding-standards 技能的官方描述是:「跨项目的基线编码规范,覆盖命名、可读性、不可变性与代码质量审查;框架特定模式请使用更细粒度的前端或后端技能」。它被明确定位为共享底线(shared floor),而不是详细的框架手册。技能文档开头的分工指引是:

  • React、状态、表单、渲染与 UI 架构 → 使用 frontend-patterns 技能;
  • 仓储/服务分层、端点设计、校验与服务端关注点 → 使用 backend-patternsapi-design 技能;
  • 只需要最短可复用规则层、而非完整技能走查时 → 使用 rules/common/coding-style.md

这一定位与仓库的安装体系相互印证:在 manifests/install-components.json 中,该组件登记为 skill:coding-standards,描述为「语言无关的编码标准与最佳实践」,归属 framework-language 模块——即选择框架/语言类安装模块时,它会与前端/后端模式技能一起被选中,但各自职责不重叠。

1.1 元数据与隐式调用

该技能在 .agents/skills/coding-standards/agents/openai.yaml 中声明了 Agent 接口元数据:

interface:
  display_name: "Coding Standards"
  short_description: "Cross-project coding conventions and review"
  brand_color: "#3B82F6"
  default_prompt: "Use $coding-standards to review code against cross-project standards."
policy:
  allow_implicit_invocation: true

其中 allow_implicit_invocation: true 意味着 Agent 在判断任务属于「代码质量/命名审查且没有更细粒度框架技能适用」时,可以隐式激活该技能,无需用户显式指定;default_prompt 则给出了标准触发句式:用 $coding-standards 按跨项目标准审查代码。另外,仓库根目录下还有一份带 metadata.origin: ECC 的同源技能 skills/coding-standards/SKILL.md,内容与 .agents 版本一致,体现了技能在两套目录结构中的同步分发。

二、激活时机与作用边界

2.1 何时激活(When to Activate)

技能文档列出了六类典型激活场景:

  1. 启动新项目或新模块时;
  2. 审查代码质量与可维护性时;
  3. 重构既有代码使其符合约定时;
  4. 强制统一命名、格式化或结构一致性时;
  5. 配置 lint、格式化或类型检查规则时;
  6. 向新贡献者灌输编码约定(onboarding)时。

2.2 作用边界(Scope Boundaries)

技能对自身边界给出了双向清单,这是 ECC「技能分层」设计的核心:激活于——

  • 描述性命名(descriptive naming);
  • 不可变性默认值(immutability defaults);
  • 可读性、KISS、DRY、YAGNI 的强制执行;
  • 错误处理预期与代码坏味道(code smell)审查。

不作为主要来源于——

  • React 组合、hooks、渲染模式;
  • 后端架构、API 设计、数据库分层;
  • 任何已有更窄 ECC 技能覆盖的领域特定框架指导。

从源码结构看,这条边界规则在仓库中是被系统性遵守的:语言专属技能(如 skills/cpp-coding-standards/SKILL.mdskills/java-coding-standards/SKILL.md)与框架专属技能(skills/frontend-patterns/skills/backend-patterns/)与 coding-standards 并列存在于技能目录中,而 rules/common/code-review.md 进一步把审查职责按语言拆分给 typescript-reviewerpython-reviewergo-reviewerrust-reviewer 等 Agent——coding-standards 负责「语言无关的公约数」,具体语言问题下沉到更专的审查者,避免一个技能包揽一切。

三、代码质量四原则

技能将代码质量原则压缩为四条,每条都有可直接执行的子项:

3.1 可读性优先(Readability First)

  • 代码被阅读的次数远多于被编写的次数;
  • 使用清晰的变量名与函数名;
  • 自文档化代码优于注释;
  • 保持格式化一致性。

3.2 KISS(Keep It Simple, Stupid)

  • 选择能工作的最简方案;
  • 避免过度设计;
  • 不做过早优化;
  • 易理解 > 取巧。

3.3 DRY(Don't Repeat Yourself)

  • 把常见逻辑抽成函数;
  • 构建可复用组件;
  • 跨模块共享工具函数;
  • 杜绝复制粘贴式编程。

3.4 YAGNI(You Aren't Gonna Need It)

  • 不在需要之前构建功能;
  • 避免投机性泛化;
  • 只在必要时增加复杂度;
  • 从简单开始,需要时再重构。

这四条在规则层 rules/common/coding-style.md 中对应存在更短的「规则版」表述,例如 DRY 一节额外补充了「当重复是真实的而非推测的,才引入抽象」,YAGNI 一节则补充「先简单,当压力真实到来时再重构」——技能给出完整走查,规则层给出一页可快速引用的底线,这正是文档开头「用规则层代替完整技能」建议的落点。

四、TypeScript/JavaScript 标准:六个维度的可执行约定

这是技能文档的主体部分,全部采用 PASS/FAIL 对照示例,可直接作为代码审查对照表。

4.1 变量命名

// PASS: GOOD: Descriptive names
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000

// FAIL: BAD: Unclear names
const q = 'election'
const flag = true
const x = 1000

规则层 coding-style.md 对此有更完整的命名约定表:变量与函数用描述性 camelCase;布尔量优先 is/has/should/can 前缀;接口、类型与组件用 PascalCase;常量用 UPPER_SNAKE_CASE;自定义 hooks 用 use 前缀加 camelCase

4.2 函数命名:动词-名词模式

// PASS: GOOD: Verb-noun pattern
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }

// FAIL: BAD: Unclear or noun-only
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }

4.3 不可变模式(标记为 CRITICAL)

// PASS: ALWAYS use spread operator
const updatedUser = {
  ...user,
  name: 'New Name'
}

const updatedArray = [...items, newItem]

// FAIL: NEVER mutate directly
user.name = 'New Name'  // BAD
items.push(newItem)     // BAD

这是整个技能中标记最重的条款。规则层给出了其伪代码形式与理由:update(original, field, value) 应返回带变更的新副本,而非原地修改;理由是「不可变数据消除隐藏副作用、让调试更容易、并支持安全的并发」。值得注意的是,技能允许有充分理由的例外——在注释章节的示例中,items.push(newItem) 在「刻意为了大数组性能使用 mutation」的注释下被判定为 PASS,说明该规范追求的是「例外必须显式声明」,而不是教条。

4.4 错误处理

// PASS: GOOD: Comprehensive error handling
async function fetchData(url: string) {
  try {
    const response = await fetch(url)

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}: ${response.statusText}`)
    }

    return await response.json()
  } catch (error) {
    console.error('Fetch failed:', error)
    throw new Error('Failed to fetch data')
  }
}

// FAIL: BAD: No error handling
async function fetchData(url) {
  const response = await fetch(url)
  return response.json()
}

规则层对错误处理的要求与之同构:每一层显式处理错误、UI 侧给出用户友好信息、服务端记录详细上下文、绝不静默吞错。

4.5 异步/并发最佳实践

// PASS: GOOD: Parallel execution when possible
const [users, markets, stats] = await Promise.all([
  fetchUsers(),
  fetchMarkets(),
  fetchStats()
])

// FAIL: BAD: Sequential when unnecessary
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()

无依赖关系的多个请求应并行执行,而非串行等待。

4.6 类型安全

// PASS: GOOD: Proper types
interface Market {
  id: string
  name: string
  status: 'active' | 'resolved' | 'closed'
  created_at: Date
}

function getMarket(id: string): Promise<Market> {
  // Implementation
}

// FAIL: BAD: Using 'any'
function getMarket(id: any): Promise<any> {
  // Implementation
}

偏好联合字面量类型('active' | 'resolved' | 'closed')收窄取值域,杜绝 any

五、React 最佳实践

技能为 React 场景给出了四组可直接套用的模式(注意:边界章节已声明 React 深层模式应交给 frontend-patterns,这里只保留与「命名/结构/不可变」相关的公约数部分)。

5.1 组件结构

// PASS: GOOD: Functional component with types
interface ButtonProps {
  children: React.ReactNode
  onClick: () => void
  disabled?: boolean
  variant?: 'primary' | 'secondary'
}

export function Button({
  children,
  onClick,
  disabled = false,
  variant = 'primary'
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {children}
    </button>
  )
}

// FAIL: BAD: No types, unclear structure
export function Button(props) {
  return <button onClick={props.onClick}>{props.children}</button>
}

要点:Props 显式建模为接口、可选属性声明 ?、解构参数带默认值。

5.2 自定义 Hooks

// PASS: GOOD: Reusable custom hook
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value)

  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value)
    }, delay)

    return () => clearTimeout(handler)
  }, [value, delay])

  return debouncedValue
}

// Usage
const debouncedQuery = useDebounce(searchQuery, 500)

注意该示例同时体现了两条规范:泛型 <T> 保持类型安全,清理函数 clearTimeout 避免副作用泄漏。

5.3 状态更新

// PASS: GOOD: Proper state updates
const [count, setCount] = useState(0)

// Functional update for state based on previous state
setCount(prev => prev + 1)

// FAIL: BAD: Direct state reference
setCount(count + 1)  // Can be stale in async scenarios

基于前值的函数式更新 setCount(prev => prev + 1) 是默认写法,直接引用闭包中的旧状态在异步场景下会读到陈旧值——这与不可变原则一脉相承。

5.4 条件渲染

// PASS: GOOD: Clear conditional rendering
{isLoading && <Spinner />}
{error && <ErrorMessage error={error} />}
{data && <DataDisplay data={data} />}

// FAIL: BAD: Ternary hell
{isLoading ? <Spinner /> : error ? <ErrorMessage error={error} /> : data ? <DataDisplay data={data} /> : null}

短路与逻辑(&&)优于嵌套三元的「ternary hell」。

六、API 设计标准

6.1 REST 约定

GET    /api/markets              # List all markets
GET    /api/markets/:id          # Get specific market
POST   /api/markets              # Create new market
PUT    /api/markets/:id          # Update market (full)
PATCH  /api/markets/:id          # Update market (partial)
DELETE /api/markets/:id          # Delete market

# Query parameters for filtering
GET /api/markets?status=active&limit=10&offset=0

资源用名词复数、方法语义明确(PUT 全量更新 / PATCH 部分更新)、过滤与分页走查询参数。

6.2 统一响应格式

// PASS: GOOD: Consistent response structure
interface ApiResponse<T> {
  success: boolean
  data?: T
  error?: string
  meta?: {
    total: number
    page: number
    limit: number
  }
}

// Success response
return NextResponse.json({
  success: true,
  data: markets,
  meta: { total: 100, page: 1, limit: 10 }
})

// Error response
return NextResponse.json({
  success: false,
  error: 'Invalid request'
}, { status: 400 })

成功与失败共享同一个 ApiResponse<T> 泛型外壳,客户端可以按单一契约解析。

6.3 输入校验(Zod schema)

import { z } from 'zod'

// PASS: GOOD: Schema validation
const CreateMarketSchema = z.object({
  name: z.string().min(1).max(200),
  description: z.string().min(1).max(2000),
  endDate: z.string().datetime(),
  categories: z.array(z.string()).min(1)
})

export async function POST(request: Request) {
  const body = await request.json()

  try {
    const validated = CreateMarketSchema.parse(body)
    // Proceed with validated data
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json({
        success: false,
        error: 'Validation failed',
        details: error.errors
      }, { status: 400 })
    }
  }
}

这正对应规则层的「输入校验」条款:在系统边界校验、优先 schema 化、快速失败并给出清晰错误信息、永不信任外部数据。校验失败时返回 details: error.errors,把 schema 级错误明细透出给调用方。

七、文件组织与命名

7.1 项目结构

src/
├── app/                    # Next.js App Router
│   ├── api/               # API routes
│   ├── markets/           # Market pages
│   └── (auth)/           # Auth pages (route groups)
├── components/            # React components
│   ├── ui/               # Generic UI components
│   ├── forms/            # Form components
│   └── layouts/          # Layout components
├── hooks/                # Custom React hooks
├── lib/                  # Utilities and configs
│   ├── api/             # API clients
│   ├── utils/           # Helper functions
│   └── constants/       # Constants
├── types/                # TypeScript types
└── styles/              # Global styles

7.2 文件命名

components/Button.tsx          # PascalCase for components
hooks/useAuth.ts              # camelCase with 'use' prefix
lib/formatDate.ts             # camelCase for utilities
types/market.types.ts         # camelCase with .types suffix

规则层 coding-style.md 在此之上给出了量化约束:多小文件 > 少大文件,单文件 200–400 行为常态、800 行为软性维护上限(测试、生成代码、vendored 文件可例外),按功能/领域而非按类型组织目录。这套量化阈值与 rules/common/code-review.md 的审查清单完全对齐——「函数 < 50 行」「源文件处于 800 行软上限内或附例外理由」都直接写入了合并前的检查项。

八、注释与文档

8.1 何时写注释:解释 WHY,而非 WHAT

// PASS: GOOD: Explain WHY, not WHAT
// Use exponential backoff to avoid overwhelming the API during outages
const delay = Math.min(1000 * Math.pow(2, retryCount), 30000)

// Deliberately using mutation here for performance with large arrays
items.push(newItem)

// FAIL: BAD: Stating the obvious
// Increment counter by 1
count++

// Set name to user's name
name = user.name

该示例同时示范了两类合格注释:解释算法选择动机(指数退避防止故障期压垮 API),以及为「规范例外」留痕(大数组场景刻意使用 mutation)。

8.2 公共 API 的 JSDoc

/**
 * Searches markets using semantic similarity.
 *
 * @param query - Natural language search query
 * @param limit - Maximum number of results (default: 10)
 * @returns Array of markets sorted by similarity score
 * @throws {Error} If OpenAI API fails or Redis unavailable
 *
 * @example
 * ```typescript
 * const results = await searchMarkets('election', 5)
 * console.log(results[0].name) // "Trump vs Biden"
 * ```
 */
export async function searchMarkets(
  query: string,
  limit: number = 10
): Promise<Market[]> {
  // Implementation
}

完整 JSDoc 应覆盖 @param(含默认值)、@returns@throws@example,让公共 API 的行为契约自解释。

九、性能最佳实践

9.1 记忆化

import { useMemo, useCallback } from 'react'

// PASS: GOOD: Memoize expensive computations
// Copy before sorting - Array.prototype.sort mutates in place
const sortedMarkets = useMemo(() => {
  return [...markets].sort((a, b) => b.volume - a.volume)
}, [markets])

// PASS: GOOD: Memoize callbacks
const handleSearch = useCallback((query: string) => {
  setSearchQuery(query)
}, [])

注意 [...markets].sort(...) 的写法:Array.prototype.sort 原地修改数组,先拷贝再排序——性能技巧与不可变原则在这里合流,注释行正是 8.1 节「解释 WHY」范式的实例。

9.2 懒加载

import { lazy, Suspense } from 'react'

// PASS: GOOD: Lazy load heavy components
const HeavyChart = lazy(() => import('./HeavyChart'))

export function Dashboard() {
  return (
    <Suspense fallback={<Spinner />}>
      <HeavyChart />
    </Suspense>
  )
}

9.3 数据库查询

// PASS: GOOD: Select only needed columns
const { data } = await supabase
  .from('markets')
  .select('id, name, status')
  .limit(10)

// FAIL: BAD: Select everything
const { data } = await supabase
  .from('markets')
  .select('*')

只取需要的列、显式限制条数。审查规则 code-review.md 的性能清单进一步补充了 N+1 查询、缺分页、无界查询、缺缓存四类问题——这些是「查询侧性能」的系统化检查项,与本文的列选择约束互为补充。

十、测试标准

10.1 AAA 结构(Arrange / Act / Assert)

test('calculates similarity correctly', () => {
  // Arrange
  const vector1 = [1, 0, 0]
  const vector2 = [0, 1, 0]

  // Act
  const similarity = calculateCosineSimilarity(vector1, vector2)

  // Assert
  expect(similarity).toBe(0)
})

10.2 测试命名

// PASS: GOOD: Descriptive test names
test('returns empty array when no markets match query', () => { })
test('throws error when OpenAI API is missing', () => { })
test('falls back to substring search when Redis unavailable', () => { })

// FAIL: BAD: Vague test names
test('works', () => { })
test('test search', () => { })

测试名即规格说明:主语 + 条件 + 期望结果(如「Redis 不可用时回退到子串搜索」),workstest search 这类名字被明确判为坏味道。

十一、代码坏味道检测清单

技能将高频反模式固化为三类可检测清单,这也是「审查」场景下该技能最直接的交付物:

11.1 长函数(> 50 行)

// FAIL: BAD: Function > 50 lines
function processMarketData() {
  // 100 lines of code
}

// PASS: GOOD: Split into smaller functions
function processMarketData() {
  const validated = validateData()
  const transformed = transformData(validated)
  return saveData(transformed)
}

11.2 深层嵌套(> 4~5 层):用早返回压平

// FAIL: BAD: 5+ levels of nesting
if (user) {
  if (user.isAdmin) {
    if (market) {
      if (market.isActive) {
        if (hasPermission) {
          // Do something
        }
      }
    }
  }
}

// PASS: GOOD: Early returns
if (!user) return
if (!user.isAdmin) return
if (!market) return
if (!market.isActive) return
if (!hasPermission) return

// Do something

11.3 魔法数字:用命名常量替代

// FAIL: BAD: Unexplained numbers
if (retryCount > 3) { }
setTimeout(callback, 500)

// PASS: GOOD: Named constants
const MAX_RETRIES = 3
const DEBOUNCE_DELAY_MS = 500

if (retryCount > MAX_RETRIES) { }
setTimeout(callback, DEBOUNCE_DELAY_MS)

常量命名遵循 UPPER_SNAKE_CASE(如 DEBOUNCE_DELAY_MS 带单位后缀),与 4.1 节命名约定呼应。

十二、与 ECC 仓库工程实践的印证

技能不是孤立的文档:它给出的每条阈值,都能在 ECC 仓库自身的工程配置中找到执行机制。

  1. 审查清单闭环rules/common/code-review.md 把技能中的软性约定变成硬性合并门槛:函数 < 50 行、文件 < 800 行(或附例外理由)、嵌套 ≤ 4 层、错误显式处理、无硬编码密钥、无遗留 console.log、新功能有测试且覆盖率 ≥ 80%。它还定义了严重度分级——CRITICAL(安全漏洞/数据丢失风险)必须 BLOCK、HIGH 应 WARN、MEDIUM(含「超过 800 行软上限且无说明」)为 INFO、LOW 为 NOTE——这意味着「文件过长」这类坏味道会被正式记入审查意见。
  2. Lint 兜底。仓库根部的 eslint.config.jsno-unused-varsno-undef 设为 erroreqeqeq 设为 warn,并对 _ 前缀参数提供豁免——「未使用变量」「未定义引用」「非严格相等」这类可由工具机器判定的问题交给 ESLint,技能与规则层则聚焦于机器难以判定的命名语义、结构坏味道与设计取舍,形成「机器检查 + 规范审查」的双层防线。
  3. 规则层与技能层分离rules/common/coding-style.md 末尾附有一份「代码质量检查清单」(函数 < 50 行、文件 < 800 行、嵌套 ≤ 4 层、无 mutation、无硬编码值等勾选项),与技能全文形成「短规则层 + 完整技能走查」的两级分发,Agent 可按上下文深度选择加载哪一级。

十三、落地建议:如何在你的项目中使用这套技能

结合技能文档与 ECC 的安装体系,推荐以下使用路径:

  1. 按模块安装:该技能归属 framework-language 安装模块(见 manifests/install-components.json),在 ECC 的选择性安装流程中选择框架/语言模块即可获得 coding-standards 及配套的语言/框架技能。
  2. 审查场景显式调用:对 Agent 使用元数据中的默认提示「Use $coding-standards to review code against cross-project standards」,或依赖其 allow_implicit_invocation: true 在代码质量审查任务中自动激活。
  3. 分层使用:日常快速对齐用规则层 rules/common/coding-style.md 的一页清单;完整走查、新人 onboarding、新项目初始化时加载完整技能;React 细节交给 frontend-patterns,服务端细节交给 backend-patterns/api-design
  4. 审查前自检:合并前对照 code-review.md 的清单逐项勾选,按 CRITICAL/HIGH/MEDIUM/LOW 分级处理审查意见。

总结:ECC 的 coding-standards 技能把「命名要描述性、默认不可变、KISS/DRY/YAGNI、错误显式处理、坏味道可枚举」这些跨语言公约数,组织成一份带 PASS/FAIL 对照示例、量化阈值(函数 50 行、文件 800 行、嵌套 4 层)和明确作用边界的可执行规范,并通过规则层短清单、审查清单与 ESLint 配置三层机制保证其在 Agent 工作流中被持续执行。正如文档末尾所强调的:代码质量不可谈判(Code quality is not negotiable)——清晰、可维护的代码才能支撑快速开发与有信心的重构。

登录后查看全文
热门项目推荐
相关项目推荐