golang

Giới thiệu

Nếu bạn đã viết mã bằng Golang, ắt hẳn bạn sẽ gặp kiểu error. Go sử dụng các giá trị error để chỉ ra sự bất thường của ứng dụng. Cho ví dụ, hàm os.Open trả về 1 giá trị error khi không mở được tệp:

Đoạn mã sau sử dụng os.Open để mở một tệp. Nếu một lỗi xảy ra, nó sẽ gọi log.Fatal để in thông báo lỗi và dừng lại:

Bạn có thể làm được nhiều việc với kiểu error trong Go, nhưng trong bài viết này, chúng ta sẽ xem xét kỹ hơn về lỗi và thảo luận một số phương pháp hay để xử lý lỗi trong Go.

Kiểu error

Kiểu error thực chất là 1 kiểu interface. Một biến error đại diện cho bất kỳ giá trị nào có thể mô tả chính nó dưới dạng một string. Đây là khai báo của interface:

Với kiểu error, thì Go đã tích hợp sẵn được khai báo trước trong universe block.

Cách triển khai lỗi thường được sử dụng nhất là package errors không hỗ trợ kiểu errorString:

Bạn có thể tạo 1 trong những giá trị này với hàm errors.New. Nó nhận 1 string và convert thành errors.errorString, sau đó chuyển về giá trị error:

Đây là cách bạn sử dụng errors.New trong hàm tính căn bậc 2:

Khi người dùng cố tình tính cân bậc 2 của số âm, sẽ có 1 thông báo lỗi đưa ra:

math: square root of negative number.

Chúng ta sử dụng hàm trên như sau:

Gói fmt hiển thị error bằng cách gọi phương thức Error() string của nó.

Trách nhiệm của thông báo lỗi là phải mô tả đầy đủ ngữ cảnh của nó. Hàm os.Open trả về lỗi là “open /etc/passwd: permission denied,” chứ không mỗi “permission denied.”  Lỗi do hàm Sqrt của chúng ta trả về là thiếu thông tin về đối số không hợp lệ. Để thêm thông tin chúng ta thêm gói fmt.Errorf. Nó định dạng một chuỗi theo các quy tắc của Printf và trả về nó dưới dạng một lỗi tạo bởi error.New:

Trong nhiều trường hợp thì fmt.Errorf là đủ tốt, nhưng vì error là 1 interface, nên bạn có thể chỉnh sửa tùy ý data của bạn như các giá trị error, để bạn kiểm tra tùy theo ngữ cảnh của bạn.

Cho ví dụ, bạn muốn khôi phục giá trị không hợp lệ của đối số truyền vào Sqrt, bạn có thể định nghĩa 1 kiểu mới thay vì dùng string:

Sau đó chúng ta có thể sử dụng type assertion để kiểm tra NegativeSqrtError và xử lý 1 cách đặc biệt, mà các hàm fmt.Println or log.Fatal sẽ không nhìn thấy sự thay đổi đó.

Một ví dụ khác, package  json chỉ định loại SyntaxError mà hàm json.Decode sẽ trả về lỗi khi không parsing được JSON:

Trường Offset sẽ không được hiển thị ở format của error, nhưng chúng ta có thể sử dụng biến line để thêm thông tin về lỗi đó:

Đoạn code trên thuộc dự án Camlistore.

Interface error chỉ có 1 method Error, triển khai lỗi cụ thể có thể có các methods bổ sung. Cho ví dụ, package net  trả về lỗi thuộc kiểu error, tuân theo quy ước thông thường, nhưng một số triển khai error có các methods bổ sung được xác định bởi interface net.Error:

Client có thể test với net.Error bằng cách kiểm tra các assertion, sau đó phân biệt lỗi mạng tạm thời với lỗi vĩnh viễn. Cho ví dụ, trình crawler web có thể sleep và thử lại khi gặp 1 lỗi tạm thời và từ bỏ nếu gặp lỗi vĩnh viễn:

Đơn giản hóa việc xử lý lỗi lặp lại

Trong Go xử lý lỗi là rất quan trọng. Thiết kế và quy ước của ngôn ngữ khuyến khích bạn kiểm tra nơi xảy ra lỗi,(khác biệt với quy ước trong các ngôn ngữ khác là ném ra các ngoại lệ và đôi khi bắt chúng). Trong một số trường hợp, điều này làm cho mã Go trở nên dài dòng, nhưng may mắn thay, bạn có thể sử dụng một số kỹ thuật để giảm thiểu việc xử lý lỗi lặp đi lặp lại.

Hãy xem xét ứng dụng App Engine , với xử lý HTTP lấy các bản ghi từ datastore và định dạng nó bằng 1 template:

Hàm xử lý lỗi trả về bởi hàm datastore.Get, và hàm viewTemplate’s Execute. Trong 2 trường hợp, nó đều trả về 1 lỗi đơn giản với thông báo mã 500 (“Internal Server Error”). Bạn nhận ra là code đã lặp lại.

Để giảm sự lặp lại, bạn định nghĩa 1 kiểu HTTP là appHandler như sau:

Sau đó chúng ta có thể thay đổi hàm viewRecord như sau:

Điều này trông đơn giản hơn, nhưng package http  không hiểu kiểu trả về error. Để khắc phục, chúng ta triển khai interface ServeHTTP của http.Handler như sau:

Phương thức ServeHTTP gọi hàm appHandler và trả về error nếu có tới user.

Bây giờ chúng ta đăng ký hàm Handle( thay vì hàm HandleFunc) như appHandler là 1 http.Handler(nó không phải là 1 http.HandlerFunc):

Với cách xử lý này, chúng ta làm nó thân thiện hơn với user. Để hiển thị string thông báo lỗi và HTTP error code, chúng ta tạo 1 struct appError như sau:

Tiếp theo chúng ta sửa giá trị trả về thuộc kiểu *appError:

Và làm cho method appHandler’s ServeHTTP hiển thị thông báo lỗi appError’ chính xác với error code và message cho developer:

Cuối cùng chúng ta cập nhật lại hàm viewRecord để trả về chi tiết hơn khi gặp lỗi:

Phiên bản này có cùng độ dài với phiên bản ban đầu, nhưng mỗi dòng đều có ý nghĩa riêng và thân thiện hơn với người dùng. Nếu bạn muốn cải thiện thì sau đây là 1 số ý tưởng:

  1. Cung cấp cho trình xử lý error bằng 1 template HTML.
  2. Giúp việc debug dễ dàng hơn bằng stack trace vào HTTP response nếu user đó là quản trị viên.
  3. Viết 1 constructor function vào appError để lưu trữ  stack trace cho việc debug dễ dàng hơn.
  4. recover panic bên trong appHandler, logging trên console bằng “Critical”, và thông báo cho user “a serious error has occurred.” Đây là một cách tốt để tránh cho người dùng thấy các thông báo lỗi khó hiểu do lỗi lập trình gây ra. Xem thêm Defer, Panic, and Recover.

Kết luận

Xử lý lỗi là 1 phần quan trọng trong việc viết 1 phần mềm tốt. Bằng việc sử dụng các kỹ thuật trong bài đăng này sẽ giúp bạn viết code Go 1 cách ngắn gọn và đáng tin cậy hơn.

Code Toàn Bug

Code nhiều bug nhiều!

Leave a Reply

Your email address will not be published.