Handle Error
Chúng ta sẽ đi qua error
, fmt.Errorf
, errors.Is()
, errors.As()
, và cách tạo Custom Error Handling một cách chuyên sâu, kèm ví dụ thực tế và các best practices.
1. error
& fmt.Errorf
error
& fmt.Errorf
error
là gì?
Trong Go,
error
là một interface được định nghĩa như sau:
type error interface {
Error() string
}
Bất kỳ struct nào implement phương thức
Error() string
đều là mộterror
.Đây là cách Go xử lý lỗi: trả về
error
như một giá trị, không dùng exception như Java hay Python.
fmt.Errorf
fmt.Errorf
là hàm tiện ích trong packagefmt
để tạo mộterror
với chuỗi định dạng.Cú pháp:
fmt.Errorf(format string, args...)
.Nó trả về một đối tượng
error
đơn giản, không chứa thông tin ngữ cảnh phức tạp (chỉ là chuỗi).
Ví dụ cơ bản:
package main
import (
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero: %d / %d", a, b)
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err) // Error: division by zero: 10 / 0
return
}
fmt.Println("Result:", result)
}
Khi nào dùng fmt.Errorf
?
Khi lỗi đơn giản, không cần ngữ cảnh bổ sung.
Thường dùng trong các hàm không yêu cầu phân tích lỗi chi tiết.
Giới hạn:
fmt.Errorf
chỉ tạo một chuỗi tĩnh, không hỗ trợ so sánh lỗi hoặc trích xuất thông tin chi tiết.
2. errors.Is()
& errors.As()
errors.Is()
& errors.As()
Giới thiệu
Từ Go 1.13, package
errors
cung cấp hai hàm mạnh mẽ để xử lý lỗi:errors.Is()
: Kiểm tra xem một lỗi có phải là một lỗi cụ thể không (so sánh với target error).errors.As()
: Trích xuất một lỗi thành kiểu cụ thể để lấy thông tin chi tiết.
errors.Is()
So sánh lỗi với một giá trị lỗi cụ thể (thường là biến toàn cục).
Hỗ trợ error wrapping (dùng
fmt.Errorf
với%w
để bọc lỗi).
Ví dụ:
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func fetchData(id int) error {
if id == 0 {
return fmt.Errorf("fetch failed: %w", ErrNotFound)
}
return nil
}
func main() {
err := fetchData(0)
if errors.Is(err, ErrNotFound) {
fmt.Println("Data not found!") // Data not found!
}
fmt.Println("Full error:", err) // Full error: fetch failed: not found
}
errors.As()
Chuyển đổi lỗi thành một kiểu cụ thể để truy cập thông tin chi tiết.
Yêu cầu lỗi được wrap hoặc implement interface phù hợp.
Ví dụ:
package main
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Msg string
}
func (e *MyError) Error() string {
return fmt.Sprintf("error %d: %s", e.Code, e.Msg)
}
func process() error {
return &MyError{Code: 404, Msg: "resource not found"}
}
func main() {
err := process()
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("Code: %d, Msg: %s\n", myErr.Code, myErr.Msg) // Code: 404, Msg: resource not found
}
}
Wrapping và Kết hợp:
Dùng
%w
trongfmt.Errorf
để bọc lỗi, kết hợpIs
vàAs
:
err := fmt.Errorf("additional context: %w", &MyError{Code: 500, Msg: "server error"})
if errors.Is(err, &MyError{}) { // Chỉ kiểm tra kiểu
fmt.Println("Is MyError!")
}
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Println("Extracted:", myErr.Code, myErr.Msg)
}
Best Practices:
Dùng
errors.Is()
khi chỉ cần kiểm tra lỗi cụ thể.Dùng
errors.As()
khi cần truy cập dữ liệu từ lỗi tùy chỉnh.
3. Tạo Custom Error Handling
Tại sao cần Custom Error?
Lỗi mặc định (
errors.New
,fmt.Errorf
) không đủ linh hoạt cho các ứng dụng lớn.Custom error cho phép:
Thêm metadata (mã lỗi, stack trace, context).
Xử lý lỗi có cấu trúc trong hệ thống phức tạp (ví dụ: API, microservices).
Cách triển khai:
Tạo struct cho lỗi:
Implement interface
error
.Thêm các trường bổ sung (code, message, cause, v.v.).
Wrap lỗi:
Dùng
%w
hoặc một cơ chế tùy chỉnh để giữ lỗi gốc.
Xử lý lỗi:
Kết hợp
errors.Is()
vàerrors.As()
.
Ví dụ thực tế:
package main
import (
"errors"
"fmt"
)
// CustomError là struct lỗi tùy chỉnh
type CustomError struct {
Code int
Message string
Cause error // Lỗi gốc (nếu có)
}
// Implement interface error
func (e *CustomError) Error() string {
if e.Cause != nil {
return fmt.Sprintf("error %d: %s (%v)", e.Code, e.Message, e.Cause)
}
return fmt.Sprintf("error %d: %s", e.Code, e.Message)
}
// Unwrap để hỗ trợ errors.Is và errors.As
func (e *CustomError) Unwrap() error {
return e.Cause
}
// Hàm tạo lỗi
func NewCustomError(code int, message string, cause error) error {
return &CustomError{
Code: code,
Message: message,
Cause: cause,
}
}
func fetchResource(id int) error {
if id < 0 {
return NewCustomError(400, "invalid id", nil)
}
if id == 0 {
return NewCustomError(404, "resource not found", fmt.Errorf("db query failed"))
}
return nil
}
func main() {
// Trường hợp lỗi 400
err := fetchResource(-1)
if err != nil {
fmt.Println("Error:", err) // Error: error 400: invalid id
var customErr *CustomError
if errors.As(err, &customErr) {
fmt.Printf("Code: %d, Message: %s\n", customErr.Code, customErr.Message)
}
}
// Trường hợp lỗi 404 với cause
err = fetchResource(0)
if err != nil {
fmt.Println("Error:", err) // Error: error 404: resource not found (db query failed)
var customErr *CustomError
if errors.As(err, &customErr) {
fmt.Printf("Code: %d, Message: %s, Cause: %v\n", customErr.Code, customErr.Message, customErr.Cause)
}
}
}
Phân tích:
CustomError
: CóCode
,Message
, vàCause
để lưu trữ thông tin chi tiết.Unwrap()
: Hỗ trợ unwrap lỗi gốc, cho phéperrors.Is()
vàerrors.As()
hoạt động.NewCustomError
: Hàm tiện ích để tạo lỗi một cách nhất quán.
Ứng dụng thực tế:
API Server:
package main
import (
"errors"
"fmt"
"net/http"
)
type APIError struct {
Status int
Message string
Cause error
}
func (e *APIError) Error() string {
return fmt.Sprintf("status %d: %s", e.Status, e.Message)
}
func (e *APIError) Unwrap() error {
return e.Cause
}
func handler(w http.ResponseWriter, r *http.Request) {
err := processRequest()
if err != nil {
var apiErr *APIError
if errors.As(err, &apiErr) {
http.Error(w, apiErr.Message, apiErr.Status)
return
}
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}
func processRequest() error {
return &APIError{
Status: 400,
Message: "bad request",
Cause: fmt.Errorf("invalid input"),
}
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
Trả về mã HTTP và thông điệp lỗi phù hợp dựa trên
APIError
.
Best Practices:
Định nghĩa các lỗi toàn cục (như
ErrNotFound
) cho các trường hợp phổ biến.Thêm stack trace (dùng package
github.com/pkg/errors
nếu cần).Đảm bảo lỗi có thể unwrap để tận dụng
errors.Is()
vàerrors.As()
.
Tổng kết
error
&fmt.Errorf
: Cơ bản, dùng cho lỗi đơn giản.errors.Is()
&errors.As()
: Công cụ mạnh mẽ để kiểm tra và trích xuất lỗi.Custom Error: Tạo struct lỗi tùy chỉnh với metadata, hỗ trợ wrap/unwrap.
Last updated