0

[Java Backend Zero to Hello] BÀI 6.6: CLEAN CODE

Java Backend Zero to Hello

📚 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí