☰ Mục lục series
Tài liệu Go tiếng Việt
  1. Bắt đầu (Getting started)
    1. Tổng quan
    2. Tải về và cài đặt
    3. Bắt đầu với Go
    4. Quản lý các bản cài đặt Go
    5. Plugin trình soạn thảo và IDE
  2. Dạo quanh Go (A Tour of Go)
    1. Chào mừng
    2. Package, biến và hàm
    3. Điều khiển luồng
    4. Struct, slice và map
    5. Method và interface
    6. Generics
    7. Xử lý đồng thời
  3. Hướng dẫn (Tutorials)
    1. Danh sách hướng dẫn
    2. Tạo một Go module
    3. Gọi mã của bạn từ một module khác
    4. Trả về và xử lý lỗi
    5. Trả về lời chào ngẫu nhiên
    6. Trả về lời chào cho nhiều người
    7. Thêm một test
    8. Biên dịch và cài đặt ứng dụng
    9. Kết luận
    10. Bắt đầu với workspace nhiều module
    11. Phát triển RESTful API với Go và Gin
    12. Truy cập cơ sở dữ liệu quan hệ
    13. Bắt đầu với generics
    14. Bắt đầu với fuzzing
    15. Làm việc với JSON
    16. Tìm và sửa các dependency có lỗ hổng với govulncheck
    17. Tìm và khắc phục phụ thuộc có lỗ hổng với VS Code Go
    18. Viết ứng dụng web
  4. Viết code Go
    1. Cách viết mã Go
    2. Effective Go
    3. Doc Comment trong Go
  5. Module
    1. Phát triển và publish module
    2. Quản lý phụ thuộc
    3. Tổ chức một module Go
    4. Quản lý mã nguồn của module
    5. Cách đánh số phiên bản module
    6. Quy trình phát hành và đánh phiên bản module
    7. Publish một module
    8. Phát triển một bản cập nhật major version
    9. Tham chiếu tệp go.mod
    10. Ngừng hỗ trợ 'go get' cho việc cài đặt tệp thực thi
  6. Cơ sở dữ liệu (Database)
    1. Truy cập cơ sở dữ liệu quan hệ
    2. Mở một handle cơ sở dữ liệu
    3. Thực thi câu lệnh SQL không trả về dữ liệu
    4. Truy vấn dữ liệu
    5. Dùng prepared statement
    6. Thực thi transaction
    7. Huỷ các thao tác đang chạy
    8. Quản lý kết nối
    9. Tránh rủi ro SQL injection
  7. Chẩn đoán và hiệu năng
    1. Chẩn đoán
    2. Hướng dẫn về Go Garbage Collector
    3. Tối ưu hoá dựa trên profile
    4. Data Race Detector
    5. Gỡ lỗi mã Go bằng GDB
    6. Hỗ trợ coverage profiling cho integration test
  8. Bảo mật (Security)
    1. Tổng quan bảo mật
    2. Thực hành tốt về bảo mật cho lập trình viên Go
    3. Quản lý lỗ hổng trong Go
    4. Cơ sở dữ liệu lỗ hổng của Go
    5. Quét lỗ hổng trong IDE
    6. Chính sách CNA của Go
    7. Go Fuzzing
    8. Chi tiết kỹ thuật của Go Fuzzing
    9. Chính sách bảo mật của Go
    10. Threat Model của Go
    11. Các quyết định về bảo mật của Go
    12. Tuân thủ FIPS 140-3
  9. Công cụ và toolchain
    1. Tài liệu về các lệnh
    2. Về lệnh go
    3. Go Toolchains
    4. Telemetry của Go
    5. Cài đặt Go từ mã nguồn
    6. Cài đặt và sử dụng gccgo
  10. Tham khảo
    1. Câu hỏi thường gặp (FAQ)
    2. Go 1 và tương lai của các chương trình Go
    3. Hướng dẫn chuyển đổi sang encoding/json/v2
    4. Hướng dẫn đóng góp
    5. Đóng góp cho frontend gccgo
  11. Phụ lục
    1. Bảng thuật ngữ
Viết code Go

Cách viết mã Go

Bản dịch từ go.dev/doc/code

Giới thiệu

Tài liệu này minh hoạ việc phát triển một package Go đơn giản bên trong một module, và giới thiệu công cụ go — cách chuẩn để tải về, build và cài đặt các module, package và lệnh của Go.

Tổ chức mã nguồn

Chương trình Go được tổ chức thành các package. Một package là tập hợp các tệp mã nguồn nằm trong cùng một thư mục và được biên dịch cùng nhau. Các hàm, kiểu dữ liệu, biến và hằng số được định nghĩa trong một tệp mã nguồn thì thấy được ở mọi tệp mã nguồn khác trong cùng package đó.

Một repository chứa một hoặc nhiều module. Một module là tập hợp các package Go liên quan được phát hành cùng nhau. Một repository Go thường chỉ chứa một module, nằm ở thư mục gốc của repository. Tệp tên go.mod ở đó khai báo module path: tiền tố import path cho mọi package trong module. Module bao gồm các package nằm trong thư mục chứa tệp go.mod của nó cũng như các thư mục con của thư mục ấy, cho tới thư mục con kế tiếp có chứa một tệp go.mod khác (nếu có).

Lưu ý rằng bạn không cần công bố mã của mình lên một repository từ xa thì mới build được nó. Một module có thể được định nghĩa ngay trên máy mà không thuộc repository nào. Tuy vậy, tổ chức mã như thể một ngày nào đó bạn sẽ công bố nó vẫn là một thói quen tốt.

Module path của mỗi module không chỉ đóng vai trò tiền tố import path cho các package của nó, mà còn cho biết lệnh go nên tìm ở đâu để tải module đó về. Ví dụ, để tải module golang.org/x/tools, lệnh go sẽ tra cứu repository được chỉ ra bởi https://golang.org/x/tools (mô tả kỹ hơn tại đây).

Import path là một chuỗi dùng để import một package. Import path của một package là module path của nó ghép với thư mục con của package đó trong module. Ví dụ, module github.com/google/go-cmp chứa một package trong thư mục cmp/. Import path của package đó là github.com/google/go-cmp/cmp. Các package trong thư viện chuẩn thì không có tiền tố module path.

Chương trình đầu tiên của bạn

Để biên dịch và chạy một chương trình đơn giản, trước hết hãy chọn một module path (chúng tôi sẽ dùng example/user/hello) rồi tạo tệp go.mod khai báo nó:

$ mkdir hello # Alternatively, clone it if it already exists in version control.
$ cd hello
$ go mod init example/user/hello
go: creating new go.mod: module example/user/hello
$ cat go.mod
module example/user/hello

go 1.16
$

Câu lệnh đầu tiên trong một tệp mã nguồn Go phải là package name. Các lệnh thực thi được luôn phải dùng package main.

Tiếp theo, tạo tệp tên hello.go trong thư mục đó với nội dung mã Go sau:

package main

import "fmt"

func main() {
    fmt.Println("Hello, world.")
}

Bây giờ bạn có thể build và cài đặt chương trình đó bằng công cụ go:

$ go install example/user/hello
$

Lệnh này build lệnh hello, tạo ra một binary thực thi được. Sau đó nó cài binary ấy thành $HOME/go/bin/hello (hoặc, trên Windows là %USERPROFILE%\go\bin\hello.exe).

Thư mục cài đặt được điều khiển bởi các biến môi trường GOPATH và GOBIN. Nếu GOBIN được đặt, binary sẽ được cài vào thư mục đó. Nếu GOPATH được đặt, binary sẽ được cài vào thư mục con bin của thư mục đầu tiên trong danh sách GOPATH. Ngược lại, binary sẽ được cài vào thư mục con bin của GOPATH mặc định ($HOME/go hoặc %USERPROFILE%\go).

Bạn có thể dùng lệnh go env để đặt giá trị mặc định cho một biến môi trường theo cách đa nền tảng, áp dụng cho các lệnh go về sau:

$ go env -w GOBIN=/somewhere/else/bin
$

Để bỏ đặt một biến đã được đặt trước đó bằng go env -w, hãy dùng go env -u:

$ go env -u GOBIN
$

Những lệnh như go install áp dụng trong ngữ cảnh của module chứa thư mục làm việc hiện tại. Nếu thư mục làm việc không nằm trong module example/user/hello, go install có thể thất bại.

Để tiện dụng, các lệnh go chấp nhận đường dẫn tương đối so với thư mục làm việc, và mặc định dùng package trong thư mục làm việc hiện tại nếu không có đường dẫn nào khác được đưa ra. Vì vậy, trong thư mục làm việc của chúng ta, các lệnh sau là tương đương:

$ go install example/user/hello
$ go install .
$ go install

Tiếp theo, hãy chạy chương trình để chắc chắn nó hoạt động. Để tiện hơn nữa, chúng tôi sẽ thêm thư mục cài đặt vào PATH cho việc chạy binary được dễ dàng:

# Windows users should consult /wiki/SettingGOPATH
# for setting %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

Nếu bạn đang dùng một hệ thống quản lý mã nguồn, đây là lúc thích hợp để khởi tạo một repository, thêm các tệp vào và commit thay đổi đầu tiên. Xin nhắc lại, bước này là tuỳ chọn: bạn không cần dùng quản lý mã nguồn để viết mã Go.

$ git init
Initialized empty Git repository in /home/user/hello/.git/
$ git add go.mod hello.go
$ git commit -m "initial commit"
[master (root-commit) 0b4507d] initial commit
 1 file changed, 7 insertion(+)
 create mode 100644 go.mod hello.go
$

Lệnh go xác định repository chứa một module path cho trước bằng cách yêu cầu một URL HTTPS tương ứng rồi đọc phần metadata nhúng trong phản hồi HTML (xem go help importpath). Nhiều dịch vụ lưu trữ mã đã sẵn cung cấp metadata đó cho các repository chứa mã Go, nên cách dễ nhất để module của bạn dùng được bởi người khác thường là làm cho module path của nó trùng với URL của repository.

Import package từ chính module của bạn

Hãy viết một package morestrings và dùng nó từ chương trình hello. Trước hết, tạo thư mục cho package tên là $HOME/hello/morestrings, rồi tạo tệp tên reverse.go trong thư mục đó với nội dung sau:

// Package morestrings implements additional functions to manipulate UTF-8
// encoded strings, beyond what is provided in the standard "strings" package.
package morestrings

// ReverseRunes returns its argument string reversed rune-wise left to right.
func ReverseRunes(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

Vì hàm ReverseRunes của chúng ta bắt đầu bằng chữ in hoa, nó được export, và có thể dùng được ở những package khác có import package morestrings của chúng ta.

Hãy kiểm tra xem package biên dịch được hay không bằng go build:

$ cd $HOME/hello/morestrings
$ go build
$

Lệnh này sẽ không tạo ra tệp kết quả nào. Thay vào đó nó lưu package đã biên dịch vào build cache trên máy.

Sau khi đã xác nhận package morestrings build được, hãy dùng nó từ chương trình hello. Để làm vậy, hãy sửa tệp $HOME/hello/hello.go ban đầu để dùng package morestrings:

package main

import (
    "fmt"

    "example/user/hello/morestrings"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
}

Cài đặt chương trình hello:

$ go install example/user/hello

Chạy phiên bản mới của chương trình, bạn sẽ thấy một thông điệp mới, đã bị đảo ngược:

$ hello
Hello, Go!

Import package từ module ở xa

Một import path có thể mô tả cách lấy mã nguồn của package bằng một hệ thống quản lý phiên bản như Git hay Mercurial. Công cụ go dùng tính chất này để tự động tải package về từ các repository ở xa. Chẳng hạn, để dùng github.com/google/go-cmp/cmp trong chương trình của bạn:

package main

import (
    "fmt"

    "example/user/hello/morestrings"
    "github.com/google/go-cmp/cmp"
)

func main() {
    fmt.Println(morestrings.ReverseRunes("!oG ,olleH"))
    fmt.Println(cmp.Diff("Hello World", "Hello Go"))
}

Giờ bạn đã có một phụ thuộc vào module bên ngoài, bạn cần tải module đó về và ghi lại phiên bản của nó trong tệp go.mod. Lệnh go mod tidy sẽ thêm các yêu cầu module còn thiếu cho những package được import và loại bỏ yêu cầu với những module không còn được dùng nữa.

$ go mod tidy
go: finding module for package github.com/google/go-cmp/cmp
go: found github.com/google/go-cmp/cmp in github.com/google/go-cmp v0.5.4
$ go install example/user/hello
$ hello
Hello, Go!
  string(
-     "Hello World",
+     "Hello Go",
  )
$ cat go.mod
module example/user/hello

go 1.16

require github.com/google/go-cmp v0.5.4
$

Các phụ thuộc module được tự động tải về thư mục con pkg/mod của thư mục do biến môi trường GOPATH chỉ ra. Nội dung đã tải về của một phiên bản module cho trước được dùng chung giữa mọi module khác có require phiên bản đó, nên lệnh go đánh dấu các tệp và thư mục ấy là chỉ đọc. Để xoá toàn bộ module đã tải về, bạn có thể truyền cờ -modcache cho go clean:

$ go clean -modcache
$

Kiểm thử

Go có một framework kiểm thử nhẹ gồm lệnh go test và package testing.

Bạn viết một test bằng cách tạo một tệp có tên kết thúc bằng _test.go chứa các hàm tên TestXXX với chữ ký func (t *testing.T). Framework kiểm thử sẽ chạy từng hàm như vậy; nếu hàm gọi một hàm báo lỗi như t.Error hay t.Fail, test được xem là thất bại.

Hãy thêm một test vào package morestrings bằng cách tạo tệp $HOME/hello/morestrings/reverse_test.go chứa mã Go sau.

package morestrings

import "testing"

func TestReverseRunes(t *testing.T) {
    cases := []struct {
        in, want string
    }{
        {"Hello, world", "dlrow ,olleH"},
        {"Hello, 世界", "界世 ,olleH"},
        {"", ""},
    }
    for _, c := range cases {
        got := ReverseRunes(c.in)
        if got != c.want {
            t.Errorf("ReverseRunes(%q) == %q, want %q", c.in, got, c.want)
        }
    }
}

Sau đó chạy test bằng go test:

$ cd $HOME/hello/morestrings
$ go test
PASS
ok  	example/user/hello/morestrings 0.165s
$

Hãy chạy go help test và xem tài liệu về package testing để biết thêm chi tiết.

Tiếp theo là gì

Đăng ký danh sách thư golang-announce để được thông báo khi có phiên bản Go ổn định mới được phát hành.

Xem Effective Go để có những lời khuyên viết mã Go rõ ràng, đúng phong cách.

Hãy trải nghiệm Dạo quanh Go để học bản thân ngôn ngữ.

Ghé trang tài liệu để đọc một loạt bài viết chuyên sâu về ngôn ngữ Go cùng các thư viện và công cụ của nó.

Tìm trợ giúp

Để được giúp đỡ theo thời gian thực, hãy hỏi các gopher nhiệt tình tại máy chủ Slack gophers do cộng đồng vận hành (lấy lời mời tại đây).

Danh sách thư chính thức để thảo luận về ngôn ngữ Go là Go Nuts.

Hãy báo lỗi qua trình theo dõi issue của Go.

Nội dung gốc: Go Documentation © The Go Authors, giấy phép CC BY 3.0. Bản dịch và ghi chú: Lê Văn Dũng.