in Go

Kullanmanız Gereken Go Çalımları

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)
    }
ya da range’in döndürdüğü değerleri pas geçebilirsiniz.
    for range list {
        fmt.Println("döndü")
    }
Dikkat ederseniz range’in döndürdüğü key-value değerlerini almak istemedik ve herhangi bir değişken set etmedik. Bu kullanım genelde Channel’lar ile çalışırken kullanılıyor. Burada maksat range’in döndürdüğü değerleri almadan kullanabileceğinizi göstermek istemem aslında.

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

Go programlama dilinde bir kalıtım (inheritance) veya polymorphic hareketler, klasik OOP mantığında çalışmıyor demiştik. Onun yerine bir tipi diğerine Embed ederek diğer tipin özelliklerini kullanabiliyorsunuz.
Aşağıda Sayac tipini thread-safe çalıştırmak istiyorum. Bunun için mutex tipini embed etmem ve fonksiyonları gerekli yerlerde kullanmam yeterli.
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.

main fonksiyonun içinde de f isminde bir değişkene fonksiyonu tanımlıyoruz. İşte burası higher-order function’ın etkisi. Fonksiyonumuzun ne yapacağını yazıp ArabaBoya fonksiyonuna parametre olarak geçip olayı kapatıyoruz. Şu yazıda özellik tasarımında kullan mıştım.
Fonksiyonlarla ilgili baya bir atraksiyon var Go’da hepsini yazmak başka bir blog yazısı konusu ama anahtar kelimeleri vereyim: first class functionsUser defined function, Closures

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

Hazır döngülerden konu açılmışken continue ve  fallthrough statement’larına da bir bakın. Continue’de label alabiliyor.

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 için de bir trick vermesek olmaz tabi. Genelde switch’in case’leri tek bir koşul üzerinden kullanılır fakat dinamik bazı yapılarda switch içinde bir değişken tanımlayıp case koşullarını daha dinamik hale getirebilirsiniz. Aşağıda ayın hangi diliminde olduğumuzu söyleyen bir switch hareketi var.
    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ı")
    }
}
MaestroPanel’de API isminde git paketim var. Bunu import ederken başına mp koydum. Dikkat ederseniz paketin URL’si içinde “v1” var. Eğer API’in ikinci bir versiyonu çıkarsa veya paket ismi api’den api2 olursa aşağıdaki kodu etkilemeyecek. Yine mp ile erişilebilecek.

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ı")
    }
}
Import’un başına nokta koyduğumuzda sanki api paketinin içindeymiş gibi direkt New() dediğimizde aslında api.New() fonksiyonu çağırıyoruz. Pakete alias vermek istemiyorsanız alternatif bir kullanım olabilir.
Yazı baya bir uzayabilir ama bir yerde dur demek lazım. Sizin de yukarıdakilere benzer çalımlar varsa yorumlarda güzel durur.

Yorum Bırak

Comment

  1. sed “s/fonksiyon olarak parametre alan fonksiyonlara/parametre olarak fonksiyon alan fonksiyonlara/”

  2. Çok teşekkür ederiz, eline sağlık. Güzel ayıklanmış, başlığa tam uyumlu olmuş.