Files
douban-login/typescript-spec.md
2025-10-24 20:23:16 +08:00

527 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 的类型系统与语言特性,降低维护成本并提升交付质量。