Bir programlama dilinde ustalaşmak o dili öğrendikten sonra ve belirli bir tecrübe edindikten sonra başlıyor. Go ile bir kaç proje tamamladıktan sonra edindiğim kod pratiklerinin listesini hazırladım. Aşağıda Go kullanırken verimlilik sağlayabilecek çalımları (tricks) bulabilirsiniz.
Map
Go’da map ile epey bir iş çözüyoruz. Aslında baya hokus-pokus yapan bir şey. İlk başlarda hemen farkedilmez ama map’in içinde bir anahtarın (key) olup olmadığını Go’nun multi-valued özelliği sayesinde kolayca öğrenebilirsiniz.
list := map[string]int{ "A": 1, "B": 2, } v, ok := list["A"] if ok { fmt.Println("Değer var:", v) }
yukarıdaki list[“A”] direktifi eğer isterseniz geriye map içinde istenilen anahtarın olup olmadığını size döner. Burada “ok” değişkenine anahtarın var olup olmadığını bool olarak atanıyor ve bir sonraki satırda kontrol edilmesine olanak veriyor. “ok” değişkeni yazmadan da aşağıdaki gibi direkt değeri alabilirsiniz compiler bunu anlar.
v := list["A"]
Map’i tek harekette başlatmak (initialize) kullanışlı olabiliyor. Örneğin anonim bir map oluşturup içine elemanları doldurmak için aşağıdaki şekilde bir söz dizimi (syntax) gerekir.
arabalar := map[int]struct { Marka string Model string Yil int }{ 1: {"tofaş", "doğan", 1995}, 2: {"tofaş", "şahin", 2000}, }
if
if’in klasik kullanımı dışında yine Go’ya pascal’dan geçmiş bir kullanım söz konusu. If’i belirli bir kapsam (scope) oluşturarak kullanabilmeniz mümkün. Örneğimize bakalım;
func main() { if v, err := justDoIt(); err == nil { fmt.Println("value:", v) } } func justDoIt() (string, error) { return "", fmt.Errorf("error") }
justDoIt() den dönen multi-valued değeri, if koşulunun içinde bir değişkene atayıp hemen arkasından bir nevi ternary conditional içindeymiş gibi hareket edebiliyorsunuz. Sık kullanılan bir yöntem ama okunabilirliği azalttığından çok tercih etmiyorum şahsen.
Nested Struct
Go’nun class yapısı struct üzerinden dönüyor. Tabi OOP kafası bir class sihirbazlığı beklemeyin ama yine de aşağıdaki gibi kullanımlara olanak sağlıyor. Örneğimize bir bakalım;
type Araba struct { Motor struct { Marka string Model string Tip struct { Elektrik bool Hybrid bool YakitTipi struct { Benzin bool LPG bool Dizel bool Margarin bool } } } }
Çok uzatmamak şartı ile bu kullanımı seviyorum modelleri yazarken (özellikle marshalling olacak modelleri) kullanışlı olabiliyor. Yukarıda Araba tipi’nin altında Motor isimli başka bir tip tanımladık ve Motor’un da altına ayrıca alan (Field) olarak bazı değişkenler ekledik. Şu şekilde erişilebiliyor.
a := Araba{}.Motor.Marka
Unutmadan Go anonim bir tip oluşturmanıza da izin veriyor. Yukarıdaki tipin anonim versiyonu da aşağıdaki şekilde oluşturabilirsiniz.
func main() { Araba := struct { Motor struct { Marka string Model string Tip struct { Elektrik bool Hybrid bool YakitTipi struct { Benzin bool LPG bool Dizel bool Margarin bool } } } }{} fmt.Println("Model:", Araba.Motor.Model) }
Dikkat ederseniz Araba ismindeki anonim tipi oluştururken main fonksiyonu içinde kullandık ve sonunda constructor niyetine {} ekledik. Bu allocation için gerekir. İsterseniz de tiplerin değerlerini vermek için {} alanını kullanabilirsiniz.
Anonim Struct demişken. Struct’u başlatmak için (initialize) kolay bir yol da aşağıdaki gibi. Direkt sırasına (index) göre compiler değişkenleri eşleştiriyor. Sevdiğim pratiklerden biri. O son vigülü koyacaksın ama.
(Bu arada Go compiler’i her satıra “;” işaretini koyuyor. Burada virgülü koyduktan sonraki nüans nedir merak ettim?)
araba := struct { Marka string Model string Yil int }{ "tofaş", "doğan", 1995, }
Anonim bir diziye (Array) ihtiyacınız olursa da aşağıdaki kullanım gayet pratik ve kod’u kısaltır nitelikte. Bunu da çok kullanıyorum. Özellikle test senaryolarında. Şuraya bir bakın demek istediğimi anlarsınız.
arabalar := []struct { Marka string Yil int }{ {"tofaş", 2001}, {"tofaş", 2002}, {"tofaş", 2003}, }
O son virgülü koyacan…
Range
For döngüsünü range anahtar kelimesi ile (keyword) ile yönetebiliyorsunuz. Örneğin bir key-value çiftini aşağıdaki şekilde range kullanarak gezebilirsiniz.
list := map[string]string{ "A": "Siyah", "B": "Beyaz", } for k, v := range list { fmt.Println("Key:", k, "Value:", v) }
for range list { fmt.Println("döndü") }
Embedded Fields
Embedded olayını özetlemek gerekirse isim vermeden direkt bir tipi, diğer bir tipin içine gömmek gibi yüzeysel bir şey söyleyebiliriz.
Örneğin aşağıdaki Araba tipine dikkat ederseniz herhangi bir ismi olmayan Arac tipini isimsiz bir alan (field) olarak eklenmiş olduğunu göreceksiniz. Bu bir nevi kalıtım (inheritance) olarak kabul edebiliriz.
package main import "fmt" type Arac struct { marka string } func (a Arac) String() string { return fmt.Sprintf("arac tipindeki string fonksiyonu: %v", a.marka) } type Araba struct { Arac } func main() { a := Araba{} a.marka = "tofaş" fmt.Println(a) }
Embed ettiğiniz tip direkt olarak embed edilen tip’e tüm özelliklerini aktarıyor. Yukarıdaki örnekde Araba tipi Arac tipinin marka alanını ve string fonksiyonunu almış oluyor. Marka alanına atanan “tofaş” değeri ile beraber direkt Println ile ekrana basmak istersek String() fonksiyonu çalışacaktır.
Bunun nedeni fmt paketindeki Stringer interface’idir. Biz Arac tipine “func String() string” fonksiyonunu yazarak fmt.Stringer interface’ini implemente etmiş olduk ve println buna göre davranarak o fonksiyonu çağırdı. Go’nun native interface isimlerini de bilmek lazım aslında.
Embedded Lock
var Sayac struct { sync.Mutex Sayi int } func main() { Sayac.Lock() Sayac.Sayi++ Sayac.Unlock() }
Sayac.Lock() dediğimizde aslında sync.Mutex.Lock() ‘u çağırmış oluyoruz.
(Burada aslında shadowing davranışı nasıl onu test etmek lazım. Acaba Lock()’u override edebiliyor muyum?)
Method Expressions
Fonksiyonu değişken olarak kullanmak veya method parametresi olarak kullanmak gerçekten kullanışlı olabiliyor. Örneğin gubrak veya go-linq projelerini incelerseniz method expressions olaylarının güzel kullanım örneklerini görebilirsiniz. En basitinden aşağıdaki örneğimize bir bakalım;
var fn func(string) fn = func(marka string) { println(marka) } fn("tofaş")
Var ile fn isminde bir method değişkeni oluşturduk ve 2. satırda bunu allocate ettik. fn(“tofaş”) çağrısını yaptığımız anda fonksiyon çalışmış oluyor. fn değişkeni func(string) imzasına sahip olan bütün değer fonksiyonlarını alabilir.
Higher-Order Functions
Go, higher-order functions‘ı kısaca parametre olarak fonksiyon alan fonksiyonlara deniyor. Aşağıdaki örneğimize bakalım;
package main import "fmt" func ArabaBoya(fn func(string), renk string) { fn(renk) } func main() { f := func(renk string) { fmt.Printf("araba artık %s\n", renk) } ArabaBoya(f, "mavi") }
ArabaBoya diye bir fonksiyonumuz var ve fn isminde bir fonksiyon alıyor. Onuda renk parametresini alıp çalıştırıyor.
Hızlı Değişken Tanımları
Go da özellikle import, const ve var keywordleri için kolay kullanım için düşünüşmüş bir kaç çalım var. Aşağıda örnekleri verdim.
var Araba string var Marka string var Yil int
bunun yerine,
var ( Araba string Marka string Yil int )
Bu yaklaşımı const ve import için de yapabilirsiniz.
Hızlı Değişken Değerleri
Tek harakette aşağıdaki gibi bir combo yapabiliyorsunuz.
a, b, c := 1, 2, 3 fmt.Println(a, b, c)
ayrıca var ile değişken tanımlayıp direkt değer geçebiliyorsunuz.
var araba string = "tofaş"
diğer bir kullanım aşağıdaki gibi. Birden fazla tipi tek satır olarak tanımlayabilirsiniz.
package main type Araba struct { Marka string Yil int } func main() { a1, a2, a3 := Araba{"tofaş", 2000}, Araba{"tofaş", 2001}, Araba{"tofaş", 2002} }
Type Alias
Type Alias‘da kullanışlı özelliklerden bir tanesi. Örneğin bir string’e alias tanımlayıp string’in yeni yeteneklere sahip olmasını sağlayabilirsiniz. Örneğin:
package main type Katar string func (k Katar) Yaz() { println(k) } func main() { var gelen Katar gelen = "şu yoğurdu saklasakta mı saklasak?" gelen.Yaz() }
Builtin olan string tipi için Katar (string’in Türkçesi) isminde yeni bir tip oluşturduk aslında bu Katar, string için oluşturduğumuz bir takma isim bu sayede Yaz fonksiyonunu string’in bir fonksiyonmuş gibi kullanabiliyoruz.
Örneğin Go’da byte ismi aslında uint8 tipinin alias’ı dır veya rune aslında bir int32 alias’ıdır.
type byte = uint8 type rune = int32
Naked Return
Bir yerde okumuştum komik olduğu için aklımda Naked Return olarak kaldı bu hadise. Go’da buna Named Result Parameters deniyor ama Naked Return demek daha güzel.
Kısaca şu. Fonksiyonun geri dönüş değerlerine bir isim verirseniz return keyword’ünü yalnız kullanabilirsiniz. (Bak bunun ismine “solo return” falan da denir.)
func Araba() (marka string, model int) { return "tofaş", 1995 }
func Araba() (marka string, model int) { marka = "tofaş" model = 1995 return }
gibi kullanabiliyorsunuz.
Bu aşamada direkt değişkenlere değer atamadan da return ile dönebilirsiniz. Genelde böyle kullanıyorum. Geri dönüşler değiştiğinde fonksiyon içindeki tüm return’leri ellememiş oluyorum.
Break ve Goto
goto’nun ünü her ne kadar OOP’de kötü olsada Go gibi basit tasarlanmış bir dilde baya etkili hareketlere neden olabiliyor. Aşağıdaki örneğimize bakalım;
package main import "fmt" func main() { n := 10 Level1: for i := 0; i < n; i++ { for x := 0; x < n; x++ { if x%3 > 0 { break Level1 } } if i == 10 { goto Level2 } } Level2: fmt.Println("Exit") }
N değeri kadar dönen bir döngü var. Onun içinde de aynı şekilde dönen başka bir döngü var, fakat iterasyon 3’e tam bölünüyorsa döngüden çıkıp direkt Level1 label’indan program devam ediyor (evet break label alabiliyor).
Üşenmeyip örnek olsun diye aynı minvalde goto’yu da kullandım. Onuda böyle zıplamalar için kullanıyorum. Özellikle error handling de iş görüyor.
Error Handling
Hata yakalam Go’da baya kötü bunu kalbul edelim. Go2’de çok seksi bir kullanım vaadediyorlar ama gerçeklere dönersek elimizdeki tek şey if err != nil {} bunun pek efektif bir kullanımı bulunmuyor ama try…catch özleminizi defer, panic, recover keyword’leri ile giderebiliyorsunuz.
func execute() { defer func() { if e := recover(); e != nil { fmt.Println("hata yakalandı:", e) } }() panic("exception fırlat") }
execute() isminde bir fonksiyonumuz var. defer biliyorsunuz fonksiyondaki işler tamamlandıktan sonra çalışacak. Biz tam burada recover() ile error’u catch ediyoruz ve iş akışını çalıştırıyoruz. Bu genel bir error handling sağlıyor. Yalnız error’u panic ile fırlatmak gerekiyor.
Printf
Go’da fmt.Sprinf’i çok kullanıyorum. Özelikle bir string birleştirme (concat) olayı için (bu arada + kullanarak string birleştirmek daha hızlı Go’da). Yalnız format için kullanılan fonksiyonlar tekrar ettiği zaman aşağıdaki gibi durum çıkıyor.
fmt.Printf("Toplam :%d(%s) Kalan:%d(%s)", 50, "MB", 5, "MB")
dikkat edersenia 2 tane “MB” parametresi geçildi. Çünkü hem Toplam hem de Kalan label’larının unit’leri MB. Aşağıdaki kullanım bizi iki kere “MB” değişkenini geçmekten kurtarıyor.
fmt.Printf("Toplam: %d(%s), Kalan: %d(%[2]s)", 50,"MB",5)
Yalnız okunabilirlik ile alakalı yorum yapmayacağım!
Interface Checks
Go’da interface’ler gerçekten çok pratik. Gel gelelim satisface interface katı bir yapı sunmuyor. Bazen fonksiyonları kaçırabiliyorsun. Interface Checks, belirli bir interface’in implemente edilip edilmediğini kontrol ediyor.
Aşağıdaki gibi;
package main import "fmt" type A interface { Add() bool Del() bool } type TA struct { } func (TA) Add() bool { return true } func (TA) Del() bool { return false } func main() { var t interface{} t = TA{} v, ok := t.(A) if !ok { fmt.Println("interface implemente edilmemiş.") } fmt.Printf("%T\n", v) }
TA tipine Add() ve Del() fonksiyonlarını eklediğimiz zaman interface’i implemente etmiş oluyoruz. v, ok := t.(A) ise interface’i check ettiğimiz yer. ok direkt interface uygulanmış mı yoksa uygulanmamış mı diye bool değer döndürüyor. Örneğin TA tipindeki Del() fonksiyonu Del2() diye değiştirin false dönecektir.
Anonymous Interface
Anonim inferface ile çok fantastik hareketler yapılabiliyor. Örneğin başka bir paketin içindeki private fonksiyonu çağırmak için bu interface kullanılabilir. Aşağıda ufak bir örnek hazırladım.
package main import "fmt" type Arac struct { marka string } func (a Arac) String() string { return fmt.Sprintf("Arac tipindeki string fonksiyonu: %v", a.marka) } func (a Arac) yil() int { return 1995 } type Araba struct { Arac } func main() { a := Araba{} a.marka = "tofaş" var s interface { yil() int } = a fmt.Println(s.yil()) }
Anonim Interface kullanımı s değişkeni ile tanımlanıyor ve aha önce tanımlanmış Araba tipinde olan a değişkenini bu interface’e atıyoruz. Anonim interface’imiz sayesinde yil() fonksiyonu çağırılabiliyor. Bunu farklı bir paket içinde de yapabiliyorsunuz.
Switch
switch gun := time.Now().Day(); { case gun < 10: fmt.Println("Ayın daha başı") case gun > 10 && gun < 20: fmt.Println("Ayın ortası") case gun > 20: fmt.Println("Ayın sonu") default: fmt.Println("Bilemedim") }
Yine kullanışlı bir switch kullamımı olarak tek bir case’de beklenen değerleri gruplayabilirsiniz.
func TurkceMi(c rune) bool { switch c { case 'ş', 'ü', 'ğ', 'ö', 'ç': return true } return false }
Package
Go, geliştirdiğiniz uygulamalara import ettiğiniz ve kullandığınız paketleri yönetebilmeniz için bir kaç kullanım kolaylığı sunuyor. Bunlardan en çok kullanılanı bir pakete alias tanımlamak. Örneğin:
package main import ( mp "github.com/maestropanel/v1/api" ) func main() { client := mp.New() if !client.Test(){ panic("bağlanılamadı") } }
Başka bir import çalımı da import’un başına nokta koyarak bütün pakete embed edebileceğiniz çalım. Örneğe bir bakalım.
package main import ( . "github.com/maestropanel/v1/api" ) func main() { client := New() if !client.Test(){ panic("bağlanılamadı") } }
güzel bir yazı olmuş elinize sağlık
sed “s/fonksiyon olarak parametre alan fonksiyonlara/parametre olarak fonksiyon alan fonksiyonlara/”
sed “s/hight-order/higher-order/” | sed “s/High-Order/Higher-order”
Teşekkürler İsmail, düzelttim.
Mükemmel bir yazı olmuş teşekkürler :)
Çok teşekkür ederiz, eline sağlık. Güzel ayıklanmış, başlığa tam uyumlu olmuş.
Teşekkürler güzel çalımlar.