Aşırı Uzun Satırlardan Kaçının

Okuyucuları yatay olarak kaydırmak veya belgeyi aşırı döndürmek zorunda bırakan kod satırlarından kaçının.

99 karakteri aşmayan satır uzunluğunu öneriyoruz. Yazarlar, bu sınıra ulaşmadan önce satırı bölmelidir, ancak bu sert bir kural değildir. Kodun bu sınırları aşması müsaadedir.

Uyum

Bu belgede belirtilen standartların bazıları öznel değerlendirmelere, senaryolara veya bağlamlara dayanmaktadır. Ancak en önemli olan nokta uyumun sağlanmasıdır.

Tutarlı kod bakımı daha kolay, daha mantıklı, daha az öğrenme maliyeti gerektirir ve yeni kurallar ortaya çıktığında veya hatalar meydana geldiğinde geçiş yapmak, güncellemek ve hataları düzeltmek daha kolaydır.

Aksine, bir kod tabanına birden fazla tamamen farklı veya çelişen kod stili eklemek, bakım maliyetlerinin artmasına, belirsizliğe ve bilişsel önyargılara yol açar. Bunların hepsi doğrudan daha yavaş hız, acı veren kod incelemeleri ve artan hata sayısına neden olur.

Bu standartları bir kod tabanına uygularken, değişiklikleri paket (veya daha büyük) seviyesinde yapmanız önerilir. Alt paket seviyesinde birden fazla stil uygulamak yukarıdaki endişeleri ihlal eder.

Benzer Bildirimleri Gruplama

Go dilinin benzer bildirimleri gruplamasını destekler.

Tavsiye Edilmez:

import "a"
import "b"

Tavsiye Edilir:

import (
  "a"
  "b"
)

Bu aynı zamanda sabit, değişken ve tip bildirimleri için de geçerlidir:

Tavsiye Edilmez:

const a = 1
const b = 2

var a = 1
var b = 2

type Area float64
type Volume float64

Tavsiye Edilir:

const (
  a = 1
  b = 2
)

var (
  a = 1
  b = 2
)

type (
  Area float64
  Volume float64
)

Sadece ilgili bildirimleri bir araya getirin ve ilgili olmayan bildirimleri bir araya getirmekten kaçının.

Tavsiye Edilmez:

type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
  EnvVar = "MY_ENV"
)

Tavsiye Edilir:

type Operation int

const (
  Add Operation = iota + 1
  Subtract
  Multiply
)

const EnvVar = "MY_ENV"

Gruplandırmanın nerede kullanılacağına dair bir kısıtlama yoktur. Örneğin, onları bir fonksiyon içinde kullanabilirsiniz:

Tavsiye Edilmez:

func f() string {
  red := color.New(0xff0000)
  green := color.New(0x00ff00)
  blue := color.New(0x0000ff)

  ...
}

Tavsiye Edilir:

func f() string {
  var (
    red   = color.New(0xff0000)
    green = color.New(0x00ff00)
    blue  = color.New(0x0000ff)
  )

  ...
}

Özel Durum: Değişken bildirimleri, özellikle işlev yerel bildirimleri içinde diğer değişkenlere bitişikse, bir arada gruplandırılmalıdır. Bunu, özellikle ilişkisiz değişkenler birlikte bildirilse bile yapın.

Tavsiye Edilmez:

func (c *client) request() {
  caller := c.name
  format := "json"
  timeout := 5*time.Second
  var err error
  // ...
}

Tavsiye Edilir:

func (c *client) request() {
  var (
    caller  = c.name
    format  = "json"
    timeout = 5*time.Second
    err error
  )
  // ...
}

İçe Aktarma Gruplama

İçe aktarmalar iki kategoriye ayrılmalıdır:

  • Standart kütüphane
  • Diğer kütüphaneler

Genellikle goimports tarafından uygulanan bu gruplamadır. Tavsiye Edilmez:

import (
  "fmt"
  "os"
  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)

Tavsiye Edilir:

import (
  "fmt"
  "os"

  "go.uber.org/atomic"
  "golang.org/x/sync/errgroup"
)

Paket İsmi

Bir paket ismi belirlerken lütfen şu kurallara uyun:

  • Tüm küçük harfler, büyük harfler veya alt çizgiler olmamalıdır.
  • Çoğu durumda içe aktarırken yeniden adlandırmaya gerek yoktur.
  • Kısa ve öz olmalıdır. İsim, kullanıldığı her yerde tamamen nitelikli olduğunu unutmayın.
  • Çoğul kullanımından kaçının. Örneğin, net/url yerine net/urls kullanın.
  • "common," "util," "shared," veya "lib" kullanmaktan kaçının. Bunlar yeterince bilgilendirici değildir.

Fonksiyon İsimlendirme

Go topluluğu kılavuzunun önerdiği şekilde, fonksiyon isimlerini MixedCaps kuralına göre kullanıyoruz. Bir istisna, ilgili test vakalarını gruplama durumunda fonksiyon isminin alt çizgi içerebileceği durumdur, örneğin: TestMyFunction_WhatIsBeingTested.

İçe Aktarma Takma Adları

Paket adı, içe aktarma yolunun son öğesiyle eşleşmiyorsa, bir içe aktarma takma adı kullanılmalıdır.

import (
  "net/http"

  client "example.com/client-go"
  trace "example.com/trace/v2"
)

Diğer durumlarda, doğrudan içe aktarmalar arasında bir çakışma olmadıkça içe aktarma takma adlarından kaçınılmalıdır. Önerilmez:

import (
  "fmt"
  "os"

  nettrace "golang.net/x/trace"
)

Tavsiye Edilen:

import (
  "fmt"
  "os"
  "runtime/trace"

  nettrace "golang.net/x/trace"
)

Fonksiyon Gruplama ve Sıralaması

  • Fonksiyonlar, yaklaşık olarak çağrıldıkları sıra ile sıralanmalıdır.
  • Aynı dosyadaki fonksiyonlar alıcı tarafından gruplandırılmalıdır.

Bu nedenle, dışa aktarılan fonksiyonlar dosyanın başında yer almalı ve struct, const ve var tanımlamalarından sonra yer almalıdır.

newXYZ()/NewXYZ() tip tanımlamalarından sonra ancak alıcı görevlerinin kalanı öncesinde yer alabilir.

Fonksiyonlar alıcı tarafından gruplandırıldığından, genel yardımcı fonksiyonlar dosyanın sonunda yer almalıdır. Önerilmez:

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{}
}

Tavsiye Edilen:

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 {...}

İç İçe Geçmeyi Azaltma

Kod, hata/özel durumları mümkün olduğunca erken ele alarak iç içe geçmeyi azaltmalı ve ya döndürmeli ya da döngüyü devam etmelidir. İç içe geçmeyi azaltmak, birden fazla düzeydeki kod miktarını azaltır.

Önerilmez:

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("Geçersiz v: %v", v)
  }
}

Tavsiye Edilen:

for _, v := range data {
  if v.F1 != 1 {
    log.Printf("Geçersiz v: %v", v)
    continue
  }

  v = process(v)
  if err := v.Call(); err != nil {
    return err
  }
  v.Send()
}

Gereksiz else

Bir değişkenin if'in her iki dalında da ayarlandığı durumlar, tek bir if ifadesi ile değiştirilebilir.

Önerilmez:

var a int
if b {
  a = 100
} else {
  a = 10
}

Tavsiye Edilen:

a := 10
if b {
  a = 100
}

Üst Düzey Değişken Bildirimi

Üst düzeyde standart var anahtar kelimesini kullanın. İfade türünden farklıysa türü belirtmeyin.

Önerilmez:

var _s string = F()

func F() string { return "A" }

Tavsiye Edilen:

var _s = F()
// F açıkça bir string türü döndürdüğü için _s için türü açıkça belirtmemiz gerekmiyor.

func F() string { return "A" }

İfade ile tam olarak eşleşmiyorsa türü belirtin.

type myError struct{}

func (myError) Error() string { return "hata" }

func F() myError { return myError{} }

var _e error = F()
// F, myError türünün bir örneğini döndürse de, bizim error türüne ihtiyacımız var

Üst Düzey İhrac Edilmeyen Constant ve Değişkenler için '_' Önekini Kullanın

İhrac edilmeyen üst düzey vars ve consts için, kullanıldığında global doğalarını açıkça belirtmek için bir alt çizgi _ ile öne ekleyin.

Temel mantık: Üst düzey değişkenler ve sabitler paket düzeyinde kapsama sahiptir. Genel isimler kullanmak, diğer dosyalarda yanlışlıkla yanlış değeri kullanmanıza kolayca neden olabilir.

Tavsiye Edilmez:

// foo.go

const (
  defaultPort = 8080
  defaultUser = "user"
)

// bar.go

func Bar() {
  defaultPort := 9090
  ...
  fmt.Println("Varsayılan port", defaultPort)

  // Bar()'ın ilk satırı silinirse derleme hatası almayız.
}

Tavsiye Edilen:

// foo.go

const (
  _defaultPort = 8080
  _defaultUser = "user"
)

İstisna: İhrac edilmeyen hata değerleri alt tire olmadan err öneki kullanabilir. Hata adlandırma öğesine bakınız.

Struct Yerleştirme

Yerleştirilmiş türler (örneğin mutex), yapının içindeki alan listesinin üst kısmına yerleştirilmeli ve yerleşik alanları düzenli alanlardan boş bir satırla ayırmalıdır.

Tavsiye Edilmez:

type Client struct {
  version int
  http.Client
}

Tavsiye Edilen:

type Client struct {
  http.Client

  version int
}

Yerleştirme, semantik olarak uygun bir şekilde işlevsellik eklemek veya geliştirmek gibi somut faydalar sağlamalıdır. Kullanıcı üzerinde herhangi bir olumsuz etkisi olmaksızın kullanılmalıdır. (Ayrıca bakınız: Genel yapılar içine tür yerleştirmelerinden kaçının)

İstisnalar: İhrac edilmeyen türlerde bile, Mutex yerleştirilmiş alan olarak kullanılmamalıdır. Ayrıca bakınız: Sıfır değerli Mutex geçerlidir.

Yerleştirme yapmamalı:

  • Sadece estetik veya kolaylık için var olmalıdır.
  • Dış türün oluşturulmasını veya kullanılmasını daha zor hale getirmemelidir.
  • Dış türün sıfır değerini etkilememelidir. Dış türün kullanışlı bir sıfır değeri varsa, iç türü yerleştirdikten sonra da kullanışlı bir sıfır değeri olmalıdır.
  • Yerleştirilmiş iç türden ilişkisiz işlevleri veya alanları açığa çıkarmamalıdır.
  • İhrac edilmeyen türleri açığa çıkarmamalıdır.
  • Dış türün klonlama formunu etkilememelidir.
  • API'yi veya türün anlamını değiştirmemelidir.
  • İç türü standart olmayan bir biçimde yerleştirmemelidir.
  • Dış türün uygulama ayrıntılarını açığa çıkarmamalıdır.
  • Kullanıcıların iç türü gözlemlemesine veya kontrol etmesine izin vermemelidir.
  • Kullanıcıları şaşırtabilecek şekilde iç işlevlerin genel davranışını değiştirmemelidir.

Kısacası, bilinçli ve amaçlı bir şekilde yerleştirme yapın. İyi bir test, "İç türden tüm bu ihraç edilmiş yöntemler/alanlar doğrudan dış tipe eklenecek mi?" sorusuna verilen cevap bazı veya hayır ise iç türü yerleştirmeyin - bunun yerine alanları kullanın.

Tavsiye Edilmez:

type A struct {
    // Kötü: A.Lock() ve A.Unlock() artık kullanılabilir
    // Hiçbir işlevsel fayda sağlamaz ve kullanıcının A'nın iç ayrıntılarını kontrol etmesine izin verir.
    sync.Mutex
}

Tavsiye Edilen:

type countingWriteCloser struct {
    // İyi: Write(), dış seviyede belirli bir amaç için sağlanır
    // ve iç türün Write()'ına işi devreder.
    io.WriteCloser
    count int
}
func (w *countingWriteCloser) Write(bs []byte) (int, error) {
    w.count += len(bs)
    return w.WriteCloser.Write(bs)
}

Yerel Değişken Bildirimleri

Bir değişken açıkça bir değere ayarlanıyorsa, kısa değişken bildirimi formu (:=) kullanılmalıdır.

Tavsiye Edilmez:

var s = "foo"

Tavsiye Edilen:

s := "foo"

Ancak bazı durumlarda, varsayılan değerler için var anahtar kelimesinin kullanılması daha açıklayıcı olabilir.

Tavsiye Edilmez:

func f(list []int) {
  filtered := []int{}
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}

Tavsiye Edilen:

func f(list []int) {
  var filtered []int
  for _, v := range list {
    if v > 10 {
      filtered = append(filtered, v)
    }
  }
}

nil geçerli bir dilimdir

nil, uzunluğu 0 olan geçerli bir dilimdir, bu şu anlama gelir:

  • Kesinlikle uzunluğu sıfır olan bir dilim döndürmemelisiniz. Bunun yerine nil döndürün.

Tavsiye edilmez:

if x == "" {
  return []int{}
}

Tavsiye edilen:

if x == "" {
  return nil
}
  • Bir dilimin boş olup olmadığını kontrol etmek için her zaman len(s) == 0 yerine nil kullanmayın.

Tavsiye edilmez:

func isEmpty(s []string) bool {
  return s == nil
}

Tavsiye edilen:

func isEmpty(s []string) bool {
  return len(s) == 0
}
  • Sıfır değerli dilimler (var ile tanımlanan dilimler) make() çağrısı yapmadan hemen kullanılabilir.

Tavsiye edilmez:

nums := []int{}
// veya, nums := make([]int)

if add1 {
  nums = append(nums, 1)
}

if add2 {
  nums = append(nums, 2)
}

Tavsiye edilen:

var nums []int

if add1 {
  nums = append(nums, 1)
}

if add2 {
  nums = append(nums, 2)
}

Unutmayın, nil dilimi geçerli olsa da, uzunluğu 0 olan bir dilimle eşit değildir (birisi nil, diğeri değil) ve farklı durumlarda farklı şekilde işlem görebilir (örneğin, serileştirme).

Değişken Kapsamını Daraltın

Mümkünse, değişkenlerin kapsamını daraltmaya çalışın, bu, iç içe geçme kuralıyla çakışmıyorsa yapılmalıdır.

Tavsiye edilmez:

err := os.WriteFile(name, data, 0644)
if err != nil {
 return err
}

Tavsiye edilen:

if err := os.WriteFile(name, data, 0644); err != nil {
 return err
}

Eğer if bloğu dışındaki bir fonksiyon çağrısının sonucunu kullanmanız gerekiyorsa, değişkenin kapsamını daraltmaya çalışmayın.

Tavsiye edilmez:

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
}

Tavsiye edilen:

data, err := os.ReadFile(name)
if err != nil {
   return err
}

if err := cfg.Decode(data); err != nil {
  return err
}

fmt.Println(cfg)
return nil

Çıplak Parametrelerden Kaçının

Anlaşılmayan parametreler, fonksiyon çağrılarının okunurluğuna zarar verebilir. Parametre isimlerinin anlamı açık olmadığında, C tarzı yorumlar (/* ... */) ekleyin.

Tavsiye edilmez:

// func printInfo(name string, isLocal, done bool)

printInfo("foo", true, true)

Tavsiye edilen:

// func printInfo(name string, isLocal, done bool)

printInfo("foo", true /* isLocal */, true /* done */)

Yukarıdaki örneğe göre, daha iyi bir yaklaşım, bool türlerini özel türlerle değiştirmek olabilir. Bu şekilde, parametre gelecekte sadece iki durum (doğru/yanlış) değil de daha fazlasını destekleyebilir.

type Bolge int

const (
  BilinmeyenBolge Bolge = iota
  Yerel
)

type Durum int

const (
  HazirDurum Durum = iota + 1
  TamamlandiDurum
  // Belki gelecekte BirDurumdaDevamEdiyor olabilir.
)

func printInfo(name string, bolge Bolge, durum Durum)

Kaçın Raw string literaller kullanarak özel karakterlerin kaçırılmasından

Go, özel karakterlerin belirgin bir şekilde temsil edildiği raw string literaller kullanımını destekler. Kaçırma gerektiren senaryolarda, daha zor okunabilen manuel şekilde kaçırılmış dizelerin yerine bu yaklaşımı kullanmalıyız.

Çok satırlı olabilir ve tırnak içerebilir. Bu dizelerin kullanımı, daha zor okunabilen manuel şekilde kaçırılmış dizelerden kaçınmamızı sağlar.

Tavsiye edilmez:

wantError := "unknown name:\"test\""

Tavsiye edilen:

wantError := `unknown error:"test"`

Yapıları Başlatın

Alan adlarını kullanarak yapıları başlatın

Bir yapı başlatılırken, alan adlarının neredeyse her zaman belirtilmesi gerekmektedir. Şu anda bunun go vet ile zorunlu hale getirilmiştir.

Tavsiye edilmez:

k := User{"John", "Doe", true}

Tavsiye edilir:

k := User{
    FirstName: "John",
    LastName: "Doe",
    Admin: true,
}

İstisna: 3 veya daha az alan olduğunda, test tablolarında alan adları atlanabilir.

tests := []struct{
  op Operation
  want string
}{
  {Add, "add"},
  {Subtract, "subtract"},
}

Sıfır değer alanlarını yapı içinde atlayın

Adlandırılmış alanlara sahip bir yapıyı başlatırken, anlamlı bağlam sağlanmadıkça sıfır değerdeki alanları göz ardı edin. Yani, bunları otomatik olarak sıfır değerler olarak ayarlayalım.

Tavsiye edilmez:

user := User{
  FirstName: "John",
  LastName: "Doe",
  MiddleName: "",
  Admin: false,
}

Tavsiye edilir:

user := User{
  FirstName: "John",
  LastName: "Doe",
}

Bu, bağlamda varsayılan değerleri atlayarak okuma engellerini azaltmaya yardımcı olur. Yalnızca anlamlı değerleri belirtin.

Alan adları anlamlı bağlam sağladığında sıfır değer de dahil edin. Örneğin, test tablosundaki test durumları alanları adlandırmak faydalı olabilir, hatta bunlar sıfır değerleri olsalar bile.

tests := []struct{
  give string
  want int
}{
  {give: "0", want: 0},
  // ...
}

Sıfır değerli yapılar için var kullanın

Bir yapıda tüm alanlar bildirilmeden önce, yapıyı bildirmek için var kullanın.

Tavsiye edilmez:

user := User{}

Tavsiye edilir:

var user User

Bu, sıfır değerli yapıları sıfır olmayan değer alanlarından ayırt etmeye yardımcı olur, örneğin boş bir dilim bildirirken tercih ettiğimiz gibi.

Yapı referanslarını başlatın

Yapı referanslarını başlatırken, new(T) yerine &T{} kullanarak yapıların başlatılmasını tutarlı hale getirin.

Tavsiye edilmez:

sval := T{Name: "foo"}

// tutarsız
sptr := new(T)
sptr.Name = "bar"

Tavsiye edilir:

sval := T{Name: "foo"}

sptr := &T{Name: "bar"}

Haritaları Başlat

Boş bir harita için, make(..) kullanarak başlatın ve harita programatik olarak doldurulur. Bu, harita başlatmanın görünümünü bildirimden farklı hale getirir ve ayrıca make'den sonra boyut ipuçlarını eklemenize olanak tanır.

Tavsiye edilmez:

var (
  // m1 okuma-yazma güvenli; 
  // m2 yazarken panik oluşur
  m1 = map[T1]T2{}
  m2 map[T1]T2
)

Tavsiye edilir:

var (
  // m1 okuma-yazma güvenli; 
  // m2 yazarken panik oluşur
  m1 = make(map[T1]T2)
  m2 map[T1]T2
)

| Bildirim ve başlatma görünüşü çok benzer. | Bildirim ve başlatma görünüşü çok farklı. |

Mümkünse, başlatılırken harita kapasite boyutunu belirtin, ayrıntılar için Harita Kapasitesi Belirtme bölümüne bakın.

Ayrıca, haritanın sabit bir liste elemanı içerdiği durumlarda harita literallerini kullanarak haritayı başlatın.

Tavsiye edilmez:

m := make(map[T1]T2, 3)
m[k1] = v1
m[k2] = v2
m[k3] = v3

Tavsiye edilir:

m := map[T1]T2{
  k1: v1,
  k2: v2,
  k3: v3,
}

Temel kılavuz, başlangıçta sabit bir dizi eleman eklemek için harita literallerini kullanmaktır. Aksi takdirde, make'i (ve mümkünse harita kapasitesini belirtin) kullanın.

Printf-Style Fonksiyonlar için String Formatı

Bir Printf-stilindeki fonksiyonun format dizisini bir fonksiyon dışında bildirirseniz, bu formatı bir const sabit olarak ayarlayın.

Bu, go vet'in format dizisinde statik analiz yapmasına yardımcı olur.

Tavsiye edilmez:

msg := "beklenmeyen değerler %v, %v\n"
fmt.Printf(msg, 1, 2)

Tavsiye edilir:

const msg = "beklenmeyen değerler %v, %v\n"
fmt.Printf(msg, 1, 2)

Printf-Style Fonksiyonların İsimlendirilmesi

Printf-tarzı fonksiyonları bildirirken, go vet'in format dizesini algılayıp kontrol edebilmesini sağlayın.

Bu, mümkün olduğunca önceden tanımlanmış Printf-tarzı fonksiyon adlarını kullanmanız gerektiği anlamına gelir. go vet bunları varsayılan olarak kontrol edecek. Daha fazla bilgi için Printf Ailesi bölümüne bakın.

Önceden tanımlı adlar kullanılamadığında, seçilen adı f ile bitirin: Wrap yerine Wrapf. go vet, belirli Printf-stil isimlerin kontrol edilmesini isteyebilir, ancak ad f ile bitmelidir.

go vet -printfuncs=wrapf,statusf