Golang Kodlama Standart Temel İlkeleri
Kaynakları serbest bırakmak için defer kullanın
Dosya ve kilit gibi kaynakları serbest bırakmak için defer kullanın.
Tavsiye edilmez:
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount
// Birden fazla dönüş dalı olduğunda kilidi açmayı unutmak kolaydır
Tavsiye edilir:
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
// Daha okunaklı
Defer'in maliyeti son derece düşüktür, bu nedenle fonksiyonun yürütme süresinin nanosaniye düzeyinde olduğunu kanıtlayabildiğinizde dışlanmalıdır. Okunabilirliği artırmak için defer kullanmak, bunun maliyetinin ihmal edilebilir olduğu için değerlidir. Bu özellikle yalnızca basit bellek erişiminin ötesindeki daha büyük yöntemler için geçerlidir, diğer hesaplamaların kaynak tüketimi, defer
'ıninkinden çok daha fazla olduğunda.
Kanalın boyutu 1 veya puffersiz olmalıdır
Kanallar genellikle 1 boyutunda veya puffersiz olmalıdır. Varsayılan olarak kanallar puffersizdir ve boyutları sıfırdır. Herhangi başka bir boyutta kesinlikle gözden geçirilmelidir. Kanalın boyutunu belirlemenin nasıl olduğu, yüksek yük altında kanalın yazma işlemini engelleyen şeyin ne olduğu ve bloke olduğunda sistem mantığında hangi değişikliklerin meydana geldiği düşünülmelidir.
Tavsiye edilmez:
// Herhangi bir durumu karşılamak için yeterli olmalıdır!
c := make(chan int, 64)
Tavsiye edilir:
// Boyut: 1
c := make(chan int, 1) // veya
// Puffersiz kanal, boyutu 0
c := make(chan int)
Enum'lar 1'den başlar
Go'da enum tanıtmak için standart metot, iota kullanarak custom bir tip ve bir const grup bildirmektir. Değişkenlerin varsayılan değeri 0 olduğundan, enumlar genellikle sıfırdan farklı bir değerle başlamalıdır.
Tavsiye edilmez:
type Operation int
const (
Add Operation = iota
Subtract
Multiply
)
// Add=0, Subtract=1, Multiply=2
Tavsiye edilir:
type Operation int
const (
Add Operation = iota + 1
Subtract
Multiply
)
// Add=1, Subtract=2, Multiply=3
Bazı durumlarda sıfır değeri kullanmak mantıklı olabilir (enumlar sıfırdan başlar), örneğin sıfır değeri ideal varsayılan davranış ise.
type LogOutput int
const (
LogToStdout LogOutput = iota
LogToFile
LogToRemote
)
// LogToStdout=0, LogToFile=1, LogToRemote=2
Atomic kütüphanesini kullanın
İlkellik türleri (int32
, int64
, vb.) üzerinde işlem yapmak için sync/atomic paketinden atomic işlemleri kullanın, çünkü değişkenleri okumak veya değiştirmek için atomic işlemleri kullanmayı unutmak kolaydır.
go.uber.org/atomic bu işlemlere tip güvenliğini gizleyerek ekler. Ayrıca kullanışlı bir atomic.Bool
tipini içerir.
Tavsiye edilmeyen yaklaşım:
type foo struct {
running int32 // atomic
}
func (f* foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
// zaten çalışıyor…
return
}
// Foo'yu başlat
}
func (f *foo) isRunning() bool {
return f.running == 1 // yarış koşulu!
}
Tavsiye edilen yaklaşım:
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
// zaten çalışıyor…
return
}
// Foo'yu başlat
}
func (f *foo) isRunning() bool {
return f.running.Load()
}
Değişkenlerin Değiştirilebilir Global Değişkenlerden Kaçının
Global değişkenleri değiştirmekten kaçınmak için bağımlılık enjeksiyon yaklaşımını kullanın. Bu, işlev işaretçileri için geçerli olduğu gibi diğer değer tipleri için de geçerlidir.
Tavsiye edilmeyen yaklaşım 1:
// sign.go
var _timeNow = time.Now
func sign(msg string) string {
now := _timeNow()
return signWithTime(msg, now)
}
Tavsiye edilen yaklaşım 1:
// sign.go
type signer struct {
now func() time.Time
}
func newSigner() *signer {
return &signer{
now: time.Now,
}
}
func (s *signer) Sign(msg string) string {
now := s.now()
return signWithTime(msg, now)
}
Tavsiye edilmeyen yaklaşım 2:
// sign_test.go
func TestSign(t *testing.T) {
oldTimeNow := _timeNow
_timeNow = func() time.Time {
return someFixedTime
}
defer func() { _timeNow = oldTimeNow }()
assert.Equal(t, want, sign(give))
}
Tavsiye edilen yaklaşım 2:
// sign_test.go
func TestSigner(t *testing.T) {
s := newSigner()
s.now = func() time.Time {
return someFixedTime
}
assert.Equal(t, want, s.Sign(give))
}
Önceden Tanımlanmış Kimlikleri Kullanmaktan Kaçının
Go Dil Belirtimi Go projelerinde kullanılmaması gereken birkaç önceden tanımlanmış kimliği açıklar. Bu önceden tanımlanmış kimlikler farklı bağlamlarda adların tekrar kullanılmaması gerektiğini belirtir, aksi takdirde mevcut kapsamda (veya herhangi bir iç içe kapsamda) orijinal kimlikleri gizleyebilir ve potansiyel olarak kod karışıklığına neden olabilir. En iyi durumda derleyici bir hata verir; en kötü durumda ise bu tür kodlar potansiyel, geri kazanılması zor hatalara neden olabilir.
Tavsiye edilmeyen Uygulama 1:
var hata string
// `error` önceden tanımlı kimliği zımnen gizler
// veya
func hataMesajıİşle(error string) {
// `error` önceden tanımlı kimliği zımnen gizler
}
Tavsiye edilen Uygulama 1:
var hataMesajı string
// `error` artık gölgelememiş önceden tanımlı kimliğe işaret eder
// veya
func hataMesajıİşle(mesaj string) {
// `error` artık gölgelememiş önceden tanımlı kimliğe işaret eder
}
Tavsiye edilmeyen Uygulama 2:
type Foo struct {
// Bu alanların teknik olarak gölgelemesi olmasa da, `error` veya `string` şimdi tekrar tanımlanabilir hale gelir.
hata error
string string
}
func (f Foo) Hata() error {
// `error` ve `f.error` görsel olarak benzer görünür
return f.hata
}
func (f Foo) String() string {
// `string` ve `f.string` görsel olarak benzer görünür
return f.string
}
Tavsiye edilen Uygulama 2:
type Foo struct {
// `error` ve `string` artık açıktır.
hata error
str string
}
func (f Foo) Hata() error {
return f.hata
}
func (f Foo) String() string {
return f.str
}
Derleyici, önceden tanımlı kimlikleri kullanırken hata oluşturmaz, ancak go vet
gibi araçlar bu ve diğer zımni ilişkili sorunları doğru bir şekilde belirtecektir.
init()
Kullanmaktan Kaçının
Mümkün olduğunca init()
kullanmaktan kaçının. init()
kullanılmaması mümkün olmadığı veya tercih edildiği durumlarda, kod aşağıdaki hususlara dikkat etmelidir:
- Program ortamından veya çağrıdan bağımsız olarak eksiksiz olmasını sağlayın.
-
init()
işlevlerinin sırasına veya yan etkilerine bağlı kalmaktan kaçının.init()
'in sırası açık olmasına rağmen, kod değişebileceğindeninit()
işlevleri arasındaki ilişki kodu kırılgan ve hata eğilimli hale getirebilir. - Makine bilgileri, ortam değişkenleri, çalışma dizinleri, program parametreleri/girdileri vb. gibi global veya ortam durumlarına erişimi veya manipülasyonunu engelleyin.
- Dosya sistemleri, ağ ve sistem çağrıları dahil olmak üzere I/O'ya erişimi engelleyin.
Bu gereksinimleri karşılamayan kodlar, main()
çağrısının bir parçası (veya programın yaşam döngüsünün başka bir yerinde) olarak veya main()
'in kendisi olarak yazılmalıdır. Özellikle diğer programlar tarafından kullanılması amaçlanan kütüphaneler, "init sihirbazlığı" yerine eksiksizliğe özel bir dikkat göstermelidir.
Önerilmeyen yaklaşım 1:
type Foo struct {
// ...
}
var _defaultFoo Foo
func init() {
_defaultFoo = Foo{
// ...
}
}
Tavsiye edilen yaklaşım 1:
var _defaultFoo = Foo{
// ...
}
// veya, daha iyi test edilebilirlik için:
var _defaultFoo = defaultFoo()
func defaultFoo() Foo {
return Foo{
// ...
}
}
Önerilmeyen yaklaşım 2:
type Config struct {
// ...
}
var _config Config
func init() {
// Kötü: mevcut dizine bağlı
cwd, _ := os.Getwd()
// Kötü: I/O
raw, _ := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
yaml.Unmarshal(raw, &_config)
}
Tavsiye edilen yaklaşım 2:
type Config struct {
// ...
}
func loadConfig() Config {
cwd, err := os.Getwd()
// hata işle
raw, err := os.ReadFile(
path.Join(cwd, "config", "config.yaml"),
)
// hata işle
var config Config
yaml.Unmarshal(raw, &config)
return config
}