Structlar, methodlar & interfaceler
Bu bölümün bütün kodlarını burada bulabilirsiniz
Varsayalım ki uzunluğu ve yüksekliği verilen bir dörtgenin çevresini hesaplayan geometri koduna ihtiyacımız var. Dönüş tipi float64
, 123.45 gibi, küsüratlı sayı (floating-point) olan Perimeter(width float64, height float64)
isminde fonksiyon yazarız.
TDD aşamalarına artık oldukça aşinasınızdır.
İlk olarak test yaz
Yeni string formatını farkettiniz mi? f
tanımı float64
içindir ve .2
virgülden sonra kaç basamağın yazılacağını belirtir.
Testi çalıştırmayı dene
./shapes_test.go:6:9: undefined: Perimeter
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
Sonuç shapes_test.go:10: got 0.00 want 40.00
.
Testi geçecek kadar kod yaz
Şimdiye kadar çok kolaydı. Şimdi Area(width, height float64)
isminde dörtgenin alanını dönen fonksiyonu yazalım.
TDD aşamalarını takip ederek kendiniz denemeye çalışın.
En sonunda testleriniz bunun gibi olacaktır.
Kodumuzda buna benzeyecektir
Refactor
Kodumuz işini yapıyor ancak dikdörtgenler hakkında açık bir şey içermiyor. Dikkatsiz bir geliştirici, yanlış cevap elde edeceğini fark etmeden bu fonksiyonlara bir üçgenin genişliğini ve yüksekliğini vermeye çalışabilir.
Fonksiyonlara RectangleArea
gibi daha spesifik isimler verebilir. Daha iyi bir çözüm ise bu konsepti bizim için kapsayan kendi tipimiz olan Rectangle
'ı tanımlamaktır.
Struct kullanarak basitçe kendi tipimizi oluşturabiliriz. Verilerimizi depoladığımız fieldların koleksiyonuna Struct denir.
Aşağıdaki gibi bir struct tanımlayın
float64
yerine Rectangle
kullanmak için testlerimizi değiştirelim.
Düzeltmeye çalışmadan önce testlerinizi çalıştırmayı unutmayın. Testler aşağıdakine benzer faydalı bir hata mesajı göstermeli.
Structın fieldlarına myStruct.field
sözdizimi ile erişebilirsiniz.
Testi düzeltmek için iki fonksiyonu değşitirin.
Bir fonksiyona Rectangle
vermenin amacımızı daha net ifade ettiğini kabul edeceğinizi umuyorum. Struct kullanmanın avantajlarını ileride ele alacağız.
Sıradaki koşulumuz ise çemberler için Area
fonksiyonu yazmak.
İlk testi yaz
Görebileceğiniz gibi f
iyi bir nedenle g
ile yer değiştirdi. g
kullanımı, hata mesajında daha kesin bir ondalık sayı yazdıracaktır (fmt seçenekleri). Örneğin, yarıçapı 1.5 olan bir çemberin alanı hesaplandığında, f
'in yazdıracağı değer 7.068583
iken g
'nin yazdıracağı değe 7.0685834705770345
olacaktır.
Testi çalıştırmayı dene
./shapes_test.go:28:13: undefined: Circle
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
Circle
tipini tanımlamamız gerekiyor.
Şimdi testi çalıştırmayı tekrar dene
./shapes_test.go:29:14: cannot use circle (type Circle) as type Rectangle in argument to Area
Bazı programlama dilleri bunun gibi bir şeyler yapmanıza izin verir:
Ancak Go'da yapamazsınız
./shapes.go:20:32: Area redeclared in this block
İki seçeneğimiz var:
Farklı paketlerde aynı fonksiyonlar tanımlamak.
Area(Circle)
yeni bir pakette oluşturabilir ancak bu fazla abartılmış olur.Bunun yerine yeni tanımladığımız tipler üzerine metotlar tanımlayabiliriz.
Metotlar nedir?
Şimdiye kadar sadece fonksiyonlar yazdık ancak metotları kullanıyorduk . t.Errorf
çağırdığımızda aslında t
(testing.T
)'nin instanceını Errorf
metodunu çağırıyorduk.
Metotlar, receiverı olan fonksiyonlardır. Metot tanımı, bir tanımlayıcıyı, metot adını bir metota bağlar ve metodu receiverın temel türüyle ilişkilendirir.
Metotlar fonksiyonlara çok benzerdir ancak onlar belirli bir tipin instanceından çağırılır. Area(rectangle)
gibi Fonksiyonları ise istedğiniz yerden çağırabilirsiniz ancak metotları sadece "instance" üzerinden çağırabilirsiniz.
Örnek yardımcı olacaktır, bu yüzden önce testlerimizi fonksiyon yerine metotları çağırmak için değiştirelim ve ardından kodu düzeltelim.
Eğer testleri çalıştırmayı denersek aşağıdakileri elde ederiz
type Circle has no field or method Area
Derleyicinin burada ne kadar harika olduğunu tekrarlamak istiyorum. Aldığınız hata mesajlarını yavaş yavaş okumak için zaman ayırmanız o kadar önemlidir ki uzun vadede size yardımcı olacaktır.
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
Tiplerimize biraz metot ekleyelim
Metot tanımlamanın sözdizimi neredeyse fonksiyonlar ile aynıdır bu yüzden oldukça benzerlerdir. Tek fark metotların recieverıdır func (receiverName ReceiverType) MethodName(args)
.
Metodunuz bu tip bir değişken üzerinde çağrıldığında, onun verilerine referansınızı receiverName
değişkeni aracılığıyla alırsınız. Diğer birçok programlama dilinde bu örtük olarak yapılır ve alıcıya this
aracılığıyla erişirsiniz.
Go'da receiver değişkeninin türün ilk harfi olması bir kuraldır.
Eğer testleri tekrardan çalıştırmayı denerseniz şimdi derlenecektirler ve hata çıktısı vereceklerdir.
Testi geçecek kadar kod yaz
Şimdi yeni metodumuzu düzelterek dikdörtgen testlerimizi geçelim
Eğer dörtgen testlerini tekrardan çalıştırırsanız testi geçeceklerdi ancak çember hala başarısız olacaktır.
To make circle's Area
function pass we will borrow the Pi
constant from the math
package (remember to import it).
Refactor
Testlerimizde bazı tekrarlar var.
Yapmak istediğimiz, şekillerin koleksiyonunu almak, her birinin Area()
metotlarını çağırmak ve sonuçlarını kontrol etmek.
Rectangle
ve Circle
parametre olarak gönderebileceğimiz checkArea
fonksiyonu yazmak istiyoruz ama bu şekillerin dışında bir şey gönderdiğimizde derlenmemesini istiyoruz.
Go'da bu işi interfacelar ile yapabiliyoruz
Interfaceler, Go gibi statik tipli dillerde çok güçlü konseptlerdir çünkü fonksiyonların farklı tipler ile kullanımasına izin verir ve tip güvenliğini korurken highly-decoupled kod oluşturur.
Testlerimizi düzenleyerek interfaceleri tanıtalım.
Diğer alıştırmalarda olduğu gibi yardımcı fonksiyon oluşturuyoruz ama bu sefer paramtere için Shape
istiyoruz. Shape
olmayan bir değer ile çağırdığımızda derlenmeyecektir.
Bir şeyler nasıl Shape
oluyor? Interface tanımı ile Go'ya neyin Shape
olduğunu söylüyoruz
Rectangle
ve Circle
'da olduğu gibi yeni bir type
oluşturuyoruz ama bu sefer struct
yerine interface
kullanarak.
Birkere bunu eklediğinizde, testler geçecektir.
Bekle, ne?
Burdaki interfacelar çoğu programlama dillerinden farklılar. Normalde My type Foo implements interface Bar
buna benzer bir kod yazmanız gerekir.
Ancak bizim durumumuzda
Rectangle
,Area
ismindefloat64
dönen bir metoda sahip,Shape
interfaceini karşılıyorCircle
,Area
ismindefloat64
dönen bir metoda sahip,Shape
interfaceini karşılıyorstring
böyle bir methoda sahip değil, bu yüzden interfacei karşılamıyorvb.
Go'da interface tanımlama belirtmeleri kararlıdır. Parametre olarak gönderdiğiniz değerler interface'ın istediği ise derlenir.
Decoupling
Yardımcımızın şeklin Rectangle
, Circle
veya Triangle
olup olmadığıyla ilgilenmesine gerek olmadığına dikkat edin.Bir interface tanımlayarak, yardımcı somut türlerden ayrışmış (decoupled) olur ve yalnızca işini yapması için ihtiyaç duyduğu metota sahip olur.
Yalnızca ihtiyacınız olanı tanımlamak için interfaceleri kullanma yaklaşımı, yazılım tasarımında çok önemlidir ve sonraki bölümlerde daha ayrıntılı olarak ele alınacaktır.
Daha fazla refactoring
Artık structları anladığınıza göre, "table driven testlere" girebiliriz.
Table driven testler, aynı şekilde test edilebilecek bir test senaryoları listesi oluşturmak istediğinizde kullanışlıdır.
Buradaki tek yeni sözdizimi "anonymous structtır", areaTests
. shape
ve want
fieldlarına sahip []struct
ile struct slice'ı tanımlıyoruz. Daha sonra sliceları durumlar ile dolduruyoruz.
Daha sonra, testlerimizi çalıştırmak için struct fieldları kullanarak, diğer slicelarda yaptığımız gibi bunları iterate ediyoruz.
Bir geliştiricinin yeni bir shape tanıtmasının, Area
uygulamasını ve ardından onu test senaryolarına eklemesinin ne kadar kolay olacağını görebilirsiniz. Ek olarak, Area
ile ilgili bir hata bulunursa, düzeltmeden önce alıştırma yapmak için yeni bir test senaryosu eklemek çok kolaydır.
Table driven testler alet çantanızda harika bir araç olabilir ancak testlerde ekstra gürültüye ihtiyacınız var. Bir interfacein çeşitli implemente etme yollarını test etmek istediğinizde veya bir fonksiyona iletilen verilerin test edilmesi gereken birçok farklı gereksinimi varsa, bunlar çok uygundur.
Tüm bunları, bir üçgene başka bir şekil ekleyerek ve test ederek gösterelim.
İlk olarak test yaz
Yeni şeklimiz için test eklemek çok kolay. Sadece listemize {Triangle{12, 6}, 36.0},
'ı ekleyin.
Testi çalıştırmayı dene
Testi çalıştırmayı denemeye devam etmeyi ve derleyicinin bir çözüme rehberlik etmesine izin vermeyi hatırla.
Testin çalışması için minimum kodu yaz ve başarısız test çıktılarını kontrol et
./shapes_test.go:25:4: undefined: Triangle
Henüz Triangle
'ı tanımlamadık
Tekrar dene
Bize Triangle
'ı shape olarak kullanamayacağımızı çünkü Area()
metoduna sahip olmadığını söylüyor, o zaman testin çalışması için boş bir uygulamasını ekleyelim.
Sonunda kod derleniyor ve hata kodumuzu alıyoruz
shapes_test.go:31: got 0.00 want 36.00
Testi geçecek kadar kod yaz
Ve testimiz geçiyor!
Refactor
Tekrar, uyarlamamız iyi ancak testlerimiz biraz iyileştirme yapabilir.
Bunu taradığınızda
Testlerinizin kolayca anlaşılmasını amaçlamalısınız, burada tüm sayıların neyi temsil ettiği hemen belli olmuyor.
Şimdiye kadar size yalnızca MyStruct{val1, val2}
structının örneklerini oluşturmak için sözdizimi gösterildi, ancak isteğe bağlı olarak fieldları adlandırabilirsiniz.
Şimdi nasıl gözüktüğüne bakalım
Test-Driven Development by Example kitabında Kent Beck, bazı testleri bir noktaya kadar yeniden düzenler ve şunları iddia eder:
Eğer test, bir dizi işlemin değil, doğruluk iddiasında bulunursa, bize daha anlaşılır olur.
(altındaki vurgu bana aittir)
Şimdi testlerimiz - daha doğrusu test senaryolarının listesi - şekiller ve alanları hakkında doğruluk iddiasında bulunuyor.
Test çıktılarınızın sorunu bulmakta yardımcı olduğundan emin olun
Triangle
implemente ettiğimizde testimiz başarısız olmuştu hatırladınız mı? Hata mesajı olarak shapes_test.go:31: got 0.00 want 36.00
yazdırmıştı.
Bunun Triangle
ile ilgili olduğunu biliyorduk çünkü sadece onunla çalışıyorduk. Peki ya tablodaki 20 vakadan birinde sistemde bir bug olursa? Hangi durumun hatalı olduğunu geliştirici nasıl bilecek? Geliştirici için harika bir deneyim değil, hangi durumun başarısız olduğunu bulmak için manuel olarak hepsine bakmak zorundalar.
Hata mesajımızın formatını değiştiriyoruz %#v got %g want %g
. %#v
formatı structın fieldlarındaki veriler ile birlikte yazdırır bu sayede geliştirici test edilen propertyleri bir bakışta görebilir.
Testlerimizin okunabilirliğini daha da arttırmak için want
fieldını daha açıklayıcı olan hasArea
olarak değiştirebiliriz.
Table driven testler ile ilgili son ipucu t.Run
kullanmak ve test senaryolarını adlandırmak.
Her senaryoyu t.Run
ile sarmalarsanız testleriniz başarısız olduğunda daha temiz bir çıktı elde edersiniz
go test -run TestArea/Rectangle
komutu ile tablonuz içerisinde specific testleri çalıştırabilirsiniz.
Bu durumu kapsayana final test kodumuz
Özetlersek
Bu daha gerçekci, iyi bir TDD pratiğiydi. Temel matematik problemlerine yönelik bildiklerimizi tekrarlamamıza ve testlerimiz ile öğrenme sürecini daha başarılı kıldığımız yeni dilin özelliklerini kullanmamıza yardımcı oldu.
Struct tanımlayarak ilgili verileri bir araya getirmenize ve kodunuzun amacını daha net hale getirmenize olanak tanıyan kendi veri türlerinizi oluşturma
Interface tanımlayarak, farklı türler tarafından kullanılabilecek fonksiyonları tanımlamak (parametric polymorphism)
Metotlar ekleyerek veri tiplerine fonksiyonalite ekleme ve interfaceleri implemente etme
Table driven testler ile assertionları daha anlaşılır hale getirme, testleri genişletme ve bakımını kolaylaştırma
Bu önemli bir bölümdü çünkü artık kendi türlerimizi tanımlamaya başlıyoruz. Go gibi statik olarak yazılan dillerde, kendi türlerinizi tasarlayabilmek, anlaşılması, birleştirilmesi ve test edilmesi kolay yazılımlar oluşturmak için çok önemlidir.
Interfaceler, sistemin karmaşıklığını gizlemek için harika bir araç. Bizim durumumuzda, test yardımcı kodumuz, iddia ettiği shapein ne olduğunu tam olarak bilmek zorunda değildi, sadece onun alanını nasıl "isteyeceğini" bilmek zorundaydı.
Go'ya daha da aşina oldukça, interfacelerin ve standart kütüphanenin(go std) gerçek gücünü görmeye başlayacaksınız. Her yerde kullanılan, standart kütüphanede tanımlı interfaceleri öğreneceksiniz ve bunların kendi türlerinize karşı uygulamalarını öğreneceksiniz, çok sayıda harika işlevi çok hızlı bir şekilde yeniden kullanabileceksiniz.
Bu sayfa @bariscanyilmaz tarafından çevrildi.
Last updated