Java: Ưu điểm của ngoại lệ

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Bây giờ bạn đã biết thế nào là ngoại lệ cũng như cách sử dụng nó, ở bài viết này ta sẽ tìm hiểu về những ưu điểm của ngoại lệ khi sử dụng chúng trong các chương trình của ta.

Ưu điểm 1: Tách mã xử lý lỗi khỏi mã chuẩn

Ngoại lệ cung cấp các phương tiện để tách các chi tiết về những gì cần làm khi một điều gì đó khác thường xảy ra từ logic chính của một chương trình. Trong lập trình truyền thống, việc phát hiện, báo cáo và xử lý lỗi thường làm cho mã lệnh khó hiểu. Ví dụ, hãy xét các phương thức giả dưới đây dùng để đọc toàn bộ tập tin vào bộ nhớ.

readFile {
    open the file;
    determine its size;
    allocate that much memory;
    read the file into memory;
    close the file;

}

Ta thấy rằng hàm trên có vẻ đơn giản, nhưng nó bỏ qua tất cả các lỗi tiềm tàng sau đây:

  • Điều gì xảy ra nếu các tập tin không thể mở được?
  • Điều gì xảy ra nếu chiều dài của tập tin không thể được xác định?
  • Điều gì xảy ra nếu không thể cấp phát đủ bộ nhớ?
  • Điều gì xảy ra nếu việc đọc bị lỗi?
  • Điều gì xảy ra nếu tập tin không thể bị đóng?

Để xử lý các trường hợp trên thì hàm readFile phải có thêm mã để phát hiện, báo cáo và xử lý lỗi. Dưới đây là ví dụ cụ thể:

errorCodeType readFile {
    initialize errorCode = 0;

    open the file;
    if (theFileIsOpen) {
        determine the length of the file;
        if (gotTheFileLength) {
            allocate that much memory;
            if (gotEnoughMemory) {
                read the file into memory;
                if (readFailed) {
                    errorCode = -1;
                }
            } else {
                errorCode = -2;
            }
        } else {
            errorCode = -3;
        }
        close the file;
        if (theFileDidntClose && errorCode == 0) {
            errorCode = -4;
        } else {
            errorCode = errorCode and -4;
        }
    } else {
        errorCode = -5;
    }
    return errorCode;
}

Như bạn thấy ở đoạn mã trên, có rất nhiều lỗi có thể được phát hiện, báo cáo, và trả về ở đây, nhưng mã lệnh gốc ban đầu đã bị lộn xộn. Tệ hơn nữa, dòng chảy logic của mã cũng bị mất, điều này làm cho nó trở nên khó hiểu và ta cũng không rõ liệu điều ta đang làm là đúng: Tập tin có thực sự được đóng nếu hàm gặp lỗi cấp phát đủ bộ nhớ không? Thậm chí còn khó khăn hơn để đảm bảo rằng mã vẫn tiếp tục thực hiện đúng khi bạn sửa đổi phương thức ba tháng sau khi viết nó. Nhiều lập trình viên giải quyết vấn đề này bằng cách chỉ đơn giản là bỏ qua nó - lỗi được thông báo khi chương trình của họ sụp đổ.

Ngoại lệ cho phép bạn viết các dòng chảy chính của mã lệnh của bạn và để đối phó với các trường hợp đặc biệt ở những nơi khác nhau. Nếu hàm readFile sử dụng ngoại lệ thay vì kỹ thuật quản lý lỗi truyền thống thì nó sẽ trông giống như sau:

readFile {
    try {
        open the file;
        determine its size;
        allocate that much memory;
        read the file into memory;
        close the file;

    } catch (fileOpenFailed) {
       doSomething;
    } catch (sizeDeterminationFailed) {
        doSomething;
    } catch (memoryAllocationFailed) {
        doSomething;
    } catch (readFailed) {
        doSomething;
    } catch (fileCloseFailed) {
        doSomething;
    }
}

Lưu ý rằng ngoại lệ không giúp bạn khắc phục được những việc phát hiện, báo cáo và xử lý các lỗi, nhưng chúng giúp bạn tổ chức công việc và mã lệnh hiệu quả hơn.

Ưu điểm 2: Chuyển lỗi lên Call Stack

Một lợi thế thứ hai của ngoại lệ là khả năng chuyển lỗi lên call stack. Giả sử rằng phương thức readFile là phương thức thứ tư trong một loạt các lời gọi phương thức lồng nhau được thực hiện bởi chương trình chính: method1 gọi method2, method2 gọi method3, cuối cùng gọi readFile.

method1 {
    call method2;
}

method2 {
    call method3;
}

method3 {
    call readFile;
}

Giả sử rằng method1 là phương thức duy nhất quan tâm đến các lỗi có thể xảy ra trong readFile. Các kỹ thuật thông báo lỗi truyền thống áp dụng đối với method2method3 chuyển các mã lỗi trả về bởi readFile lên call stack cho đến khi các mã lỗi cuối cùng chạm đến method1 - phương thức duy nhất quan tâm đến chúng.

method1 {
    errorCodeType error;
    error = call method2;
    if (error)
        doErrorProcessing;
    else
        proceed;
}

errorCodeType method2 {
    errorCodeType error;
    error = call method3;
    if (error)
        return error;
    else
        proceed;
}

errorCodeType method3 {
    errorCodeType error;
    error = call readFile;
    if (error)
        return error;
    else
        proceed;
}

Nhớ lại rằng môi trường thời gian chạy Java sẽ tìm kiếm những lạc hậu thông qua call stack để tìm thấy bất kỳ phương thức nào quan tâm đến việc xử lý một ngoại lệ cụ thể. Một phương thức có thể tránh bất kỳ ngoại lệ nào ném vào trong nó, do đó cần cho phép một phương thức xa hơn trên call stack bắt nó. Vì thế, chỉ có các phương thức quan tâm đến các lỗi mới phải "lo lắng" về việc phát hiện lỗi.

method1 {
    try {
        call method2;
    } catch (exception e) {
        doErrorProcessing;
    }
}

method2 throws exception {
    call method3;
}

method3 throws exception {
    call readFile;
}

Tuy nhiên, như ta thấy ở các giả mã ở trên, để tránh một ngoại lệ thì đòi hỏi một số nỗ lực trên một phần của phương thức trung gian. Bất kỳ ngoại lệ checked nào có thể được ném trong một phương thức đều phải được quy định trong mệnh đề throws của nó.

Ưu điểm 3: Phân nhóm và Phân biệt các loại lỗi

Bởi vì tất cả các ngoại lệ được ném vào một chương trình là các đối tượng, nên việc nhóm hoặc phân loại các ngoại lệ là kết quả tự nhiên của hệ thống phân cấp lớp. Một ví dụ của một nhóm các lớp ngoại lệ liên quan trong nền tảng Java được định nghĩa trong java.io - IOException và các lớp con cháu của nó. IOException là ngoại lệ phổ biến nhất và đại diện cho bất kỳ loại lỗi nào có thể xảy ra khi thực hiện nhập/xuất tập tin. Các lớp con cháu của nó đại diện cho các lỗi cụ thể hơn. Ví dụ, FileNotFoundException có nghĩa là một tập tin không thể được đưa trên đĩa.

Mỗi phương thức có thể viết các trình xử lý cụ thể và có thể xử lý một ngoại lệ cụ thể. Lớp FileNotFoundException không có lớp con, vì vậy trình xử lý sau đây chỉ có thể xử lý một loại ngoại lệ.

catch (FileNotFoundException e) {
    ...
}

Mỗi phương thức có thể bắt một ngoại lệ dựa trên nhóm hoặc loại nói chung bằng cách xác định bất kỳ lớp cha nào của ngoại lệ trong câu lệnh catch. Ví dụ, để bắt tất cả các ngoại lệ I/O mà không phân biệt loại cụ thể nào, thì trình xử lý ngoại lệ sẽ chỉ định đối số IOException.

catch (IOException e) {
    ...
}

Trình xử lý này sẽ có thể bắt tất cả các ngoại lệ I/O, bao gồm FileNotFoundException, EOFException, ... Bạn có thể tìm thấy chi tiết về những gì xảy ra bằng cách truy vấn tham số truyền cho trình xử lý ngoại lệ. Ví dụ sau đây dùng để in ra stack trace.

catch (IOException e) {
    // Output tới System.err.
    e.printStackTrace();
    // Gửi vết tới stdout (standard output).
    e.printStackTrace(System.out);
}

Ta cũng có thể thiết lập một trình xử lý ngoại lệ để xử lý bất kỳ ngoại lệ Exception nào như sau đây:

// trình xử lý ngoại lệ phổ biến
catch (Exception e) {
    ...
}

Lớp Exception nằm gần phía trên cùng của hệ thống phân cấp lớp Throwable. Vì vậy, trình xử lý này sẽ bắt được nhiều loại ngoại lệ khác nhau. Bạn có thể muốn xử lý các ngoại lệ theo cách này nếu tất cả những gì bạn muốn chương trình làm, chẳng hạn như in ra một thông báo lỗi cho người dùng và sau đó thoát ra.

Tuy nhiên, trong hầu hết các tình huống, bạn muốn các trình xử lý ngoại lệ càng cụ thể càng tốt. Lý do là: điều đầu tiên một trình xử lý phải làm là xác định loại ngoại lệ xảy ra trước khi nó có thể quyết định về chiến lược phục hồi tốt nhất. Trong thực tế, bằng cách không bắt lỗi cụ thể, trình xử lý phải đáp ứng được bất kỳ khả năng nào. Trình xử lý ngoại lệ trên với tham số Exception là quá chung chung nên có thể làm cho nhiều mã dễ bị lỗi do việc bắt và xử lý các ngoại lệ mà không được dự đoán bởi các lập trình viên và cho các trình xử lý không hề được dự định.

Như vậy, bạn có thể tạo các nhóm ngoại lệ và xử lý các ngoại lệ theo một cách thức chung, hoặc bạn có thể sử dụng các loại ngoại lệ một cách cụ thể để phân biệt các ngoại lệ và xử lý các ngoại lệ theo cách thức tốt nhất.

» Tiếp: throw và throws
« Trước: Ngoại lệ unchecked - Tranh luận
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!