0

Tổng hợp những lỗi cơ bản thường gặp khi mới bắt đầu lập trình Go (Golang) và cách khắc phục

Golang (Go) nổi tiếng là ngôn ngữ đơn giản, hiện đại và có cú pháp tinh gọn. Tuy nhiên, đối với người mới bắt đầu, sự khắt khe của trình biên dịch Go thường dẫn đến những lỗi rất đặc trưng. Dưới đây là danh sách đầy đủ 50 lỗi phổ biến nhất.

Nhóm 1: Lỗi Cú pháp & Khai báo biến (Lỗi 1 - 10)

  1. Quên đuôi .go khi chạy lệnh

    • Triệu chứng: package main is not in std.

    • Khắc phục: Luôn dùng lệnh go run main.go thay vì go run main.

  2. Nhầm lẫn giữa Rune (') và String (")

    • Triệu chứng: more than one character in rune literal.

    • Khắc phục: Dùng dấu nháy kép " cho chuỗi và nháy đơn ' cho một ký tự đơn.

  3. Biến khai báo nhưng không dùng đến (Unused Variable)

    • Triệu chứng: x declared and not used

    • Khắc phục: Xóa biến hoặc dùng dấu gạch dưới _ = x nếu bắt buộc phải giữ lại.

  4. Package được import nhưng không sử dụng

    • Triệu chứng: imported and not used: "fmt"

    • Khắc phục: Xóa dòng import hoặc dùng dấu gạch dưới import _ "fmt" nếu chỉ cần gọi hàm init().

  5. Khai báo biến kiểu ngắn (:=) ngoài hàm

    • Triệu chứng: non-declaration statement outside function body

    • Khắc phục: Sử dụng từ khóa var khi khai báo biến ở cấp độ toàn cục (package level).

  6. Đặt dấu mở ngoặc nhọn { sai dòng

    • Triệu chứng: unexpected newline, expecting {

    • Khắc phục: Đặt dấu { ngay trên cùng một dòng với câu lệnh if, for, func.

  7. Quên dấu phẩy trong khai báo Struct, Slice hoặc Map

    • Triệu chứng: syntax error: unexpected newline, expecting comma

    • Khắc phục: Luôn thêm dấu phẩy ở cuối mỗi dòng phần tử (Go bắt buộc dấu phẩy ở phần tử cuối cùng trong multi-line).

  8. Khai báo lại biến cục bộ bằng := trong cùng phạm vi

    • Triệu chứng: no new variables on left side of :=

    • Khắc phục: Dùng dấu = thông thường để gán lại giá trị cho biến đã tồn tại.

  9. Sử dụng sai kiểu dữ liệu khi gán giá trị

    • Triệu chứng: cannot use X (type A) as type B in assignment

    • Khắc phục: Go không tự động ép kiểu (implicit conversion). Phải ép kiểu tường minh như int(floatVal).

  10. Quên truyền đủ đối số cho hàm

-   _Triệu chứng:_ `not enough arguments in call to ...`
    
-   _Khắc phục:_ Kiểm tra lại chữ ký (signature) của hàm và truyền đúng số lượng tham số.

Nhóm 2: Lỗi về Slice, Array và Map (Lỗi 11 - 20)

  1. Truy cập Map chưa được khởi tạo (Nil Map)
-   _Triệu chứng:_ `assignment to entry in nil map` (Panic runtime).
    
-   _Khắc phục:_ Khởi tạo map bằng hàm `make`: `m := make(map[string]int)`.
  1. Truy xuất phần tử vượt quá giới hạn Slice/Array
-   _Triệu chứng:_ `index out of range [x] with length y`
    
-   _Khắc phục:_ Kiểm tra độ dài `len()` trước khi truy xuất phần tử.
  1. Hiểu nhầm giữa Array (mảng tĩnh) và Slice (mảng động)
-   _Triệu chứng:_ Gán slice này sang slice khác thay đổi dữ liệu ngầm do chung con trỏ nền.
    
-   _Khắc phục:_ Sử dụng hàm `copy()` khi muốn sao chép độc lập.
  1. Nối slice sai cách với hàm append
-   _Triệu chứng:_ Quên gán lại kết quả: `append(s, 1)` thay vì `s = append(s, 1)`.
    
-   _Khắc phục:_ Luôn nhớ `s = append(s, val)`.
  1. Sử dụng cap()len() nhầm lẫn
-   _Triệu chứng:_ Lỗi logic khi duyệt dữ liệu.
    
-   _Khắc phục:_ Nhớ rằng `len` là số phần tử hiện tại, `cap` là sức chứa tối đa trước khi phải cấp phát lại bộ nhớ.
  1. Truy xuất key không tồn tại trong Map trả về giá trị gì?
-   _Triệu chứng:_ Lỗi logic do tưởng key có giá trị mà thực ra trả về giá trị zero value của kiểu dữ liệu.
    
-   _Khắc phục:_ Sử dụng cú pháp kiểm tra tồn tại: `val, ok := m[key]`.
  1. Cố gắng dùng vòng lặp for để sửa trực tiếp giá trị mảng qua biến tạm
-   _Triệu chứng:_ Giá trị mảng gốc không đổi.
    
-   _Khắc phục:_ Dùng chỉ mục `arr[i] = ...` thay vì dùng biến `v` trong `for _, v := range arr`.
  1. Dùng slice làm key cho map
-   _Triệu chứng:_ `invalid map key type []int`
    
-   _Khắc phục:_ Chuyển slice thành `string` hoặc dùng array thuần (`[N]int`) vì slice không thể so sánh (`comparable`).
  1. Khởi tạo Slice bằng make sai tham số
-   _Triệu chứng:_ `len` lớn hơn `cap` hoặc truyền sai đối số.
    
-   _Khắc phục:_ Cú pháp chuẩn: `make([]T, len, cap)`.
  1. Cấp phát bộ nhớ nhầm giữa new()make()
-   _Triệu chứng:_ Dùng `new()` cho map/slice/channel dẫn đến giá trị `nil`.
    
-   _Khắc phục:_ Dùng `new()` cho con trỏ kiểu cơ bản/struct, dùng `make()` cho slice, map, channel.

Nhóm 3: Lỗi Con trỏ, Struct và Interface (Lỗi 21 - 30)

  1. Gọi phương thức trên biến Nil Pointer
-   _Triệu chứng:_ `invalid memory address or nil pointer dereference` (Panic).
    
-   _Khắc phục:_ Kiểm tra biến con trỏ khác `nil` trước khi gọi method.
  1. Nhầm lẫn giữa phương thức nhận giá trị (Value Receiver) và con trỏ (Pointer Receiver)
-   _Triệu chứng:_ Dữ liệu trong struct không bị thay đổi sau khi gọi hàm.
    
-   _Khắc phục:_ Dùng `func (s *Struct) Method()` nếu muốn thay đổi trạng thái thực thể.
  1. Gán trực tiếp giá trị vào Struct vô danh hoặc trường không export
-   _Triệu chứng:_ Không truy cập được trường viết thường ở package khác.
    
-   _Khắc phục:_ Viết hoa chữ cái đầu tiên của tên trường (Exported fields) nếu muốn dùng ngoài package.
  1. So sánh hai Struct chứa kiểu không so sánh được
-   _Triệu chứng:_ `invalid operation: struct1 == struct2 (struct containing slice cannot be compared)`
    
-   _Khắc phục:_ Viết hàm so sánh thủ công hoặc dùng thư viện ngoài (như `reflect.DeepEqual`).
  1. Kiểm tra Interface nil sai cách
-   _Triệu chứng:_ Hàm trả về interface chứa con trỏ `nil` nhưng kiểm tra `if err != nil` lại ra `true`.
    
-   _Khắc phục:_ Hiểu rõ Interface lưu cả **type** lẫn **value**. Interface chỉ thực sự `nil` khi cả type và value đều bằng `nil`.
  1. Quên giải tham số con trỏ khi in hoặc gán
-   _Triệu chứng:_ In ra địa chỉ ô nhớ thay vì giá trị thực tế (ví dụ: `0xc0000a2040`).
    
-   _Khắc phục:_ Thêm dấu sao `*ptr` để lấy giá trị.
  1. Truy cập trường của struct thông qua con trỏ mà quên dấu ngoặc (hoặc hiểu lầm cú pháp)
-   _Triệu chứng:_ Lỗi cú pháp biên dịch.
    
-   _Khắc phục:_ Go hỗ trợ tự động giải tham số (automatic dereference), bạn có thể gọi trực tiếp `ptr.field` thay vì `(*ptr).field`.
  1. Nhúng Struct (Struct Embedding) gây trùng tên trường
-   _Triệu chứng:_ `ambiguous selector`
    
-   _Khắc phục:_ Gọi tường minh tên struct cha khi truy xuất trường bị trùng.
  1. Ép kiểu Interface sai (Type Assertion thất bại gây Panic)
-   _Triệu chứng:_ `interface conversion: interface {} is string, not int` (Panic).
    
-   _Khắc phục:_ Dùng cú pháp an toàn: `val, ok := x.(int)`.
  1. Sử dụng reflect quá mức cho các tác vụ thông thường
-   _Triệu chứng:_ Code chậm, khó đọc, dễ phát sinh lỗi runtime.
    
-   _Khắc phục:_ Ưu tiên generic (từ Go 1.8+) hoặc interface tĩnh thay vì dùng `reflect`.

Nhóm 4: Lỗi Lập trình Đồng thời (Concurrency: Goroutine & Channel) (Lỗi 31 - 40)

  1. Deadlock khi dùng Channel không có bộ đệm (Unbuffered Channel)
-   _Triệu chứng:_ `fatal error: all goroutines are asleep - deadlock!`
    
-   _Khắc phục:_ Đảm bảo có goroutine nhận dữ liệu trước (hoặc song song) khi gửi vào channel, hoặc dùng Buffered Channel `make(chan T, size)`.
  1. Gửi dữ liệu vào Channel đã bị đóng (Closed Channel)
-   _Triệu chứng:_ `panic: send on closed channel`
    
-   _Khắc phục:_ Chỉ đóng channel ở phía bên gửi (sender) và tránh đóng nhiều lần.
  1. Đọc dữ liệu từ Channel đã đóng
-   _Triệu chứng:_ Không báo lỗi panic mà trả về giá trị zero value và `false` ở lần đọc thứ hai.
    
-   _Khắc phục:_ Kiểm tra trạng thái đóng qua cú pháp `v, ok := <-ch`.
  1. Rò rỉ Goroutine (Goroutine Leak)
-   _Triệu chứng:_ Chương trình tốn dần bộ nhớ do goroutine bị kẹt chờ đọc/ghi channel không bao giờ kết thúc.
    
-   _Khắc phục:_ Sử dụng `context` để hủy (cancel) goroutine khi không cần thiết.
  1. Race Condition (Xung đột dữ liệu)
-   _Triệu chứng:_ Dữ liệu bị sai lệch ngẫu nhiên khi nhiều goroutine cùng đọc/ghi một biến chung.
    
-   _Khắc phục:_ Chạy kiểm tra bằng lệnh `go run -race main.go` và dùng `sync.Mutex` hoặc `channel` để đồng bộ.
  1. Quên chờ Goroutine chạy xong trước khi hàm main kết thúc
-   _Triệu chứng:_ Chương trình thoát ngay lập tức mà không in ra kết quả gì từ các goroutine phụ.
    
-   _Khắc phục:_ Sử dụng `sync.WaitGroup` hoặc `channel` để đồng bộ tiến trình chính.
  1. Sử dụng biến vòng lặp trong Goroutine sai cách (Closure Capture)
-   _Triệu chứng:_ Tất cả goroutine in ra cùng một giá trị cuối cùng của vòng lặp.
    
-   _Khắc phục:_ Truyền biến vào hàm goroutine dạng tham số hoặc khai báo biến cục bộ bên trong vòng lặp (`v := v`).
  1. Đóng Channel từ phía người nhận (Receiver)
-   _Triệu chứng:_ Gây panic nếu các goroutine gửi khác vẫn đang cố gắng gửi dữ liệu vào.
    
-   _Khắc phục:_ Quy tắc vàng: Người gửi duy nhất mới là người đóng channel.
  1. Quên gọi Unlock() cho Mutex
-   _Triệu chứng:_ Khóa vĩnh viễn (Deadlock) ở các luồng gọi sau.
    
-   _Khắc phục:_ Sử dụng từ khóa `defer mu.Unlock()` ngay sau `mu.Lock()`.
  1. Sử dụng time.Sleep để đồng bộ luồng thay vì cơ chế chuẩn
-   _Triệu chứng:_ Chương trình chạy chậm bất thường hoặc vẫn bị lỗi timing trên máy cấu hình yếu.
    
-   _Khắc phục:_ Dùng `sync.WaitGroup`, `Channel` hoặc `Context`.

Nhóm 5: Lỗi Quản lý Package, Biến môi trường & Hệ thống (Lỗi 41 - 50)

  1. Lỗi Import vòng tròn (Circular Dependency)
-   _Triệu chứng:_ `import cycle not allowed`
    
-   _Khắc phục:_ Tách logic chung sang một package độc lập thứ ba.
  1. Không khởi tạo module Go (go mod init)
-   _Triệu chứng:_ `go: cannot find main module; see 'go help modules'`
    
-   _Khắc phục:_ Chạy lệnh `go mod init <ten-module>` tại thư mục gốc dự án.
  1. Sử dụng sai biến môi trường hệ thống
-   _Triệu chứng:_ Lỗi `os.Getenv()` trả về chuỗi rỗng (`""`).
    
-   _Khắc phục:_ Kiểm tra xem đã thiết lập biến môi trường ở OS chưa hoặc dùng thư viện hỗ trợ file `.env` (như `godotenv`).
  1. Quên xử lý lỗi trả về (Unchecked Error)
-   _Triệu chứng:_ Bỏ qua giá trị `err` từ các hàm I/O, Database.
    
-   _Khắc phục:_ Luôn kiểm tra `if err != nil { ... }`.
  1. Đóng kết nối cơ sở dữ liệu hoặc file quá sớm với defer
-   _Triệu chứng:_ Lỗi `file already closed` hoặc mất kết nối khi thực thi hàm.
    
-   _Khắc phục:_ Đặt `defer file.Close()` ngay sau khi mở file thành công, kiểm tra xem có mở trong vòng lặp hay không.
  1. Quên gọi db.Close() hoặc dùng quá nhiều kết nối (Connection Leak)
-   _Triệu chứng:_ Ứng dụng bị treo sau một thời gian chạy do cạn kiệt pool kết nối DB.
    
-   _Khắc phục:_ Sử dụng `sql.DB` đúng chuẩn Singleton và thiết lập `SetMaxOpenConns`, `SetMaxIdleConns`.
  1. Sử dụng panic thay vì trả về error cho lỗi nghiệp vụ thông thường
-   _Triệu chứng:_ Ứng dụng sập toàn bộ (crash) khi người dùng nhập sai dữ liệu đầu vào.
    
-   _Khắc phục:_ Chỉ dùng `panic` cho các lỗi hệ thống trầm trọng không thể phục hồi ở giai đoạn khởi động.
  1. Cài đặt phiên bản Go không tương thích với go.mod
-   _Triệu chứng:_ Lỗi cú pháp của các tính năng mới (ví dụ dùng generic trên bản Go cũ).
    
-   _Khắc phục:_ Cập nhật bản Go mới nhất hoặc điều chỉnh dòng `go 1.x` trong file `go.mod`.
  1. Thiếu file go.sum khi tải thư viện bên thứ ba
-   _Triệu chứng:_ Lỗi checksum mismatch hoặc thiếu phụ thuộc khi build ở môi trường CI/CD.
    
-   _Khắc phục:_ Chạy `go mod tidy` để tự động làm sạch và cập nhật các gói phụ thuộc.
  1. Nhầm lẫn giữa log.Fatallog.Println
-   _Triệu chứng:_ Chương trình đột ngột thoát (exit code 1) ngay sau khi in dòng log đầu tiên.
    
-   _Khắc phục:_ Dùng `log.Fatal` khi muốn dừng hẳn ứng dụng khi gặp lỗi chí mạng, dùng `log.Println` cho log thông thường.

Hy vọng danh sách tổng hợp trên sẽ là cẩm nang hữu ích giúp bạn nhanh chóng nhận diện và xử lý mượt mà mọi lỗi thường gặp khi chinh phục ngôn ngữ Go!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí