clean-typescript-functions

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Clean Functions

简洁函数

F1: Too Many Arguments

F1: 参数过多

ts
// Bad - too many parameters
function createUser(
  name: string,
  email: string,
  age: number,
  country: string,
  timezone: string,
  language: string,
  newsletter: boolean
) {
  // ...
}

// Good - use a typed object
type UserData = {
  name: string;
  email: string;
  age: number;
  country: string;
  timezone: string;
  language: string;
  newsletter: boolean;
};

function createUser(data: UserData) {
  // ...
}
More than 3 arguments is a design smell. Keep simple call sites simple, but use a typed object when arguments form a concept or callers cannot read the meaning at a glance.
ts
// Bad - too many parameters
function createUser(
  name: string,
  email: string,
  age: number,
  country: string,
  timezone: string,
  language: string,
  newsletter: boolean
) {
  // ...
}

// Good - use a typed object
type UserData = {
  name: string;
  email: string;
  age: number;
  country: string;
  timezone: string;
  language: string;
  newsletter: boolean;
};

function createUser(data: UserData) {
  // ...
}
参数超过3个是设计上的不良信号。保持简单的调用简洁明了,但当参数形成一个概念,或者调用者无法一眼理解其含义时,使用类型化对象。

F2: No Output Arguments

F2: 避免输出参数

Don't modify arguments as side effects. Return values instead.
ts
type Report = {
  content: string;
};

// Bad - modifies argument
function appendFooter(report: Report): void {
  report.content += "\n---\nGenerated by System";
}

// Good - returns new value
function withFooter(report: Report): Report {
  return {
    ...report,
    content: `${report.content}\n---\nGenerated by System`,
  };
}
不要通过副作用修改参数,应返回值替代。
ts
type Report = {
  content: string;
};

// Bad - modifies argument
function appendFooter(report: Report): void {
  report.content += "\n---\nGenerated by System";
}

// Good - returns new value
function withFooter(report: Report): Report {
  return {
    ...report,
    content: `${report.content}\n---\nGenerated by System`,
  };
}

F3: No Flag Arguments

F3: 避免布尔标志参数

Boolean flags mean your function does at least two things.
ts
// Bad - function does two different things
function render(isTest: boolean) {
  if (isTest) {
    renderTestPage();
  } else {
    renderProductionPage();
  }
}

// Good - split into two functions
function renderTestPage() {}
function renderProductionPage() {}
Boolean state is fine when it is the domain value being set or returned. It becomes a flag argument when it selects different behavior inside the function.
布尔标志意味着你的函数至少在做两件事。
ts
// Bad - function does two different things
function render(isTest: boolean) {
  if (isTest) {
    renderTestPage();
  } else {
    renderProductionPage();
  }
}

// Good - split into two functions
function renderTestPage() {}
function renderProductionPage() {}
当布尔值是要设置或返回的领域值时是没问题的,但当它在函数内部选择不同行为时,就变成了标志参数。

F4: Delete Dead Functions

F4: 删除无用函数

If it's not called, delete it. No "just in case" code. Git preserves history.
如果函数未被调用,就删除它。不要保留‘以防万一’的代码,Git会保留历史记录。

F5: Reduce Nesting

F5: 减少嵌套

Aim for at most one or two nested levels in control flow or iterations. Use early returns, helper functions, component extraction, or a real refactor when
if
,
for
,
while
,
map
,
forEach
, or
reduce
nesting makes intent hard to scan.
ts
// Bad - the useful path is buried
function getInvoiceTotal(invoice: Invoice): number {
  if (invoice.status !== "void") {
    if (invoice.items.length > 0) {
      return invoice.items.reduce((total, item) => total + item.price, 0);
    }
  }

  return 0;
}

// Good - guard clauses keep the normal path visible
function getInvoiceTotal(invoice: Invoice): number {
  if (invoice.status === "void") {
    return 0;
  }

  if (invoice.items.length === 0) {
    return 0;
  }

  return invoice.items.reduce((total, item) => total + item.price, 0);
}
控制流或迭代中最多保留一到两层嵌套。当
if
for
while
map
forEach
reduce
的嵌套使得意图难以识别时,使用提前返回、辅助函数、组件提取或真正的重构。
ts
// Bad - the useful path is buried
function getInvoiceTotal(invoice: Invoice): number {
  if (invoice.status !== "void") {
    if (invoice.items.length > 0) {
      return invoice.items.reduce((total, item) => total + item.price, 0);
    }
  }

  return 0;
}

// Good - guard clauses keep the normal path visible
function getInvoiceTotal(invoice: Invoice): number {
  if (invoice.status === "void") {
    return 0;
  }

  if (invoice.items.length === 0) {
    return 0;
  }

  return invoice.items.reduce((total, item) => total + item.price, 0);
}

F6: Keep One Level Of Abstraction Per Function

F6: 每个函数保持单一抽象层次

A function should not mix high-level policy with low-level details. Extract the details or inline the abstraction so every line reads at the same conceptual level.
ts
// Bad - business rule, string parsing, and storage detail are interleaved
function activateUser(rawUser: string, storage: Storage) {
  const [id, email] = rawUser.split(",");
  if (!email.includes("@")) {
    throw new Error("Invalid email");
  }
  storage.setItem(`user:${id}`, JSON.stringify({ id, email, active: true }));
}

// Good - this function reads at the policy level
function activateUser(rawUser: string, storage: UserStorage) {
  const user = parseUser(rawUser);
  assertCanActivate(user);
  storage.save(activate(user));
}
函数不应混合高层策略与底层细节。提取细节或内联抽象,让每一行代码都处于相同的概念层次。
ts
// Bad - business rule, string parsing, and storage detail are interleaved
function activateUser(rawUser: string, storage: Storage) {
  const [id, email] = rawUser.split(",");
  if (!email.includes("@")) {
    throw new Error("Invalid email");
  }
  storage.setItem(`user:${id}`, JSON.stringify({ id, email, active: true }));
}

// Good - this function reads at the policy level
function activateUser(rawUser: string, storage: UserStorage) {
  const user = parseUser(rawUser);
  assertCanActivate(user);
  storage.save(activate(user));
}

F7: Name And Simplify Complex Conditions

F7: 命名并简化复杂条件

Extract complex conditions into named predicates when the condition represents a domain idea. Use De Morgan's laws when a negated compound condition is harder to read than the equivalent positive form.
ts
const isPrivilegedUser = user.role === "admin" || user.role === "owner";

// Bad - negated compound condition
if (!(isPrivilegedUser || user.hasBillingAccess)) {
  return false;
}

// Good - De Morgan's law makes each rejected case visible
if (!isPrivilegedUser && !user.hasBillingAccess) {
  return false;
}
ts
// Bad - hard to tell what rule is being checked
if (!(user.role === "admin" || user.role === "owner") || user.suspended || !account.active) {
  return false;
}

// Good - the condition has a name
if (!canManageAccount(user, account)) {
  return false;
}

function canManageAccount(user: User, account: Account): boolean {
  const hasPrivilegedRole = user.role === "admin" || user.role === "owner";
  return hasPrivilegedRole && !user.suspended && account.active;
}
Prefer positive condition names such as
canManageAccount
or
isEligibleForRetry
. Avoid names like
isNotInvalid
unless the domain already uses that wording.
当条件代表领域概念时,将复杂条件提取为命名谓词。当否定的复合条件比等效的肯定形式更难阅读时,使用德摩根定律。
ts
const isPrivilegedUser = user.role === "admin" || user.role === "owner";

// Bad - negated compound condition
if (!(isPrivilegedUser || user.hasBillingAccess)) {
  return false;
}

// Good - De Morgan's law makes each rejected case visible
if (!isPrivilegedUser && !user.hasBillingAccess) {
  return false;
}
ts
// Bad - hard to tell what rule is being checked
if (!(user.role === "admin" || user.role === "owner") || user.suspended || !account.active) {
  return false;
}

// Good - the condition has a name
if (!canManageAccount(user, account)) {
  return false;
}

function canManageAccount(user: User, account: Account): boolean {
  const hasPrivilegedRole = user.role === "admin" || user.role === "owner";
  return hasPrivilegedRole && !user.suspended && account.active;
}
优先使用肯定的条件名称,如
canManageAccount
isEligibleForRetry
。除非领域本身使用该表述,否则避免使用
isNotInvalid
这类名称。

F8: Separate Commands From Queries

F8: 分离命令与查询

A function should usually either answer a question or change state, not both. If a read also creates, saves, logs, caches, navigates, or mutates, make that behavior explicit in the name or split the operation.
ts
// Bad - a getter changes storage
function getSession(userId: string): Session {
  return sessions.get(userId) ?? createSession(userId);
}

// Good - side effect is explicit
function getOrCreateSession(userId: string): Session {
  return sessions.get(userId) ?? createSession(userId);
}
函数通常应要么回答问题,要么改变状态,而不是两者兼具。如果读取操作同时包含创建、保存、日志记录、缓存、导航或突变行为,请在名称中明确该行为,或者拆分操作。
ts
// Bad - a getter changes storage
function getSession(userId: string): Session {
  return sessions.get(userId) ?? createSession(userId);
}

// Good - side effect is explicit
function getOrCreateSession(userId: string): Session {
  return sessions.get(userId) ?? createSession(userId);
}

F9: Keep Side Effects Explicit And Isolated

F9: 让副作用明确且隔离

Prefer pure transforms for domain calculations. When a function must touch I/O, time, randomness, storage, navigation, logging, global state, or mutation, keep that effect near a boundary or make it obvious at the call site.
ts
// Bad - calculation secretly writes
function calculateInvoiceTotal(invoice: Invoice): Money {
  auditLog.write("invoice-total-calculated");
  return sumInvoiceLines(invoice.lines);
}

// Good - pure calculation, explicit effect
function calculateInvoiceTotal(invoice: Invoice): Money {
  return sumInvoiceLines(invoice.lines);
}
领域计算优先使用纯转换。当函数必须涉及I/O、时间、随机性、存储、导航、日志记录、全局状态或突变时,让该效果靠近边界,或在调用位置使其明显可见。
ts
// Bad - calculation secretly writes
function calculateInvoiceTotal(invoice: Invoice): Money {
  auditLog.write("invoice-total-calculated");
  return sumInvoiceLines(invoice.lines);
}

// Good - pure calculation, explicit effect
function calculateInvoiceTotal(invoice: Invoice): Money {
  return sumInvoiceLines(invoice.lines);
}