Clean code - P3

Xử lý lỗi là một điều mà tất cả lập trình viên cần phải làm khi viết code.

Use exceptions rather than return codes

Cùng xem ví dụ dưới đây :

public class DeviceController {
   ...
   public void sendShutDown() {
      DeviceHandle handle = getHandle(DEV1);
     // Check the state of the device
      if (handle != DeviceHandle.INVALID) {
         // Save the device status to the record field
         retrieveDeviceRecord(handle);
         // If not suspended, shut down
         if (record.getStatus() != DEVICE_SUSPENDED) {
            pauseDevice(handle);
            clearDeviceWorkQueue(handle);
            closeDevice(handle);
         } else {
            logger.log("Device suspended. Unable to shut down");
         }
      } else {
         logger.log("Invalid handle for: " + DEV1.toString());
      }
   }
   ...
}

Rõ ràng các method kiểu như getHandle(DEV1); gây lộn xộn và làm phức tạp đoạn code. Nơi gọi method này phải kiểm tra trực tiếp xem method này trả về gì rồi xử lý.
Cách tốt hơn ta nên throw ra exception khi gặp lỗi. Thử nhất nó làm mã code sạch hơn, thứ hai không cần thêm bước kiểm tra xem method đó trả về cái gì.

public class DeviceController {
   ...
   public void sendShutDown() {
      try {
         tryToShutDown();
      } catch (DeviceShutDownError e) {
         logger.log(e);
      }
   }

   private void tryToShutDown() throws DeviceShutDownError {
      DeviceHandle handle = getHandle(DEV1);
      DeviceRecord record = retrieveDeviceRecord(handle);

      pauseDevice(handle);
      clearDeviceWorkQueue(handle);
      closeDevice(handle);
   }

   private DeviceHandle getHandle(DeviceID id) {
      ...
      throw new DeviceShutDownError("Invalid handle for: " + id.toString());
      ...
   }
   ...
}

Write Your Try-Catch-Finally Statement First

Sử dụng try-catch-finally. Ví dụ viết một đoạn code với mục đích truy cập file và đọc một số đối tượng theo thứ tự sắp xếp.
Chúng ta sẽ bắt đầu với Unit test cho trường hợp ngoại lệ do truy cập file không tồn tại.

// test
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
   sectionStore.retrieveSection("invalid - file");
}

// xử lý
public List<RecordedGrip> retrieveSection(String sectionName) {
   // dummy return until we have a real implementation
   return new ArrayList<RecordedGrip>();
}

Test này sẽ fail do xử lý chưa ném ra ngoại lệ. Tiếp theo ta sẽ thay đổi xử lý truy cập đến một file không tồn tại:

public List<RecordedGrip> retrieveSection(String sectionName) {
   try {
      FileInputStream stream = new FileInputStream(sectionName)
   } catch (Exception e) {
      throw new StorageException("retrieval error", e);
   } finally {
      stream.close(); 
   }
   return new ArrayList<RecordedGrip>();
}

Lúc này test đã pass bởi vì nó đã bắt được ngoại lệ do xử lý throw ra.

Don't return null

Khi một method return null, đồng nghĩa với việc ta tự mua việc cho mình, đồng thời phát sinh vấn đề cho người gọi method này.
Xem ví dụ dưới:

List<Employee> employees = getEmployees();
if (employees != null) {
   for(Employee e : employees) {
      totalPay += e.getPay();
   }
}

Bây giờ getEmployees có thể trả về null, nhưng nó có cần thiết phải làm như vậy không? Nếu chúng ta thay đổi getEmployees rằng nó trả về một danh sách rỗng, chúng ta có thể dọn dẹp code như sau:

List<Employee> employees = getEmployees();
for(Employee e : employees) {
   totalPay += e.getPay();
}

public List<Employee> getEmployees() {
   if( .. there are no employees .. )
      return Collections.emptyList(); 
// Trả về một danh sách xác định trước không thay đổi trong Java
}

Nếu code như vậy sẽ giảm thiểu nguy cơ NullPointerExceptions và code sẽ sạch hơn.

Don't pass null

Trả về null đã tồi, việc truyền null vào tham số càng tồi hơn. Cần tránh pass null trong code bất cứ khi nào có thể.

public class MetricsCalculator {
   public double xProjection(Point p1, Point p2) {
      return (p2.x – p1.x) * 1.5;
   }
   …
}

Điều gì xảy ra nếu một người gọi method này và truyền vào null.
calculator.xProjection(null, new Point(12, 13));
Chúng ta sẽ có ngay một NullPoiterException. Đoạn code trên có thể refactor lại như sau:

public class MetricsCalculator {
   public double xProjection(Point p1, Point p2) {
      if (p1 == null || p2 == null) {
         throw InvalidArgumentException(
            "Invalid argument for MetricsCalculator.xProjection");
      }
      return (p2.x – p1.x) * 1.5;
   }
}

// Có một thay thế khác tốt hơn:

public class MetricsCalculator {
   public double xProjection(Point p1, Point p2) {
      assert p1 != null : "p1 should not be null";
      assert p2 != null : "p2 should not be null";
      return (p2.x – p1.x) * 1.5;
   }
}