[Java Backend Zero to Hello] BÀI 6.6: CLEAN CODE
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 6: REST API & Best Practices | Bài 65/86
BÀI 6.6: CLEAN CODE
Mục tiêu
- Viết code dễ đọc, dễ hiểu
- Áp dụng các nguyên tắc Clean Code
- Refactor code xấu thành code tốt
1. CLEAN CODE LÀ GÌ?
Clean code là code:
- Dễ đọc - Developer khác hiểu ngay
- Dễ bảo trì - Sửa lỗi nhanh
- Dễ mở rộng - Thêm tính năng dễ
- Ít bug - Code rõ ràng, ít sai sót
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand." - Martin Fowler
2. ĐẶT TÊN (NAMING)
2.1 Tên có ý nghĩa
// ❌ Xấu
int d; // elapsed time in days
List<int[]> list = getList();
// ✅ Tốt
int elapsedTimeInDays;
List<Cell> gameBoard = getCells();
2.2 Tránh thông tin sai
// ❌ Xấu
List<Account> accountList = new ArrayList<>(); // List là implementation detail
// ✅ Tốt
List<Account> accounts = new ArrayList<>();
2.3 Đặt tên có thể phát âm
// ❌ Xấu
Date genymdhms; // generation date, year, month, day, hour, minute, second
// ✅ Tốt
Date generationTimestamp;
2.4 Tránh mã hóa (Hungarian Notation)
// ❌ Xấu
String strName;
int iAge;
// ✅ Tốt
String name;
int age;
2.5 Class và Object
// Class: danh từ
User, Order, Product, PaymentProcessor
// Method: động từ
save(), delete(), findById(), calculateTotal()
// Boolean: is/has/can
isActive, hasPermission, canDelete
3. FUNCTION
3.1 Nhỏ và làm 1 việc
// ❌ Xấu - làm nhiều việc
public void processOrder(Order order) {
validate(order);
calculateTotal(order);
save(order);
sendEmail(order);
updateInventory(order);
log(order);
}
// ✅ Tốt - tách thành nhiều method
public void processOrder(Order order) {
validate(order);
Order saved = saveOrder(order);
notifyCustomer(saved);
updateInventory(saved);
}
private void notifyCustomer(Order order) {
emailService.sendConfirmation(order);
}
3.2 Ít tham số
// ❌ Xấu - quá nhiều tham số
public void createUser(String name, String email, String phone,
String address, int age, String role) { ... }
// ✅ Tốt - dùng object
public void createUser(CreateUserRequest request) { ... }
3.3 Không có side effect
// ❌ Xấu - side effect ẩn
public boolean checkPassword(String username, String password) {
User user = userRepository.findByUsername(username);
if (user != null) {
session.setAttribute("user", user); // ❌ Side effect
return passwordEncoder.matches(password, user.getPassword());
}
return false;
}
// ✅ Tốt - tách biệt
public boolean checkPassword(String username, String password) {
User user = userRepository.findByUsername(username);
return user != null && passwordEncoder.matches(password, user.getPassword());
}
3.4 Command Query Separation
// ❌ Xấu - vừa truy vấn vừa thay đổi
public boolean setName(String name) {
if (name != null && !name.isEmpty()) {
this.name = name;
return true;
}
return false;
}
// ✅ Tốt
public void setName(String name) {
if (name == null || name.isEmpty()) {
throw new IllegalArgumentException("Name cannot be empty");
}
this.name = name;
}
4. COMMENT
4.1 Comment tốt
// Giải thích WHY, không phải WHAT
// We need to subtract 1 because the database uses 1-based indexing
int displayIndex = dbIndex - 1;
// TODO: Refactor when API v2 is ready
// FIXME: Handle null case
4.2 Comment xấu
// ❌ Comment thừa
int age; // declare age variable
// ❌ Comment giải thích code xấu
// Check if user is not null and not empty
if (user != null && !user.getName().isEmpty()) { ... }
// ✅ Tốt hơn: refactor code
if (user != null && user.hasName()) { ... }
4.3 Javadoc cho public API
/**
* Tính tổng giá trị đơn hàng sau khi áp dụng giảm giá.
*
* @param order đơn hàng cần tính
* @param discountStrategy chiến lược giảm giá
* @return tổng giá trị sau giảm giá
* @throws IllegalArgumentException nếu order null
*/
public double calculateTotal(Order order, DiscountStrategy discountStrategy) {
// ...
}
5. FORMAT
5.1 Khoảng cách dòng
// ✅ Tốt
public void method() {
int x = 1;
if (x > 0) {
doSomething();
}
}
5.2 Giới hạn độ dài dòng
// Tối đa 120 ký tự
public UserResponse createUser(@Valid CreateUserRequest request) {
User user = userMapper.toEntity(request);
user.setPassword(passwordEncoder.encode(request.password()));
return userMapper.toResponse(userRepository.save(user));
}
5.3 Thứ tự method
public class UserService {
// 1. Public methods
public UserResponse create(...) { }
public UserResponse update(...) { }
// 2. Private methods
private void validate(...) { }
private void notify(...) { }
}
6. XỬ LÝ LỖI
6.1 Dùng Exception thay vì return code
// ❌ Xấu
public int deleteUser(Long id) {
if (id == null) return -1;
if (!userRepository.existsById(id)) return -2;
try {
userRepository.deleteById(id);
return 0;
} catch (Exception e) {
return -3;
}
}
// ✅ Tốt
public void deleteUser(Long id) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
if (!userRepository.existsById(id)) {
throw new NotFoundException("User not found");
}
userRepository.deleteById(id);
}
6.2 Không nuốt exception
// ❌ Xấu
try {
doSomething();
} catch (Exception e) {
// ignore
}
// ✅ Tốt
try {
doSomething();
} catch (SpecificException e) {
log.error("Failed to do something", e);
throw new BusinessException("Operation failed", e);
}
7. CLASS
7.1 Tổ chức class
public class UserService {
// 1. Constants
private static final int MAX_LOGIN_ATTEMPTS = 5;
// 2. Static fields
private static Logger log = LoggerFactory.getLogger(UserService.class);
// 3. Instance fields
private final UserRepository repository;
// 4. Constructors
public UserService(UserRepository repository) {
this.repository = repository;
}
// 5. Public methods
public User findById(Long id) { ... }
// 6. Private methods
private void validate(User user) { ... }
}
7.2 Encapsulation
// ❌ Xấu
public class User {
public String name;
public String email;
}
// ✅ Tốt
public class User {
private String name;
private String email;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
8. TESTING
8.1 Code dễ test
// ❌ Khó test - phụ thuộc trực tiếp
public class UserService {
private UserRepository repo = new UserRepositoryImpl();
}
// ✅ Dễ test - inject dependency
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository repo;
}
8.2 Test tên rõ ràng
// ❌ Xấu
@Test
public void test1() { ... }
// ✅ Tốt
@Test
void shouldThrowExceptionWhenEmailIsInvalid() { ... }
@Test
void shouldReturnUserWhenIdExists() { ... }
9. CODE SMELLS
9.1 Long Method
// ❌ Method quá dài (> 20 dòng)
// → Tách thành nhiều method nhỏ
9.2 Large Class
// ❌ Class quá nhiều trách nhiệm
// → Tách theo SRP
9.3 Duplicate Code
// ❌ Code lặp lại
// → Extract method, tạo utility class
9.4 Dead Code
// ❌ Code không dùng
// → Xóa
9.5 Magic Numbers
// ❌ Xấu
if (attempts > 5) { ... }
// ✅ Tốt
private static final int MAX_LOGIN_ATTEMPTS = 5;
if (attempts > MAX_LOGIN_ATTEMPTS) { ... }
10. BÀI TẬP THỰC HÀNH
Bài 1: Refactor
Refactor một method dài thành nhiều method nhỏ.
Bài 2: Naming
Đặt lại tên biến, method, class cho rõ ràng.
Bài 3: Magic Numbers
Thay thế magic numbers bằng constants.
11. TÓM TẮT
| Nguyên tắc | Mô tả |
|---|---|
| Naming | Tên có ý nghĩa, dễ đọc |
| Function | Nhỏ, làm 1 việc |
| Comment | Giải thích WHY |
| Format | Nhất quán, dễ đọc |
| Error | Dùng Exception |
| Class | Tổ chức rõ ràng |
| Testing | Code dễ test |
Bài tiếp theo: 6.7 Pagination & Sorting
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 6.5: DESIGN PATTERNS THƯỜNG GẶP
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 6.7: PAGINATION & SORTING
All rights reserved