深入剖析 Claude Code:代码演练与最佳实践(第四部分)
2026-03-31
Claude Code TypeScript 最佳实践 设计模式 代码质量
深入剖析 Claude Code:代码演练与最佳实践
系列文章第四部分:实战经验与最佳实践
通过前三篇文章,我们了解了 Claude Code 的架构、设计模式和核心功能实现。今天,让我们聚焦于代码质量,从实际代码中学习最佳实践。
设计模式实战
工具模式:统一接口的力量
Claude Code 中所有工具都实现统一接口,这带来了极大的可组合性。
// 工具接口定义
interface Tool {
name: string
description: string
parameters?: ToolParameter[]
call(params: any, context: ToolUseContext): AsyncGenerator
}
// 实际实现示例
class CustomTool implements Tool {
name = 'custom_operation'
description = 'Performs custom operation'
async* call(
params: { input: string },
context: ToolUseContext
): AsyncGenerator {
// 发送进度
yield { type: 'progress', data: 'Starting...' }
// 检查取消信号
if (context.abortController.signal.aborted) {
return
}
// 执行操作
const result = await this.process(params.input)
// 返回结果
yield { type: 'result', data: result }
}
private async process(input: string): Promise {
// 实现细节
}
}
// 工具注册
const toolRegistry = new Map()
toolRegistry.set('custom_operation', new CustomTool())
// 统一调用方式
async function executeTool(
toolName: string,
params: any,
context: ToolUseContext
): AsyncGenerator {
const tool = toolRegistry.get(toolName)
if (!tool) {
throw new Error(`Tool not found: ${toolName}`)
}
yield* tool.call(params, context)
} 策略模式:不同工具的不同执行策略
// 定义策略接口
interface ExecutionStrategy {
execute(params: any, context: ToolUseContext): Promise
}
// Bash 执行策略
class BashExecutionStrategy implements ExecutionStrategy {
async execute(params: { command: string }, context: ToolUseContext): Promise {
const { command } = params
const { signal } = context.abortController
const process = spawn(command, { shell: true })
// 监听取消信号
signal.addEventListener('abort', () => {
process.kill()
})
return new Promise((resolve, reject) => {
process.on('close', (code) => {
resolve({ exitCode: code })
})
process.on('error', reject)
})
}
}
// 文件操作策略
class FileOperationStrategy implements ExecutionStrategy {
async execute(params: { path: string; content: string }, context: ToolUseContext): Promise {
await fs.writeFile(params.path, params.content)
return { success: true }
}
}
// 策略工厂
class StrategyFactory {
private strategies = new Map()
register(toolType: string, strategy: ExecutionStrategy): void {
this.strategies.set(toolType, strategy)
}
get(toolType: string): ExecutionStrategy {
const strategy = this.strategies.get(toolType)
if (!strategy) {
throw new Error(`No strategy for: ${toolType}`)
}
return strategy
}
}
// 使用
const factory = new StrategyFactory()
factory.register('bash', new BashExecutionStrategy())
factory.register('file', new FileOperationStrategy())
const strategy = factory.get('bash')
await strategy.execute({ command: 'npm test' }, context) 观察者模式:AsyncGenerator 本质
AsyncGenerator 是观察者模式的自然实现:
// 事件发射器
class EventEmitter {
private listeners = new Map>()
on(event: string, listener: Listener): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set())
}
this.listeners.get(event)!.add(listener)
}
emit(event: string, data: any): void {
const listeners = this.listeners.get(event)
if (listeners) {
listeners.forEach(listener => listener(data))
}
}
}
// 转换为 AsyncGenerator
async function* eventToGenerator(
emitter: EventEmitter,
event: string
): AsyncGenerator {
let resolver: (value: any) => void
emitter.on(event, (data) => {
if (resolver) {
resolver(data)
}
})
while (true) {
const data = await new Promise(resolve => {
resolver = resolve
})
yield data
}
}
// 使用
const emitter = new EventEmitter()
// 在另一个线程
setTimeout(() => {
emitter.emit('data', { message: 'Hello' })
}, 1000)
// 消费
for await (const data of eventToGenerator(emitter, 'data')) {
console.log(data)
break // 只接收一次
} 命令模式:CLI 结构
import { Command } from '@commander-js/extra-typings'
// 定义命令
const program = new Command()
program
.name('claude')
.description('AI coding assistant')
.version('0.2.8')
// 添加子命令
program
.command('cost')
.description('Show cost information')
.action(showCost)
program
.command('doctor')
.description('Run diagnostics')
.action(runDoctor)
// 命令实现
async function showCost() {
const tracker = new CostTracker()
const summary = tracker.getSessionSummary()
console.log(`
Session Cost:
Tokens: ${summary.tokens}
Cost: ${summary.formatted}
`)
}
// 解析并执行
program.parse()错误处理策略
分层错误处理
// 自定义错误类型
class ToolExecutionError extends Error {
constructor(
public toolName: string,
public originalError: Error,
message?: string
) {
super(message || `Tool ${toolName} failed: ${originalError.message}`)
this.name = 'ToolExecutionError'
}
}
class ValidationError extends Error {
constructor(
public field: string,
public value: any,
message: string
) {
super(message)
this.name = 'ValidationError'
}
}
// 错误处理中间件
async function withErrorHandling(
operation: () => Promise,
context: string
): Promise {
try {
return await operation()
} catch (error) {
if (error instanceof ValidationError) {
// 验证错误:返回友好消息
throw new Error(`Validation failed for ${error.field}: ${error.message}`)
} else if (error instanceof ToolExecutionError) {
// 工具执行错误:记录并包装
logError(error, context)
throw new Error(
`Failed to execute ${error.toolName}: ${error.originalError.message}`
)
} else {
// 未知错误:记录并重新抛出
logError(error, context)
throw error
}
}
}
// 使用示例
async function executeToolSafe(
tool: Tool,
params: any,
context: ToolUseContext
): Promise {
return withErrorHandling(async () => {
// 验证参数
validateParams(params, tool.parameters)
// 执行工具
const results = []
for await (const result of tool.call(params, context)) {
results.push(result)
}
return results[results.length - 1] // 返回最后一个结果
}, `tool:${tool.name}`)
} 优雅降级
// 可选功能:失败时降级
async function getOptionalFeature(): Promise {
try {
return await loadFeature()
} catch (error) {
logError(error, 'optional-feature')
return null // 降级:不使用该功能
}
}
// 使用
const feature = await getOptionalFeature()
if (feature) {
await feature.enhanceBehavior()
} else {
// 使用基本行为
await basicBehavior()
} 用户友好的错误消息
// 错误消息格式化
function formatErrorMessage(error: Error, context: string): string {
// 根据错误类型生成不同消息
if (error.message.includes('ENOENT')) {
return `File not found: ${context}`
} else if (error.message.includes('EACCES')) {
return `Permission denied: ${context}`
} else if (error.message.includes('ETIMEDOUT')) {
return `Operation timed out. Please try again.`
}
// 默认消息
return `An error occurred: ${error.message}`
}
// 使用
try {
await readFile(path)
} catch (error) {
const userMessage = formatErrorMessage(error, path)
console.error(userMessage)
// 不会显示原始错误堆栈给用户
}Sentry 集成
// src/services/sentry.ts
import * as Sentry from '@sentry/node'
export function initSentry(): void {
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
}
// 捕获错误
export function captureError(error: Error, context?: Record): void {
Sentry.withScope(scope => {
if (context) {
Object.entries(context).forEach(([key, value]) => {
scope.setExtra(key, value)
})
}
Sentry.captureException(error)
})
}
// 使用
try {
await riskyOperation()
} catch (error) {
captureError(error, {
operation: 'riskyOperation',
userId: getCurrentUser()?.id
})
} 类型安全最佳实践
严格的类型检查
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}精心设计的接口
// 工具参数使用联合类型
type ToolParams =
| { tool: 'bash'; params: BashParams }
| { tool: 'read_file'; params: ReadFileParams }
| { tool: 'edit_file'; params: EditFileParams }
// 类型守卫
function isBashParams(params: ToolParams): params is { tool: 'bash'; params: BashParams } {
return params.tool === 'bash'
}
// 使用
function executeTool(params: ToolParams): Promise {
if (isBashParams(params)) {
return executeBash(params.params)
} else if (params.tool === 'read_file') {
return readFile(params.params)
}
// TypeScript 会确保处理所有情况
exhaustiveCheck(params)
}
// 编译时检查完整性
function exhaustiveCheck(value: never): never {
throw new Error(`Unexpected value: ${value}`)
} 泛型的正确使用
// 工具结果类型
interface ToolResult {
type: 'progress' | 'data' | 'result' | 'error'
data?: T
error?: string
}
// 工具接口
interface Tool {
name: string
description: string
call(
params: TParams,
context: ToolUseContext
): AsyncGenerator>
}
// 具体工具实现
class BashTool implements Tool<{ command: string }, { exitCode: number }> {
name = 'bash'
description = 'Execute bash commands'
async* call(
params: { command: string },
context: ToolUseContext
): AsyncGenerator> {
// TypeScript 知道 params.command 存在
// 返回类型被正确推断
yield { type: 'result', data: { exitCode: 0 } }
}
} 类型守卫和断言
// 消息类型
type Message = UserMessage | AssistantMessage | SystemMessage
interface UserMessage {
type: 'user'
content: string
}
interface AssistantMessage {
type: 'assistant'
content: string
toolUse?: ToolUseBlock[]
}
interface SystemMessage {
type: 'system'
content: string
}
// 类型守卫
function isAssistantMessage(message: Message): message is AssistantMessage {
return message.type === 'assistant'
}
// 使用
function hasToolUse(message: Message): boolean {
if (isAssistantMessage(message)) {
return message.toolUse !== undefined
}
return false
}
// 类型断言函数
function assertIsAssistantMessage(message: Message): asserts message is AssistantMessage {
if (message.type !== 'assistant') {
throw new Error('Expected assistant message')
}
}
// 使用
try {
assertIsAssistantMessage(message)
// 现在 message 被认为是 AssistantMessage
console.log(message.toolUse)
} catch (error) {
console.error('Not an assistant message')
}性能优化技巧
流式响应实现
// 不好的做法:等待完整响应
async function badExample(): Promise {
const response = await fetch(url)
const text = await response.text()
return text
}
// 好的做法:流式处理
async function* goodExample(): AsyncGenerator {
const response = await fetch(url)
const reader = response.body!.getReader()
while (true) {
const { done, value } = await reader.read()
if (done) break
const chunk = new TextDecoder().decode(value)
yield chunk
}
}
// 使用
for await (const chunk of goodExample()) {
process.stdout.write(chunk)
} 记忆化模式
import { memoize } from 'lodash-es'
// 简单记忆化
const memoizedExpensive = memoize(async (input: string) => {
return await expensiveOperation(input)
})
// 带缓存的记忆化
class Cache {
private cache = new Map()
get(key: string): any | null {
const entry = this.cache.get(key)
if (!entry) return null
if (Date.now() > entry.expiry) {
this.cache.delete(key)
return null
}
return entry.value
}
set(key: string, value: any, ttl: number): void {
this.cache.set(key, {
value,
expiry: Date.now() + ttl
})
}
}
const cache = new Cache()
async function getCachedOrCompute(
key: string,
compute: () => Promise,
ttl: number = 60000
): Promise {
const cached = cache.get(key)
if (cached !== null) {
return cached
}
const value = await compute()
cache.set(key, value, ttl)
return value
} 并行处理
// 串行处理(慢)
async function sequential(items: Item[]): Promise {
const results = []
for (const item of items) {
const result = await processItem(item)
results.push(result)
}
return results
}
// 并行处理(快)
async function parallel(items: Item[]): Promise {
return Promise.all(items.map(item => processItem(item)))
}
// 限制并发数
async function parallelWithLimit(
items: Item[],
limit: number = 10
): Promise {
const results: Result[] = []
for (let i = 0; i < items.length; i += limit) {
const batch = items.slice(i, i + limit)
const batchResults = await Promise.all(
batch.map(item => processItem(item))
)
results.push(...batchResults)
}
return results
} 资源清理
// 使用 AbortController 清理
async function withCleanup(
operation: (signal: AbortSignal) => Promise
): Promise {
const controller = new AbortController()
try {
return await operation(controller.signal)
} finally {
controller.abort()
}
}
// 使用
await withCleanup(async (signal) => {
const response = await fetch(url, { signal })
return await response.json()
})
// 清理事件监听器
class ManagedEventEmitter extends EventEmitter {
private listeners: Array<{ event: string; listener: Function }> = []
on(event: string, listener: Function): this {
super.on(event, listener)
this.listeners.push({ event, listener })
return this
}
removeAll(): void {
this.listeners.forEach(({ event, listener }) => {
this.off(event, listener)
})
this.listeners = []
}
} 测试策略
单元测试
import { describe, it, expect, vi } from 'vitest'
describe('BashTool', () => {
it('should execute command successfully', async () => {
const tool = new BashTool()
const mockContext = {
abortController: new AbortController(),
options: {}
}
// Mock spawn
vi.mock('child_process', () => ({
spawn: vi.fn(() => ({
stdout: { on: vi.fn(), emit: 'data' },
on: vi.fn((event, callback) => {
if (event === 'close') callback(0)
})
}))
}))
const results = []
for await (const result of tool.call(
{ command: 'echo hello' },
mockContext
)) {
results.push(result)
}
expect(results).toHaveLength(2) // progress + result
expect(results[1].data.exitCode).toBe(0)
})
it('should handle command timeout', async () => {
const tool = new BashTool()
const abortController = new AbortController()
// 立即取消
abortController.abort()
const results = []
for await (const result of tool.call(
{ command: 'sleep 10' },
{ abortController }
)) {
results.push(result)
}
// 应该提前退出
expect(results.length).toBeLessThan(2)
})
})Mock AI 响应
// Mock Claude API
class MockClaudeAPI {
async *create(
params: CreateMessageParams
): AsyncGenerator {
// 返回预设响应
yield {
type: 'assistant',
content: 'This is a mock response'
}
}
}
// 测试中使用
describe('QueryEngine', () => {
it('should process AI response', async () => {
const mockAPI = new MockClaudeAPI()
const engine = new QueryEngine(mockAPI)
const response = await engine.query('test message')
expect(response.content).toBe('This is a mock response')
})
}) 集成测试
describe('Tool Integration', () => {
it('should execute multiple tools in sequence', async () => {
const context = {
abortController: new AbortController(),
readFileTimestamps: {}
}
// 1. 读取文件
const readTool = new FileReadTool()
const readResults = []
for await (const result of readTool.call(
{ file_path: 'test.txt' },
context
)) {
readResults.push(result)
}
// 2. 编辑文件
const editTool = new FileEditTool()
const editResults = []
for await (const result of editTool.call(
{
file_path: 'test.txt',
old_string: 'old',
new_string: 'new'
},
context
)) {
editResults.push(result)
}
// 验证
expect(editResults[editResults.length - 1].data.success).toBe(true)
})
})代码组织
模块化设计
// 文件结构
src/
├── tools/ # 工具实现
│ ├── BashTool/
│ ├── FileReadTool/
│ └── index.ts # 工具注册
├── services/ # 服务层
│ ├── claude.ts
│ └── mcp.ts
├── utils/ # 工具函数
│ ├── config.ts
│ └── log.ts
└── types.ts # 类型定义
// 每个模块导出清晰的接口
// src/tools/index.ts
export { BashTool } from './BashTool/BashTool.js'
export { FileReadTool } from './FileReadTool/FileReadTool.js'
export type { Tool } from './Tool.js'
export function getTools(): Tool[] { /* ... */ }关注点分离
// 数据访问层
class ConfigRepository {
async load(path: string): Promise {
const content = await fs.readFile(path, 'utf-8')
return JSON.parse(content)
}
async save(path: string, config: Config): Promise {
await fs.writeFile(path, JSON.stringify(config, null, 2))
}
}
// 业务逻辑层
class ConfigService {
constructor(private repo: ConfigRepository) {}
async getProjectConfig(): Promise {
const config = await this.repo.load('.claude/config.json')
return this.validate(config)
}
private validate(config: any): ProjectConfig {
// 验证逻辑
}
}
// 表现层
async function showConfig() {
const repo = new ConfigRepository()
const service = new ConfigService(repo)
const config = await service.getProjectConfig()
console.log(config)
} 可测试性考虑
// 依赖注入使代码可测试
class ToolExecutor {
constructor(
private toolRegistry: ToolRegistry,
private permissionChecker: PermissionChecker,
private logger: Logger
) {}
async execute(toolName: string, params: any): Promise {
// 可以轻松 mock 这些依赖
if (!await this.permissionChecker.canUse(toolName)) {
throw new Error('Permission denied')
}
this.logger.info(`Executing ${toolName}`)
// ...
}
}
// 测试中
it('should check permissions', async () => {
const mockPermissionChecker = {
canUse: vi.fn().mockResolvedValue(false)
}
const executor = new ToolExecutor(
toolRegistry,
mockPermissionChecker,
logger
)
await expect(executor.execute('bash', {})).rejects.toThrow('Permission denied')
}) 代码质量检查清单
在提交代码前,确保:
- 所有函数都有类型签名
- 错误被正确处理和记录
- 异步操作支持取消
- 资源被正确清理
- 性能关键路径被优化
- 代码被适当测试
- 变量命名清晰
- 复杂逻辑有注释
- 没有重复代码
下一步
在最后一篇文章中,我们将总结:
- 关键经验总结:从项目中提取的核心经验
- 实践应用:如何将这些经验应用到自己的项目
- 未来方向:AI Agent 的发展趋势
- 学习资源:进一步学习的建议
系列目录