0

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

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 6: REST API & Best Practices 📖 Nội dung: BÀI 6.6: CLEAN CODE 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


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í