Go – Xử lý lỗi

error-handling

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*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.

 

 

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.