Bu bölümde Go’nun, neredeyse diğer tüm dillerden farklı özelliklerine değineceğiz.
Go, hataları, istisnalarla(exceptions) değil geri dönüş değerleri vasıtasıyla karşılıyor. Örnek olarak, string tipinde değer alıp tam sayıya çeviren strconv.Atoi
fonksiyonunu baz alalım:
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) != 2 {
os.Exit(1)
}
n, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("not a valid number")
} else {
fmt.Println(n)
}
}
Kendi hata tiplerinizi oluşturabilirsiniz; bunun için tek şart, dahili olarak gelen error
arayüzünün kontratına uymaktır:
type error interface {
Error() string
}
Kendi hata mesajlarınızı, errors
paketini içe aktararak New
fonksiyonu ile kullanmamız yaygın bir yöntemdir:
import (
"errors"
)
func process(count int) error {
if count < 1 {
return errors.New("Invalid count")
}
...
return nil
}
Go’nun standart kütüphanesinde, hata değişkenlerini kullanmanın yaygın bir yöntemi vardır. Örneğin, io
paketi içerisinde, aşağıdaki şekilde tanımlanmış bir EOF
hata değişkeni vardır:
var EOF = errors.New("EOF")
Bu değişken, paket dışından erişilebilen (ilk harfi büyük) bir paket değişkenidir. Dosyadan veya STDIN‘den okuma yaparken, çeşitli fonksiyonlar bu hatayı döndürebilir. Bağlamsal olarak mantıklı geldiği durumlarda, siz de bu hatayı kullanabilirsiniz. Ayrıca kullanıcı olarak da bu hatayı kullanabiliriz:
package main
import (
"fmt"
"io"
)
func main() {
var input int
_, err := fmt.Scan(&input)
if err == io.EOF {
fmt.Println("no more input!")
}
}
Final notu olarak; Go, panic
ve recover
fonksiyonlarına sahiptir. panic
fonksiyonu istisnayı açığa vururken, recover
fonksiyonu bir nevi catch
gibi davranır.
Go’nun çöp toplayıcısı (garbage collector) olmasına rağmen, bazı kaynaklar açıkça serbest bırakmamızı gerektirir. Örneğin, dosyalarla işimiz bittikten sonra, onları Close
fonksiyonu ile kapatmalıyız. Bu tür kodlar her zaman tehlikelidir. Birincisi, bir fonksiyon yazarken 10 satır önce tanımladığımız şeyi Close
ile kapatmayı unutmak kolaydır; ikincisi ise bir fonksiyonun birden fazla return
ifadesi bulundurabilecek olmasıdır. Go’nun bu duruma çözümü defer
anahtar kelimesidir.
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.Open("a_file_to_read")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
// dosya okuma kodları buraya yazılır
}
Eğer üstteki kodu çalıştırmayı denerseniz, muhtemelen hata alırsınız (dosya mevcut değil). Buradaki amaç defer
ifadesinin nasıl çalıştığını göstermektir. Burada defer
ile belirttiğiniz şey her ne olursa olsun, fonksiyonun bitişinde çalıştırılır. Böylece, serbest bırakılması gereken kaynağı tanımladığımız yere yakın bir yerde belirtmiş ve birden fazla return
ifadesine rağmen bu işlemin gözden kaçmasını engellemiş olursunuz.
Go’da yazılan birçok program aynı formatlama kurallarını takip eder, yani girinti olarak bir tab karakteri kullanır ve parantezler ifadeleriyle birlikte aynı satırda bulunurlar.
Biliyorum, sizin de alıştığınız kendi stilleriniz var ve buna bağlı kalmak istiyorsunuz, ancak sizi bu kurallara uymaya büyük oranda alıştıracak go fmt
komutu var. Kullanımı kolay ve tutarlıdır.
Bir proje içerisindeyken, hem geçerli projeye hem de alt projelerin tümüne kuralları bu komutla uygulayabilirsiniz:
go fmt ./...
Kodunuzun girintisini ayarlamanın yanısıra, ifadeleri hizalar ve içe aktarılan (import) ifadelerini alfabetik olarak sıralar.
Go, koşul çalıştırılmadan hemen önce, atama ifadesi yazmanızı sağlayan, biraz modifiye edilmiş bir if
yapısını destekler:
if x := 10; count > x {
...
}
Bu çok saçma bir örnek. Daha gerçekçi bir örnek görmek isteyebilirsiniz:
if err := process(); err != nil {
return err
}
Burada tanımlanan değerler, sadece if
yapısının içerisinden ve ona bağlı else if
veya else
ifadeleri içerisinden erişilebilir.
Çoğu nesne yönelimli dillerde, object
olarak isimlendirilen baz sınıflar, tüm sınıfların üst sınıfıdır. Go’da kalıtım yoktur, dolayısıyla böyle bir üst sınıf da yoktur. Go’nun bu göreve üstlenen elemanı, herhangi bir metoda sahip olmayan interface{}
yapısıdır. Arayüzler dolaylı olarak implemente edildiği için tüm tipler boş bir arayüzün sözleşmesine uyabilir.
Örneğin, add
isminde aşağıdaki gibi bir fonksiyon oluşturabiliriz:
func add(a interface{}, b interface{}) interface{} {
...
}
Bir arayüzü, belirli bir tipe dönüştürmek için .(TİP)
ifadesini kullanabiliriz:
return a.(int) + b.(int)
Unutmayın, bu örnekte, eğer arayüzün altındaki tip int
değilse hata oluşacaktır.
Ayrıca etkili bir switch
yapısını da böylelikle kullanabilirsiniz:
switch a.(type) {
case int:
fmt.Printf("a bir int ve değeri: %d\n", a)
case bool, string:
// ...
default:
// ...
}
Boş arayüzleri düşündüğünüzden fazla görecek ve kullanacaksınız. Hiç kuşkusuz ki bu tip kodlar çok temiz değildir. Tip dönüşümleri tehlikelidir ve çirkin kodlar meydana getirir. Ancak, statik tipli dillerde bazen tek seçeneğiniz budur.
Dizgiler(strings) ve baytlar yakından ilişkilidir. Birini diğerine kolayca dönüştürebilirsiniz:
stra := "the spice must flow"
byts := []byte(stra)
strb := string(byts)
Aslında, bu dönüştürme yöntemi çeşitli türlerde de yaygındır. Bazı fonksiyonlar açıkça bir int32
veya int64
ya da bunların imzasız (unsigned) şeklini bekler. Kendinizi aşağıdaki gibi bir dönüştürme yaparken bulabilirsiniz:
int64(count)
Tekrardan sıklıkla kullanacağınız bayt ve dizgi(string) dönüşümüne dönelim. Unutmayın ki []byte(X)
veya string(X)
dönüşümlerini kullandığınızda, verinin kopyasını oluşturuyorsunuz. Bu gereklidir, çünkü dizgiler sabittir(immutable).
Dizgiler runes
adındaki unicode kodlardan meydana gelir. Bir dizginin uzunluğunu alırsanız, muhtemelen beklediğiniz çıktıyı alamayacaksınız. Örneğin, aşağıdaki kodun çıktısı 3 olacaktır:
fmt.Println(len("椒"))
Eğer range
ile bir dizginin üzerinde gezinirseniz, byte değil runes
tipini alırsınız. Elbette dizgiyi []byte
tipine dönüştürürseniz doğru veriyi alacaksınız.
Fonksiyonlar; değişken tipi, parametre ve geri dönüş tipi olarak her yerde kullanılabilecek birinci sınıf tiplerdir:
type Add func(a int, b int) int
Fonksiyonların aşağıdaki örnekteki gibi kullanılması, arayüzlerde elde ettiğimiz gibi kodun belirli uygulamalardan soyutlatmasına yardımcı olabilir:
package main
import (
"fmt"
)
type Add func(a int, b int) int
func main() {
fmt.Println(process(func(a int, b int) int{
return a + b
}))
}
func process(adder Add) int {
return adder(1, 2)
}
Kitap kaynağı: https://github.com/karlseguin/the-little-go-book