⚠️ 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
- Luôn kiểm tra error:
if err != nillà bắt buộc - Error message chi tiết: Giúp debug nhanh
- Wrap errors: Dùng
%wđể giữ lỗi gốc - Custom errors cho business logic: Dễ xử lý riêng
- 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:
💡 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!