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 .
Bitcoinlerimizi barındırabildiğimiz bir Wallet structı yapalım.
İlk olarak test yaz
func TestWallet(t *testing.T) {
wallet := Wallet{}
wallet.Deposit(10)
got := wallet.Balance()
want := 10
if got != want {
t.Errorf("got %d want %d", got, want)
}
}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, balanceları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
Was this helpful?