Exception trong Java là cơ chế báo và xử lý lỗi khi chương trình đang chạy. Thay vì để chương trình sập, bạn bắt lỗi lại và quyết định làm gì tiếp. Chúng tôi thấy phần này hay bị học vội. Nó lại quyết định chương trình của bạn chịu lỗi tốt đến đâu.
Ngoại lệ là gì và gồm những loại nào

Mọi ngoại lệ đều là đối tượng kế thừa từ Throwable. Nhánh dưới nó chia hai hướng. Error là sự cố nghiêm trọng của máy ảo, như hết bộ nhớ. Exception là những lỗi mà chương trình có thể xử lý.
Trong nhánh Exception, ta lại phân biệt hai nhóm. Ngoại lệ checked, ví dụ IOException, buộc bạn phải bắt hoặc khai báo. Ngoại lệ unchecked kế thừa RuntimeException, ví dụ NullPointerException, trình biên dịch không ép bạn xử lý.
Khối try, catch và finally

import java.io.*;
public class DocFile {
public static void main(String[] args) {
try {
BufferedReader r = new BufferedReader(new FileReader("du-lieu.txt"));
System.out.println(r.readLine());
r.close();
} catch (FileNotFoundException e) {
System.out.println("Không tìm thấy tệp: " + e.getMessage());
} catch (IOException e) {
System.out.println("Lỗi đọc tệp");
} finally {
System.out.println("Luôn chạy dòng này");
}
}
}
Java tìm khối catch đầu tiên khớp với loại ngoại lệ. Vì vậy hãy đặt ngoại lệ cụ thể trước và ngoại lệ chung sau. Nếu đặt Exception lên đầu, các khối bên dưới sẽ không bao giờ chạy và trình biên dịch báo lỗi.
Khối finally chạy dù có lỗi hay không, kể cả khi trong try có lệnh return. Nó thích hợp để giải phóng tài nguyên. Tuy vậy, từ Java 7 có cách gọn hơn là try-with-resources.
try (BufferedReader r = new BufferedReader(new FileReader("du-lieu.txt"))) {
System.out.println(r.readLine());
} catch (IOException e) {
e.printStackTrace();
}
Tài nguyên khai báo trong ngoặc tròn sẽ tự đóng khi khối try kết thúc. Bạn khỏi lo quên gọi close().
Từ khóa throws và throw

Hai từ khóa này rất dễ nhầm. throw dùng để ném một ngoại lệ cụ thể ngay tại dòng code. throws nằm ở khai báo phương thức. Nó báo rằng phương thức đó có thể ném ngoại lệ, người gọi phải tự lo.
static int chia(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("Mẫu số bằng 0");
}
return a / b;
}
Với ngoại lệ checked, bạn có thể không bắt ngay tại chỗ. Khi đó hãy thêm throws để đẩy trách nhiệm lên phương thức gọi. Đẩy lên mãi đến main mà không ai bắt thì chương trình dừng và in ra stack trace.
Tự định nghĩa ngoại lệ

Khi nghiệp vụ có lỗi riêng, ví dụ số dư không đủ, hãy tạo lớp ngoại lệ riêng. Đừng dùng Exception chung chung. Tên lớp rõ nghĩa giúp người đọc log hiểu ngay chuyện gì xảy ra.
class SoDuKhongDuException extends Exception {
SoDuKhongDuException(String msg) { super(msg); }
}
Phần constructor của lớp trên gọi super(msg) để truyền thông báo lên lớp cha. Nếu cần ôn lại cơ chế này, hãy đọc bài về constructor trong Java.
Lỗi hay gặp khi xử lý ngoại lệ
- Bắt ngoại lệ rồi để khối catch rỗng; lỗi biến mất nhưng bạn không còn dấu vết để sửa.
- Bắt
Exceptionquá rộng, che mất những lỗi lập trình thật sự. - Dùng ngoại lệ để điều khiển luồng thông thường, làm chương trình chậm và khó đọc.
Ngoại lệ thường xuất hiện khi duyệt dữ liệu, ví dụ truy cập chỉ số ngoài phạm vi trong một vòng lặp. Bài vòng lặp for trong Java cho thấy cách tránh lỗi đó. Để tra cứu đầy đủ danh sách ngoại lệ, bạn có thể xem phần ngoại lệ trong tutorial của Oracle.
Gói ngoại lệ và giữ nguyên nguyên nhân
Trong ứng dụng nhiều lớp, lớp dịch vụ không nên để lộ ngoại lệ của lớp dữ liệu. Cách làm phổ biến là bắt ngoại lệ gốc. Sau đó ném lại một ngoại lệ nghiệp vụ, kèm ngoại lệ gốc làm nguyên nhân.
try {
docDuLieu();
} catch (IOException e) {
throw new DichVuException("Không đọc được dữ liệu", e);
}
Tham số thứ hai là nguyên nhân gốc. Khi in stack trace, Java hiển thị cả chuỗi nguyên nhân, nên bạn vẫn lần ra được lỗi đầu tiên. Nếu bạn quên truyền e, thông tin quan trọng nhất sẽ biến mất.
Ghi log đúng cách
Một thói quen xấu là vừa ghi log vừa ném lại ngoại lệ ở mọi tầng. Khi đó file log lặp đi lặp lại cùng một lỗi. Quy tắc chúng tôi dùng khá đơn giản. Ghi log tại nơi cuối cùng xử lý lỗi, còn các tầng giữa chỉ gói lại và ném tiếp.
Khi ghi log, hãy đưa vào cả đối tượng ngoại lệ thay vì chỉ e.getMessage(). Thông báo đơn thuần thường không đủ để biết lỗi xảy ra ở dòng nào.
Multi-catch và ngoại lệ bị chặn
Từ Java 7, bạn có thể bắt nhiều loại ngoại lệ trong một khối bằng dấu gạch đứng, ví dụ catch (IOException | SQLException e). Cách này tránh việc lặp cùng một đoạn xử lý. Lưu ý rằng các loại ngoại lệ trong multi-catch không được là cha con của nhau. Biến e khi đó là hằng số, không gán lại được.
Trong try-with-resources, thân try và thao tác đóng tài nguyên có thể cùng ném ngoại lệ. Khi đó ngoại lệ từ thân try được ném ra, còn ngoại lệ lúc đóng được lưu dưới dạng suppressed. Bạn lấy chúng bằng getSuppressed(). Đây là chi tiết nhỏ nhưng có ích khi gỡ lỗi.
Cần lưu nhiều lỗi để báo cáo một lượt? Hãy xem các collection trong Java để chọn cấu trúc chứa phù hợp.
Hãy thử viết một chương trình đọc số từ bàn phím và cố tình nhập chữ để thấy InputMismatchException. Thấy lỗi tận mắt sẽ giúp bạn nhớ cách xử lý lâu hơn việc đọc lý thuyết.



