DevToolBox免费
博客

代码整洁之道:命名规范、SOLID 原则、代码异味、重构与最佳实践

24 分钟阅读作者 DevToolBox Team

整洁代码:编写可维护软件的原则、实践与模式

掌握整洁代码原则:命名规范、函数设计(SRP、小函数)、代码异味与重构、SOLID 原则、DRY/KISS/YAGNI、注释与文档、错误处理模式、测试与代码质量、代码审查最佳实践、契约式设计、整洁架构分层,以及包含重构前后对比的实战示例。

TL;DR — 60 秒速览整洁代码原则
  • 命名即文档:变量、函数、类的名字应该揭示意图,消除猜测
  • 函数要小、只做一件事、保持单一抽象层级
  • SOLID 原则是面向对象设计的基石:SRP、OCP、LSP、ISP、DIP
  • DRY 消除重复,KISS 抵抗过度工程,YAGNI 防止投机设计
  • 代码异味是深层设计问题的信号——识别并通过重构消除它们
  • 错误处理优先使用异常而非错误码,永远不要返回或传递 null
核心要点
  • 整洁代码的核心是可读性——代码读写比例约为 10:1
  • 好的命名能消除注释的需要:如果你需要注释来解释代码,先尝试重命名
  • 每个函数应该是一个小故事:一个标题(名字)、一个情节(逻辑)、一个结局(返回值)
  • SOLID 原则帮助创建松耦合、高内聚的系统
  • 重构是持续的过程,不是一次性事件——遵循童子军规则
  • 代码审查是团队提升代码质量的最有效手段
  • 整洁架构让系统可测试、独立于框架,并对变化有弹性

1. 命名规范:让代码自文档化

命名是编程中最重要也最被低估的技能。一个好的名字可以消除对注释的需要,而一个糟糕的名字会让每个读代码的人浪费时间。名字应该揭示意图、防止误导、并且可以搜索。

变量命名规则

// Bad: What does d mean? What are these numbers?
const d = 86400;
const x = users.filter(u => u.a > 18);
let flag = true;

// Good: Intention-revealing names
const SECONDS_PER_DAY = 86400;
const adultUsers = users.filter(user => user.age > 18);
let isEligibleForDiscount = true;

函数命名规则

// Bad: vague verb, unclear purpose
function process(data) { /* ... */ }
function handle(req) { /* ... */ }
function doStuff(items) { /* ... */ }

// Good: verb + noun, describes action
function calculateMonthlyRevenue(transactions) { /* ... */ }
function validateEmailAddress(email) { /* ... */ }
function sendWelcomeNotification(user) { /* ... */ }

布尔值与类命名

// Booleans should read as yes/no questions
const isActive = true;
const hasPermission = user.roles.includes("admin");
const canEditDocument = !doc.isLocked && hasPermission;
const shouldRetry = attempts < MAX_RETRIES;

// Classes: nouns that describe what they represent
class InvoiceGenerator { /* ... */ }    // Good
class UserRepository { /* ... */ }       // Good
class OrderValidator { /* ... */ }       // Good
class DataManager { /* ... */ }          // Bad: too generic
class Utility { /* ... */ }              // Bad: meaningless
规则
揭示意图d, temp, valelapsedDays, currentUser
避免误导accountList (not a List)accounts, accountGroup
可搜索7, e, tMAX_RETRIES, error, task
可发音genymdhms, modymdhmsgenerationTimestamp
一致性fetch/get/retrieve mixedget everywhere

2. 函数设计:小、专注、单一职责

函数是代码的基本构建单元。整洁的函数应该很小(理想情况下不超过 20 行),只做一件事,参数尽量少,不产生副作用,并在一个抽象层级上运作。

重构前:一个做太多事情的函数

// Bad: This function validates, transforms, saves, AND sends email
function processOrder(orderData) {
  // Validation
  if (!orderData.items || orderData.items.length === 0) {
    throw new Error("No items");
  }
  if (!orderData.customer.email) {
    throw new Error("No email");
  }
  for (const item of orderData.items) {
    if (item.quantity <= 0) throw new Error("Bad quantity");
    if (item.price < 0) throw new Error("Bad price");
  }

  // Calculation
  let total = 0;
  for (const item of orderData.items) {
    total += item.price * item.quantity;
  }
  const tax = total * 0.1;
  const finalTotal = total + tax;

  // Save to database
  const order = db.orders.insert({
    ...orderData,
    total: finalTotal,
    tax,
    status: "pending",
    createdAt: new Date()
  });

  // Send email
  emailService.send({
    to: orderData.customer.email,
    subject: "Order Confirmation",
    body: "Your order total is " + finalTotal
  });

  return order;
}

重构后:每个函数只做一件事

// Good: Orchestrator function at a high abstraction level
function processOrder(orderData) {
  validateOrder(orderData);
  const pricing = calculateOrderPricing(orderData.items);
  const order = saveOrder(orderData, pricing);
  sendOrderConfirmation(order);
  return order;
}

// Each function is small, focused, and testable
function validateOrder(orderData) {
  if (!orderData.items?.length) {
    throw new InvalidOrderError("Order must contain items");
  }
  if (!orderData.customer?.email) {
    throw new InvalidOrderError("Customer email required");
  }
  orderData.items.forEach(validateLineItem);
}

function validateLineItem(item) {
  if (item.quantity <= 0) throw new InvalidOrderError("Quantity must be positive");
  if (item.price < 0) throw new InvalidOrderError("Price cannot be negative");
}

function calculateOrderPricing(items) {
  const subtotal = items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  const tax = subtotal * TAX_RATE;
  return { subtotal, tax, total: subtotal + tax };
}

函数参数:越少越好

// Bad: Too many positional arguments
function createUser(name, email, age, role, department, isActive) {
  /* ... */
}
createUser("Alice", "a@b.com", 30, "admin", "eng", true);

// Good: Use an options object for 3+ parameters
function createUser({ name, email, age, role, department, isActive }) {
  /* ... */
}
createUser({
  name: "Alice",
  email: "a@b.com",
  age: 30,
  role: "admin",
  department: "eng",
  isActive: true,
});

3. 代码异味与重构

代码异味(Code Smell)是 Martin Fowler 提出的概念,指代码中暗示更深层设计问题的表面迹象。异味本身不是 Bug,但它们增加了 Bug 出现的概率和维护的难度。识别异味是重构的第一步。

异味症状重构方法
长方法超过 20 行,多层嵌套Extract Method
大类多个不相关的职责Extract Class
重复代码相似逻辑在多处出现Extract Method / Template Method
长参数列表超过 3 个参数Introduce Parameter Object
特性依恋方法过多使用其他类的数据Move Method
基本类型偏执用字符串/数字代替领域对象Replace with Value Object
开关语句长 switch/if-else 链Replace with Polymorphism
死代码永远不会执行的代码Delete it

重构示例:消除 Switch 语句

// Before: Switch statement that will grow with each new type
function calculateShippingCost(order) {
  switch (order.shippingType) {
    case "standard":
      return order.weight * 1.5;
    case "express":
      return order.weight * 3.0 + 5;
    case "overnight":
      return order.weight * 5.0 + 15;
    default:
      throw new Error("Unknown shipping type");
  }
}

// After: Strategy pattern — open for extension, closed for modification
const shippingStrategies = {
  standard:  (weight) => weight * 1.5,
  express:   (weight) => weight * 3.0 + 5,
  overnight: (weight) => weight * 5.0 + 15,
};

function calculateShippingCost(order) {
  const strategy = shippingStrategies[order.shippingType];
  if (!strategy) {
    throw new UnknownShippingTypeError(order.shippingType);
  }
  return strategy(order.weight);
}

4. SOLID 原则详解

SOLID 是面向对象设计的五个核心原则,由 Robert C. Martin 整理推广。这些原则帮助创建松耦合、高内聚、易扩展的系统。即使在函数式编程和现代 JavaScript/TypeScript 中,SOLID 的思想同样适用。

S — 单一职责原则 (SRP)

一个类或模块应该只有一个改变的理由。如果一个类同时处理业务逻辑和数据库访问,它就有两个改变的理由。

// Violates SRP: Report class does formatting AND data access
class Report {
  getData() { /* queries database */ }
  formatAsHTML() { /* builds HTML string */ }
  formatAsPDF() { /* generates PDF */ }
  sendEmail() { /* sends report via email */ }
}

// Follows SRP: Each class has one responsibility
class ReportDataProvider {
  getData() { /* queries database */ }
}

class HTMLReportFormatter {
  format(data) { /* builds HTML string */ }
}

class PDFReportFormatter {
  format(data) { /* generates PDF */ }
}

class ReportEmailSender {
  send(report, recipient) { /* sends report via email */ }
}

O — 开闭原则 (OCP)

软件实体应该对扩展开放,对修改关闭。添加新功能时不应该修改已有的、经过测试的代码。

// Violates OCP: Must modify this function for every new shape
function calculateArea(shape) {
  if (shape.type === "circle") return Math.PI * shape.radius ** 2;
  if (shape.type === "rectangle") return shape.width * shape.height;
  // Adding triangle requires modifying this function
}

// Follows OCP: New shapes extend without modifying existing code
interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}
  area() { return Math.PI * this.radius ** 2; }
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  area() { return this.width * this.height; }
}

// Adding Triangle requires zero changes to existing code
class Triangle implements Shape {
  constructor(private base: number, private height: number) {}
  area() { return 0.5 * this.base * this.height; }
}

L — 里氏替换原则 (LSP)

子类型必须能够替换其基类型而不破坏程序的正确性。经典的反例是 Square 继承 Rectangle——设置宽度不应意外改变高度。

I — 接口隔离原则 (ISP)

客户端不应该被迫依赖它不使用的接口。宁可有多个专用的小接口,也不要一个大而全的接口。

// Violates ISP: Printer interface forces all implementations
// to implement methods they may not support
interface Printer {
  print(doc: Document): void;
  scan(doc: Document): void;
  fax(doc: Document): void;
  staple(doc: Document): void;
}

// Follows ISP: Segregated interfaces
interface Printable {
  print(doc: Document): void;
}
interface Scannable {
  scan(doc: Document): void;
}
interface Faxable {
  fax(doc: Document): void;
}

// Simple printer only implements what it needs
class SimplePrinter implements Printable {
  print(doc: Document) { /* ... */ }
}

// Multi-function device implements multiple interfaces
class MultiFunctionDevice implements Printable, Scannable, Faxable {
  print(doc: Document) { /* ... */ }
  scan(doc: Document) { /* ... */ }
  fax(doc: Document) { /* ... */ }
}

D — 依赖倒置原则 (DIP)

高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

// Violates DIP: High-level OrderService depends on low-level MySQLDatabase
class OrderService {
  private db = new MySQLDatabase(); // tightly coupled
  save(order) { this.db.query("INSERT INTO ..."); }
}

// Follows DIP: Depend on abstraction, inject implementation
interface OrderRepository {
  save(order: Order): Promise<void>;
  findById(id: string): Promise<Order | null>;
}

class OrderService {
  constructor(private repo: OrderRepository) {} // injected
  async placeOrder(order: Order) {
    await this.repo.save(order);
  }
}

// Now you can swap implementations freely
class MySQLOrderRepo implements OrderRepository { /* ... */ }
class MongoOrderRepo implements OrderRepository { /* ... */ }
class InMemoryOrderRepo implements OrderRepository { /* ... */ } // for tests

5. DRY、KISS 与 YAGNI

这三个原则是软件开发的黄金三角。DRY 消除重复,KISS 抵抗复杂性,YAGNI 防止过度设计。它们互相补充,但也需要平衡——过度追求 DRY 可能导致不必要的抽象,违反 KISS。

原则含义违反的信号
DRY每条知识只在一个地方表达改一个逻辑需要改多处
KISS选择最简单的可行方案团队成员难以理解代码
YAGNI不到需要时不构建存在未使用的抽象层或功能

DRY 的正确与过度应用

// Good DRY: Extract shared validation logic
function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// Used in multiple places — single source of truth
function registerUser(data) { validateEmail(data.email); /* ... */ }
function updateProfile(data) { validateEmail(data.email); /* ... */ }
function inviteUser(email) { validateEmail(email); /* ... */ }

// Bad DRY (over-abstraction): These look similar but serve
// different purposes and will diverge over time
// DO NOT force them into one generic function
function formatUserForAdmin(user) {
  return { name: user.name, email: user.email, role: user.role };
}
function formatUserForPublic(user) {
  return { name: user.name, avatar: user.avatar };
}
// These should stay separate — they change for different reasons

6. 注释与文档

好的代码应该是自解释的。注释不是用来解释代码做什么(What),而是解释为什么这样做(Why)。如果你需要注释来解释一段代码做什么,优先考虑重命名变量或提取函数来让代码自文档化。

好注释 vs 坏注释

// BAD: Restating the code (noise comment)
// Increment counter by one
counter += 1;

// BAD: Journal comments (use git log instead)
// 2024-01-15 - Added feature X
// 2024-01-16 - Fixed bug in feature X

// BAD: Commented-out code (delete it, git has history)
// const oldValue = calculateLegacy(data);
// if (oldValue > threshold) { sendAlert(); }

// GOOD: Explain WHY, not WHAT
// We use a 30-second timeout because the payment gateway
// occasionally takes 20+ seconds during peak hours
const PAYMENT_TIMEOUT_MS = 30_000;

// GOOD: Warn about consequences
// WARNING: This cache is shared across all tenants.
// Flushing it will cause a ~5 second latency spike.
function flushGlobalCache() { /* ... */ }

// GOOD: Explain complex business rules
// Tax-exempt if: registered non-profit AND purchase
// is for educational purposes (IRS ruling 2023-47)
function isTaxExempt(org, purchase) { /* ... */ }

// GOOD: TODO with context and ownership
// TODO(@alice): Replace with streaming API once backend
// supports SSE (tracked in JIRA-1234, ETA: Q2 2026)

7. 错误处理模式

错误处理是整洁代码中经常被忽视的部分。好的错误处理应该使代码更健壮而不是更混乱。核心原则:使用异常而非错误码、永远不要返回或传递 null、在正确的抽象层级处理错误。

自定义异常类层次结构

// Define a domain-specific error hierarchy
class AppError extends Error {
  constructor(
    message: string,
    public readonly code: string,
    public readonly statusCode: number = 500,
    public readonly isOperational: boolean = true
  ) {
    super(message);
    this.name = this.constructor.name;
  }
}

class NotFoundError extends AppError {
  constructor(resource: string, id: string) {
    super(
      `\${resource} with id \${id} not found`,
      "RESOURCE_NOT_FOUND",
      404
    );
  }
}

class ValidationError extends AppError {
  constructor(public readonly fields: Record<string, string>) {
    super("Validation failed", "VALIDATION_ERROR", 400);
  }
}

// Usage: clear, specific, informative
throw new NotFoundError("User", userId);
throw new ValidationError({ email: "Invalid format", age: "Must be positive" });

永远不要返回 null

// Bad: Caller must always check for null
function findUser(id: string): User | null {
  return db.users.find(u => u.id === id) || null;
}
// Every call site needs: if (user === null) { ... }

// Better: Throw for "must exist" scenarios
function getUser(id: string): User {
  const user = db.users.find(u => u.id === id);
  if (!user) throw new NotFoundError("User", id);
  return user;
}

// Better: Return empty collection instead of null
function getOrdersByUser(userId: string): Order[] {
  return db.orders.filter(o => o.userId === userId); // [] not null
}

// Better: Use Optional/Result pattern for "may not exist"
type Result<T, E = Error> =
  | { success: true; data: T }
  | { success: false; error: E };

function parseConfig(raw: string): Result<Config> {
  try {
    return { success: true, data: JSON.parse(raw) };
  } catch (e) {
    return { success: false, error: new Error("Invalid config") };
  }
}

8. 测试与代码质量

测试是整洁代码的守护者。没有测试的重构是冒险行为。测试代码本身也应该是整洁的——可读、可维护、遵循 AAA(Arrange-Act-Assert)模式。测试不仅验证正确性,还作为代码的活文档。

AAA 模式与清晰的测试

// Clean test: readable, focused, follows AAA
describe("OrderPricingService", () => {
  describe("calculateTotal", () => {
    it("applies percentage discount to subtotal", () => {
      // Arrange
      const items = [
        { name: "Widget", price: 100, quantity: 2 },
        { name: "Gadget", price: 50, quantity: 1 },
      ];
      const discount = { type: "percentage", value: 10 };

      // Act
      const total = calculateTotal(items, discount);

      // Assert
      expect(total).toBe(225); // (200 + 50) * 0.9
    });

    it("does not allow negative totals from over-discount", () => {
      const items = [{ name: "Widget", price: 10, quantity: 1 }];
      const discount = { type: "fixed", value: 50 };

      const total = calculateTotal(items, discount);

      expect(total).toBe(0); // Floor at zero
    });
  });
});

测试命名规范

模式示例
应该...当..."should return 404 when user not found"
给定...当...那么..."given empty cart, when checkout, then throws"
动词 + 条件"rejects invalid email format"

测试覆盖率目标应为 80%,但不要为了数字而测试琐碎代码。聚焦于业务关键路径、边界条件和错误处理。每个 Bug 修复应伴随一个防止回归的测试。

9. 代码审查最佳实践

代码审查是团队提升代码质量最有效的手段。好的审查关注设计、可读性和正确性,而不是纠结格式问题(那些应该由 linter 和 formatter 自动处理)。

审查者应关注审查者应避免
设计和架构选择缩进和格式(交给工具)
边界条件和错误处理个人风格偏好
命名和可读性吹毛求疵的小问题
测试覆盖和质量重写他人代码
安全和性能影响情绪化评论

审查者清单

# Code Review Checklist

## Correctness
- [ ] Does the code do what it claims to do?
- [ ] Are edge cases handled (null, empty, negative, overflow)?
- [ ] Are race conditions possible in concurrent code?

## Design
- [ ] Does it follow SOLID principles?
- [ ] Is the abstraction level appropriate?
- [ ] Could this be simplified without losing functionality?

## Readability
- [ ] Are names intention-revealing?
- [ ] Can I understand this without asking the author?
- [ ] Are functions small and focused?

## Testing
- [ ] Are there tests for happy path AND failure cases?
- [ ] Do tests cover the new code adequately?
- [ ] Are tests readable and maintainable?

## Security
- [ ] Is user input validated and sanitized?
- [ ] Are secrets kept out of code?
- [ ] Are SQL/NoSQL injection risks mitigated?

10. 契约式设计

契约式设计(Design by Contract)由 Bertrand Meyer 提出,通过定义前置条件(Preconditions)、后置条件(Postconditions)和不变量(Invariants)来明确函数和类的契约。这让代码行为可预测,Bug 更早被发现。

// Design by Contract with runtime assertions
class BankAccount {
  private balance: number;

  constructor(initialBalance: number) {
    // Precondition
    assert(initialBalance >= 0, "Initial balance must be non-negative");
    this.balance = initialBalance;
    // Invariant established
    this.checkInvariant();
  }

  withdraw(amount: number): void {
    // Preconditions
    assert(amount > 0, "Withdrawal amount must be positive");
    assert(amount <= this.balance, "Insufficient funds");

    const previousBalance = this.balance;
    this.balance -= amount;

    // Postcondition
    assert(
      this.balance === previousBalance - amount,
      "Balance must decrease by exactly the withdrawal amount"
    );
    // Invariant preserved
    this.checkInvariant();
  }

  private checkInvariant(): void {
    assert(this.balance >= 0, "Invariant: balance must never be negative");
  }
}

function assert(condition: boolean, message: string): asserts condition {
  if (!condition) throw new ContractViolationError(message);
}

在 TypeScript 中,类型系统充当了编译时的契约。结合 Zod 或 io-ts 等库进行运行时验证,可以在系统边界(API 入口、用户输入、外部数据)强制执行契约。

// Zod: Runtime contracts at system boundaries
import { z } from "zod";

const CreateUserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
});

type CreateUserInput = z.infer<typeof CreateUserSchema>;

// At the API boundary, validate and parse
function createUserHandler(req: Request) {
  const input = CreateUserSchema.parse(req.body);
  // input is now typed AND validated
  // No null checks needed downstream
  return userService.create(input);
}

11. 整洁架构分层

整洁架构(Clean Architecture)由 Robert C. Martin 提出,将系统组织为同心圆层级。核心思想是依赖规则:依赖必须指向内部。内层不能知道外层的存在。这让核心业务逻辑独立于框架、数据库和 UI。

层级内容示例
实体核心业务规则、领域模型Order, User, Product
用例应用特定业务逻辑PlaceOrder, RegisterUser
接口适配器控制器、Presenter、网关OrderController, UserPresenter
框架与驱动Web 框架、数据库、外部 APIExpress, PostgreSQL, Stripe API

项目结构示例

src/
  domain/                # Entities & business rules (innermost)
    entities/
      Order.ts           # Pure domain model, no dependencies
      User.ts
    value-objects/
      Money.ts
      EmailAddress.ts

  application/           # Use cases (depends only on domain)
    use-cases/
      PlaceOrder.ts      # Orchestrates domain logic
      RegisterUser.ts
    ports/               # Interfaces for external dependencies
      OrderRepository.ts # Interface, not implementation
      PaymentGateway.ts

  infrastructure/        # Adapters & frameworks (outermost)
    persistence/
      PostgresOrderRepo.ts  # Implements OrderRepository
    payment/
      StripeGateway.ts      # Implements PaymentGateway
    web/
      controllers/
        OrderController.ts  # HTTP -> Use Case
      middleware/
        auth.ts

用例实现示例

// application/use-cases/PlaceOrder.ts
// Depends ONLY on domain entities and port interfaces
import { Order } from "../domain/entities/Order";
import { OrderRepository } from "../ports/OrderRepository";
import { PaymentGateway } from "../ports/PaymentGateway";
import { NotificationService } from "../ports/NotificationService";

export class PlaceOrderUseCase {
  constructor(
    private orderRepo: OrderRepository,
    private payment: PaymentGateway,
    private notifications: NotificationService
  ) {}

  async execute(input: PlaceOrderInput): Promise<Order> {
    // 1. Create domain entity (validates business rules)
    const order = Order.create(input.items, input.customer);

    // 2. Process payment through port
    const payment = await this.payment.charge(
      order.total,
      input.paymentMethod
    );

    // 3. Persist through port
    order.confirmPayment(payment.id);
    await this.orderRepo.save(order);

    // 4. Notify through port
    await this.notifications.sendOrderConfirmation(order);

    return order;
  }
}

12. 实战重构示例

以下是三个真实世界的重构场景,展示如何将混乱的代码逐步变成整洁的代码。每个示例都包含重构前后的对比和应用的原则。

示例 1:提取早期返回(守卫子句)

// Before: Deep nesting, hard to follow
function getDiscount(customer) {
  let discount = 0;
  if (customer !== null) {
    if (customer.isActive) {
      if (customer.orders.length > 10) {
        if (customer.loyaltyYears >= 5) {
          discount = 0.25;
        } else {
          discount = 0.15;
        }
      } else {
        discount = 0.05;
      }
    }
  }
  return discount;
}

// After: Guard clauses eliminate nesting
function getDiscount(customer) {
  if (!customer || !customer.isActive) return 0;
  if (customer.orders.length <= 10) return 0.05;
  if (customer.loyaltyYears >= 5) return 0.25;
  return 0.15;
}

示例 2:替换魔法数字和基本类型偏执

// Before: Magic numbers, unclear intent
function calculatePrice(quantity, type) {
  if (type === 1) return quantity * 29.99;
  if (type === 2) return quantity * 49.99;
  if (type === 3) return quantity * 99.99 * 0.9;
  return 0;
}

// After: Named constants, value objects, clear semantics
const PricingTier = {
  BASIC:      { name: "Basic",      unitPrice: 29.99, discount: 0 },
  STANDARD:   { name: "Standard",   unitPrice: 49.99, discount: 0 },
  ENTERPRISE: { name: "Enterprise", unitPrice: 99.99, discount: 0.1 },
} as const;

type TierKey = keyof typeof PricingTier;

function calculatePrice(quantity: number, tier: TierKey): number {
  const { unitPrice, discount } = PricingTier[tier];
  const subtotal = quantity * unitPrice;
  return subtotal * (1 - discount);
}

示例 3:用多态替换条件逻辑

// Before: Growing if-else chain for notification types
function sendNotification(type, recipient, message) {
  if (type === "email") {
    const html = buildEmailHTML(message);
    smtpClient.send(recipient.email, html);
    logSentEmail(recipient, message);
  } else if (type === "sms") {
    const text = truncate(message, 160);
    twilioClient.send(recipient.phone, text);
    logSentSMS(recipient, message);
  } else if (type === "push") {
    const payload = { title: "New Message", body: message };
    firebaseClient.send(recipient.deviceToken, payload);
    logSentPush(recipient, message);
  }
  // Adding "slack" requires modifying this function
}

// After: Strategy pattern — each channel is independent
interface NotificationChannel {
  send(recipient: Recipient, message: string): Promise<void>;
}

class EmailChannel implements NotificationChannel {
  async send(recipient, message) {
    const html = buildEmailHTML(message);
    await smtpClient.send(recipient.email, html);
  }
}

class SMSChannel implements NotificationChannel {
  async send(recipient, message) {
    await twilioClient.send(recipient.phone, truncate(message, 160));
  }
}

// Adding Slack: zero changes to existing code
class SlackChannel implements NotificationChannel {
  async send(recipient, message) {
    await slackClient.postMessage(recipient.slackId, message);
  }
}

// Orchestrator is simple and stable
class NotificationService {
  constructor(private channels: Map<string, NotificationChannel>) {}
  async send(type: string, recipient: Recipient, message: string) {
    const channel = this.channels.get(type);
    if (!channel) throw new Error(`Unknown channel: \${type}`);
    await channel.send(recipient, message);
  }
}

总结

整洁代码不是一次性的成就,而是一种持续的纪律。遵循童子军规则——每次离开代码时,让它比你发现时更干净一点。从好的命名开始,保持函数小而专注,应用 SOLID 和 DRY/KISS/YAGNI 原则,编写有意义的测试,并在代码审查中互相提升。

整洁架构让你的系统可测试、可替换、对变化有弹性。契约式设计让行为可预测。重构是安全的——只要你有测试覆盖。最重要的是,整洁代码是对团队的尊重:你写的每一行代码,都会被其他人(包括未来的你)阅读数十次。

开始行动:选择你代码库中最混乱的一个文件,应用本文中的一个原则进行重构。然后重复。这就是通往整洁代码的道路。

常见问题

What is clean code and why does it matter?

Clean code is code that is easy to read, understand, and modify. It matters because developers spend far more time reading code than writing it — studies suggest the ratio is 10:1. Clean code reduces bugs, lowers onboarding time for new team members, decreases maintenance costs, and makes refactoring safer. Writing clean code is a professional discipline that pays dividends throughout the lifetime of a software project.

What are the SOLID principles in software design?

SOLID is an acronym for five design principles: Single Responsibility Principle (a class should have one reason to change), Open/Closed Principle (open for extension, closed for modification), Liskov Substitution Principle (subtypes must be substitutable for their base types), Interface Segregation Principle (prefer many specific interfaces over one general interface), and Dependency Inversion Principle (depend on abstractions, not concrete implementations). Together they promote loosely coupled, highly cohesive, and maintainable code.

What is the difference between DRY, KISS, and YAGNI?

DRY (Don't Repeat Yourself) means every piece of knowledge should have a single authoritative representation — avoid duplicating logic. KISS (Keep It Simple, Stupid) means prefer the simplest solution that works — avoid unnecessary complexity. YAGNI (You Aren't Gonna Need It) means don't build features or abstractions until you actually need them. These three principles complement each other: DRY eliminates duplication, KISS fights over-engineering, and YAGNI prevents speculative design.

How do I identify code smells and when should I refactor?

Code smells are symptoms of deeper design problems. Common smells include: long methods (over 20 lines), large classes, duplicate code, long parameter lists (more than 3 parameters), feature envy (a method using another class's data more than its own), and primitive obsession (overusing primitives instead of domain objects). Refactor when you encounter a smell during development (Boy Scout Rule), before adding new features to affected code, or when bugs cluster in a particular module.

What are good naming conventions for variables, functions, and classes?

Good names are intention-revealing, pronounceable, and searchable. Variables should describe what they hold (userAge, not x). Functions should describe what they do using verb phrases (calculateTotalPrice, not process). Classes should be nouns describing what they represent (InvoiceGenerator, not Manager). Booleans should read as questions (isActive, hasPermission). Avoid abbreviations, single-letter names (except loop indices), and generic names like data, info, or temp.

How should I write functions for clean code?

Clean functions should be small (ideally under 20 lines), do exactly one thing (Single Responsibility), operate at one level of abstraction, have descriptive names, take few arguments (0-2 ideal, 3 maximum), have no side effects, and either return a value (query) or change state (command) but not both (Command-Query Separation). Extract nested logic into well-named helper functions. Each function should read like a paragraph in a well-written essay.

What are the best practices for error handling in clean code?

Prefer exceptions over error codes for cleaner control flow. Write try-catch blocks first when implementing error-prone code. Create custom exception classes for domain-specific errors. Never return null — use Optional/Maybe types or throw exceptions instead. Never pass null as a function argument. Log errors with full context (what, where, why) at appropriate levels. Handle errors at the right level of abstraction — low-level code throws, high-level code catches and handles.

How do clean architecture layers work and why are they important?

Clean Architecture organizes code into concentric layers: Entities (core business rules), Use Cases (application-specific logic), Interface Adapters (controllers, presenters, gateways), and Frameworks/Drivers (web frameworks, databases, external services). The dependency rule states that dependencies must point inward — inner layers must not know about outer layers. This structure makes the codebase testable without frameworks, independent of UI and database choices, and resilient to external changes.

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterJSTypeScript to JavaScriptTSJSON to TypeScript

相关文章

软件设计模式指南:创建型、结构型与行为型模式

全面的设计模式指南,涵盖工厂、建造者、单例、适配器、装饰器、代理、外观、观察者、策略、命令、状态模式,附 TypeScript 和 Python 实例。

软件测试策略指南:单元测试、集成测试、E2E、TDD 与 BDD

全面的测试策略指南,涵盖单元测试、集成测试、端到端测试、TDD、BDD、测试金字塔、Mock、覆盖率、CI 管线和性能测试,附 Jest/Vitest/Playwright/Cypress 示例。

数据结构与算法指南:数组、树、图、哈希表与大 O 表示法

全面的数据结构与算法指南。学习数组、链表、树、图、哈希表、堆、栈、队列、大 O 分析、排序和搜索算法,附 TypeScript 和 Python 代码实例。