Pointerlar & errorlar
Bu bölümün bütün kodlarını burada bulabilirsiniz
Son bölümde, bir dizi değerleri tutmamızı sağlayan structları öğrendik.
Bazı nedenlerden dolayı, bir durumu yönetmek için structları kullanmak isteyebilirsiniz, sizin kontrol ettiğiniz, istediğiniz şekilde kullanıcıların bir durumu değiştirmesine izin veren metotları da açığa çıkarabilirsiniz.
Fintech, Go'yu seviyor ya bitcoinler? Öyleyse, ne kadar harika bir bankacılık sistemi yapabildiğimizi görelim .
Bitcoin
lerimizi barındırabildiğimiz bir Wallet
structı yapalım.
İlk olarak test yaz
Bir önceki örnekte, structın bileşenlerine, alanlarına doğrudan eriştik. Şimdiyse, çok güvenli olması gereken wallet
ımızı dünyaya açmak istemiyoruz ve bunu metotlar aracılığı ile yapacağız.
Testi çalıştırmayı dene
./wallet_test.go:7:12: undefined: Wallet
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
Derleyici(compiler), Wallet
'ı bilmiyor. Öyleyse tanımlayalım.
Cüzdanımızı tanıttık, tekrar testi çalıştırmayı deneyin.
Wallet için tanımlanmamış metotlar ve şimdiyse onları tanımlamamız gerekiyor
Sadece testleri çalıştırmaya yetecek kadar kod yazmayı unutmayın! Şimdilik sadece testimizin anlaşılır bir hata mesajı ile doğru şekilde başarısız olduğunu görmeliyiz.
Bu sözdizimi tanıdık gelmediyse, geri dönün ve struct bölümünü okuyun.
Yazılımımız derlenmeli ve testler çalışmalıdır.
wallet_test.go:15: got 0 want 10
Testi geçecek kadar kod yaz
balance durumunu saklamak için bir çeşit değişkene ihtiyacımız olacak.
Go'da bir bileşenin(variablelar, typelar, fonksiyonlar ve benzerleri) isimlendirmesi küçük harfle başlıyorsa, bu private acces modifier'a sahiptir ve o paketin dışından ulaşılamaz.
Biz bu değeri manipüle etmek istiyoruz fakat sadece paket içindeki metotlar yardımı ile yapmak istiyoruz yani paket dışında herhangi bir yerde bunun mümkün olmasını istemiyoruz.
Hatırlayın, internal balance
fieldına "receiver" ile verilen değişkenini kullanarak ulaşabiliriz.
Şimdi tekrardan test paketini çalıştırın ve güvence altında olan fintech sektöründeki kariyerinizin tadını geçen testleri izleyerek çıkarın
wallet_test.go:15: got 0 want 10
????
Ne oldu, kodumuzun çalışması gerekiyordu. Yeni bakiyemizi ekliyoruz ve Balance
metotu ile eklediğimiz yeni değeri döndürmesi gerekiyordu
Go'da bir fonksiyonu veya yöntemi çağırdınızda onun aldığı argümanlar kopyalanır
func (w Wallet) Deposit(amount int)
metotunu çağırdığımızda buradaki w
argümanı çağrıldığı yerdeki yapının bir kopyası olarak gelir.
Bir tanımı oluşturduğunuzda, bu bellekte bir yerlerde saklanır ve her tanımın bir adresi vardır. &
kullanarak &myVal
şeklinde bir çağrı ile bellekte saklandığı adrese ulaşabilirsiniz.
Kodunuzda bazı noktalara çıktı almak için print
ekleyerek bu durumu gözlemleyin
Bir tanımın başına &
karakterini koyarak onun bellekteki adresini, point ettiği adresi alabiliriz.
Testleri tekrar çalıştıralım
Görebildiğiniz üzere, balance
ların adresleri farklı. Deposit
metotunu kullanırken, test kodundan gelen yapının bir kopyasını kullanıyoruz. Bu durumdan dolayı, testteki balance
değişmiyor.
Bu problemi pointerları kullanarak aşabiliriz. Pointerlar, bazı değerlerin adreslerini almamıza ve onları değiştirmemize izin verir. Dolayısıyla, cüzdanının bir kopyasını almak yerine içindeki orjinal değerleri değiştirebilmemiz için metotlarda argüman olarak pointer almalıyız.
*Wallet
ve Wallet
arasındaki fark, *Wallet
'ın çağrıldığı yerdeki cüzdana point eden bir argüman olacağını belirtiyoruz Wallet
ise çağrıldığı yerden gelen bir kopya.
Testleri tekrardan çalıştırın, geçmiş olmaları gerekir.
Testlerin neden geçtiğini merak ediyor olabilirsiniz. Metotdaki pointerin refaransını şu şekile getirmedik:
ve doğrudan nesneye adres etti. Doğrusu yukarıdaki (*w)
şeklinde olan kullanım kesinlikle geçerlidir. Yine de, Go'nun yapımcıları bu sözdizimini gereksiz buldular, bu sebeple açık bir referanslama olmadan w.balance
şeklinde kullanıma izin verdiler. Structlara yönelik bu point etme şeklinin kendine has ismi bile var: struct pointers. - automatically dereferenced
Technically you do not need to change Balance
to use a pointer receiver as taking a copy of the balance is fine. However, by convention you should keep your method receiver types the same for consistency.
Refactor
Bitcoin için bir cüzdan yapacağımızı söyledik fakat bitcoin'e dair herhangi bir şeyden bahsetmedik. Şimdilik sadece int
tipini kullandık, çünkü int
birşeyleri saymak için iyidir.
Bunun için sıfırdan bir struct
oluşturmak biraz abartılı olabilir, int
yeterlidir, fakat bu durumu açıklamak için, yeterli bir veri tipi değildir.
Go mevcut olan bir veri tipinden başka bir veri tipi oluşturmanıza olanak sağlar,
Şu sözdizimi ile varolan bir veri tipinden, başka bir veri tipi türetebilirsiniz: type MyName OriginalType
Bitcoin
oluşturmak için Bitcoin(999)
sözdizimini kullanabilirsin.
Bunu yaparak, yeni bir tür oluşturuyoruz ve bu tipe özel methodlar tanımlayabiliriz. Tipe özel methodlar oluşturmamız, alanlara özgü bazı işlevler eklemek istediğimiz zaman çok yararlı olabilir.
Bitcoin için Stringer'ı implemente edelim.
Stringer interfaceı, fmt
paketinde tanımlanmıştır ve string olarak çıktı alırken %s
gibi bir formatlayıcı ile nasıl yazdıralacağını tanımlamamıza olanak sağlar.
Gördüğünüz gibi, tipler için metot oluşturma sözdizimi, structlarda olduğu gibidir.
Şimdiyse, testleri güncelleyerek bu oluşturduğumuz formatlı olarak string dönüşü yapan String()
metotunu kullanmasını sağlayacağız.
Testlerin çalışmasını kasıtlı olarak bozarak, yeni çıktıyı görelim
wallet_test.go:18: got 10 BTC want 20 BTC
Bu testlerimizde ve değiştirdiğimiz kodlar ile neler olduğunu daha anlaşılır olmasını sağlayacak.
Sonraki yapacağımız geliştirme, Withdraw
için olacak.
İlk olarak test yaz
Withdraw
tam olarak Deposit
'ın tersi olacak
Testi çalıştırmayı dene
./wallet_test.go:26:9: wallet.Withdraw undefined (type Wallet has no field or method Withdraw)
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
wallet_test.go:33: got 20 BTC want 10 BTC
Testi geçecek kadar kod yaz
Refactor
Test kodlarımızda, tekrar eden kodlar var. Testlerimizi refactor edelim
Hesapta kalan paradan daha fazlasını Withdraw
etmeye çalışırsanız ne olacak? Şimdiki ihtiyacımız, kredili bir mevduat hesabının olmadığını varsaymak olacak
Withdraw
metotunu kullanırken, bir hata oluşursa bunu çağrıldığı yere nasıl bildirebiliriz?
Go'da, bir hata oluştuğunu belirtmek istiyorsanız "idiomatic" bir seçeneğiniz var. Sizin yazdığınız fonksiyonunu çağırıldığı yere bir error
döndürmesini ve onu kontrol ederek harekete geçmesini sağlayabilirsiniz.
Bunu bir test ile yapmayı deneyelim,
İlk önce testi yaz
Hesap sahibinin, sahip olduğundan daha fazla parayı çekmeye çalıştığında bunu yapamamasını ve bir hata dönmesini istiyoruz.
We then check an error has returned by failing the test if it is nil
.
nil
, diğer programlama dillerinde olan null
ile eşanlamlıdır. Withdraw
'ın dönüşü nil
olabilir çünkü return type olarak error
kullanılacak ve bu bir interfacedır. Interface dönen veya parametre olarak alan fonksiyonlar görürseniz, bilinki bunların aldığı veya döndüğü değerler nil
olabilir.
null
'da olduğu gibi, nil
bir değere ulaşmaya çalışırsanız runtime panic hatası fırlatılacaktır. Bunun için, nil
olabilecek değerlere ulaşmaya çalışmadan önce nil
olup, olmadığını kontrol etmelisiniz.
Dene ve testi çalıştır
./wallet_test.go:31:25: wallet.Withdraw(Bitcoin(100)) used as value
Hata ifadesi biraz anlaşılmaz fakat amacımız sadece Withdraw
'ı çağırmaktı, şuan herhangi bir değer döndürmüyor. Değer döndürmesini sağlamak için metotu dönüş tipine sahip olacak şekilde değiştirmeliyiz.
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
Tekrardan compile etmesini sağlayacak kadar kod yazmalıyız, Withdraw
metotunu bir error
döndüreceği şekile getirdik. Şimdilik sadece nil
dönmesini sağlıyoruz.
Testi geçecek kadar kod yaz
errors
paketini kodunuza import etmeyi unutmayın.
errors.New
, bizim oluşturduğumuz bir mesaj ile error
oluşturuyor.
Refactor
Testin okunabilirliğini arttırmak için, önceden yaptığımız gibi assert ile bir helper oluşturalım.
Umarım, "oh no" hata mesajını tekrardan düzenleyeceğimizi düşünüyorsunuzdur çünkü bu hata mesajını dönmenin pek bir faydası olmaz, boş bir hata mesajı dönmekten pek bir farkı yok.
Hatanın kullanıcıya döndüğünü varsayarak, testlerimizi bir hatanın varlığından ziyade hatanın mesajı üzerinden iddia oluşturacak şekilde güncelleyelim.
İlk önce testi yaz
string
ile karşılaştırma yapmak için test helperimizi güncelleyelim
Çağırma yöntemimizi değiştirelim,
t.Fatal
çağrıldığı zaman testlerimiz duracak, bunu yapmamızın sebebi hata almamız gereken yerde hata almadık fakat burada kesinlikle hata almamız gerekiyordu ve verdiğimiz hata mesajı ile neden durulduğunundan bahsettik. Bu olmazsa, gelen değer nil
olduğu için panic atacaktı ve yine duracaktı fakat panic attığı zaman hatanın ne olduğunu anlamak için kafa patlatmamız gerekirdi. nil
yüzünden panic almamak için önceden mudahale edip didn't get an error but wanted one
cümlesi ile sorunun ne olduğunu bildirdik.
Testi çalıştırmayı dene
wallet_test.go:61: got err 'oh no' want 'cannot withdraw, insufficient funds'
Testi geçecek kadar kod yaz
Refactor
Withdraw
ve bunun için yazılmış test kodlarından hata mesajlarının kopyası var.
Birisi hata mesajını tekrardan düzenlemek isterse, testler üzerinde de gidip bunu değiştirmesi çok can sıkıcı olabilir. Hata mesajının tam olarak neyi ifade ettiği umurumuzda değil, sadece belirli koşullarda para çekme işlemi yapıldığında bir hata mesajı alacağız.
Go'da errorlar birer değerdir. Yani bunları bir değişkene atayıp tek bir kaynaktan edinmeyi sağlayabiliriz.
var
keywordu ile global olarak paket içinde bir değişken tanımlıyoruz, bildiğiniz üzere bu değişkenin adı büyük harfle başladığı için aslında paket dışından da ulaşılabilir.
Bu şekilde bir değişiklik ile Withdraw
metotumuz çok daha anlaşılır görünüyor.
Ardından, test kodlarımızda belirli string tanımlamaları yerine, yeni yaptığımız hata tanımlamasını kullanabiliriz ve bunun dışında helperlar ile ilgili bir refactorde uygulayalım.
Test kodlarını takip etmek ve anlamak çok daha kolay oldu.
Helperları ana test fonksiyonunun dışına çıkardık, böylece biri bu dosyayı açtığında yardımcıları görmek yerine testler için iddia ettiklerimizi görecek.
Test yazmanın faydaları arasında kodumuzun gerçek kullanımının nasıl olduğunu başka geliştiricilerinde anlamasına yardımcı olması vardır.
Kontrol edilmemiş hatalar
Go derleyicisi size çok fazla yardımcı olmasına rağmen gözden kaçırabileceğiniz şeyler olur ve işlenecek hataları bulmak, görmek bazen zor olabilir.
Test etmediğimiz bir senaryo daha var, bunu bulmak için Go'da mevcut birçok linterdan biri olan errcheck
'i kurabilirsiniz. errcheck
'ı edinmek için terminalde şunu çalıştırın:
go get -u github.com/kisielk/errcheck
errcheck
paketini edindikten sonra, kontrol etmek istediğiniz dizine gidin ve terminalde errcheck .
komutunu çalıştırın.
Şu şekilde bir şey göreceksin:
wallet_test.go:17:18: wallet.Withdraw(Bitcoin(10))
Gördüğünüz şeyin bize söylediği, wallet_test.go
dosyasında belirtilen satırdaki error checki yapmadığımız. Bu senaryo içinde err check'in yapılmasını sağlayabiliriz.
Test kodlarımızın en son hali şu şekilde olacak:
Özetlersek
Pointerlar
Go metotlara veya fonksiyonlara ilettiğiniz değerlerin bir kopyasını(
pass by value
) gönderir. Bu nedenle, bir değerin gerçek durumunun değiştirilmesi gerektiği durumalarda pointer kullanmalısınız.Go'nun değerlerin kopyasını göndermesi bir çok durumda yararlıdır fakat bazı durumlarda sisteminizin bir verinin kopyası üzerinde çalışmasını veya gerçek veriler dışında bir kopya oluşturması istemezsiniz, bu durumlarda o değeri referans edecek bir pointer kullanmalısınız. Örnek olarak, çok büyük veri yapıları ile çalışırken veya yalnızca bir verinin sadece bir örneği olması gerektiği durumlar mesela: veritabanı bağlantıları.
nil
Pointerlar nil olabilir
Bir fonksiyon bir şeye point eden bir değer döndürdüğü zaman, nil olup olmadığını kontrol ettiğinizden emin olmalısınız. Etmezseniz, runtime panic oluşturursunuz ve derleyici size burada yardımcı olamaz.
Unutulabilecek bir değeri tanımlamak istediğiniz zaman biçilmiş bir kaftandır.
Errorlar
Hatalar bir fonksiyonun veya metot çağırırken, çağrıldığı yerde olan kod bloklarında bir hatanın olduğunu belirtmek için kullanılır.
Test kodlarımızı gözlemleyerek, bir
string
üzerinden hata kontrolu yapmamız tuhaf bir şey olacağını varsaydık. Bu nedenle, hata döndürme implementasyonumuzda daha anlamlı bir şekilde hata dönmesini sağladık ve bu kodun daha da kolay test edilebilmesini sağladı. Ayrıca bu yaptığımız, bizim kodumuz bir API'ya olarak kullanacak başka geliştiriciler için de daha kolay anlaşılabilir olacağına karar verdik.Bu hata işleme ile ilgili hikayenin sadece başlangıcıydı. Daha karmaşık şeylerde yapabiliriz. Sonraki bölümlerde hata yakalama ile ilgili daha fazla strateji ele alınacak.
Mevcüt olan tiplerden, yenisini türetmek
Değerler için çalışılan alana özgü daha anlaşılır bir anlam yüklendirebileceğiniz durumlarda kullanışlıdır.
Arayüzleri implemente etmenize izin verir
Go dili ile yazılımlar üretirken hataları ve pointerları bolca kullanacaksınız, bu duruma dair bir önyargınızın, çekincenizin olmaması lazım. Go derleyicisi, size genellikle yardımcı olur, bir hata yaptığınızda acele etmeden hata mesajını okuyun, hata mesajları sizi çözüme götürecektir.
Bu sayfa @halilkocaoz tarafından çevrildi.
Last updated