Clojure - Xử lý ngoại lệ

Exception handlingđược yêu cầu trong bất kỳ ngôn ngữ lập trình nào để xử lý các lỗi thời gian chạy để có thể duy trì luồng bình thường của ứng dụng. Ngoại lệ thường làm gián đoạn luồng thông thường của ứng dụng, đó là lý do tại sao chúng ta cần sử dụng xử lý ngoại lệ trong ứng dụng của mình.

Ngoại lệ được phân loại rộng rãi thành các loại sau:

  • Checked Exception- Các lớp mở rộng lớp Throwable ngoại trừ RuntimeException và Error được gọi là các ngoại lệ đã kiểm tra. Ví dụ: IOException, SQLException, v.v. Các ngoại lệ đã chọn được kiểm tra tại thời điểm biên dịch.

Hãy xem xét chương trình sau đây thực hiện một thao tác trên tệp có tên là Example.txt. Tuy nhiên, luôn có thể xảy ra trường hợp trong đó tệp Example.txt không tồn tại.

(ns clojure.examples.example
   (:gen-class))

;; This program displays Hello World
(defn Example []
   (def string1 (slurp "Example.txt"))
   (println string1))
(Example)

Nếu tệp Example.txt không tồn tại, thì chương trình sẽ tạo ra ngoại lệ sau.

Caused by: java.io.FileNotFoundException: Example.txt (No such file or
directory)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at clojure.java.io$fn__9185.invoke(io.clj:229)
at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)
at clojure.java.io$fn__9197.invoke(io.clj:258)
at clojure.java.io$fn__9098$G__9091__9105.invoke(io.clj:69)

Từ ngoại lệ trên, chúng ta có thể thấy rõ ràng rằng chương trình đã tạo ra một FileNotFoundException.

  • Unchecked Exception- Các lớp mở rộng RuntimeException được gọi là các ngoại lệ không được kiểm tra. Ví dụ, ArithmeticException, NullPointerException, ArrayIndexOutOfBoundsException, v.v. Các ngoại lệ chưa được kiểm tra không được kiểm tra tại thời điểm biên dịch thay vì chúng được kiểm tra trong thời gian chạy.

Một trường hợp cổ điển là ArrayIndexOutOfBoundsException xảy ra khi bạn cố gắng truy cập chỉ mục của một mảng lớn hơn độ dài của mảng. Sau đây là một ví dụ điển hình của loại sai lầm này.

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (aget (int-array [1 2 3]) 5)
      (catch Exception e (println (str "caught exception: " (.toString e))))
      (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

Khi đoạn mã trên được thực thi, ngoại lệ sau sẽ được đưa ra.

caught exception: java.lang.ArrayIndexOutOfBoundsException: 5
This is our final block
Let's move on

lỗi

Lỗi không thể khôi phục được ví dụ như OutOfMemoryError, VirtualMachineError, AssertionError, v.v. Đây là những lỗi mà chương trình không bao giờ có thể khôi phục được và sẽ khiến chương trình bị sập. Bây giờ chúng ta cần một số cơ chế để bắt các ngoại lệ này để chương trình có thể tiếp tục chạy nếu các ngoại lệ này tồn tại.

Sơ đồ sau đây cho thấy cách tổ chức phân cấp các ngoại lệ trong Clojure. Tất cả đều dựa trên hệ thống phân cấp được xác định trong Java.

Bắt ngoại lệ

Cũng giống như các ngôn ngữ lập trình khác, Clojure cung cấp khối 'try-catch' bình thường để bắt các ngoại lệ khi và khi chúng xảy ra.

Sau đây là cú pháp chung của khối try-catch.

(try
   (//Protected code)
   catch Exception e1)
(//Catch block)

Tất cả mã của bạn có thể tạo ra một ngoại lệ được đặt trong Protected code block.

bên trong catch block, bạn có thể viết mã tùy chỉnh để xử lý ngoại lệ của mình để ứng dụng có thể khôi phục khỏi ngoại lệ.

Hãy xem ví dụ trước đó của chúng tôi đã tạo ra ngoại lệ không tìm thấy tệp và xem cách chúng ta có thể sử dụng khối try catch để bắt ngoại lệ do chương trình nêu ra.

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      (catch Exception e (println (str "caught exception: " (.getMessage e))))))
(Example)

Chương trình trên tạo ra kết quả sau.

caught exception: Example.txt (No such file or directory)

Từ đoạn mã trên, chúng tôi đưa ra mã bị lỗi trong try block. Trong khối bắt, chúng tôi chỉ bắt ngoại lệ của mình và xuất ra thông báo rằng một ngoại lệ đã xảy ra. Vì vậy, bây giờ chúng ta có một cách có ý nghĩa để nắm bắt ngoại lệ, được tạo ra bởi chương trình.

Nhiều khối bắt

Người ta có thể có nhiều khối bắt để xử lý nhiều loại ngoại lệ. Đối với mỗi khối bắt, tùy thuộc vào loại ngoại lệ được nêu ra, bạn sẽ viết mã để xử lý nó cho phù hợp.

Hãy sửa đổi mã trước đó của chúng tôi để bao gồm hai khối bắt, một khối dành riêng cho tệp của chúng tôi không tìm thấy ngoại lệ và khối còn lại dành cho khối ngoại lệ chung.

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.getMessage e))))
      
      (catch Exception e (println (str "caught exception: " (.getMessage e)))))
   (println "Let's move on"))
(Example)

Chương trình trên tạo ra kết quả sau.

caught file exception: Example.txt (No such file or directory)
Let's move on

Từ đầu ra ở trên, chúng ta có thể thấy rõ rằng ngoại lệ của chúng ta đã bị chặn bởi khối bắt 'FileNotFoundException' chứ không phải khối chung chung.

Cuối cùng là Chặn

Khối cuối cùng theo sau khối thử hoặc khối bắt. Một khối mã cuối cùng luôn thực thi, bất kể sự xuất hiện của Ngoại lệ.

Sử dụng khối cuối cùng cho phép bạn chạy bất kỳ câu lệnh loại dọn dẹp nào mà bạn muốn thực thi, bất kể điều gì xảy ra trong mã được bảo vệ. Sau đây là cú pháp cho khối này.

(try
   (//Protected code)
   catch Exception e1)
(//Catch block)
(finally
   //Cleanup code)

Hãy sửa đổi đoạn mã trên và thêm khối mã cuối cùng. Sau đây là đoạn mã.

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.getMessage e))))
      
      (catch Exception e (println (str "caught exception: " (.getMessage e))))
      (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

Chương trình trên tạo ra kết quả sau.

caught file exception: Example.txt (No such file or directory)
This is our final block
Let's move on

Từ chương trình trên, bạn có thể thấy rằng khối cuối cùng cũng được thực hiện sau khi khối catch bắt được ngoại lệ bắt buộc.

Vì Clojure bắt nguồn xử lý ngoại lệ của nó từ Java, tương tự như Java, các phương pháp sau có sẵn trong Clojure để quản lý các ngoại lệ.

  • public String getMessage()- Trả về một thông báo chi tiết về ngoại lệ đã xảy ra. Thông báo này được khởi tạo trong phương thức khởi tạo Throwable.

  • public Throwable getCause() - Trả về nguyên nhân của ngoại lệ như được đại diện bởi một đối tượng Throwable.

  • public String toString() - Trả về tên của lớp được ghép với kết quả của getMessage ().

  • public void printStackTrace() - In kết quả của toString () cùng với dấu vết ngăn xếp lên System.err, luồng đầu ra lỗi.

  • public StackTraceElement [] getStackTrace()- Trả về một mảng chứa mỗi phần tử trên dấu vết ngăn xếp. Phần tử ở chỉ số 0 đại diện cho phần trên cùng của ngăn xếp cuộc gọi và phần tử cuối cùng trong mảng đại diện cho phương thức ở cuối ngăn xếp cuộc gọi.

  • public Throwable fillInStackTrace() - Làm đầy dấu vết ngăn xếp của đối tượng Throwable này với dấu vết ngăn xếp hiện tại, thêm vào bất kỳ thông tin nào trước đó trong dấu vết ngăn xếp.

Sau đây là mã ví dụ sử dụng một số phương pháp được liệt kê ở trên.

(ns clojure.examples.example
   (:gen-class))
(defn Example []
   (try
      (def string1 (slurp "Example.txt"))
      (println string1)
      
      (catch java.io.FileNotFoundException e (println (str "caught file
         exception: " (.toString e))))
      
      (catch Exception e (println (str "caught exception: " (.toString e))))
   (finally (println "This is our final block")))
   (println "Let's move on"))
(Example)

Chương trình trên tạo ra kết quả sau.

caught file exception: java.io.FileNotFoundException: Example.txt (No such file
or directory)
This is our final block
Let's move on