Code Quality
Overview
Principles and practices for writing maintainable, readable, and reliable code.
Clean Code Principles
Meaningful Names
// ❌ Cryptic names const d = new Date(); const u = getU(); const arr = data.filter(x => x.s === 'a');
// ✅ Descriptive names const currentDate = new Date(); const currentUser = getCurrentUser(); const activeUsers = users.filter(user => user.status === 'active');
// ❌ Hungarian notation (outdated) const strName = 'John'; const arrItems = []; const bIsActive = true;
// ✅ Let the type system handle types const name = 'John'; const items: Item[] = []; const isActive = true;
Functions
// ❌ Does too much
function processUserData(userId: string) {
const user = db.findUser(userId);
const orders = db.findOrders(userId);
const total = orders.reduce((sum, o) => sum + o.amount, 0);
sendEmail(user.email, Your total: ${total});
updateAnalytics(userId, total);
return { user, orders, total };
}
// ✅ Single responsibility function getUser(userId: string): User { return db.findUser(userId); }
function getUserOrders(userId: string): Order[] { return db.findOrders(userId); }
function calculateTotal(orders: Order[]): number { return orders.reduce((sum, o) => sum + o.amount, 0); }
function sendOrderSummary(user: User, total: number): void {
sendEmail(user.email, Your total: ${total});
}
// ❌ Too many parameters function createUser(name, email, age, role, department, manager, startDate) {}
// ✅ Use object parameter interface CreateUserParams { name: string; email: string; age?: number; role: Role; department: string; managerId?: string; startDate: Date; }
function createUser(params: CreateUserParams): User {}
Comments
// ❌ Redundant comment // Increment counter by 1 counter++;
// ❌ Outdated comment (code changed, comment didn't) // Returns the user's full name function getUserEmail(user: User) { return user.email; }
// ✅ Explains WHY, not WHAT // Use binary search because the list is sorted and can have 100k+ items const index = binarySearch(sortedItems, target);
// ✅ Warns about non-obvious behavior // IMPORTANT: This function mutates the input array for performance reasons function quickSort(arr: number[]): number[] { // ... }
// ✅ TODO with context // TODO(john): Remove after migration completes - tracking in JIRA-1234 const legacyAdapter = new LegacyAdapter();
SOLID Principles
Single Responsibility Principle
// ❌ Multiple responsibilities class UserManager { createUser(data: UserData) { /* DB logic / } validateEmail(email: string) { / Validation logic / } sendWelcomeEmail(user: User) { / Email logic / } generateReport(users: User[]) { / Report logic */ } }
// ✅ Single responsibility each class UserRepository { create(data: UserData): User { /* DB logic / } findById(id: string): User | null { / DB logic */ } }
class UserValidator { validateEmail(email: string): boolean { /* Validation / } validatePassword(password: string): ValidationResult { / Validation */ } }
class EmailService { sendWelcomeEmail(user: User): void { /* Email logic */ } }
class UserReportGenerator { generate(users: User[]): Report { /* Report logic */ } }
Open/Closed Principle
// ❌ Must modify to add new payment methods class PaymentProcessor { process(payment: Payment) { if (payment.type === 'credit') { // Credit card logic } else if (payment.type === 'paypal') { // PayPal logic } else if (payment.type === 'crypto') { // Crypto logic - had to modify existing code! } } }
// ✅ Open for extension, closed for modification interface PaymentMethod { process(amount: number): Promise<PaymentResult>; }
class CreditCardPayment implements PaymentMethod { async process(amount: number): Promise<PaymentResult> { /* ... */ } }
class PayPalPayment implements PaymentMethod { async process(amount: number): Promise<PaymentResult> { /* ... */ } }
// New payment method - no modification to existing code class CryptoPayment implements PaymentMethod { async process(amount: number): Promise<PaymentResult> { /* ... */ } }
class PaymentProcessor { constructor(private method: PaymentMethod) {}
async process(amount: number): Promise<PaymentResult> { return this.method.process(amount); } }
Liskov Substitution Principle
// ❌ Violates LSP - Square breaks Rectangle contract class Rectangle { constructor(public width: number, public height: number) {}
setWidth(w: number) { this.width = w; } setHeight(h: number) { this.height = h; } getArea() { return this.width * this.height; } }
class Square extends Rectangle { setWidth(w: number) { this.width = w; this.height = w; // Unexpected side effect! } setHeight(h: number) { this.width = h; this.height = h; // Unexpected side effect! } }
// ✅ Proper abstraction interface Shape { getArea(): number; }
class Rectangle implements Shape { constructor(private width: number, private height: number) {} getArea() { return this.width * this.height; } }
class Square implements Shape { constructor(private side: number) {} getArea() { return this.side * this.side; } }
Interface Segregation Principle
// ❌ Fat interface interface Worker { work(): void; eat(): void; sleep(): void; attendMeeting(): void; writeReport(): void; }
// Robot can't eat or sleep! class Robot implements Worker { work() { /* ... */ } eat() { throw new Error('Robots do not eat'); } // Forced to implement sleep() { throw new Error('Robots do not sleep'); } // ... }
// ✅ Segregated interfaces interface Workable { work(): void; }
interface Eatable { eat(): void; }
interface Sleepable { sleep(): void; }
class Human implements Workable, Eatable, Sleepable { work() { /* ... / } eat() { / ... / } sleep() { / ... */ } }
class Robot implements Workable { work() { /* ... */ } }
Dependency Inversion Principle
// ❌ High-level depends on low-level class OrderService { private db = new MySQLDatabase(); // Concrete dependency private mailer = new SendGridMailer(); // Concrete dependency
createOrder(data: OrderData) { const order = this.db.insert('orders', data); this.mailer.send(data.email, 'Order confirmed'); return order; } }
// ✅ Depend on abstractions interface Database { insert(table: string, data: unknown): unknown; find(table: string, query: unknown): unknown[]; }
interface Mailer { send(to: string, message: string): void; }
class OrderService { constructor( private db: Database, private mailer: Mailer ) {}
createOrder(data: OrderData) { const order = this.db.insert('orders', data); this.mailer.send(data.email, 'Order confirmed'); return order; } }
// Now we can inject any implementation const service = new OrderService( new PostgresDatabase(), new SESMailer() );
Code Review Best Practices
What to Look For
Code Review Checklist
Correctness
- Logic is correct and handles edge cases
- Error handling is appropriate
- No obvious bugs or regressions
Design
- Code is at the right abstraction level
- No unnecessary complexity
- Follows existing patterns in codebase
Readability
- Clear naming and intent
- Comments explain "why" not "what"
- Code is self-documenting where possible
Testing
- Adequate test coverage
- Tests are meaningful (not just coverage padding)
- Edge cases are tested
Performance
- No obvious N+1 queries or inefficiencies
- Appropriate data structures used
- Caching considered if needed
Security
- Input validation present
- No secrets in code
- Authentication/authorization correct
Giving Feedback
Good Review Comments
✅ Specific and actionable
"This loop has O(n²) complexity. Consider using a Map for O(n) lookup."
✅ Explain the why
"Let's extract this to a separate function - it makes the logic easier to test and the main function more readable."
✅ Offer alternatives
"Instead of mutating the array, consider using filter() which returns
a new array: const active = items.filter(i => i.active)"
✅ Distinguish severity
- "nit: " - Minor style issue, optional
- "suggestion: " - Good to have, not blocking
- "blocking: " - Must fix before merge
Avoid
❌ Vague criticism
"This code is confusing"
❌ Personal attacks
"You always make this mistake"
❌ No explanation
"Use a different approach"
Code Metrics
Cyclomatic Complexity
// High complexity (10+) - hard to test and maintain function processOrder(order: Order): Result { if (order.status === 'pending') { // +1 if (order.paymentMethod === 'card') { // +1 if (order.amount > 1000) { // +1 // ... } else if (order.amount > 100) { // +1 // ... } else { // ... } } else if (order.paymentMethod === 'cash') { // +1 // ... } } else if (order.status === 'processing') { // +1 // ... } // ... more branches }
// Lower complexity - extract conditions function processOrder(order: Order): Result { const processor = getProcessor(order.paymentMethod); const tier = getPricingTier(order.amount); return processor.process(order, tier); }
Metrics to Track
Metric Target Why
Cyclomatic Complexity < 10 per function Testability
Function Length < 50 lines Readability
File Length < 400 lines Maintainability
Test Coverage
80% Confidence
Duplication < 3% DRY principle
Linting & Formatting
ESLint Configuration
// .eslintrc.js module.exports = { extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', ], rules: { // Prevent bugs 'no-unused-vars': 'error', '@typescript-eslint/no-floating-promises': 'error', '@typescript-eslint/no-misused-promises': 'error',
// Code quality
'complexity': ['warn', 10],
'max-lines-per-function': ['warn', 50],
'max-depth': ['warn', 3],
// Consistency
'prefer-const': 'error',
'no-var': 'error',
} };
Pre-commit Hooks
// package.json { "husky": { "hooks": { "pre-commit": "lint-staged" } }, "lint-staged": { ".{ts,tsx}": [ "eslint --fix", "prettier --write" ], ".{json,md}": [ "prettier --write" ] } }
Related Skills
-
[[refactoring]] - Improving existing code
-
[[testing-strategies]] - Ensuring quality through tests
-
[[design-patterns]] - Proven solutions
Sharp Edges(常見陷阱)
這些是程式碼品質中最常見且代價最高的錯誤
SE-1: 過度工程 (Over-engineering)
-
嚴重度: high
-
情境: 為了「未來可能需要」而增加不必要的抽象和複雜度
-
原因: YAGNI 原則被忽視、設計模式濫用、「萬一以後要...」的心態
-
症狀:
-
簡單功能需要改動 10+ 個檔案
-
新人看不懂架構
-
程式碼比需求複雜 10 倍
-
檢測: Factory.Factory|Abstract.Abstract|interface.{.}(?=.interface.{.*})|Strategy.*Strategy
-
解法: YAGNI(You Aren't Gonna Need It)、先寫最簡單的實作、需要時再重構
SE-2: 命名不一致
-
嚴重度: medium
-
情境: 同一個概念在不同地方用不同名稱,或不同概念用相似名稱
-
原因: 沒有統一術語、多人開發沒有對齊、複製貼上沒改名
-
症狀:
-
user , customer , client , account 指同一件事
-
搜尋不到相關程式碼
-
新人經常問「這個和那個有什麼差別?」
-
檢測: (user|customer|client|account).*=.*find|(get|fetch|retrieve|load).*User
-
解法: 建立術語表(Ubiquitous Language)、Code Review 時檢查命名、重構統一命名
SE-3: 深層巢狀 (Deep Nesting)
-
嚴重度: medium
-
情境: if-else 或 callback 巢狀超過 3-4 層,難以閱讀
-
原因: 沒有提早 return、沒有抽取函數、callback hell
-
症狀:
-
需要橫向捲動才能看到程式碼
-
很難追蹤哪個 } 對應哪個 {
-
Cyclomatic complexity 超高
-
檢測: {.{.{.*{|if.*if.if.if|.then(..then(..then(
-
解法: Guard clause(提早 return)、抽取函數、使用 async/await 取代 callback
SE-4: 神奇數字/字串 (Magic Numbers)
-
嚴重度: medium
-
情境: 程式碼中直接使用意義不明的數字或字串
-
原因: 懶得定義常數、「只用一次不用抽出來」
-
症狀:
-
看到 86400 不知道是什麼(一天的秒數)
-
修改時需要全域搜尋 replace
-
同一個數字在不同地方意義不同但值相同
-
檢測: \b(86400|3600|1000|60000|1024|65535)\b|status\s*===?\s*['"][^'"]+['"]
-
解法: 抽取為有意義名稱的常數、使用 enum、集中管理設定值
SE-5: 大泥球 (Big Ball of Mud)
-
嚴重度: critical
-
情境: 程式碼沒有明確架構,所有東西混在一起,到處都有依賴
-
原因: 沒有模組化設計、趕時間「先 work 再說」、缺乏重構
-
症狀:
-
改 A 會壞 B
-
單一檔案 1000+ 行
-
所有東西都 import 所有東西
-
檢測: import.from.../../../|require(.........)|lines.>\s*1000
-
解法: 分層架構、明確的模組邊界、定期重構、嚴格的 import 規則
Validations
V-1: 禁止 console.log (生產代碼)
-
類型: regex
-
嚴重度: medium
-
模式: console.(log|debug|info)(
-
訊息: console.log should not be in production code
-
修復建議: Use a proper logger (winston, pino) or remove debug statements
-
適用: *.ts , *.js
V-2: 禁止 any 類型
-
類型: ast
-
嚴重度: high
-
模式: TSAnyKeyword
-
訊息: 'any' type defeats TypeScript's type safety
-
修復建議: Use specific type, 'unknown', or generic type parameter
-
適用: *.ts , *.tsx
V-3: 函數參數過多
-
類型: regex
-
嚴重度: medium
-
模式: function\s+\w+\s*([^)],\s[^)],\s[^)],\s[^)],\s[^)])|=>\s([^)],\s[^)],\s[^)],\s[^)],\s[^)]*)
-
訊息: Function has more than 4 parameters - consider using an object
-
修復建議: Replace multiple params with single options object: function(options: Options)
-
適用: *.ts , *.js
V-4: TODO 無追蹤
-
類型: regex
-
嚴重度: low
-
模式: //\sTODO(?!.#\d|.JIRA|.\w+-\d+)
-
訊息: TODO comment without tracking reference
-
修復建議: Add issue reference: // TODO(#123): description or create ticket
-
適用: *.ts , *.js , *.tsx , *.jsx
V-5: 深層 import 路徑
-
類型: regex
-
嚴重度: medium
-
模式: import.from\s+['"]../../../|require\s(\s*['"]../../../
-
訊息: Deep relative imports indicate poor module boundaries
-
修復建議: Use path aliases: import { X } from '@/modules/x'
-
適用: *.ts , *.js , *.tsx , *.jsx