Clean Code - Chapter 7: Error Handling
Bài đăng này đã không được cập nhật trong 5 năm
Use Exceptions Rather Than Return Codes (Sử dụng ngoại lệ hơn là trả về Codes)
Bạn có thể thiết lập một error flag hoặc trả về một mã lỗi mà người gọi có thể kiểm tra.
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());
}
}
...
}
Vấn đề ở đây là nó lộn xộn với người gọi. Người gọi phải kiểm tra trực tiếp sau khi gọi. Không may, nó rất dễ bị quên lãng. Vì lý do đó, tốt hơn là cho nó ra một exception khi bạn gặp phải lỗi. Nó làm mã được sạch hơn và logic không bị che khuất bởi xử lý lỗi.
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());
...
}
...
}
Code tốt hơn bởi vì có hai quan hệ lộn xộn ở đây, thuật toán cho thiết bị tắt máy và xử lý lỗi, và bây giờ nó đã được tách ra.
Write Your Try-Catch-Finally Statement First
Try - Như transactions. Catch - Rời khỏi chương trình và không vấn đề xảy ra trong try.
Sử dụng try-catch-finally là một cách tốt để bắt đầu viết code khi bạn đang viết code và ném đi những ngoại lệ. Điều này giúp chúng ta định nghĩa được những gì mà người dùng nên mong đợi, không vấn đề gì xảy ra đối với các đoạn code ở trong try.
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 sắp xếp theo thứ tự.
Chúng ta bắt đầu với Unit Test cho chúng ta biết ngoai lệ khi file không tồn tại:
@Test(expected = StorageException.class)
public void retrieveSectionShouldThrowOnInvalidFileName() {
sectionStore.retrieveSection("invalid - file");
}
Test này thúc đẩy chúng ta viết bản sơ khai:
public List<RecordedGrip> retrieveSection(String sectionName) {
// dummy return until we have a real implementation
return new ArrayList<RecordedGrip>();
}
Test này thất bại nó chưa ném ra ngoại lệ. Tiếp theo chúng ta thay đổi hiện thực, do đó mà nó cố gắng truy cập vào một tập tin không hợp lệ. Thao tác này thows một exception:
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName)
} catch (Exception e) {
throw new StorageException("retrieval error", e);
}
return new ArrayList<RecordedGrip>();
}
Test passes bởi vì nó đã bắt lấy một exception. Tái cấu trúc lại:
public List<RecordedGrip> retrieveSection(String sectionName) {
try {
FileInputStream stream = new FileInputStream(sectionName);
stream.close();
} catch (FileNotFoundException e) {
throw new StorageException("retrieval error”, e);
}
return new ArrayList<RecordedGrip>();
}
Use Unchecked Exceptions
Checked: are the exceptions that are checked at compile time. Unchecked are the exceptions that are not checked at compiled time. C# doesn’t have checked exceptions. Checked exceptions can sometimes be useful if you are writing a critical library: You must catch them. But in general application development the dependency costs outweigh the benefits.
Provide Context with Exceptions (Cung cấp bối cảnh cho ngoại lệ)
Mỗi trường hợp ngoại lệ bạn nên cung cấp ngữ cảnh để xác định nguồn gốc và vị trí của lỗi. Trong Java, bạn sẽ nhận được một stack truy tìm (trace) từ bất kỳ ngoại lệ nào. Tuy nhiên stack đó không cho bạn biết ý nghĩ của các ngoại lệ không thành công. Tạo một thông điệp báo lỗi và truyền cho chúng với ngoại lệ của bạn. Đề cập đến các hành động gây ra lỗi và loại lỗi.
Don't Pass Null
Trả về null từ phương thức đã xấu, nhưng vượt qua null còn tồi tệ hơn. Trừ khi bạn đang làm việc với một API hy vọng vượt qua null, bạn nên tránh passing 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 đi qua một tham số gán null? calculator.xProjection(null, new Point(12, 13));
Chúng ta sẽ có một NullPointerException. Sửa chưa lại:
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;
}
}
Trong hầu hết các ngôn ngữ lập trình không có cách nào đối phó với việc thông qua một Null với một lời gọi vô tình. Bởi vì đây là trường hợp tiêp cận với cấm truyền Null theo mặc định. Khi bạn làm bạn có thể code với sự hiểu biết rằng một Null xuất hiện trong danh sách tham số là dấu hiệu của vấn đề và kết thúc nó với ít lỗi bất cẩn nhất,
Conclusion
Code sạch là code có thể đọc được, nhưng nó cũng cần phải mạnh mẽ. Đây không phải là điều mâu thuẫn. Chúng ta có thể viết code sạch và mạnh mẽ nếu chúng ta thấy được xử lý lỗi là một mối quan tâm riêng, đôi khi có thể xem nó như không phụ thuộc với logic cơ bản của chúng ta. Để đến mức độ chúng ta làm được điều đó, chúng ta cần lý giải về nó một cách độc lập, và chúng ta có thể tiến tới bước tiến lớn trong việc bảo trì code của chúng ta.
All rights reserved