Giới Thiệu
Trong quá trình làm việc tại Bito, tôi đã dành nhiều thời gian để thực hiện đánh giá mã nguồn AI, cả trên các dự án của mình và với đội ngũ. Qua thời gian, tôi nhận thấy một mẫu hình rõ rệt. Những đánh giá không chỉ chỉ ra các vấn đề cú pháp hay thiếu kiểm thử. Chúng thường xuất hiện một điều gì đó tinh tế hơn - mùi code.
Ban đầu, tôi không nghĩ nhiều về nó. Nếu mã biên dịch và chức năng hoạt động, có phải là đủ tốt không? Nhưng càng làm việc với mã dở, tôi càng nhận ra rằng nó có thể kéo bạn xuống nhanh chóng. Bạn mở một tệp và bỗng nhiên mất 20 phút chỉ để hiểu điều gì đang xảy ra. Bạn đi sửa một lỗi, và bạn sợ phải chạm vào bất cứ điều gì vì một thay đổi có thể làm hỏng năm thứ khác. Bạn giao nó cho một đồng đội, và họ nhìn bạn như thể bạn vừa nguyền rủa họ.
Đó là lúc tôi nhận ra: mùi code không phải là lỗi, nhưng chúng là dấu hiệu cảnh báo. Nếu bỏ qua chúng, bạn có thể bị chôn vùi trong nợ kỹ thuật. Nhận diện chúng sớm, bạn sẽ tiết kiệm được rất nhiều đau đớn sau này.
Tại Sao Mùi Code Quan Trọng
Mùi code dễ dàng bị bỏ qua vào đầu. Chương trình chạy, kiểm thử vượt qua, và mọi thứ trông có vẻ ổn trên bề mặt. Nhưng theo thời gian, những vấn đề nhỏ đó bắt đầu lớn lên.
- Hàm dài trở nên khó theo dõi mỗi khi có ai đó thêm một điều kiện mới.
- Lớp lớn trở thành nơi chứa mọi thứ mà không ai nhớ cái gì thuộc về đâu.
- Logic sao chép có nghĩa là bạn phải sửa một lỗi ở ba nơi khác nhau.
Vấn đề không phải là mã dừng hoạt động. Vấn đề là nó trở nên khó đọc hơn, khó kiểm thử hơn và khó thay đổi hơn mà không làm hỏng thứ gì khác. Đây chính là cách mà nợ kỹ thuật xuất hiện.
Tôi đã thấy điều này trong các đánh giá. Một đoạn mã có vẻ "đủ tốt" lúc đầu sau đó lại mất gấp đôi thời gian để gỡ lỗi vì thiết kế đã xuống cấp. Nếu bạn làm việc trong một đội, nó cũng làm chậm mọi người lại, bởi vì họ cần phải gỡ rối đang trước khi bắt đầu thêm tính năng mới.
Đó là lý do tại sao mùi code quan trọng. Chúng không chỉ về mã sạch vì lý do mã sạch. Chúng còn về việc cứu bản thân bạn và nhóm của bạn khỏi những cơn đau sau này.
Những Mẫu Mùi Code Phổ Biến Nhất
Theo thời gian, bạn bắt đầu nhận thấy những loại mùi giống nhau xuất hiện trở lại. Chúng trông khác nhau trong mỗi mã nguồn, nhưng các mẫu thì lại rất phổ biến. Dưới đây là một số mẫu nổi bật nhất.
1. Hàm Dài
Hàm dài làm khó theo dõi logic và khó kiểm thử. Hãy chia chúng thành những hàm nhỏ thực hiện một việc duy nhất.
Mùi:
javascript
function processOrder(order) {
// xác thực
if (!order.id || !order.items) throw new Error('invalid')
// tính tổng
let subtotal = 0
for (const item of order.items) {
subtotal += item.price * item.qty
}
let tax = subtotal * 0.08
// áp dụng giảm giá
if (order.coupon) {
subtotal -= order.coupon.amount
}
// xây dựng payload
const payload = { id: order.id, total: subtotal + tax }
// gửi đến thanh toán
sendToBilling(payload)
// thông báo người dùng
sendEmail(order.userEmail, 'đơn hàng đã được xử lý')
}
Refactor:
javascript
function validateOrder(order) {
if (!order.id || !order.items) throw new Error('invalid')
}
function calculateTotal(items, coupon) {
let subtotal = 0
for (const item of items) subtotal += item.price * item.qty
if (coupon) subtotal -= coupon.amount
const tax = subtotal * 0.08
return subtotal + tax
}
function processOrder(order) {
validateOrder(order)
const total = calculateTotal(order.items, order.coupon)
sendToBilling({ id: order.id, total })
sendEmail(order.userEmail, 'đơn hàng đã được xử lý')
}
2. Mã Trùng Lặp
Logic trùng lặp gây ra nhiều lần sửa chữa. Trích xuất mã chung một lần, sau đó tái sử dụng.
Mùi:
javascript
function createAdminUser(data) {
const user = {
name: data.name,
email: data.email,
role: 'admin',
createdAt: new Date()
}
saveUser(user)
}
function createGuestUser(data) {
const user = {
name: data.name,
email: data.email,
role: 'guest',
createdAt: new Date()
}
saveUser(user)
}
Refactor:
javascript
function buildUser(data, role) {
return {
name: data.name,
email: data.email,
role,
createdAt: new Date()
}
}
function createAdminUser(data) {
saveUser(buildUser(data, 'admin'))
}
function createGuestUser(data) {
saveUser(buildUser(data, 'guest'))
}
3. Lớp Lớn (God Objects)
Một lớp có nhiều trách nhiệm trở thành khó thay đổi. Hãy tách trách nhiệm thành các lớp tập trung.
Mùi:
javascript
class OrderService {
constructor(db) { this.db = db }
createOrder(data) { /* xác thực, tính toán, lưu, thông báo */ }
calculateTotals(items) { /* nhiều logic */ }
sendInvoice(order) { /* logic email */ }
exportOrdersCsv() { /* logic tệp */ }
}
Refactor:
javascript
class OrderCalculator {
calculate(items, coupon) { /* logic tổng */ }
}
class OrderRepository {
constructor(db) { this.db = db }
save(order) { /* lưu db */ }
}
class OrderNotifier {
sendInvoice(order) { /* logic email */ }
}
class OrderService {
constructor(calc, repo, notifier) {
this.calc = calc
this.repo = repo
this.notifier = notifier
}
createOrder(data) {
const total = this.calc.calculate(data.items, data.coupon)
const order = { ...data, total }
this.repo.save(order)
this.notifier.sendInvoice(order)
}
}
4. Nỗi Ám Ảnh Về Nguyên Thủy
Truyền các nguyên thủy thô ẩn giấu ý định, và làm cho việc xác thực và hành vi trở nên phân tán. Tạo các loại nhỏ hoặc đối tượng.
Mùi:
javascript
function createUser(name, email, addressLine1, addressLine2, city, zip) {
const user = { name, email, addressLine1, addressLine2, city, zip }
saveUser(user)
}
Refactor:
javascript
function buildAddress(line1, line2, city, zip) {
return { line1, line2, city, zip }
}
function createUser(name, email, address) {
const user = { name, email, address }
saveUser(user)
}
// sử dụng
const addr = buildAddress('123 St', '', 'Pune', '411001')
createUser('Asha', 'asha@example.com', addr)
5. Ghen Tị Tính Năng
Khi một phương thức lấy dữ liệu từ một đối tượng khác, logic có khả năng thuộc về gần dữ liệu đó hơn. Chuyển hành vi đến đúng vị trí.
Mùi:
javascript
class OrderFormatter {
format(order) {
return `${order.user.firstName} ${order.user.lastName} đã đặt hàng ${order.id}`
}
}
Refactor:
javascript
class User {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
class OrderFormatter {
format(order) {
return `${order.user.fullName()} đã đặt hàng ${order.id}`
}
}
6. Cụm Dữ Liệu
Nếu cùng một nhóm giá trị di chuyển cùng nhau, hãy đóng gói chúng vào một đối tượng. Điều này giảm lỗi và làm rõ ý định.
Mùi:
javascript
function scheduleMeeting(title, startDate, endDate, organizerName, organizerEmail) {
// nhiều tham số được truyền xung quanh
}
Refactor:
javascript
function scheduleMeeting(title, range, organizer) {
// range là { startDate, endDate }
// organizer là { name, email }
}
const range = { startDate: '2025-09-01', endDate: '2025-09-01' }
const organizer = { name: 'Sam', email: 'sam@example.com' }
scheduleMeeting('sync', range, organizer)
Cách Nhận Diện Mùi Code Trong Thực Tế
Mùi code rất khó nhận diện vì chúng không làm hỏng bản xây dựng hoặc ném ra lỗi. Mã vẫn chạy, điều này khiến cho việc bỏ qua chúng trở nên dễ dàng. Nhưng một khi bạn biết phải tìm gì, chúng sẽ bắt đầu nổi bật ở khắp nơi.
1. Đánh Giá Đồng Nghiệp và Lập Trình Đôi
Có một cặp mắt khác nhìn vào mã của bạn rất có ích. Một đồng đội chưa nhìn vào cùng một tệp trong nhiều giờ sẽ nhanh chóng nhận ra khi một phương thức quá dài, một lớp quá nặng, hoặc logic cảm thấy không đúng chỗ.
2. Công Cụ Tự Động
Các công cụ phân tích tĩnh có thể phát hiện một số mùi, như mã trùng lặp hoặc biến không sử dụng. Các công cụ đánh giá mã AI đi xa hơn và chỉ ra các vấn đề ở cấp độ thiết kế mà con người có thể bỏ qua trong một đợt làm việc bận rộn. Đây là điều tôi thấy hàng ngày khi sử dụng Bito. Blog của chúng tôi về phát hiện mùi code đi sâu hơn vào cách mà các đánh giá AI giúp phát hiện các mẫu này sớm.
3. Cảm Giác Của Bạn Là Một Lập Trình Viên
Đôi khi bạn chỉ biết điều gì đó không đúng. Nếu bạn phải cuộn quá nhiều, truyền quá nhiều tham số, hoặc viết mã giống nhau hai lần, đó thường là một dấu hiệu. Hãy tin vào cảm giác đó và xem xét kỹ hơn.
Mục tiêu không phải là ám ảnh về mọi điều nhỏ nhặt, mà là phát triển nhận thức. Một khi bạn có thể nhận diện những tín hiệu này, bạn có thể chọn những tín hiệu nào đáng sửa ngay bây giờ và những tín hiệu nào có thể chờ đợi.
Khi Nào Sửa Chữa So Với Khi Nào Để Lại
Một trong những phần khó khăn nhất của việc xử lý mùi code là biết khi nào nên hành động. Không phải mọi mùi đều xứng đáng nhận được sự chú ý của bạn ngay lập tức. Một số là quái vật vô hại, trong khi những cái khác sẽ làm chậm đội nhóm của bạn nếu bạn để chúng yên.
Sửa Ngay
Nếu một mùi code đang chặn khả năng đọc, làm chậm việc gỡ lỗi, hoặc tạo ra logic trùng lặp, thường thì đáng sửa ngay lập tức. Ví dụ, một phương thức dài mà bạn đang chỉnh sửa là ứng cử viên hoàn hảo cho một cuộc dọn dẹp nhanh. Những thay đổi nhỏ được thực hiện trong bối cảnh thường là những chiến thắng dễ dàng nhất.
Để Đó (tạm thời)
Nếu mã hoạt động, hiếm khi được chạm vào, và không ai gặp khó khăn với nó, bạn có thể không cần phải refactor ngay lập tức. Một hàm tiện ích lộn xộn mà chỉ chạy một lần mỗi tháng không khẩn cấp như một bộ điều khiển mà mọi lập trình viên đều chạm vào hàng ngày.
Quy Tắc Hướng Dẫn Cậu Bé
Một cách tốt để cân bằng điều này là tuân theo Quy Tắc Hướng Dẫn Cậu Bé: hãy để mã sạch hơn so với khi bạn tìm thấy nó. Nếu bạn chạm vào một tệp cho một tính năng mới hoặc một lỗi, hãy dành một chút thời gian để dọn dẹp những mùi tồi tệ nhất trong khi bạn ở đó. Theo thời gian, toàn bộ mã nguồn sẽ cải thiện mà không cần dự án refactor lớn.
Kỹ năng thực sự không phải là sửa mọi thứ, mà là biết cái gì cần sửa ngay và cái gì có thể để lại. Kỹ năng đó giúp bạn tiết kiệm thời gian trong khi vẫn giữ cho mã nguồn khỏe mạnh.