Menghindari Baris yang Terlalu Panjang
Hindari menggunakan baris kode yang membuat pembaca harus menggulir secara horizontal atau memutar dokumen secara berlebihan.
Kami sarankan untuk membatasi panjang baris hingga 99 karakter. Penulis harus memecah baris sebelum batas ini, tetapi ini bukan aturan yang ketat. Diperbolehkan bagi kode untuk melebihi batas ini.
Konsistensi
Beberapa standar yang diuraikan dalam dokumen ini didasarkan pada penilaian subyektif, skenario, atau konteks. Namun, aspek paling penting adalah mempertahankan konsistensi.
Kode yang konsisten lebih mudah untuk dipelihara, lebih rasional, meminta biaya pembelajaran yang lebih sedikit, dan lebih mudah untuk bermigrasi, diperbarui, dan memperbaiki kesalahan ketika konvensi baru muncul atau kesalahan terjadi.
Sebaliknya, menyertakan beberapa gaya kode yang benar-benar berbeda atau konflik dalam sebuah basis kode mengakibatkan peningkatan biaya pemeliharaan, ketidakpastian, dan bias kognitif. Semua ini langsung mengakibatkan kecepatan yang lebih lambat, tinjauan kode yang menyakitkan, dan peningkatan jumlah bug.
Ketika menerapkan standar ini ke basis kode, disarankan untuk melakukan perubahan pada tingkat paket (atau yang lebih besar). Menggunakan beberapa gaya di tingkat subpaket melanggar kekhawatiran di atas.
Mengelompokkan Deklarasi yang Serupa
Bahasa Go mendukung pengelompokkan deklarasi yang serupa.
Tidak Disarankan:
import "a"
import "b"
Disarankan:
import (
"a"
"b"
)
Hal ini juga berlaku untuk deklarasi konstan, variabel, dan tipe:
Tidak Disarankan:
const a = 1
const b = 2
var a = 1
var b = 2
type Area float64
type Volume float64
Disarankan:
const (
a = 1
b = 2
)
var (
a = 1
b = 2
)
type (
Area float64
Volume float64
)
Hanya kelompokkan deklarasi yang terkait bersama dan hindari mengelompokkan deklarasi yang tidak terkait.
Tidak Disarankan:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
EnvVar = "MY_ENV"
)
Disarankan:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
const EnvVar = "MY_ENV"
Tidak ada batasan di mana untuk menggunakan pengelompokkan. Sebagai contoh, Anda dapat menggunakannya dalam sebuah fungsi:
Tidak Disarankan:
func f() string {
red := color.New(0xff0000)
green := color.New(0x00ff00)
blue := color.New(0x0000ff)
...
}
Disarankan:
func f() string {
var (
red = color.New(0xff0000)
green = color.New(0x00ff00)
blue = color.New(0x0000ff)
)
...
}
Pengecualian: Jika deklarasi variabel berdekatan dengan variabel lain, terutama dalam deklarasi lokal fungsi, mereka harus dikelompokkan bersama. Lakukan hal ini bahkan untuk variabel yang tidak terkait dideklarasikan bersama.
Tidak Disarankan:
func (c *client) request() {
caller := c.name
format := "json"
timeout := 5*time.Second
var err error
// ...
}
Disarankan:
func (c *client) request() {
var (
caller = c.name
format = "json"
timeout = 5*time.Second
err error
)
// ...
}
Pengelompokan Impor
Impor harus dikelompokkan menjadi dua kategori:
- Pustaka standar
- Pustaka lain
Secara default, ini adalah pengelompokkan yang diterapkan oleh goimports.
Tidak Disarankan:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Disarankan:
import (
"fmt"
"os"
"go.uber.org/atomic"
"golang.org/x/sync/errgroup"
)
Nama Paket
Saat memberi nama paket, harap ikuti aturan berikut:
- Semuanya huruf kecil, tanpa huruf kapital atau garis bawah.
- Dalam kebanyakan kasus, tidak perlu mengganti nama saat mengimpor.
- Singkat dan ringkas. Ingatlah bahwa nama tersebut sepenuhnya diakui di semua tempat penggunaannya.
- Hindari bentuk jamak. Sebagai contoh, gunakan
net/url
daripadanet/urls
. - Hindari menggunakan "common," "util," "shared," atau "lib." Ini tidak cukup informatif.
Penamaan Fungsi
Kami mengikuti konvensi komunitas Go dalam menggunakan MixedCaps untuk nama fungsi. Ada pengecualian untuk mengelompokkan kasus uji terkait, dimana nama fungsi dapat mengandung garis bawah, seperti: TestMyFunction_WhatIsBeingTested
.
Alias Impor
Jika nama paket tidak cocok dengan elemen terakhir dari jalur impor, alias impor harus digunakan.
import (
"net/http"
client "example.com/client-go"
trace "example.com/trace/v2"
)
Dalam kasus lain, alias impor sebaiknya dihindari kecuali ada konflik langsung antara impor.
Tidak Disarankan:
import (
"fmt"
"os"
nettrace "golang.net/x/trace"
)
Disarankan:
import (
"fmt"
"os"
"runtime/trace"
nettrace "golang.net/x/trace"
)
Pengelompokan Fungsi dan Urutan
- Fungsi sebaiknya diurutkan dengan kasar sesuai dengan urutan yang dipanggil.
- Fungsi dalam file yang sama sebaiknya dikelompokkan berdasarkan receiver.
Oleh karena itu, fungsi yang diekspor sebaiknya muncul pertama di file, ditempatkan setelah definisi struct
, const
, dan var
.
newXYZ()
/NewXYZ()
dapat muncul setelah definisi tipe tetapi sebelum metode receiver lainnya.
Dalam pengelompokan fungsi berdasarkan receiver, fungsi utilitas umum sebaiknya muncul di akhir file. Tidak Disarankan:
func (s *something) Cost() {
return calcCost(s.weights)
}
type something struct{ ... }
func calcCost(n []int) int {...}
func (s *something) Stop() {...}
func newSomething() *something {
return &something{}
}
Disarankan:
type something struct{ ... }
func newSomething() *something {
return &something{}
}
func (s *something) Cost() {
return calcCost(s.weights)
}
func (s *something) Stop() {...}
func calcCost(n []int) int {...}
Mengurangi Penanaman
Kode sebaiknya mengurangi penanaman dengan menangani kasus kesalahan/spesial sesegera mungkin dan mengembalikan atau melanjutkan perulangan. Mengurangi penanaman akan mengurangi jumlah kode di beberapa tingkat.
Tidak Disarankan:
for _, v := range data {
if v.F1 == 1 {
v = process(v)
if err := v.Call(); err == nil {
v.Send()
} else {
return err
}
} else {
log.Printf("v tidak valid: %v", v)
}
}
Disarankan:
for _, v := range data {
if v.F1 != 1 {
log.Printf("v tidak valid: %v", v)
continue
}
v = process(v)
if err := v.Call(); err != nil {
return err
}
v.Send()
}
Else yang Tidak Diperlukan
Jika sebuah variabel diatur dalam kedua cabang dari sebuah if, itu dapat digantikan dengan sebuah pernyataan if tunggal.
Tidak Disarankan:
var a int
if b {
a = 100
} else {
a = 10
}
Disarankan:
a := 10
if b {
a = 100
}
Deklarasi Variabel Tingkat Atas
Pada tingkat atas, gunakan kata kunci standar var
. Tidak perlu menentukan tipe kecuali berbeda dengan tipe dari ekspresi tersebut.
Tidak Disarankan:
var _s string = F()
func F() string { return "A" }
Disarankan:
var _s = F()
// Karena F secara eksplisit mengembalikan tipe string, kita tidak perlu secara eksplisit menentukan tipe untuk _s
func F() string { return "A" }
Tentukan tipe jika tidak tepat sesuai dengan tipe yang diperlukan untuk ekspresi tersebut.
type myError struct{}
func (myError) Error() string { return "error" }
func F() myError { return myError{} }
var _e error = F()
// F mengembalikan instansi tipe myError, namun kita memerlukan tipe error
Gunakan '_' sebagai Awalan untuk Konstanta dan Variabel Tingkat Atas yang Tidak Diekspor
Untuk variabel dan konstanta tingkat atas yang tidak diekspor, tambahkan awalan garis bawah _
untuk secara eksplisit menunjukkan sifat global ketika digunakan.
Dasar pemikiran: Variabel dan konstanta tingkat atas memiliki cakupan paket. Menggunakan nama-nama umum dapat dengan mudah menyebabkan penggunaan nilai yang salah di file lain.
Tidak Disarankan:
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Port default", defaultPort)
// Tidak akan ada kesalahan kompilasi yang terlihat jika baris pertama dari
// Bar() dihapus.
}
Disarankan:
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
Pengecualian: Nilai kesalahan yang tidak diekspor dapat menggunakan awalan err
tanpa garis bawah. Lihat penamaan kesalahan.
Menyematkan dalam Struktur
Tipe yang disematkan (seperti mutex) harus ditempatkan di bagian atas daftar bidang dalam struktur dan harus memiliki baris kosong yang memisahkan bidang yang disematkan dari bidang biasa.
Tidak disarankan:
type Client struct {
version int
http.Client
}
Disarankan:
type Client struct {
http.Client
version int
}
Pemasukan harus memberikan manfaat nyata, seperti menambahkan atau meningkatkan fungsionalitas dengan cara yang semantis yang sesuai. Ini harus digunakan tanpa dampak negatif pada pengguna. (Lihat juga: Hindari menyematkan tipe dalam struktur publik)
Pengecualian: Bahkan dalam tipe yang tidak diekspor, Mutex tidak boleh digunakan sebagai bidang yang disematkan. Lihat juga: Nilai Mutex nol valid.
Penyematan tidak boleh:
- Hanya ada untuk estetika atau kenyamanan.
- Membuat lebih sulit untuk membuat atau menggunakan tipe luar.
- Mempengaruhi nilai nol dari tipe luar. Jika tipe luar memiliki nilai nol yang berguna, seharusnya masih ada nilai nol yang berguna setelah menyematkan tipe dalam.
- Memiliki efek samping dari mengekspos fungsi atau bidang yang tidak terkait dari tipe dalam yang disematkan.
- Mengekspos tipe yang tidak diekspor.
- Mempengaruhi bentuk klona dari tipe luar.
- Mengubah API atau semantik tipe dari tipe luar.
- Menyematkan tipe dalam dalam bentuk non-standar.
- Mengekspos detail implementasi dari tipe luar.
- Memungkinkan pengguna untuk melihat atau mengontrol tipe internal.
- Mengubah perilaku umum fungsi internal dengan cara yang mungkin mengejutkan pengguna.
Singkatnya, sematkan dengan penuh kesadaran dan tujuan. Tes uji yang baik adalah, "Apakah semua metode/bidang yang diekspor dari tipe dalam akan langsung ditambahkan ke tipe luar?" Jika jawabannya adalah beberapa
atau tidak
, jangan sematkan tipe dalam - gunakan bidang sebagai gantinya.
Tidak disarankan:
type A struct {
// Buruk: A.Lock() dan A.Unlock() sekarang tersedia
// Tidak memberikan manfaat fungsional dan memungkinkan pengguna untuk mengontrol detail internal A.
sync.Mutex
}
Disarankan:
type countingWriteCloser struct {
// Baik: Write() disediakan pada tingkat luar untuk tujuan tertentu,
// dan mendelegasikan pekerjaan ke Write() dari tipe dalam.
io.WriteCloser
count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
w.count += len(bs)
return w.WriteCloser.Write(bs)
}
Deklarasi Variabel Lokal
Jika suatu variabel secara eksplisit diatur nilainya, bentuk deklarasi variabel pendek (:=
) harus digunakan.
Tidak disarankan:
var s = "foo"
Disarankan:
s := "foo"
Namun, dalam beberapa kasus, menggunakan kata kunci var
untuk nilai default bisa lebih jelas.
Tidak disarankan:
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
Disarankan:
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil
adalah slice yang valid
nil
adalah slice yang valid dengan panjang 0, yang berarti:
- Anda sebaiknya tidak secara eksplisit mengembalikan slice dengan panjang nol. Sebagai gantinya, kembalikan
nil
.
Tidak disarankan:
if x == "" {
return []int{}
}
Disarankan:
if x == "" {
return nil
}
- Untuk memeriksa apakah suatu slice kosong, selalu gunakan
len(s) == 0
daripadanil
.
Tidak disarankan:
func isEmpty(s []string) bool {
return s == nil
}
Disarankan:
func isEmpty(s []string) bool {
return len(s) == 0
}
- Slice dengan nilai nol (slice yang dideklarasikan dengan
var
) dapat langsung digunakan tanpa memanggilmake()
.
Tidak disarankan:
nums := []int{}
// atau, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Disarankan:
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
Ingat, meskipun slice yang bernilai nil adalah slice yang valid, namun dia tidak sama dengan slice dengan panjang 0 (satu adalah nil dan yang lain tidak), serta mereka dapat diperlakukan secara berbeda dalam situasi yang berbeda (misalnya, serialisasi).
Ruang Lingkup Variabel yang Sempit
Jika memungkinkan, cobalah untuk mempersempit ruang lingkup variabel, kecuali bertentangan dengan aturan pengurangan penanaman.
Tidak disarankan:
err := os.WriteFile(name, data, 0644)
if err != nil {
return err
}
Disarankan:
if err := os.WriteFile(name, data, 0644); err != nil {
return err
}
Jika hasil dari pemanggilan fungsi di luar pernyataan if perlu digunakan, jangan mencoba mempersempit ruang lingkup.
Tidak disarankan:
if data, err := os.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
Disarankan:
data, err := os.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
Hindari Parameter Tanpa Penjelasan
Parameter yang tidak jelas dalam pemanggilan fungsi dapat merusak keterbacaan. Ketika arti dari nama parameter tidak jelas, tambahkan komentar gaya C (/* ... */
) ke parameter.
Tidak disarankan:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true, true)
Disarankan:
// func printInfo(name string, isLocal, done bool)
printInfo("foo", true /* isLocal */, true /* done */)
Untuk contoh di atas, pendekatan yang lebih baik bisa menggantikan tipe bool
dengan tipe kustom. Dengan cara ini, parameter potensialnya dapat mendukung lebih dari dua status (benar/salah) di masa depan.
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady Status= iota + 1
StatusDone
// Mungkin kami akan memiliki StatusInProgress di masa depan.
)
func printInfo(name string, region Region, status Status)
Gunakan string mentah untuk menghindari escape
Go mendukung penggunaan string mentah, yang ditandai dengan " ` " untuk merepresentasikan string mentah. Pada skenario di mana escaping diperlukan, kita seharusnya menggunakan pendekatan ini untuk menggantikan string yang sulit dibaca secara manual yang di-escape.
String ini dapat melintasi beberapa baris dan mencakup tanda kutip. Penggunaan string ini dapat menghindari str
Tidak disarankan:
wantError := "unknown name:\"test\""
Disarankan:
wantError := `unknown error:"test"`
Inisialisasi Struktur
Menginisialisasi struktur menggunakan nama-nama bidang
Saat menginisialisasi sebuah struktur, nama-nama bidang hampir selalu harus disebutkan. Hal ini saat ini ditegakkan oleh go vet
.
Tidak disarankan:
k := User{"John", "Doe", true}
Disarankan:
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
Pengecualian: Saat ada 3 atau kurang bidang, nama-nama bidang dalam tabel uji boleh dihilangkan.
tests := []struct{
op Operation
want string
}{
{Add, "add"},
{Subtract, "subtract"},
}
Mengabaikan bidang bernilai nol dalam struktur
Saat menginisialisasi struktur dengan bidang-bidang bernama, kecuali konteks yang bermakna disediakan, abaikan bidang-bidang dengan nilai nol. Artinya, biarkan kita secara otomatis mengatur ini menjadi nilai nol.
Tidak disarankan:
user := User{
FirstName: "John",
LastName: "Doe",
MiddleName: "",
Admin: false,
}
Disarankan:
user := User{
FirstName: "John",
LastName: "Doe",
}
Ini membantu mengurangi hambatan dalam membaca dengan menghilangkan nilai default dalam konteks. Hanya tentukan nilai yang bermakna.
Sertakan nilai nol di mana nama bidang memberikan konteks yang bermakna. Misalnya, kasus uji dalam tabel dapat mendapatkan manfaat dari pemberian nama bidang, meskipun mereka adalah nilai nol.
tests := []struct{
give string
want int
}{
{give: "0", want: 0},
// ...
}
Gunakan var
untuk struktur dengan nilai nol
Jika semua bidang dari suatu struktur dihilangkan dalam deklarasinya, gunakan var
untuk mendeklarasikan struktur tersebut.
Tidak disarankan:
user := User{}
Disarankan:
var user User
Ini membedakan struktur dengan nilai nol dari yang memiliki bidang dengan nilai non-nol, mirip dengan apa yang kami lebih sukai saat mendeklarasikan slice kosong.
Menginisialisasi referensi struktur
Saat menginisialisasi referensi struktur, gunakan &T{}
daripada new(T)
untuk membuatnya konsisten dengan inisialisasi struktur.
Tidak disarankan:
sval := T{Name: "foo"}
// tidak konsisten
sptr := new(T)
sptr.Name = "bar"
Disarankan:
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}
Inisialisasi Peta
Untuk peta kosong, gunakan make(..)
untuk menginisialisasinya, dan peta diisi secara programatik. Hal ini membuat inisialisasi peta berbeda dari deklarasi secara penampilan, dan juga secara nyaman memungkinkan untuk menambahkan petunjuk ukuran setelah make.
Tidak Disarankan:
var (
// m1 aman untuk dibaca-tulis;
// m2 panic saat penulisan
m1 = map[T1]T2{}
m2 map[T1]T2
)
Disarankan:
var (
// m1 aman untuk dibaca-tulis;
// m2 panic saat penulisan
m1 = make(map[T1]T2)
m2 map[T1]T2
)
| Deklarasi dan inisialisasi terlihat sangat mirip. | Deklarasi dan inisialisasi terlihat sangat berbeda. |
Dimungkinkan, berikan ukuran kapasitas peta saat inisialisasi, lihat Menentukan Kapasitas Peta untuk detailnya.
Selain itu, jika peta berisi daftar tetap elemen, gunakan literal peta untuk menginisialisasi peta.
Tidak Disarankan:
m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3
Disarankan:
m := map[T1]T2{
k1: v1,
k2: v2,
k3: v3,
}
Pedoman dasarnya adalah untuk menggunakan literal peta untuk menambahkan set tetap elemen saat inisialisasi. Selain itu, gunakan make
(dan jika dimungkinkan, tentukan kapasitas peta).
Format String untuk Fungsi Gaya Printf
Jika Anda mendeklarasikan string format fungsi bergaya Printf
di luar fungsi, tetapkan sebagai konstanta const
.
Hal ini membantu go vet
untuk melakukan analisis statis pada string format.
Tidak Disarankan:
msg := "nilai tak terduga %v, %v\n"
fmt.Printf(msg, 1, 2)
Disarankan:
const msg = "nilai tak terduga %v, %v\n"
fmt.Printf(msg, 1, 2)
Penamaan Fungsi Printf-style
Saat mendeklarasikan fungsi Printf
-style, pastikan bahwa go vet
dapat mendeteksi dan memeriksa format string.
Ini berarti Anda harus menggunakan nama fungsi Printf-style yang telah ditentukan sebanyak mungkin. go vet
akan memeriksa hal ini secara default. Untuk informasi lebih lanjut, lihat Printf Family.
Jika nama yang telah ditentukan tidak dapat digunakan, akhiri nama yang dipilih dengan f
: Wrapf
alih-alih Wrap
. go vet
dapat meminta pengecekan nama-nama Printf-style tertentu, namun nama harus diakhiri dengan f
.
go vet -printfuncs=wrapf,statusf