Cách Viết Mã Go

Giới thiệu

Tài liệu này trình bày quá trình phát triển một package Go đơn giả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 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 trong cùng một thư mục, được biên dịch cùng nhau. Các hàm, kiểu dữ liệu, biến và hằng số được khai báo trong một tệp mã nguồn đều hiển thị với tất cả các tệp khác trong cùng package.

Một kho lưu trữ 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 kho lưu trữ Go thường chỉ chứa một module, đặt tại thư mục gốc. Một tệp tên go.mod ở đó khai báo đường dẫn module: tiền tố đường dẫn import cho tất cả các package trong module. Module bao gồm các package trong thư mục chứa tệp go.mod cũng như các thư mục con, cho đến thư mục con tiếp theo có chứa tệp go.mod khác (nếu có).

Lưu ý rằng bạn không cần phải đưa mã lên kho lưu trữ từ xa trước khi có thể build nó. Một module có thể được định nghĩa cục bộ mà không cần thuộc về một kho lưu trữ nào. Tuy nhiên, đây là thói quen tốt để tổ chức mã như thể bạn sẽ xuất bản nó sau này.

Đường dẫn của mỗi module không chỉ đóng vai trò là tiền tố đường dẫn import cho các package của nó, mà còn cho biết nơi lệnh go cần tìm để tải về module đó. Ví dụ, để tải về module golang.org/x/tools, lệnh go sẽ truy vấn kho lưu trữ được chỉ định bởi https://golang.org/x/tools (được mô tả thêm tại đây).

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

Chương trình đầu tiên

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

$ mkdir hello # Hoặc clone nó nếu nó đã tồn tại trong hệ thống quản lý phiên bản.
$ 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 luôn phải dùng package main.

Tiếp theo, tạo một tệp tên hello.go bên trong thư mục đó với 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 tệp nhị phân thực thi. Sau đó cài đặt tệp nhị phân đó là $HOME/go/bin/hello (hoặc, trên Windows, %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 GOPATHGOBIN. Nếu GOBIN được đặt, các tệp nhị phân được cài vào thư mục đó. Nếu GOPATH được đặt, các tệp nhị phân được cài vào thư mục con bin của thư mục đầu tiên trong danh sách GOPATH. Nếu không, các tệp nhị phân đượ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 một cách linh hoạt cho một biến môi trường, áp dụng cho các lệnh go về sau:

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

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

$ go env -u GOBIN
$

Các lệnh như go install hoạt động trong phạm vi 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.

Để thuận tiện, 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 cung cấp. Vì vậy, trong thư mục làm việc của chúng ta, các lệnh sau đây đều tương đương:

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

Tiếp theo, hãy chạy chương trình để đảm bảo nó hoạt động. Để thuận tiện hơn, ta sẽ thêm thư mục cài đặt vào PATH để chạy các tệp nhị phân dễ dàng:

# Người dùng Windows nên tham khảo /wiki/SettingGOPATH
# để đặt %PATH%.
$ export PATH=$PATH:$(dirname $(go list -f '{{.Target}}' .))
$ hello
Hello, world.
$

Nếu bạn đang dùng hệ thống quản lý mã nguồn, đây là thời điểm tốt để khởi tạo một kho lưu trữ, thêm các tệp và commit thay đổi đầu tiên. Lại nữa, bước này là tùy chọn: bạn không cần dùng hệ thố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 tìm kho lưu trữ chứa một đường dẫn module nhất định bằng cách gửi yêu cầu đến URL HTTPS tương ứng và đọc metadata nhúng trong phản hồi HTML (xem go help importpath). Nhiều dịch vụ lưu trữ đã cung cấp sẵn metadata đó cho các kho lưu trữ chứa mã Go, vì vậy cách dễ nhất để làm cho module của bạn khả dụng cho người khác thường là đặt đường dẫn module trùng với URL của kho lưu trữ.

Import package từ module của bạn

Hãy viết một package morestrings và dùng nó từ chương trình hello. Đầu tiên, tạo một thư mục cho package có tên $HOME/hello/morestrings, sau đó tạo một 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ữ hoa, nó được xuất ra ngoài và có thể được dùng trong các package khác import package morestrings của ta.

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

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

Lệnh này sẽ không tạo ra tệp đầu ra. Thay vào đó, nó lưu package đã biên dịch vào bộ nhớ đệm build cục bộ.

Sau khi xác nhận package morestrings build thành công, hãy dùng nó từ chương trình hello. Để làm vậy, chỉnh sửa tệp $HOME/hello/hello.go gốc để 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, đảo ngược:

$ hello
Hello, Go!

Import package từ module từ xa

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

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"))
}

Bây giờ bạn có dependency vào một module bên ngoài, bạn cần tải về module đó và ghi lại phiên bản của nó vào tệp go.mod. Lệnh go mod tidy thêm các yêu cầu module còn thiếu cho các package được import và xóa các yêu cầu đối với 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 dependency của module được tải về tự động vào thư mục con pkg/mod của thư mục được chỉ định bởi biến môi trường GOPATH. Nội dung đã tải về cho một phiên bản cụ thể của module được chia sẻ giữa tất cả các module khác require phiên bản đó, vì vậy lệnh go đánh dấu các tệp và thư mục đó là chỉ đọc. Để xóa tất cả module đã tải về, bạn có thể truyền cờ -modcache cho go clean:

$ go clean -modcache
$

Kiểm thử

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

Bạn viết kiểm thử 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ử chạy từng hàm đó; nếu hàm gọi một hàm báo thất bại như t.Error hoặc t.Fail, kiểm thử được coi là đã thất bại.

Thêm kiểm thử 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 kiểm thử với go test:

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

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

Các bước tiếp theo

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

Xem Effective Go để có các mẹo viết mã Go rõ ràng, tự nhiên.

Tham gia A Tour of Go để học ngôn ngữ một cách bài bản.

Truy cập trang tài liệu để xem tập hợp các bài viết chuyên sâu về ngôn ngữ Go, thư viện và công cụ của nó.

Nhận trợ giúp

Để được hỗ trợ trực tiếp, hãy hỏi những gopher nhiệt tình trên 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.

Báo cáo lỗi bằng trình theo dõi issue của Go.