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 daripada net/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 daripada nil.

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 memanggil make().

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