Go không cung cấp phương thức try/catch
như thông thường để xử lý lỗi, thay vào đó các lỗi được trả về như một giá trị.
Chúng ta đều hiểu rằng lỗi là một cái gì đó bất thường xảy ra trong chương trình, nhưng trong Go lỗi có một ý nghĩa khác. Lỗi chỉ là một giá trị hàm có thể trả về nếu có điều gì đó không mong đợi xảy ra.
error
được tích hợp sẵn trong Go với giá trị mặc định là nil
. Có một cách để xử lý lỗi là trả về nó như giá trị cuối cùng của hàm gọi và kiểm tra xem nó có nil
hay không.
val, err := myFunction( args... ); if err != nil { // Xử lý lỗi } else { // Thành công }
Kiểu error
Hãy cùng tìm hiểu sâu hơn về kiểu dữ liệu error
. error là kiểu tích hợp sẵn nhưng thực tế nó là một kiểu interface có sẵn, sử dụng ở mọi nơi trong chương trình Go, và nó thực thi phương thức Error()
trả về một thông báo lỗi dạng string
type error interface { Error() string }
Do đó chúng ta có thể định nghĩa các kiểu dữ liệu lỗi bằng cách thực thi cho error
interface. Hãy cùng xem ví dụ sau đây
package main import "fmt" // Tạo struct type MyError struct{} // struct thực thi phương thức `Error` func (myErr *MyError) Error() string { return "Something unexpected happend!" } func main() { // tạo error myErr := &MyError{} // in ra thông báo lỗi fmt.Println(myErr) }
Kết quả
Something unexpected happend!
Trong ví dụ bên trên, chúng ta đã tạo kiểu struct MyError
, cung cấp thực thi code cho phương thức Error
. Phương thức này trả về một string
. Do đó, struct MyError
đã thực thi code cho interface error
.
Nhưng để tạo ngay cả một error đơn giản, chúng ta cũng phải định nghĩa một struct và tạo một thực thi code cho phương thức Error()
. Để tránh điều này, Go cung cấp một package error
được tích hợp sẵn, và public hàm New
. Hàm này yêu cầu một thông báo lỗi và trả về error
.
package main import "fmt" import "errors" func main() { // tạo error myErr := errors.New("Something unexpected happend!") // in ra thông báo lỗi fmt.Println(myErr) }
Kết quả
Something unexpected happend!
Chúng ta có thể thấy rằng hàm New
yêu cầu một string, là thông báo lỗi, và trả về một error
. Chúng ta cũng hiểu rằng lỗi trả về là kiểu gì đấy thực thi code cho interface error
, vấy myError
là kiểu gì? Hãy viết lại ví dụ bên trên và thử in ra kiểu trả về.
package main import "fmt" import "errors" func main() { // Tạo error myErr := errors.New("Something unexpected happend!") // in ra kiểu dữ liệu của lỗi fmt.Printf("Type of myErr is %T \n", myErr) fmt.Printf("Value of myErr is %#v \n", myErr) }
Kết quả
Type of myErr is *errors.errorString Value of myErr is &errors.errorString{s:"Something unexpected happend!"}
Như vậy kiểu dữ liệu của myErr
là *errors.errorString
, nó là một con trỏ cho errors.errorString
.
Package error
có struct errorString
thực thi code cho error
interface.
type errorString struct { s string } func (e *errorString) Error() string { return e.s }
Hàm New
tạo và trả về con trỏ cho struct errorString
.
func New(text string) error { return &errorString{text} }
Trích xuất thêm thông tin về lỗi
Trong các ví dụ trước, chúng ta chỉ in ra các thông báo lỗi. Giả sử chúng ta viết một chương trình để mở một file text và chúng ta muốn biết đường dẫn thực sự gây ra lỗi. Có một cách đó là phân tích thông tin lỗi.
open /test.txt: No such file or directory
Chúng ta có thể phân tích thông báo lỗi và thấy được đường dẫn là /text.txt
, nhưng đây không phải là một cách tốt bởi vì thông tin của thông báo có thể thay đổi bất kỳ lúc nào khi ngôn ngữ cập nhập phiên bản mới và code của chúng ta sẽ bị lỗi.
Lấy thêm thông tin từ các trường của struct
Nếu bạn đọc kỹ tài liệu về hàm Open
, bạn sẽ thấy kiểu trả về của lỗi là *PathError
. PathError
là kiểu struct và phần thực thi code của nó như sau:
type PathError struct { Op string Path string Err error } func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() }
*PathError
thực thi code cho error interface
bằng cách khai báo phương thức Error() string
. Phương thức này trả về thông tin hoạt động (op), đường dẫn (path), và lỗi (error) nối liên tiếp với nhau. Cuối cùng, chúng ta có thông tin lỗi như sau
open /test.txt: No such file or directory
Trường path
của struct PathError
chứa đường dẫn file bị lỗi. Hãy cùng xem ví dụ sau
package main import ( "fmt" "os" ) func main() { f, err := os.Open("/test.txt") if err, ok := err.(*os.PathError); ok { fmt.Println("File at path", err.Path, "failed to open") return } fmt.Println(f.Name(), "opened successfully") }
Kết quả
File at path /test.txt failed to open
Trong ví dụ trên chúng ta thực hiện kiểm tra lỗi ở hàng 10 và lấy được giá trị của interface error
, và sau đó chúng ta in ra thông tin đường dẫn lỗi bằng cách gọi err.path
.
Lấy thêm thông tin sử dụng phương thức
Cách thứ 2 để lấy thêm thông tin về lỗi là gọi các phương thức trên kiểu struct. Để hiểu rõ hơn chúng ta cùng xem ví dụ sau
Kiểu struct DNSError
trong chuẩn thư viện được định nghĩa như sau
type DNSError struct { ... } func (e *DNSError) Error() string { ... } func (e *DNSError) Timeout() bool { ... } func (e *DNSError) Temporary() bool { ... }
Như bạn đã thấy đoạn code bên trên, struct DNSError
có 2 phương thức Timeout() bool
và Temporary() bool
trả về giá trị boolean
để chỉ định rằng lỗi là do không nhận được phản hồi (timeout) hoặc là tạm thời (temporary).
Nào hãy cùng xem ví dụ sau
package main import ( "fmt" "net" ) func main() { addr, err := net.LookupHost("vngeeks123.com") if err, ok := err.(*net.DNSError); ok { if err.Timeout() { fmt.Println("operation timed out") } else if err.Temporary() { fmt.Println("temporary error") } else { fmt.Println("generic error: ", err) } return } fmt.Println(addr) }
Trong ví dụ trên chúng ta thử lấy địa chỉ IP của domain không tồn tại vngeeks123.com
. Để kiểm tra chúng ta thử lấy thông tin khi lỗi xảy ta bằng cách sử dụng *net.DNSError
, sau đó thử kiểm tra xem lỗi là do timeout
hay là temporary
.
Trong trường hợp của chúng ta đều không phải do Timeout hay Temporary nên kết quả trả về là
generic error: lookup vngeeks123.com: no such host
Đừng bỏ qua các lỗi
Không nên bỏ qua lỗi. Bỏ qua các lỗi là tăng thêm rắc rối cho chương trình. Hãy thử viết một chương trình lấy danh sách các thư mục và bỏ qua việc xử lý lỗi
package main import ( "fmt" "path/filepath" ) func main() { files, _ := filepath.Glob("[") fmt.Println("matched files", files) }
Chương trình sẽ đưa ra một lỗi vì pattern là không hợp lệ. Đoạn code đã bỏ qua lỗi bằng cách sử dụng gạch dưới _
. Tiếp theo thực hiện ra danh sách các file.
Kết quả
matched files []
Do chúng ta bỏ qua lỗi, kết quả dường như là không có file nào phù hợp với pattern, nhưng thực tế là do pattern không hợp lệ. Nên sẽ gây khó khăn cho việc nhận biết lỗi, do đó chúng ta không nên bỏ qua lỗi.