clean-typescript-functions
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseClean 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 , , , , , or nesting makes intent hard to scan.
ifforwhilemapforEachreducets
// 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);
}控制流或迭代中最多保留一到两层嵌套。当、、、、或的嵌套使得意图难以识别时,使用提前返回、辅助函数、组件提取或真正的重构。
ifforwhilemapforEachreducets
// 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 or . Avoid names like unless the domain already uses that wording.
canManageAccountisEligibleForRetryisNotInvalid当条件代表领域概念时,将复杂条件提取为命名谓词。当否定的复合条件比等效的肯定形式更难阅读时,使用德摩根定律。
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;
}优先使用肯定的条件名称,如或。除非领域本身使用该表述,否则避免使用这类名称。
canManageAccountisEligibleForRetryisNotInvalidF8: 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);
}