Clean Code — Viết Code Người Khác (Và Chính Bạn) Có Thể Đọc
Nguyên tắc viết code sạch: đặt tên, function nhỏ, DRY, KISS — áp dụng cho mọi ngôn ngữ.
“Code sạch không phải viết cho máy tính — nó viết cho con người.” — Robert C. Martin
1. Đặt Tên — Khó Nhất Trong Lập Trình#
Biến — Cho Biết Nó Chứa Gì#
// ❌ Tệ
const d = new Date();
const arr = [1, 2, 3];
const fn = () => {};
const data = fetchData();
// ✅ Tốt
const currentDate = new Date();
const userIds = [1, 2, 3];
const handleSubmit = () => {};
const userProfile = await fetchUserProfile();typescriptBoolean — Câu Hỏi Có/Không#
// ❌ Tệ
const flag = true;
const status = false;
const login = true;
// ✅ Tốt
const isActive = true;
const hasPermission = false;
const isLoggedIn = true;typescriptHàm — Mô Tả Hành Động#
// ❌ Tệ
function data(id: number) { ... }
function handle(id: number) { ... }
// ✅ Tốt
function getUserById(id: number) { ... }
function deleteUser(id: number) { ... }
function sendEmailNotification(email: string) { ... }typescript2. Function Nhỏ — Một Hàm Một Việc#
// ❌ Tệ — function làm quá nhiều việc
function processOrder(orderId: number) {
// Validate
const order = db.orders.findById(orderId);
if (!order) throw new Error('Order not found');
// Tính toán
const total = order.items.reduce((sum, item) => sum + item.price * item.qty, 0);
const discount = total > 100 ? total * 0.1 : 0;
const finalTotal = total - discount;
// Gửi email
const user = db.users.findById(order.userId);
sendEmail(user.email, `Total: ${finalTotal}`);
// Cập nhật stock
order.items.forEach(item => {
db.products.decrementStock(item.productId, item.qty);
});
// Log
logger.info(`Order ${orderId} processed`);
}
// ✅ Tốt — tách thành nhiều hàm nhỏ
function processOrder(orderId: number) {
const order = getOrderOrThrow(orderId);
const total = calculateTotal(order);
const finalTotal = applyDiscount(total);
notifyUser(order.userId, finalTotal);
updateInventory(order.items);
logProcessing(orderId);
}typescript3. DRY — Don’t Repeat Yourself#
// ❌ Tệ — code lặp
function validateEmail(email: string) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function createUser(data: any) {
if (!validateEmail(data.email)) throw new Error('Invalid email');
// ...
}
function updateUser(data: any) {
if (!validateEmail(data.email)) throw new Error('Invalid email');
// ...
}
// ✅ Tốt — extract validation
function validateUserInput(data: any) {
const errors: string[] = [];
if (!validateEmail(data.email)) errors.push('Invalid email');
if (data.age < 18) errors.push('Must be 18+');
return errors;
}
function createUser(data: any) {
const errors = validateUserInput(data);
if (errors.length) throw new Error(errors.join(', '));
// ...
}typescript4. Comments — Code Tự Giải Thích#
// ❌ Tệ — comment giải thích điều code đã nói
// Set counter to 0
let counter = 0;
// Loop through users
users.forEach(user => {
// Check if user is active
if (user.isActive) {
// Increment counter
counter++;
}
});
// ✅ Tốt — comment giải thích WHY, không phải WHAT
// Sử dụng offset-based pagination vì cursor-based không support sort
const users = await db.users.find()
.skip(page * limit)
.limit(limit);
// Workaround: API cũ trả về field "full_name" thay vì "name"
const name = response.full_name || response.name;typescript5. Error Handling — Đừng Nuốt Lỗi#
// ❌ Tệ — nuốt lỗi
try {
await processPayment();
} catch (e) {
// Làm ngơ
}
// ❌ Tệ — log rồi throw generic
try {
await processPayment();
} catch (e) {
console.error(e);
throw new Error('Something went wrong');
}
// ✅ Tốt — specific error, meaningful message
try {
await processPayment();
} catch (error) {
logger.error({ err: error, paymentId }, 'Payment processing failed');
throw new PaymentError('Payment failed. Please try again.', { cause: error });
}typescript6. Formatting — Nhất Quán#
// Cấu trúc file
// 1. Imports (sorted)
import { z } from 'zod';
import { db } from '../db';
import { AppError } from '../shared/errors';
// 2. Constants
const MAX_RETRIES = 3;
// 3. Types/Interfaces
interface UserInput { ... }
// 4. Functions
export function createUser(input: UserInput) { ... }
// Dùng formatter tự động (Prettier, Biome)
// Dùng linter (ESLint) enforce styletypescript7. KISS — Keep It Simple, Stupid#
// ❌ Tệ — over-engineering
const result = await new Promise((resolve) => {
process.nextTick(() => {
resolve(expensiveCalculation());
});
});
// ✅ Tốt — đơn giản
const result = expensiveCalculation();typescript8. YAGNI — You Ain’t Gonna Need It#
// ❌ Tệ — viết trước khi cần
class UserService {
constructor(
private cache: CacheService,
private logger: LoggerService,
private analytics: AnalyticsService,
private eventBus: EventBus,
) {}
// ... hàng tá method chưa dùng đến
}
// ✅ Tốt — chỉ viết những gì cần NOW
class UserService {
constructor(private db: Database) {}
async findById(id: number) { ... }
async create(data: UserInput) { ... }
}typescript9. Law of Demeter — Đừng Gọi Quá Sâu#
// ❌ Tệ — train wreck
const city = user.getAddress().getCity().getName();
// ✅ Tốt — hỏi trực tiếp
const city = user.getCityName();
// Hoặc dùng optional chaining
const city = user?.address?.city?.name;typescript10. Testability — Code Dễ Test#
// ❌ Tệ — hardcode dependency
class EmailService {
send(email: string) {
const transporter = nodemailer.createTransport({ ... });
// ...
}
}
// ✅ Tốt — inject dependency
class EmailService {
constructor(private transporter: Transporter) {}
send(email: string) {
return this.transporter.sendMail(email);
}
}
// Test
const mockTransporter = { sendMail: vi.fn() };
const service = new EmailService(mockTransporter);typescriptKết Luận#
Clean code không phải “đẹp” — nó là dễ đọc, dễ sửa, dễ bảo trì. Nguyên tắc vàng: viết code như người tiếp theo đọc nó là một kẻ tâm thần biết địa chỉ nhà bạn.
- Tên rõ ràng — biến, hàm, class
- Function nhỏ — mỗi hàm một việc
- DRY — đừng lặp lại
- KISS — đơn giản
- YAGNI — chỉ viết khi cần
- Testable — inject dependency