in Go

Go Programlama Dilinde Özellik (Options) Tasarımı

Hala Go yazıyorum. Hatta yeni başladığımız kallavi bir projeyi de bu ilkel dile ile yazmaya başladık. Dil basit olduğundan çok bir tasarım yapmaya gerek kalmıyor. Örneğin Go ile bir iş yapacaksam, direkt gerilla tarzı, bam bam bam yazıp geçiyorum. Çok da eğlenceli oluyor.

Yalnız bazı zamanlarda tasarım üzerine düşünmeniz gerekiyor. Özellikle başka  developer’a vereceğiniz bir API’ı iyi tasarlamanız gerekiyor. İşte programlama zanaatında kerrakke burada anlaşılıyor.

Konumuz aslında bir paketin çalışması için gerekli olan parametrelerin dışarından nasıl alınacağı ile alakalı. Burada topluluk tarafından kabul görmüş idiomatik bir kaç yolu göstermeye çalışacağım.

Config Options Modeli

Bu örnek için gin-gonic web framework’ünün CORS middleware‘ina bir bakalım.

func main() {
	router := gin.Default()

	router.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"https://foo.com"},
		AllowMethods:     []string{"PUT", "PATCH"},
		AllowHeaders:     []string{"Origin"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		AllowOriginFunc: func(origin string) bool {
			return origin == "https://github.com"
		},
		MaxAge: 12 * time.Hour,
	}))
	router.Run()
}

Bu yaklaşımda cors paketinin New fonksiyonu yine cors paketinin içindeki Config değerleri olan bir tip (struct) parametresi alıyor. Bu tipi içinde ise paketin çalışması için gerekli olan parametreler saklı.

Güzel genişleyebilen bir sistem sunuyor. Yani, yeni bir parametre eklemek isterseniz cors.Config tipini güncellemeniz projenin her yerine ulaşmasını sağlar. İsterseniz Config tipine bir closure (go’da fonksiyon değişkeni) değer tanımlayıp da daha esneklik sağlanabiliyor.

Fakat bazı durumlarda baş ağrıtabilir. Özellikle bir parametre atlanırsa varsayılan değer saçma olabilir. Örneğin yukarıdaki örnekde MaxAge’e değer girilmez ise sıfır olacaktır. Buda MaxAge değerini kullanan iş akışının çalışmamasına neden olur.

Diğer bir sıkıntı da developer davranışında yaşanabiliyor. Config modelini oluşturup pakete parametre olarak geçmek zorunlu çünkü bu model olmadan paket çalışmıyor. Ben şahsen yeni paket kullanmaya başladığımda herhangi bir ayar yapma eğiliminde olmuyorum, burada config parametrelerini bilmek gerekiyor.

Nispeten daha iyi bir yalaşım aşağıdaki gibi.

Fonksiyon Modeli

Bu yaklaşım benim favorim. Kallavi ismi ile Self-referential Functions deniyor en azından Go tasarımcılarından Rob Pike öyle demiş.

Daha sistematik ve biraz daha kişiselleştirilebilir konfigurasyon değerlerini veya iş akışlarını pakete uygulatabiliyorsunuz.

Yazıya uygun bir örnek bulamadığımdan kendi kafama göre bir senaryo yazıp Functional Options olarak initialize edilebilen bir paket hazırladım. Değişken isimlerini Türkçe yaptığımdan biraz garip görünebilir ama maksat anlaşılması. Aşağıdaki örneğe bir bakalım.

Burada güzelliklerden bir tanesi Yeni() fonksiyonunun variadic function olması. Böylece herhangi bir parametre almak gibi bir zorunluluğu bulunmuyor. Bir parametre geçilmese bile içeride default değerler bilinçli bir şekilde tanımlanıp paketin daha stabil çalışmasını destekliyor.

Bu yapıda parametre geçmek daha kolay. Örneğin sadece port değerini değitirmek isterseniz bir önceki Config örneğindeki gibi tüm struct’ı bilmenize gerek kalmıyor. Diğer yandan dokümantasyon için de function parametreleri ayrı bir avantaj sağlıyor. Dokümantasyon önemli.

Buradaki yaklaşıma değinmişken Dave’in yazısınada bir bakın

Fluent Config Modeli

Bu yaklaşım da fena değil fakat ilk aşamada çok developer dostu gelmediğini söyleyebilirim. Yalnız paketin yapısını öğrendikten sonra gerçekten akıcı olabiliyor. Aslında bildiğimiz Fluent Interface tasarımından ibaret. Örnek aşağıdaki gibi:

Şimdi buradaki yaklaşıma baktığımızda ilk dikkat çeken Loose Coupling‘den yırtmış olduğumuz. Yani paketi başlatırken sizin uygulamanıza karışan bir tip bağımlılığı olmadan direkt kullanabiliyorsunuz. Bu da uygulamanız ile paket nesneleri arasındaki ilişkinin mümkün olduğu kadar az olmasın sağlar. İyi bir şey bu.

Arka arkaya olması gereken builder tipindeki işlerde işe görüyor. Örneğim gorm bunu çok kullanıyor.

Map Config

Loose Coupling demişken aklıma geldi. Bir ara paket’e parametre geçerken map tipini (built-in tip ya hani) kullanyordum. Kendimce şöyle bir model geliştirmiştim.

Nereden bakarsan bak berbat bir tasarım :) initialize yok, type conversions gerekiyor ve map’in anahtar isimlerini bilmek gerekiyor. Bu son örneği unutun gitsin veya Anti-Pattern olarak aklınızda olsun. Fuckup nights’da sunmak lazım aslında :)

Sonuç

Developer Friendly bir API oluşturabilmek için biraz tecrübe ve biraz da empati yapmak gerekiyor. Diğer yandan dokümantasyonun eksiksiz sunulması önemli. Genelde tasarım kararları alınırken de tahmin edilebilirlik ve hatta hack edilebilirlik (Hackable API) kavramları akılda tutmak çok daha iyi tasarımlar oluşturmanızı sağlayan yol göstericiler olabilir.

Genelde diğer SDK’ları da okumak, incelemek çok fayda sağlıyor. Aşağıya bir iki bağlantı bıraktım.

Yorum Bırak

Comment