核心原则:注释不需要也不应当重复代码上已经不言自明的信息。注释应当描述为什么要这样做,而非做了什么——背后的考量点是什么,信息的来源是哪里,从代码本身无法获知的信息。
代码注释是软件工程中最被低估的技能之一。好的注释能够将代码的可维护性提升数倍,而糟糕的注释则会成为技术债务的一部分。
| 维度 | 说明 | 示例 |
|---|---|---|
| 时间维度 | 今天的"显而易见",三个月后可能变成"谜题" | 你写的算法,三个月后连自己都看不懂 |
| 人员维度 | 代码作者与阅读者之间的知识传递 | 新团队成员如何理解业务规则 |
| 上下文维度 | 代码无法包含的外部信息 | 为什么选用这个特定的第三方库 |
| 决策维度 | 记录设计决策及其权衡 | 为什么不用更简单的方案 A |
不写注释的隐性成本:
写注释的直接成本:
ROI 结论:对于核心业务逻辑、复杂算法、关键决策点,注释的投资回报率极高。
代码本身可以清晰表达:
以下信息是代码本身无法承载的,这正是注释的价值所在:
// ❌ 糟糕的注释:重复代码已经表达的信息
/**
* get last updated time of this entity.
**/
val lastUpdated: Instant
// ✅ 优秀的注释:解释背后的考量
/**
* Need to be updated to UTC now whenever any other fields of current object changes.
* In most cases, it is automatically updated by Spring Data.
*
* It is primarily used for auditing and data syncing purpose,
* not expected to be used in any business oriented logic.
**/
val lastUpdated: Instant
// ✅ 解释业务规则来源
/**
* Calculates the settlement amount.
*
* IMPORTANT: Per regulatory requirement (CBRC-2023-047), settlement
* must be computed using T+1 exchange rate, not the transaction rate.
* This is why we query the historical rate table instead of using
* the rate stored in the transaction record.
*/
BigDecimal calculateSettlement(Transaction tx);
# ✅ 记录设计决策及其原因
# NOTE: We intentionally use a simple list here instead of a heap
# or balanced tree because:
# 1. The dataset is always small (< 100 items)
# 2. Insertion performance is not critical
# 3. Linear scan is cache-friendly and simpler to reason about
# If dataset grows beyond 1000, consider switching to SortedDict.
pending_orders = []
// TODO: Replace this workaround once upstream library fixes
// the memory leak in v3.2.1 (tracked in issue #784)
// See: https://github.com/example/lib/issues/784
function processBatch(items) {
// Current workaround: process in chunks of 100
const chunks = _.chunk(items, 100);
// ...
}
// ✅ 引用外部资源
// Implementation based on the paper "Consistent Hashing and Random Trees"
// by Karger et al. (1997): https://dl.acm.org/doi/10.1145/258533.258660
//
// We use 150 virtual nodes per physical node as recommended
// in the paper for balanced distribution.
type ConsistentHash struct {
// ...
}
这一般只是自己懒得写注释的托辞。
反驳:
代码可以解释"做什么"和"怎么做",但无法解释"为什么"。以下场景代码永远无法自解释:
结论:代码解释机制,注释解释意图。
反驳:
缓解策略:
反驳:
注释生成工具的原理,就是用代码本身已经存在的信息,以另一种格式或语言重新组织一遍。这只会提高信息的重复度,没有起到写注释应有的作用。
// ❌ 自动生成的注释:毫无价值
/**
* Gets the name.
* @return the name
*/
public String getName() { return name; }
// ✅ 手写注释:提供额外信息
/**
* Returns the display name for UI rendering.
* Note: This is NOT the unique identifier; use {@link #getId()} for that.
* The display name may contain Unicode characters and can be localized.
*/
public String getName() { return name; }
用于公共 API、库接口、框架扩展点。
目标读者:使用该代码的外部开发者
内容重点:用途、参数、返回值、异常、使用示例
格式:遵循语言特定的文档规范(Javadoc、JSDoc、docstring 等)
def calculate_compound_interest(principal, rate, time, frequency=1):
"""
Calculate compound interest with configurable compounding frequency.
Args:
principal (Decimal): Initial amount. Must be positive.
rate (Decimal): Annual interest rate as decimal (e.g., 0.05 for 5%).
time (int): Time period in years.
frequency (int): Compounding frequency per year. Default 1 (annual).
Common values: 1 (annual), 4 (quarterly), 12 (monthly), 365 (daily).
Returns:
Decimal: Final amount after compounding.
Raises:
ValueError: If principal <= 0 or rate < 0 or time < 0.
Examples:
>>> calculate_compound_interest(Decimal('1000'), Decimal('0.05'), 10)
Decimal('1628.89')
"""
用于解释代码内部的复杂逻辑、算法、关键决策。
目标读者:维护该代码的内部开发者
内容重点:设计决策、复杂算法解释、已知问题、优化说明
格式:行内注释(//)或块注释(/* */)
// ✅ 解释复杂算法
// We use a two-pass approach here:
// Pass 1: Build the frequency map in O(n) time
// Pass 2: Find the majority element using Boyer-Moore algorithm in O(n) time
// Total complexity: O(n) time, O(1) extra space
// This is optimal for the majority element problem.
int findMajority(vector<int>& nums) {
// ...
}
用于标记代码状态、待办事项、警告信息。
| 标记 | 含义 | 使用场景 |
|---|---|---|
TODO |
待实现的功能 | 已知但尚未实现的需求 |
FIXME |
需要修复的问题 | 已知缺陷或临时方案 |
HACK |
临时变通方案 | 不优雅但必要的实现 |
NOTE |
重要提示 | 需要阅读者注意的信息 |
WARNING |
潜在风险 | 可能导致问题的代码 |
REVIEW |
需要审查 | 不确定的实现,需要他人确认 |
// TODO: Add support for bulk operations once the upstream API v2 is released
// FIXME: Memory leak detected in stress testing (see JIRA-2847)
// HACK: Working around a JDK bug (JDK-8234567) - remove once fixed
// NOTE: This threshold was determined empirically; may need tuning
// WARNING: Do NOT call this method from the UI thread - it blocks
// REVIEW: Is this the right place for retry logic? Discuss in next arch meeting
位于文件顶部,描述整个模块的用途和架构定位。
// Package settlement implements the core settlement engine for cross-border
// payment transactions.
//
// Architecture:
// - SettlementCalculator: Computes net positions between parties
// - SettlementEngine: Orchestrates the settlement workflow
// - SettlementStore: Persists settlement records
//
// Design decisions:
// - All monetary amounts use Decimal to avoid floating-point errors
// - Settlement is idempotent: duplicate requests return the same result
// - Async processing via message queue for high-volume scenarios
//
// See: /docs/architecture/settlement.md for detailed design docs
package settlement
/**
* Processes a batch of payment transactions asynchronously.
*
* This method is the primary entry point for bulk payment processing.
* It validates all transactions, groups them by settlement currency,
* and submits each group to the settlement engine.
*
* ## Performance characteristics
* - Time complexity: O(n log n) where n = number of transactions
* - Memory: O(n) for the grouping phase
* - For n > 10,000, consider using [processBatchStreaming] instead
*
* ## Thread safety
* This method is thread-safe. Concurrent calls are serialized internally
* using a per-currency lock to prevent double-spending.
*
* ## Error handling
* - Invalid transactions are logged and skipped (processing continues)
* - If > 10% of transactions are invalid, the entire batch is rejected
* - Network errors trigger automatic retry with exponential backoff
*
* @param transactions List of transactions to process. Must not be empty.
* @param options Processing options. Use [BatchOptions.DEFAULT] for standard behavior.
* @return A [BatchResult] containing successful and failed transactions.
* @throws IllegalArgumentException If transactions is empty or contains null elements.
* @throws SettlementException If the settlement engine is unavailable after retries.
*
* @see BatchOptions
* @see processBatchStreaming For large batches (> 10,000 transactions)
* @since 2.3.0
*/
suspend fun processBatch(
transactions: List<Transaction>,
options: BatchOptions = BatchOptions.DEFAULT
): BatchResult {
// ...
}
// ✅ 解释非显而易见的逻辑
// Normalize to UTC because the settlement system expects UTC timestamps
// even though our domain model uses local time for user-facing fields
Instant settlementTime = transactionTime.atZone(ZoneId.systemDefault())
.toInstant();
// ✅ 解释数字常量
// Maximum batch size determined by settlement engine's gRPC message limit (4MB)
// Average transaction JSON size ~200 bytes, so 10000 * 200 = 2MB < 4MB
private static final int MAX_BATCH_SIZE = 10000;
// ✅ 解释条件判断
// Skip validation for internal transfers - they are pre-validated
// by the upstream accounting service (see ARCH-2023-045)
if (transaction.getType() == TransactionType.INTERNAL_TRANSFER) {
return ValidationResult.SKIPPED;
}
def reconcile_accounts(
local_records: List[Transaction],
remote_records: List[Transaction],
tolerance: Decimal = Decimal("0.01")
) -> ReconciliationResult:
"""Reconcile local transaction records against remote settlement data.
This function performs a three-way reconciliation:
1. Match transactions by ID and amount
2. Identify missing transactions on either side
3. Flag discrepancies within the specified tolerance
Args:
local_records: Transactions from our ledger. Must be sorted by timestamp.
remote_records: Transactions from the settlement partner. Must be sorted.
tolerance: Maximum allowed difference for amount matching. Default is
0.01 (1 cent) which is standard for USD. For JPY, use 1.0.
Returns:
ReconciliationResult containing:
- matched: Transactions that match exactly
- mismatched: Transactions with amount discrepancies
- local_only: Transactions present only in local_records
- remote_only: Transactions present only in remote_records
Raises:
ValueError: If records are not sorted or contain duplicate IDs.
ReconciliationError: If > 50% of transactions fail to match.
Example:
>>> local = load_local_transactions("2023-01-01")
>>> remote = fetch_settlement_data("partner_a", "2023-01-01")
>>> result = reconcile_accounts(local, remote, tolerance=Decimal("0.01"))
>>> assert len(result.mismatched) == 0, "Found reconciliation errors"
Note:
This function is NOT thread-safe. For concurrent reconciliation
of multiple partners, create separate Reconciler instances.
"""
# ✅ 解释异常处理
# We catch broad exceptions here because the external API returns
# various error formats (XML, JSON, plain text) depending on the
# endpoint version. The adapter normalizes all of them.
try:
response = api_client.submit(request)
except Exception as e:
logger.warning(f"API submission failed: {e}")
raise PaymentGatewayError(str(e)) from e
# ✅ 解释复杂推导式
# Build a lookup table: {(currency, date): rate} for O(1) access
# during the main processing loop. This avoids N+1 queries to the rate service.
rate_lookup = {
(r.currency, r.effective_date): r.rate
for r in historical_rates
if r.effective_date >= cutoff_date
}
/**
* Calculates the net settlement position for a given currency pair.
*
* @param {string} baseCurrency - The base currency code (e.g., 'USD')
* @param {string} quoteCurrency - The quote currency code (e.g., 'CNY')
* @param {PositionOptions} [options] - Optional calculation parameters
* @returns {Promise<NetPosition>} The calculated net position
* @throws {InvalidCurrencyError} If currency pair is not supported
* @throws {SettlementError} If settlement service is unavailable
*
* @example
* ```typescript
* const position = await calculateNetPosition('USD', 'CNY', {
* includePending: true,
* asOfDate: new Date('2023-12-31')
* });
* console.log(position.amount); // Decimal value
* ```
*
* @deprecated Since v3.0. Use {@link SettlementEngine.calculatePosition} instead.
* This method will be removed in v4.0.
*/
async function calculateNetPosition(
baseCurrency: string,
quoteCurrency: string,
options?: PositionOptions
): Promise<NetPosition> {
// ...
}
// ✅ 解释框架特定的行为
// React will batch these state updates automatically in React 18+,
// but we use flushSync here because the parent component needs
// the updated state immediately for its layout calculation.
flushSync(() => {
setItems(newItems);
setTotalCount(newItems.length);
});
// ✅ 解释类型断言
// We know this is safe because the API contract guarantees
// that all items in this array have been pre-validated
// by the middleware (see apiValidation.ts).
const validItems = items as ValidatedItem[];
// Package settlement provides the core business logic for payment settlement.
//
// The package follows a pipeline architecture:
// 1. Validation: Ensures all transactions are structurally valid
// 2. Enrichment: Adds missing metadata (exchange rates, fees)
// 3. Grouping: Organizes transactions by settlement currency and counterparty
// 4. Calculation: Computes net positions
// 5. Execution: Submits settlements to the payment rails
//
// Thread safety:
// All exported types are safe for concurrent use unless otherwise noted.
package settlement
// Calculator computes settlement amounts for cross-currency transactions.
//
// A Calculator should be created once and reused. It is safe for concurrent use.
// The zero value is NOT usable; use NewCalculator to create instances.
type Calculator struct {
// rateProvider is the source of exchange rate data.
// It is called once per unique (currency, date) pair during a calculation.
rateProvider RateProvider
// feeSchedule determines how fees are applied.
// It is consulted for each transaction based on its type and corridor.
feeSchedule FeeSchedule
}
// Calculate determines the settlement amount for a single transaction.
//
// The calculation follows the formula:
// settlement = (amount * exchange_rate) - fees - reserve
//
// Where:
// - exchange_rate is the T+1 rate per regulatory requirement
// - fees are determined by the transaction's fee tier
// - reserve is a percentage held back for chargeback protection
//
// Returns an error if:
// - The transaction fails validation
// - The exchange rate is not available for the settlement date
// - The resulting amount would be negative
func (c *Calculator) Calculate(ctx context.Context, tx Transaction) (Settlement, error) {
// ...
}
字段、方法的注释有特定的格式要求。一方面可以用于生成 API 文档,另一方面也可以用于 IDE 的代码提示。
// ❌ 错误示例:行尾注释
val lastUpdated: Instant // get last updated time of this entity.
// ❌ 错误示例:简单行注释
// get last updated time of this entity.
val lastUpdated: Instant
// ✅ 正确示例:KDoc / Javadoc 格式
/**
* Gets last updated time of this entity.
* @return Instant value in UTC.
**/
val lastUpdated: Instant
// ❌ 错误示例:自创标记格式
// ***Something to be done***: Use Instant instead of String
val lastUpdated: String
// ✅ 正确示例:遵循团队惯例
// TODO: String as it is has a customized format, but ISO-8601 should be used.
val lastUpdated: String
# ✅ 使用段落和列表提高可读性
# This function handles three special cases:
#
# 1. Weekend transactions: Processed on the next business day
# per the T+1 settlement rule.
#
# 2. Holiday transactions: Use the holiday schedule from
# the settlement calendar API.
#
# 3. Cross-border transactions: Additional FX validation
# is applied (see validate_fx_requirements).
#
# For normal transactions, the standard flow is used.
def process_transaction(tx):
pass
在 Code Review 中,注释应被检查:
# 查看某行代码的注释历史
git log -p -S "Need to be updated to UTC" -- file.kt
# 查看注释被修改的提交
git log --grep="update comment" --oneline
当多个团队协作时,注释规范尤为重要:
/**
* [ACCOUNTING-TEAM] This field is maintained by the accounting service.
* Do NOT modify directly from the payment service. Use the
* AccountingClient.updateBalance() method instead.
*
* [PAYMENT-TEAM] When adding new payment methods, ensure the
* accounting service is notified via the PaymentCompleted event.
*/
private BigDecimal accountBalance;
// [PERF] Performance-critical path. Benchmark before modifying.
// [SEC] Security-sensitive. Review with security team before changes.
// [COMPAT] Backward compatibility constraint. Breaking changes require deprecation cycle.
// [REG] Regulatory requirement. Changes need compliance review.
// [ARCH] Architecture decision. Discuss in architecture forum before changes.
现代 IDE 对注释有强大的支持:
| IDE | 功能 | 配置 |
|---|---|---|
| IntelliJ IDEA | 自动生成文档注释模板 | Settings → Editor → File and Code Templates |
| VS Code | JSDoc/TypeDoc 类型提示 | 内置支持,无需配置 |
| PyCharm | Docstring 生成和检查 | Settings → Tools → Python Integrated Tools |
| GoLand | GoDoc 预览和生成 | 内置支持 |
| 工具 | 语言 | 输出格式 |
|---|---|---|
| Javadoc | Java | HTML |
| TypeDoc | TypeScript | HTML / Markdown |
| Sphinx | Python | HTML / PDF / ePub |
| GoDoc | Go | HTML (内置) |
| Dokka | Kotlin | HTML / Markdown |
# Python: 检查文档字符串规范
pydocstyle --convention=google src/
# JavaScript: 检查 JSDoc 完整性
eslint --rule 'require-jsdoc: error' src/
# Java: 检查 Javadoc 规范
javadoc -Xdoclint:all src/
# Go: 检查 GoDoc 规范
golint ./...
# 扫描代码中的 TODO/FIXME
grep -r "TODO\|FIXME\|HACK" --include="*.py" --include="*.java" --include="*.ts" src/
# 使用专门工具
# Python: pylint --enable=W0511 (reports TODO/FIXME)
# Java: checkstyle with TodoComment check
# JS: eslint with no-warning-comments rule
# ❌ 完全重复代码
# Increment counter by 1
counter += 1
# ✅ 解释为什么需要这个操作
# Increment because we need to account for the sentinel value
# that was added at the beginning of the loop
counter += 1
// ❌ 注释与代码不符
// Returns the user's email address
public String getPhoneNumber() { ... }
// ✅ 修改代码时同步更新注释
// Returns the user's phone number in E.164 format
public String getPhoneNumber() { ... }
# ❌ 每行都注释,反而降低可读性
# Set x to 0
x = 0
# Set y to 1
y = 1
# Calculate z
z = x + y
# ✅ 只在需要时注释
# Initialize the Fibonacci sequence
x, y = 0, 1
z = x + y # Third Fibonacci number
// ❌ 用注释保留旧代码
// Old implementation:
// public void process() {
// ...
// }
// ✅ 使用版本控制,删除旧代码
// 如果需要参考旧实现,查看 git history:
// git show abc123:src/OldProcessor.java
public void process() {
// New implementation
}
# ❌ 模糊、无信息量
# Do the thing
process_data()
# ✅ 具体、清晰
# Aggregate daily transaction volumes by currency pair
# for the settlement risk report (REQ-SR-042)
process_data()
/**
* FX Rate Calculator for cross-border settlements.
*
* ## Business Context
* Cross-border payments require converting between currencies using
* exchange rates. The rate selection logic is governed by:
* - Regulatory requirements (T+1 rate for settlements)
* - Commercial agreements (contracted rates for preferred corridors)
* - Market conditions (spot rate for urgent transactions)
*
* ## Rate Selection Priority
* 1. Contracted rate (if corridor has a rate agreement)
* 2. Historical T+1 rate (for standard settlements)
* 3. Live spot rate (fallback for same-day settlement)
*
* ## Known Limitations
* - Contracted rates are cached for 1 hour (may be stale)
* - Historical rates are available from 2020-01-01 onward
* - Weekend rates use the last available business day rate
*
* ## Thread Safety
* This class is thread-safe. All mutable state is confined to
* method-local variables.
*/
public class FxRateCalculator {
/**
* Calculates the exchange rate for a settlement.
*
* Rate selection follows the priority defined in the class documentation.
* For regulatory settlements, the T+1 rate is ALWAYS used regardless
* of contracted rates (per CBRC-2023-047).
*
* @param fromCurrency Source currency code (ISO 4217, e.g., "USD")
* @param toCurrency Target currency code (ISO 4217, e.g., "CNY")
* @param settlementDate The date the settlement will be executed
* @param transactionType Determines rate selection rules
* @return The applicable exchange rate
* @throws RateNotAvailableException If no rate can be determined
* for the given parameters
*/
public BigDecimal calculateRate(
CurrencyCode fromCurrency,
CurrencyCode toCurrency,
LocalDate settlementDate,
TransactionType transactionType
) {
// Implementation...
}
}
class RiskRuleEngine:
"""Evaluates transaction risk using a configurable rule set.
## Architecture Overview
The engine uses a pipeline architecture where each transaction
flows through a series of risk rules. Rules can:
- Score: Add risk points (e.g., +10 for new device)
- Block: Reject the transaction immediately
- Challenge: Require additional authentication
- Allow: Skip remaining rules (whitelist behavior)
## Rule Ordering
Rules are evaluated in priority order (0 = highest):
1. Hard blocks (priority 0): Known fraud patterns
2. Velocity checks (priority 10): Rate limiting
3. Behavioral models (priority 20): ML-based scoring
4. Soft rules (priority 30): Device fingerprinting
## Performance
- Average evaluation time: < 50ms (p99: 200ms)
- Rule evaluation is parallelized for independent rules
- Cache hit rate for rule conditions: ~85%
## Configuration
Rules are loaded from the risk_rules table in the database.
Changes take effect within 5 minutes (cache TTL).
See: /docs/risk/rule-configuration.md
"""
def evaluate(self, transaction: Transaction) -> RiskDecision:
"""Evaluate a single transaction against all active rules.
## Decision Logic
The evaluation follows these principles:
1. Rules are evaluated in priority order
2. A BLOCK decision from any rule stops evaluation
3. CHALLENGE decisions are collected; the highest-confidence
challenge is returned if no block occurs
4. Scores are summed; if total > threshold, CHALLENGE is returned
5. If no rules trigger, ALLOW is returned
## Side Effects
- Evaluation results are logged to the risk_audit_log table
- Rule performance metrics are updated (execution time, cache hits)
- ML model features are collected for training data
Args:
transaction: The transaction to evaluate. Must have all
required fields populated (see Transaction.validate).
Returns:
RiskDecision containing:
- decision: ALLOW, CHALLENGE, or BLOCK
- score: Total risk score (0-100)
- triggered_rules: List of rules that contributed
- challenge_type: Required challenge if decision is CHALLENGE
Raises:
InvalidTransactionError: If transaction fails validation.
RuleEvaluationError: If a rule throws an unexpected exception.
This is logged and the rule is skipped (fail-open for stability).
Example:
>>> tx = Transaction(amount=Decimal("1000.00"), currency="USD")
>>> decision = engine.evaluate(tx)
>>> if decision.decision == Decision.BLOCK:
... raise PaymentRejected(decision.reason)
"""
# Implementation...
在提交代码前,检查你的注释:
"好的注释像好的代码一样,需要反复打磨。写完后,站在阅读者的角度读一遍——如果三个月后自己来看这段代码,这些注释是否足够?"
注释是开发者之间的异步沟通工具。 investing 时间在高质量的注释上,是对团队未来效率的投资。