Chuyển tới nội dung chính

⚠️ Xử Lý Sự Cố - Error Handling

Chào mừng đến với bài học về Error Handling - cách xử lý sự cố trong bếp nhà hàng!

🎯 Món Ăn Hôm Nay

Tưởng tượng bạn đang nấu phở và gặp sự cố:

  • Hết muối → Phải xử lý: Đi mua hoặc dùng nước mắm thay thế
  • Nước dùng cạn → Phải xử lý: Thêm nước nhanh
  • Thịt bị hỏng → Phải xử lý: Báo khách, đổi món khác

Đó chính là Error Handling - xử lý sự cố để món ăn vẫn hoàn hảo!

🥘 Error Handling Là Gì?

Error = Sự cố trong bếp (thứ không mong muốn xảy ra) Error Handling = Cách xử lý sự cố

// Nấu món có thể gặp sự cố
func nauPho(khoThit int) (string, error) {
if khoThit < 100 {
return "", errors.New("❌ Thiếu thịt! Cần ít nhất 100g")
}
return "🍜 Phở Bò", nil // nil = không có lỗi
}

// Sử dụng
mon, err := nauPho(50)
if err != nil {
fmt.Println("Sự cố:", err)
return
}
fmt.Println("Thành công:", mon)

🍜 Ẩn Dụ Nhà Hàng:

  • error = Sự cố trong bếp
  • nil = Mọi thứ ổn
  • if err != nil = Kiểm tra có sự cố không

👨‍🍳 Câu Chuyện Trong Bếp

Sáng nay, nhà hàng gặp nhiều sự cố:

Tình huống 1: Kiểm tra nguyên liệu

func kiemTraNguyenLieu(tenNL string, soLuong int) error {
if soLuong < 100 {
return fmt.Errorf("❌ Thiếu %s! Còn %dg, cần ít nhất 100g", tenNL, soLuong)
}
return nil
}

// Sử dụng
if err := kiemTraNguyenLieu("Thịt bò", 50); err != nil {
fmt.Println(err)
// Xử lý: Đi mua hoặc đổi món
}

Tình huống 2: Xử lý nhiều sự cố

func chuanBiMon(tenMon string) error {
// Kiểm tra nguyên liệu
if err := kiemTraKho(tenMon); err != nil {
return fmt.Errorf("lỗi kho: %w", err) // Bọc lỗi
}

// Kiểm tra đầu bếp
if err := kiemTraDauBep(); err != nil {
return fmt.Errorf("lỗi nhân sự: %w", err)
}

return nil
}

🎉 Kết quả: Error handling giúp phát hiện và xử lý sự cố kịp thời!

📝 Công Thức Nấu (Code Examples)

Ví Dụ 1: Error Cơ Bản

package main

import (
"errors"
"fmt"
)

func nauPho(khoThit int, khoNuocDung int) (string, error) {
// Kiểm tra thịt
if khoThit < 100 {
return "", errors.New("thiếu thịt")
}

// Kiểm tra nước dùng
if khoNuocDung < 500 {
return "", errors.New("thiếu nước dùng")
}

return "🍜 Phở Bò ngon", nil
}

func main() {
mon, err := nauPho(80, 600)

if err != nil {
fmt.Println("❌ Sự cố:", err)
fmt.Println("→ Cần đi mua thêm nguyên liệu!")
return
}

fmt.Println("✅", mon)
}

Ví Dụ 2: Custom Error - Lỗi Tùy Chỉnh

// Tạo kiểu lỗi riêng
type LoiThieuNguyenLieu struct {
TenNL string
SoLuong int
YeuCau int
}

func (e *LoiThieuNguyenLieu) Error() string {
return fmt.Sprintf("Thiếu %s: còn %dg, cần %dg",
e.TenNL, e.SoLuong, e.YeuCau)
}

func kiemTraKho(tenNL string, soLuong int) error {
yeuCau := 100

if soLuong < yeuCau {
return &LoiThieuNguyenLieu{
TenNL: tenNL,
SoLuong: soLuong,
YeuCau: yeuCau,
}
}

return nil
}

func main() {
err := kiemTraKho("Thịt bò", 50)

if err != nil {
fmt.Println("❌", err)

// Kiểm tra loại lỗi cụ thể
if loiNL, ok := err.(*LoiThieuNguyenLieu); ok {
thieu := loiNL.YeuCau - loiNL.SoLuong
fmt.Printf("→ Cần mua thêm %dg %s\n", thieu, loiNL.TenNL)
}
}
}

Ví Dụ 3: Error Wrapping - Bọc Lỗi

func layNguyenLieuTuKho(tenNL string) error {
// Giả sử lỗi từ tầng dưới
return errors.New("kho đóng cửa")
}

func chuanBiNguyenLieu(tenNL string) error {
err := layNguyenLieuTuKho(tenNL)
if err != nil {
// Bọc lỗi với thêm thông tin
return fmt.Errorf("không lấy được %s: %w", tenNL, err)
}
return nil
}

func nauMon(tenMon string) error {
err := chuanBiNguyenLieu("thịt bò")
if err != nil {
return fmt.Errorf("lỗi nấu %s: %w", tenMon, err)
}
return nil
}

func main() {
err := nauMon("Phở Bò")

if err != nil {
fmt.Println("Chuỗi lỗi:", err)
// Output: lỗi nấu Phở Bò: không lấy được thịt bò: kho đóng cửa

// Kiểm tra lỗi gốc
if errors.Is(err, errors.New("kho đóng cửa")) {
fmt.Println("→ Cần chờ kho mở cửa")
}
}
}

Ví Dụ 4: Defer và Error

import "os"

func docCongThuc(tenFile string) error {
file, err := os.Open(tenFile)
if err != nil {
return fmt.Errorf("không mở được công thức: %w", err)
}
defer file.Close() // Đảm bảo đóng file

// Đọc nội dung
// ...

return nil
}

func main() {
if err := docCongThuc("pho.txt"); err != nil {
fmt.Println("❌", err)
fmt.Println("→ Sử dụng công thức dự phòng")
}
}

Ví Dụ 5: Panic và Recover - Sự Cố Nghiêm Trọng

func nauMon() {
defer func() {
if r := recover(); r != nil {
fmt.Println("🚨 SỰ CỐ NGHIÊM TRỌNG:", r)
fmt.Println("→ Gọi bếp trưởng ngay!")
}
}()

// Giả sử có sự cố nghiêm trọng
panic("Bếp cháy!")
}

func main() {
fmt.Println("Bắt đầu nấu...")
nauMon()
fmt.Println("Tiếp tục hoạt động (đã xử lý)")
}

🔥 Thực Hành Trong Bếp

Bài Tập 1: Hệ Thống Đặt Món

package main

import (
"errors"
"fmt"
)

var (
ErrHetMon = errors.New("hết món")
ErrHetNguyenLieu = errors.New("hết nguyên liệu")
)

type QuanAn struct {
tonKho map[string]int
}

func (q *QuanAn) DatMon(tenMon string, soLuong int) error {
if soLuong <= 0 {
return errors.New("số lượng phải > 0")
}

kho, coMon := q.tonKho[tenMon]
if !coMon {
return fmt.Errorf("%w: %s", ErrHetMon, tenMon)
}

if kho < soLuong {
return fmt.Errorf("%w: %s (còn %d)", ErrHetNguyenLieu, tenMon, kho)
}

q.tonKho[tenMon] -= soLuong
fmt.Printf("✅ Đặt %s x%d thành công\n", tenMon, soLuong)
return nil
}

func main() {
quan := &QuanAn{
tonKho: map[string]int{
"Phở Bò": 10,
"Bún Chả": 5,
},
}

// Test các trường hợp
if err := quan.DatMon("Phở Bò", 3); err != nil {
fmt.Println("Lỗi:", err)
}

if err := quan.DatMon("Cơm Tấm", 1); err != nil {
if errors.Is(err, ErrHetMon) {
fmt.Println("→ Món này không có trong menu")
}
}

if err := quan.DatMon("Bún Chả", 10); err != nil {
if errors.Is(err, ErrHetNguyenLieu) {
fmt.Println("→ Cần bổ sung nguyên liệu")
}
}
}

Bài Tập 2: Xử Lý Thanh Toán

type LoiThanhToan struct {
SoTien int
SoDu int
ThieuBaoNhieu int
}

func (e *LoiThanhToan) Error() string {
return fmt.Sprintf("Không đủ tiền: cần %d VND, còn %d VND (thiếu %d VND)",
e.SoTien, e.SoDu, e.ThieuBaoNhieu)
}

func thanhToan(soTien int, soDu int) error {
if soDu < soTien {
return &LoiThanhToan{
SoTien: soTien,
SoDu: soDu,
ThieuBaoNhieu: soTien - soDu,
}
}

fmt.Printf("✅ Thanh toán %d VND thành công\n", soTien)
return nil
}

func main() {
err := thanhToan(150000, 100000)

if err != nil {
if loiTT, ok := err.(*LoiThanhToan); ok {
fmt.Println(loiTT)
fmt.Printf("→ Vui lòng nạp thêm %d VND\n", loiTT.ThieuBaoNhieu)
}
}
}

⚠️ Những Lỗi Đầu Bếp Thường Gặp

Lỗi 1: Không Kiểm Tra Error

// ❌ SAI: Bỏ qua lỗi
func main() {
mon, _ := nauPho(50) // Bỏ qua lỗi nguy hiểm!
fmt.Println(mon)
}

// ✅ ĐÚNG: Luôn kiểm tra
func main() {
mon, err := nauPho(50)
if err != nil {
fmt.Println("Lỗi:", err)
return
}
fmt.Println(mon)
}

Lỗi 2: Error Message Không Rõ Ràng

// ❌ SAI: Thông báo mơ hồ
return errors.New("lỗi")

// ✅ ĐÚNG: Thông báo cụ thể
return fmt.Errorf("thiếu nguyên liệu '%s': cần %dg, còn %dg",
tenNL, yeuCau, tonKho)

Lỗi 3: Panic Không Cần Thiết

// ❌ SAI: Panic cho lỗi thường
func nauPho(khoThit int) string {
if khoThit < 100 {
panic("thiếu thịt") // Quá mức!
}
return "Phở"
}

// ✅ ĐÚNG: Trả error bình thường
func nauPho(khoThit int) (string, error) {
if khoThit < 100 {
return "", errors.New("thiếu thịt")
}
return "Phở", nil
}

💡 Bí Quyết Của Đầu Bếp

  1. Luôn kiểm tra error: if err != nil là bắt buộc
  2. Error message chi tiết: Giúp debug nhanh
  3. Wrap errors: Dùng %w để giữ lỗi gốc
  4. Custom errors cho business logic: Dễ xử lý riêng
  5. Panic chỉ cho lỗi nghiêm trọng: Bếp cháy, database sập,...

🎓 Bạn Đã Học Được

  • ✅ Error = Sự cố cần xử lý
  • ✅ nil = Không có lỗi
  • ✅ errors.New() = Tạo lỗi đơn giản
  • ✅ fmt.Errorf("%w") = Bọc lỗi có context
  • ✅ Custom error = Lỗi tùy chỉnh cho business
  • ✅ panic/recover = Xử lý sự cố nghiêm trọng

🍜 Món Tiếp Theo

Đã biết xử lý sự cố! Giờ học cách test món ăn:

👉 Nếm Thử Món - Testing


💡 Lời Khuyên Cuối: Error handling giống như kỹ năng xử lý sự cố của đầu bếp - càng nhanh và chính xác, món ăn càng hoàn hảo! Luôn chuẩn bị cho mọi tình huống!