Giới thiệu về Ngoại lệ

Xử lí ngoại lệ và Debug

Lỗi chương trình là chuyện thường xảy ra. Các tình huống bất thường cũng xảy ra. Không tìm thấy file. Server bị sự cố. Ngoại lệ (exception) là thuật ngữ chỉ tình trạng sai hoặc bất thường xảy ra khi một chương trình đang chạy. Ta có thể gặp vô số các tình huống như vậy, chẳng hạn như khi chương trình thực hiện phép chia cho 0 (ngoại lệ tính toán số học), đọc phải một giá trị không nguyên trong khi đang chờ đọc một giá trị kiểu int (ngoại lệ định dạng số), hoặc truy cập tới một phần tử không nằm trong mảng (ngoại lệ chỉ số nằm ngoài mảng). Các lỗi và tình trạng bất thường có thể xảy ra là vô số.

Một chương trình dù được thiết kế tốt đến đâu thì vẫn có khả năng xảy ra lỗi trong khi thực thi. Dù có là lập trình viên giỏi đến đâu thì ta vẫn không thể kiểm soát mọi thứ. Trong những phương thức có khả năng gặp sự cố, ta cần những đoạn mã để xử lý sự cố nếu như chúng xảy ra.

Một chương trình được thiết kế tốt cần có những đoạn mã phòng chống lỗi và các tình trạng bất thường. Phần mã này nên được đưa vào chương trình ngay từ giai đoạn đầu của việc phát triển chương trình. Nhờ đó, nó có thể giúp nhận diện các trục trặc trong quá trình phát triển.

Phương pháp truyền thống cho việc phòng chống lỗi là chèn vào giữa logic chương trình những đoạn lệnh phát hiện và xử lý lỗi; dùng giá trị trả về của hàm làm phương tiện báo lỗi cho nơi gọi hàm. Tuy nhiên, phương pháp này có những nhược điểm như: các đoạn mã phát hiện và xử lý lỗi nằm lẫn trong thuật toán chính làm chương trình rối hơn, khó hiểu hơn, dẫn tới khó kiểm soát hơn; đôi khi giá trị trả về phải dành cho việc thông báo kết quả tính toán của hàm nên khó có thể tìm một giá trị thích hợp để dành riêng cho việc báo lỗi.

Trong ngôn ngữ Java, ngoại lệ (exception handling) là cơ chế cho phép xử lý tốt các tình trạng này. Nó cho phép giải quyết các ngoại lệ có thể xảy ra sao cho chương trình có thể chạy tiếp hoặc kết thúc một cách nhẹ nhàng, giúp lập trình viên tạo được các chương trình bền bỉ và chịu lỗi tốt hơn. So với phương pháp phòng chống lỗi truyền thống, cơ chế ngoại lệ có làm chương trình chạy chậm đi một chút, nhưng đổi lại là cấu trúc chương trình trong sáng hơn, dễ viết và dễ hiểu hơn.

Chương này mô tả cơ chế sử dụng ngoại lệ của Java. Ta sẽ bắt đầu bằng việc so sánh cách xử lý lỗi truyền thống trong chương trình với cơ chế xử lý ngoại lệ mặc định của Java. Tiếp theo là trình bày về cách ngoại lệ được ném và bắt (xử lý) trong một chương trình, các quy tắc áp dụng cho các loại ngoại lệ khác nhau. Cuối cùng là nội dung về cách thiết kế và cài đặt lớp con của Exception để phục vụ nhu cầu về các loại ngoại lệ tự thiết kế.

NGOẠI LỆ LÀ GÌ?

Tình huống sự cố

Đầu tiên, chúng ta lấy một ví dụ về ngoại lệ của Java. Trong Hình 11.1 là một chương trình đơn giản trong đó yêu cầu người dùng nhập hai số nguyên rồi tính thương của chúng và in ra màn hình.

import java.util.*;

public class TestException {

    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        System.out.printf("Numerator:");
        int numerator = scanner.nextInt();
        System.out.printf("Denominator:");
        int denominator = scanner.nextInt();
        int result = numerator/denominator;
        System.out.printf("\nResult: %d / %d = %d \n",numerator, denominator, result);
    }
}

Hình 11.1: Một chương trình chưa xử lý ngoại lệ

Chương trình này hoạt động đúng nhưng chưa hề có mã xử lý lỗi. Nếu khi chạy chương trình, ta nhập dữ liệu không phải số nguyên như yêu cầu, chương trình sẽ bị dừng đột ngột với lời báo lỗi được in ra trên cửa sổ lệnh, ví dụ như trong Hình 11.2. Đó là hậu quả của việc ngoại lệ chưa được xử lý.

Hình 11.2: Lỗi run-time do ngoại lệ không được xử lý

Ta lấy thêm một ví dụ khác trong Hình 11.3. Giả sử ta cần ghi một vài dòng văn bản vào một file. Ta dùng đến các lớp File và PrintWriter trong gói java.io của thư viện chuẩn Java, File quản lý file, PrintWriter cung cấp các tiện ích ghi dòng văn bản. Chương trình chỉ làm công việc rất đơn giản là (1) mở file, (2) chuẩn bị cho việc ghi file, (3) ghi vào file một dòng văn bản, và (4) đóng file. Nhưng khi biên dịch, ta gặp thông báo lỗi cho lệnh new PrintWriter với nội dung rằng ngoại lệ FileNotFoundException chưa được xử lý và nó phải được bắt hoặc được tuyên bố ném tiếp.

Hình 11.3: Lỗi biên dịch do ngoại lệ không được xử lý

Hai ví dụ trên, và các tình huống có ngoại lệ khác tương tự nhau ở những điểm sau:

1. Ta gọi một phương thức ở một lớp mà ta không viết 


2. Phương thức đó có thể gặp trục trặc khi chạy 


3. Ta cần biết rằng phương thức đó có thể gặp trục trặc 


4. Ta cần viết mã xử lý tình huống sự cố nếu nó xảy ra.

Hai điểm cuối là việc chúng ta chưa làm và sẽ nói đến trong những phần tiếp theo.

Các phương thức Java dùng các ngoại lệ để báo với phần mã gọi chúng rằng “Một tình huống không mong đợi đã xảy ra. Tôi gặp sự cố.” Cơ chế xử lý ngoại lệ của Java cho phép xử lý những tình huống bất thường xảy ra khi chương trình đang chạy, nó cho phép ta đặt tất cả những đoạn mã xử lý lỗi vào một nơi dễ đọc dễ hiểu. Cơ chế này dựa trên nguyên tắc rằng nếu ta biết ta có thể gặp một ngoại lệ nào đó ta sẽ có thể chuẩn bị để đối phó với tình huống phát sinh ngoại lệ đó.

Trước hết, điểm số 3, làm thế nào để biết một phương thức có thể ném ngoại lệ hay không và nó có thể ném cái gì? Khi biên dịch gặp lỗi hoặc khi chạy gặp lỗi như trong hai ví dụ trên, ta biết được một số ngoại lệ có thể phát sinh. Nhưng như vậy chưa đủ. Ta cần tìm đọc dòng khai báo throws tại dòng đầu tiên của khai báo phương thức, hoặc đọc tài liệu đặc tả phương thức để xem nó tuyên bố có thể ném cái gì. Phương thức nào cũng phải khai báo sẵn tất cả các loại ngoại lệ mà nó có thể ném.

Hình 11.4 là ảnh chụp trang đặc tả hàm khởi tạo PrintWriter(File) tại tài liệu API của JavaSE phiên bản 6 đặt tại trang web của Oracle. Tại đó, ta có thể tra cứu đặc tả của tất cả các lớp trong thư viện chuẩn Java.

Hình 11.4: Thông tin về ngoại lệ tại đặc tả phương thức

Đặc tả của hàm khởi tạo PrintWriter(File) nói rằng nó có thể ném FileNotFoundException, và nó sẽ ném nếu như đối tượng File được cho làm đối số không đại diện cho một file ghi được hoặc không thể tạo file với tên đã cho, hoặc nếu xảy ra lỗi nào khác trong khi mở hoặc tạo file. Như vậy, ta đã biết nếu tạo một đối tượng PrintWriter theo cách như trong Hình 11.3 thì ta phải chuẩn bị đối phó với loại ngoại lệ nào trong tình huống nào. 


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.

Leave a Reply

Your email address will not be published. Required fields are marked *