first commit
This commit is contained in:
526
typescript-spec.md
Normal file
526
typescript-spec.md
Normal file
@@ -0,0 +1,526 @@
|
||||
# TypeScript 编码最佳实践(参考 Clean Code TypeScript)
|
||||
|
||||
> 本规范以 [Clean Code TypeScript](https://github.com/labs42io/clean-code-typescript) 为基础,结合常见业务场景与团队协作经验,为 TypeScript 项目提供可执行的最佳实践。所有规则都配有简短示例,便于在代码生成、评审与重构中快速对照。
|
||||
|
||||
## 1. 变量与常量
|
||||
|
||||
- **语义化命名**:使用易读且准确的词汇描述变量含义,避免首字母缩写或无意义命名。
|
||||
示例:
|
||||
```ts
|
||||
const remainingTrialDays = totalTrialDays - usedTrialDays;
|
||||
```
|
||||
- **统一词汇表述**:同一概念使用一致命名,减少认知成本。
|
||||
示例:
|
||||
```ts
|
||||
type SubscriptionPlan = 'free' | 'pro';
|
||||
const currentPlan: SubscriptionPlan = 'pro';
|
||||
```
|
||||
- **可搜索名称**:对常量、配置使用具备辨识度的名称,方便全局检索。
|
||||
示例:
|
||||
```ts
|
||||
const DEFAULT_SESSION_TTL_MS = 30 * 60 * 1000;
|
||||
```
|
||||
- **解释性变量**:将复杂表达式抽取为具名变量,让意图自说明。
|
||||
示例:
|
||||
```ts
|
||||
const hasIncompleteProfile = !user.email || !user.phone;
|
||||
if (hasIncompleteProfile) {
|
||||
promptProfileCompletion();
|
||||
}
|
||||
```
|
||||
- **避免多余上下文**:变量名中不要重复所在模块的上下文信息。
|
||||
示例:
|
||||
```ts
|
||||
// 数据存于 billing 模块,无需前缀 billing
|
||||
const invoiceTotal = calcInvoiceTotal(items);
|
||||
```
|
||||
- **默认参数优于短路**:使用函数默认值替代 `||` 等短路写法,让可选参数更清晰。
|
||||
示例:
|
||||
```ts
|
||||
function sendReport(recipient: string, { retries = 3 }: { retries?: number } = {}) {
|
||||
/* ... */
|
||||
}
|
||||
```
|
||||
|
||||
## 2. 函数设计
|
||||
|
||||
- **函数只做一件事**:将复杂操作拆分为独立函数,保持逻辑内聚。
|
||||
示例:
|
||||
```ts
|
||||
function createUser(dto: CreateUserInput) {
|
||||
validateUser(dto);
|
||||
const user = buildUser(dto);
|
||||
return saveUser(user);
|
||||
}
|
||||
```
|
||||
- **限制参数数量**:理想状态 ≤ 2 个参数;若超出,改用具名对象并定义类型。
|
||||
示例:
|
||||
```ts
|
||||
interface ScheduleOptions {
|
||||
triggerAt: Date;
|
||||
retryCount?: number;
|
||||
}
|
||||
function scheduleReminder(userId: string, options: ScheduleOptions) { /* ... */ }
|
||||
```
|
||||
- **描述性函数名**:函数名应说明行为与结果,避免依赖注释解释。
|
||||
示例:
|
||||
```ts
|
||||
function calculateLoyaltyDiscount(order: Order): number { /* ... */ }
|
||||
```
|
||||
- **避免标志参数**:用枚举或拆分函数替代布尔开关,消除逻辑分支迷雾。
|
||||
示例:
|
||||
```ts
|
||||
type ExportMode = 'draft' | 'final';
|
||||
function exportDocument(mode: ExportMode) { /* ... */ }
|
||||
```
|
||||
- **规避副作用**:默认生成纯函数,必要副作用通过返回值或专门模块处理。
|
||||
示例:
|
||||
```ts
|
||||
function formatDisplayName(user: User): string {
|
||||
return `${user.firstName} ${user.lastName}`;
|
||||
}
|
||||
```
|
||||
- **封装条件表达式**:复杂条件抽离成具名函数或变量,降低认知负担。
|
||||
示例:
|
||||
```ts
|
||||
function isVipEligible(order: Order) {
|
||||
return order.total > 200 && order.customerTier === 'gold';
|
||||
}
|
||||
if (isVipEligible(order)) {
|
||||
applyVipBenefits(order);
|
||||
}
|
||||
```
|
||||
- **提前返回与正向逻辑**:优先正向判断并提前返回,避免深层嵌套与否定条件。
|
||||
示例:
|
||||
```ts
|
||||
function ensureActive(user: User) {
|
||||
if (!user.isActive) {
|
||||
throw new Error('User is inactive');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
```
|
||||
- **删除死代码**:及时移除无调用、无覆盖的函数或分支,保持代码整洁。
|
||||
示例:
|
||||
```ts
|
||||
// 检测到无引用后移除旧方法
|
||||
// function legacyPayment() { /* ... */ }
|
||||
```
|
||||
|
||||
## 3. 对象与数据结构
|
||||
|
||||
- **使用访问器**:通过 getter/setter 提供受控访问,便于增加校验与日志。
|
||||
示例:
|
||||
```ts
|
||||
class Account {
|
||||
constructor(private _balance = 0) {}
|
||||
get balance(): number {
|
||||
return this._balance;
|
||||
}
|
||||
deposit(amount: number) {
|
||||
this._balance += amount;
|
||||
}
|
||||
}
|
||||
```
|
||||
- **隐藏实现细节**:使用 `private` 或 `#` 字段,阻止外部直接修改内部状态。
|
||||
示例:
|
||||
```ts
|
||||
class TokenStore {
|
||||
#tokens = new Map<string, string>();
|
||||
set(token: string, value: string) {
|
||||
this.#tokens.set(token, value);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **偏好不可变数据**:使用 `Readonly<T>` 或解构复制,避免共享状态污染。
|
||||
示例:
|
||||
```ts
|
||||
const updatedOrder: Readonly<Order> = { ...order, status: 'shipped' };
|
||||
```
|
||||
- **类型守卫保障安全**:自定义类型守卫封装校验逻辑,提升复用与可读性。
|
||||
示例:
|
||||
```ts
|
||||
function isAppConfig(value: unknown): value is AppConfig {
|
||||
return typeof value === 'object' && value !== null && 'env' in value;
|
||||
}
|
||||
```
|
||||
- **利用字面量约束取值**:针对有限集合使用联合字面量或 `enum`,防止魔法字符串。
|
||||
示例:
|
||||
```ts
|
||||
type OrderStatus = 'pending' | 'paid' | 'failed';
|
||||
const status: OrderStatus = 'paid';
|
||||
```
|
||||
|
||||
## 4. 类与面向对象
|
||||
|
||||
- **组合优先于继承**:通过组合与接口扩展行为,降低继承层级带来的窜扰。
|
||||
示例:
|
||||
```ts
|
||||
class UserNotifier {
|
||||
constructor(private readonly emailSender: EmailSender) {}
|
||||
notifyWelcome(user: User) {
|
||||
return this.emailSender.sendWelcome(user.email);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **保持类职责单一**:类只关注一个领域概念,额外能力拆分为协作类。
|
||||
示例:
|
||||
```ts
|
||||
class PasswordHasher {
|
||||
hash(value: string): Promise<string> {
|
||||
return bcrypt.hash(value, 12);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **接口驱动契约**:用接口定义外部依赖,便于替换实现与注入假对象测试。
|
||||
示例:
|
||||
```ts
|
||||
interface EmailSender {
|
||||
sendWelcome(email: string): Promise<void>;
|
||||
}
|
||||
class SmtpEmailSender implements EmailSender { /* ... */ }
|
||||
```
|
||||
- **方法链只在自然场景使用**:当返回 `this` 能提升易用性(如构建器)时才启用链式调用。
|
||||
示例:
|
||||
```ts
|
||||
class QueryBuilder {
|
||||
private filters: string[] = [];
|
||||
where(condition: string) {
|
||||
this.filters.push(condition);
|
||||
return this;
|
||||
}
|
||||
build() {
|
||||
return this.filters.join(' AND ');
|
||||
}
|
||||
}
|
||||
```
|
||||
- **访问修饰符显式化**:始终标识 `public`/`private`/`protected`,并优先使用 `readonly` 属性。
|
||||
示例:
|
||||
```ts
|
||||
class FeatureFlag {
|
||||
constructor(public readonly key: string, private readonly enabled: boolean) {}
|
||||
isEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. SOLID 原则
|
||||
|
||||
- **单一职责(SRP)**:模块应聚焦唯一变更原因,降低耦合。
|
||||
示例:
|
||||
```ts
|
||||
class InvoicePrinter {
|
||||
constructor(private readonly formatter: InvoiceFormatter) {}
|
||||
print(invoice: Invoice) {
|
||||
return this.formatter.format(invoice);
|
||||
}
|
||||
}
|
||||
```
|
||||
- **开放-封闭(OCP)**:通过扩展实现新需求,而非修改已有代码。
|
||||
示例:
|
||||
```ts
|
||||
interface PricingStrategy {
|
||||
calculate(order: Order): number;
|
||||
}
|
||||
class VipPricingStrategy implements PricingStrategy { /* ... */ }
|
||||
```
|
||||
- **里氏替换(LSP)**:子类型必须兼容父类型契约,避免破坏预期。
|
||||
示例:
|
||||
```ts
|
||||
function printTotals(calculator: PricingStrategy, order: Order) {
|
||||
console.log(calculator.calculate(order));
|
||||
}
|
||||
```
|
||||
- **接口隔离(ISP)**:提供小而专注的接口,避免强迫消费者实现无关方法。
|
||||
示例:
|
||||
```ts
|
||||
interface ReadonlyRepository<T> {
|
||||
findById(id: string): Promise<T | null>;
|
||||
list(): Promise<T[]>;
|
||||
}
|
||||
```
|
||||
- **依赖反转(DIP)**:高层模块依赖抽象,低层实现通过注入传入。
|
||||
示例:
|
||||
```ts
|
||||
class PaymentService {
|
||||
constructor(private readonly gateway: PaymentGateway) {}
|
||||
pay(invoice: Invoice) {
|
||||
return this.gateway.charge(invoice);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 异步与并发
|
||||
|
||||
- **优先使用 async/await**:将 Promise 链写成同步流程,更易读并易于错误处理。
|
||||
示例:
|
||||
```ts
|
||||
export async function loadUser(id: string): Promise<User> {
|
||||
const response = await fetch(`/api/users/${id}`);
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
- **集中处理错误**:在靠近调用边界处统一捕获和记录,保留上下文。
|
||||
示例:
|
||||
```ts
|
||||
try {
|
||||
await processPayment(orderId);
|
||||
} catch (error) {
|
||||
logger.error('Payment failed', { orderId, error });
|
||||
throw new PaymentFailedError(orderId, error);
|
||||
}
|
||||
```
|
||||
- **并发任务显式管理**:使用 `Promise.allSettled` 或自定义控制,避免静默丢失异常。
|
||||
示例:
|
||||
```ts
|
||||
const results = await Promise.allSettled(tasks.map(executeTask));
|
||||
const failures = results.filter((r): r is PromiseRejectedResult => r.status === 'rejected');
|
||||
```
|
||||
- **支持超时与取消**:利用 `AbortController` 或第三方库防止长时间挂起。
|
||||
示例:
|
||||
```ts
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
||||
await fetch(url, { signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
```
|
||||
- **避免竞争条件**:在共享资源上使用锁、队列或幂等操作保障一致性。
|
||||
示例:
|
||||
```ts
|
||||
await lock.acquire('invoice:' + invoiceId, async () => {
|
||||
await issueInvoice(invoiceId);
|
||||
});
|
||||
```
|
||||
|
||||
## 7. 错误处理
|
||||
|
||||
- **不忽略错误**:捕获后要记录、包装或重新抛出,切勿空 catch。
|
||||
示例:
|
||||
```ts
|
||||
try {
|
||||
await cache.save(key, value);
|
||||
} catch (error) {
|
||||
metrics.increment('cache.save.failure');
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
- **抛得早,捕获得晚**:靠近错误发生点抛出,自上层统一处理。
|
||||
示例:
|
||||
```ts
|
||||
function ensureHasToken(token?: string): asserts token is string {
|
||||
if (!token) {
|
||||
throw new AuthError('Missing token');
|
||||
}
|
||||
}
|
||||
```
|
||||
- **提供上下文信息**:传递自定义错误类型或消息,方便排查。
|
||||
示例:
|
||||
```ts
|
||||
class PaymentFailedError extends Error {
|
||||
constructor(public readonly orderId: string, cause: unknown) {
|
||||
super(`Payment failed for order ${orderId}`);
|
||||
this.name = 'PaymentFailedError';
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
```
|
||||
- **使用 `Result` 或 `Either` 模式(可选)**:在复杂流程中用显式返回承载错误,减少异常穿透。
|
||||
示例:
|
||||
```ts
|
||||
type Result<T> = { ok: true; value: T } | { ok: false; error: Error };
|
||||
```
|
||||
|
||||
## 8. 格式化与一致性
|
||||
|
||||
- **自动化格式化**:采用 `prettier` 或团队约定,保持一致缩进与换行。
|
||||
示例:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
||||
}
|
||||
}
|
||||
```
|
||||
- **启用严格 lint**:结合 `eslint` 与 `@typescript-eslint` 推行关键规则。
|
||||
示例:
|
||||
```json
|
||||
{
|
||||
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"]
|
||||
}
|
||||
```
|
||||
- **避免深层嵌套**:通过提前返回或提取函数减少缩进层数。
|
||||
示例:
|
||||
```ts
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
if (!user.isActive) {
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
```
|
||||
- **统一命名风格**:类型与类使用帕斯卡命名,函数与变量使用驼峰命名。
|
||||
示例:
|
||||
```ts
|
||||
class PaymentGateway {}
|
||||
function processPayment() { /* ... */ }
|
||||
```
|
||||
- **集中导出入口**:通过 `index.ts` 组织导出,控制模块暴露面。
|
||||
示例:
|
||||
```ts
|
||||
export { processPayment } from './processPayment';
|
||||
export type { PaymentRequest } from './types';
|
||||
```
|
||||
|
||||
## 9. 注释与文档
|
||||
|
||||
- **注释意图而非实现**:仅在业务规则或约束难以通过代码表达时补充说明。
|
||||
示例:
|
||||
```ts
|
||||
// 使用结算日的下一工作日作为默认扣款日期
|
||||
const defaultChargeDate = getNextBusinessDay(settlementDate);
|
||||
```
|
||||
- **使用 JSDoc/TS Doc**:对外暴露的 API 写明输入、输出与异常。
|
||||
示例:
|
||||
```ts
|
||||
/**
|
||||
* 创建新的租户并返回租户 ID。
|
||||
* @throws TenantLimitExceededError 当达到租户上限时抛出。
|
||||
*/
|
||||
export async function createTenant(input: CreateTenantInput): Promise<string> { /* ... */ }
|
||||
```
|
||||
- **不要注释掉代码**:移除暂时不用的逻辑,依赖版本控制追踪历史。
|
||||
示例:
|
||||
```ts
|
||||
// 删除旧实现,改由 git 历史追溯
|
||||
```
|
||||
- **链接示例或测试**:复杂模块在注释中指向参考用例或文档。
|
||||
示例:
|
||||
```ts
|
||||
// 更多用法见 tests/invoice/generateInvoice.test.ts
|
||||
generateInvoice(orderId);
|
||||
```
|
||||
|
||||
## 10. 测试策略
|
||||
|
||||
- **安排-执行-断言 (AAA)**:测试结构清晰,便于阅读与维护。
|
||||
示例:
|
||||
```ts
|
||||
it('calculates total price', () => {
|
||||
const items = [{ price: 10 }, { price: 5 }]; // Arrange
|
||||
const result = totalPrice(items); // Act
|
||||
expect(result).toBe(15); // Assert
|
||||
});
|
||||
```
|
||||
- **一个测试一个概念**:每个测试聚焦单一行为,失败时易于定位原因。
|
||||
示例:
|
||||
```ts
|
||||
it('throws when user is inactive', () => {
|
||||
expect(() => ensureActive({ isActive: false } as User)).toThrow();
|
||||
});
|
||||
```
|
||||
- **利用假对象隔离依赖**:通过实现接口的假对象或 mock 控制外部交互。
|
||||
示例:
|
||||
```ts
|
||||
class FakeEmailSender implements EmailSender {
|
||||
public sent: string[] = [];
|
||||
async sendWelcome(email: string) { this.sent.push(email); }
|
||||
}
|
||||
```
|
||||
- **覆盖边界条件**:针对 null、异常路径与类型缩小时的行为编写测试。
|
||||
示例:
|
||||
```ts
|
||||
it('returns empty list when no orders found', () => {
|
||||
expect(loadOrders([])).toEqual([]);
|
||||
});
|
||||
```
|
||||
- **类型驱动验证**:关键类型转换或守卫应有对应测试,确保类型假设成立。
|
||||
示例:
|
||||
```ts
|
||||
it('accepts valid config objects', () => {
|
||||
const config = { env: 'prod' };
|
||||
expect(isAppConfig(config)).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
## 11. 架构最佳实践
|
||||
|
||||
- **清晰分层**:按领域、应用、基础设施划分模块,控制依赖方向仅从外层指向内层。
|
||||
示例:
|
||||
```ts
|
||||
// application/useCases/createInvoice.ts
|
||||
import { Invoice } from '../domain/invoice';
|
||||
import type { InvoiceRepository } from '../domain/ports';
|
||||
```
|
||||
- **组合根集中依赖注入**:在应用入口集中装配依赖,避免在业务代码中到处 `new` 实现。
|
||||
示例:
|
||||
```ts
|
||||
export function createInvoiceService(): InvoiceService {
|
||||
const repository = new PrismaInvoiceRepository();
|
||||
return new InvoiceService(repository);
|
||||
}
|
||||
```
|
||||
- **领域对象显式建模**:使用类型与不可变对象表达核心概念,让业务规则留在领域层。
|
||||
示例:
|
||||
```ts
|
||||
type Invoice = Readonly<{
|
||||
id: string;
|
||||
amount: number;
|
||||
issuedAt: Date;
|
||||
}>;
|
||||
```
|
||||
- **边界 DTO 与映射**:在接口适配层定义 DTO,与领域对象之间通过映射转换,保持领域纯净。
|
||||
示例:
|
||||
```ts
|
||||
type InvoiceDto = { id: string; amount: number; issued_at: string };
|
||||
|
||||
function toInvoice(dto: InvoiceDto): Invoice {
|
||||
return { id: dto.id, amount: dto.amount, issuedAt: new Date(dto.issued_at) };
|
||||
}
|
||||
```
|
||||
- **横切关注集中处理**:将日志、缓存、鉴权等横切逻辑通过中间件或装饰器统一管理。
|
||||
示例:
|
||||
```ts
|
||||
export function withCaching<TArgs extends unknown[], TResult>(
|
||||
fn: (...args: TArgs) => Promise<TResult>,
|
||||
) {
|
||||
return async (...args: TArgs) => cache.remember(JSON.stringify(args), () => fn(...args));
|
||||
}
|
||||
```
|
||||
- **配置与环境隔离**:统一从配置模块读取环境变量,并提供类型安全的访问接口。
|
||||
示例:
|
||||
```ts
|
||||
export const config = {
|
||||
payment: {
|
||||
apiKey: process.env.PAYMENT_API_KEY ?? '',
|
||||
timeoutMs: Number(process.env.PAYMENT_TIMEOUT_MS ?? 5000),
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
|
||||
## 附录:AI 辅助生成代码提示
|
||||
|
||||
- **在提示中植入约束**:明确声明需使用 `strict` TypeScript、限制参数数量、禁止 `any`。
|
||||
示例:
|
||||
```txt
|
||||
请实现 strict TypeScript 的订单接口,禁止 any,函数不超过 3 个参数,并写出必要类型定义。
|
||||
```
|
||||
- **要求输出结构化**:提示 AI 同步生成类型、实现与测试用例。
|
||||
示例:
|
||||
```txt
|
||||
输出包含类型定义、主要函数实现,以及 Jest 测试示例。
|
||||
```
|
||||
- **生成后立即校验**:运行 `tsc --noEmit`、`eslint` 与测试脚本,验证生成代码质量。
|
||||
示例:
|
||||
```bash
|
||||
npx tsc --noEmit && npm run lint && npm test
|
||||
```
|
||||
- **迭代式对话**:先让 AI 生成数据结构与接口,再补实现与异常处理,最后检查测试通过。
|
||||
示例:
|
||||
```txt
|
||||
第一步:请先提供 API 接口类型定义。
|
||||
第二步:基于上一步补充实现与错误处理。
|
||||
```
|
||||
|
||||
> 按照本规范执行,可以在保证代码整洁的前提下,充分利用 TypeScript 的类型系统与语言特性,降低维护成本并提升交付质量。
|
||||
Reference in New Issue
Block a user