4 Go Dil Eleştirileri

Yayınlanan: 2022-03-11

Go (aka Golang) insanların en çok ilgi duyduğu dillerden biridir. Nisan 2018 itibariyle TIOBE endeksinde 19. sırada yer almaktadır. Gittikçe daha fazla insan PHP, Node.js ve diğer dillerden Go'ya geçiyor ve onu üretimde kullanıyor. Go kullanılarak birçok harika yazılım (Kubernetes, Docker ve Heroku CLI gibi) yazılır.

Peki Go'nun başarının anahtarı nedir? Dilin içinde onu gerçekten harika yapan pek çok şey var. Ancak Go'yu bu kadar popüler yapan şeylerden biri, yaratıcılarından biri olan Rob Pike'ın da belirttiği gibi basitliğidir.

Sadelik harika: Çok fazla anahtar kelime öğrenmenize gerek yok. Dil öğrenmeyi çok kolay ve hızlı hale getirir. Bununla birlikte, bazen geliştiriciler, diğer dillerde sahip oldukları bazı özelliklerden yoksundur ve bu nedenle, uzun vadede geçici çözümler kodlamaları veya daha fazla kod yazmaları gerekir. Ne yazık ki, Go tasarımı gereği pek çok özellikten yoksundur ve bazen gerçekten can sıkıcıdır.

Golang, geliştirmeyi daha hızlı hale getirmeyi amaçlıyordu, ancak birçok durumda, diğer programlama dillerini kullanarak yazacağınızdan daha fazla kod yazıyorsunuz. Aşağıdaki Go dili eleştirilerimde bu tür bazı durumları anlatacağım.

4 Go Dil Eleştirisi

1. Bağımsız Değişkenler için İşlev Aşırı Yüklemesi ve Varsayılan Değerlerin Eksikliği

Burada gerçek bir kod örneği yayınlayacağım. Golang'ın Selenium bağlaması üzerinde çalışırken, üç parametreli bir fonksiyon yazmam gerekiyordu. Bunlardan ikisi isteğe bağlıydı. İşte uygulamadan sonra nasıl göründüğü:

 func (wd *remoteWD) WaitWithTimeoutAndInterval(condition Condition, timeout, interval time.Duration) error { // the actual implementation was here } func (wd *remoteWD) WaitWithTimeout(condition Condition, timeout time.Duration) error { return wd.WaitWithTimeoutAndInterval(condition, timeout, DefaultWaitInterval) } func (wd *remoteWD) Wait(condition Condition) error { return wd.WaitWithTimeoutAndInterval(condition, DefaultWaitTimeout, DefaultWaitInterval) }

Üç farklı işlevi uygulamak zorunda kaldım çünkü işlevi aşırı yükleyemedim veya varsayılan değerleri iletemedim - Go, tasarım gereği sağlamıyor. Yanlışlıkla yanlış kişiyi ararsam ne olacağını hayal edin? İşte bir örnek:

Bir sürü 'tanımsız' alırdım

İtiraf etmeliyim ki bazen işlev aşırı yüklemesi dağınık kodlara neden olabilir. Öte yandan, bu nedenle programcıların daha fazla kod yazması gerekiyor.

Nasıl geliştirilebilir?

JavaScript'teki aynı (neredeyse aynı) örnek:

 function Wait (condition, timeout = DefaultWaitTimeout, interval = DefaultWaitInterval) { // actual implementation here }

Gördüğünüz gibi, çok daha net görünüyor.

Bu konuda Elixir yaklaşımını da seviyorum. İşte Elixir'de nasıl görüneceği (yukarıdaki örnekte olduğu gibi varsayılan değerleri kullanabileceğimi biliyorum - sadece yapılabileceği bir yol olarak gösteriyorum):

 defmodule Waiter do @default_interval 1 @default_timeout 10 def wait(condition, timeout, interval) do // implementation here end def wait(condition, timeout), do: wait(condition, timeout, @default_interval) def wait(condition), do: wait(condition, @default_timeout, @default_interval) end Waiter.wait("condition", 2, 20) Waiter.wait("condition", 2) Waiter.wait("condition")

2. Jenerik Eksikliği

Bu, muhtemelen Go kullanıcılarının en çok istediği özelliktir.

Tamsayı dizisini ve tüm öğelerine uygulanacak işlevi ilettiğiniz bir harita işlevi yazmak istediğinizi hayal edin. Kulağa kolay geliyor, değil mi?

Tam sayılar için yapalım:

 package main import "fmt" func mapArray(arr []int, callback func (int) (int)) []int { newArray := make([]int, len(arr)) for index, value := range arr { newArray[index] = callback(value) } return newArray; } func main() { square := func(x int) int { return x * x } fmt.Println(mapArray([]int{1,2,3,4,5}, square)) // prints [1 4 9 16 25] }

İyi görünüyor, değil mi?

Peki, bunu dizeler için de yapmanız gerektiğini hayal edin. İmza dışında tamamen aynı olan başka bir uygulama yazmanız gerekecek. Golang, işlev aşırı yüklemesini desteklemediğinden, bu işlev farklı bir ada ihtiyaç duyacaktır. Sonuç olarak, farklı adlara sahip bir sürü benzer işleve sahip olacaksınız ve şöyle görünecek:

 func mapArrayOfInts(arr []int, callback func (int) (int)) []int { // implementation } func mapArrayOfFloats(arr []float64, callback func (float64) (float64)) []float64 { // implementation } func mapArrayOfStrings(arr []string, callback func (string) (string)) []string { // implementation }

Bu kesinlikle mümkün olduğunca az kopyala/yapıştır kodu yazmanız ve bunun yerine onu işlevlere taşımanız ve yeniden kullanmanız gerektiğini belirten DRY (Kendinizi Tekrar Etme) ilkesine aykırıdır.

Jenerik eksikliği, yüzlerce değişken fonksiyon anlamına gelir

Diğer bir yaklaşım, parametre olarak interface{} ile tekli uygulamaları kullanmak olabilir, ancak bu, çalışma zamanı tür denetimi daha fazla hataya açık olduğundan çalışma zamanı hatasına neden olabilir. Ayrıca daha yavaş olacağından, bu işlevleri tek bir işlev olarak uygulamanın basit bir yolu yoktur.

Nasıl geliştirilebilir?

Jenerik desteği içeren birçok iyi dil vardır. Örneğin, Rust'taki kodun aynısı buradadır (daha basit hale getirmek için array yerine vec kullandım):

 fn map<T>(vec:Vec<T>, callback:fn(T) -> T) -> Vec<T> { let mut new_vec = vec![]; for value in vec { new_vec.push(callback(value)); } return new_vec; } fn square (val:i32) -> i32 { return val * val; } fn underscorify(val:String) -> String { return format!("_{}_", val); } fn main() { let int_vec = vec![1, 2, 3, 4, 5]; println!("{:?}", map::<i32>(int_vec, square)); // prints [1, 4, 9, 16, 25] let string_vec = vec![ "hello".to_string(), "this".to_string(), "is".to_string(), "a".to_string(), "vec".to_string() ]; println!("{:?}", map::<String>(string_vec, underscorify)); // prints ["_hello_", "_this_", "_is_", "_a_", "_vec_"] }

Tek bir map işlevi uygulaması olduğunu ve ihtiyacınız olan her tür için, hatta özel olanlar için kullanılabileceğini unutmayın.

3. Bağımlılık Yönetimi

Go'da deneyimi olan herkes, bağımlılık yönetiminin gerçekten zor olduğunu söyleyebilir. Go araçları, go get <library repo> çalıştırarak kullanıcıların farklı kitaplıklar yüklemesine olanak tanır. Buradaki sorun sürüm yönetimidir. Kütüphane sorumlusu bazı geriye dönük uyumsuz değişiklikler yapar ve bunu GitHub'a yüklerse, bundan sonra programınızı kullanmaya çalışan herkes bir hata alır, çünkü go get deponuzu bir kütüphane klasörüne git clone başka bir şey yapmaz. Ayrıca kütüphane kurulu değilse, program bu nedenle derlenmeyecektir.

Bağımlılıkları yönetmek için Dep'i kullanarak biraz daha iyisini yapabilirsiniz (https://github.com/golang/dep), ancak buradaki sorun, tüm bağımlılıklarınızı deponuzda depolamanızdır (bu iyi değildir, çünkü deponuz yalnızca kodunuzu değil, binlerce ve binlerce satırlık bağımlılık kodunu da içerir) veya yalnızca paket listesini depolayın (ancak yine de, bağımlılığın koruyucusu geriye dönük uyumsuz bir değişiklik yaparsa, tümü çökecektir).

Nasıl geliştirilebilir?

Bence buradaki mükemmel örnek Node.js (ve genel olarak JavaScript, sanırım) ve NPM. NPM bir paket deposudur. Paketlerin farklı sürümlerini depolar, yani bir paketin belirli bir sürümüne ihtiyacınız varsa sorun değil—onu oradan alabilirsiniz. Ayrıca, herhangi bir Node.js/JavaScript uygulamasındaki şeylerden biri de package.json dosyasıdır. Burada, tüm bağımlılıklar ve sürümleri listelenmiştir, böylece hepsini yükleyebilirsiniz (ve kesinlikle kodunuzla çalışan sürümleri elde edebilirsiniz) npm install ile.

Ayrıca paket yönetiminin harika örnekleri RubyGems/Bundler (Ruby paketleri için) ve Crates.io/Cargo'dur (Rust kitaplıkları için).

4. Hata İşleme

Go'da hata işleme son derece basittir. Go'da temel olarak işlevlerden birden çok değer döndürebilirsiniz ve işlev bir hata döndürebilir. Bunun gibi bir şey:

 err, value := someFunction(); if err != nil { // handle it somehow }

Şimdi, hata döndüren üç eylemi gerçekleştiren bir işlev yazmanız gerektiğini hayal edin. Bunun gibi bir şey görünecek:

 func doSomething() (err, int) { err, value1 := someFunction(); if err != nil { return err, nil } err, value2 := someFunction2(value1); if err != nil { return err, nil } err, value3 := someFunction3(value2); if err != nil { return err, nil } return value3; }

Burada pek çok tekrarlanabilir kod var ve bu iyi değil. Ve büyük işlevlerle daha da kötü olabilir! Bunun için muhtemelen klavyenizde bir tuşa ihtiyacınız olacak:

klavyede hata işleme kodunun mizahi görüntüsü

Nasıl geliştirilebilir?

JavaScript'in bu konudaki yaklaşımını seviyorum. İşlev bir hata verebilir ve onu yakalayabilirsiniz. Örneği düşünün:

 function doStuff() { const value1 = someFunction(); const value2 = someFunction2(value1); const value3 = someFunction3(value2); return value3; } try { const value = doStuff(); // do something with it } catch (err) { // handle the error }

Çok daha açık ve hata işleme için tekrarlanabilir kod içermiyor.

Go'daki İyi Şeyler

Go'nun tasarımı gereği birçok kusuru olsa da, gerçekten harika özellikleri de var.

1. Goroutinler

Zaman uyumsuz programlama Go'da gerçekten basitleştirildi. Diğer dillerde çoklu iş parçacığı programlama genellikle zor olsa da, yeni bir iş parçacığı oluşturmak ve mevcut iş parçacığını engellememesi için içinde işlev çalıştırmak gerçekten basittir:

 func doSomeCalculations() { // do some CPU intensive/long running tasks } func main() { go doSomeCalculations(); // This will run in another thread; }

2. Go ile Birlikte Gelen Araçlar

Diğer programlama dillerinde farklı görevler için (test, statik kod biçimlendirme vb.) farklı kitaplıklar/araçlar yüklemeniz gerekse de, Go'da varsayılan olarak bulunan birçok harika araç vardır, örneğin:

  • gofmt - Statik kod analizi için bir araç. eslint veya jshint gibi ek bir bağımlılık yüklemeniz gereken JavaScript ile karşılaştırıldığında, burada varsayılan olarak bulunur. Go-stili kod yazmazsanız (bildirilen değişkenleri kullanmamak, kullanılmayan paketleri içe aktarmamak vb.) program derlenmeyecektir bile.
  • go test - Bir test çerçevesi. Yine JavaScript ile karşılaştırıldığında, test için ek bağımlılıklar yüklemeniz gerekir (Jest, Mocha, AVA, vb.). Burada, varsayılan olarak dahil edilmiştir. Ayrıca, kıyaslama, belgelerdeki kodu testlere dönüştürme vb. gibi varsayılan olarak pek çok harika şey yapmanıza olanak tanır.
  • godoc - Bir dokümantasyon aracı. Varsayılan araçlara dahil edilmesi güzel.
  • Derleyicinin kendisi. Diğer derlenmiş dillere kıyasla inanılmaz derecede hızlı!

3. Erteleme

Bence bu dildeki en güzel özelliklerden biri. Üç dosya açan bir fonksiyon yazmanız gerektiğini düşünün. Ve bir şey başarısız olursa, mevcut açık dosyaları kapatmanız gerekecektir. Bunun gibi bir sürü inşaat varsa, ortalık karışacak. Bu sözde kod örneğini düşünün:

 function openManyFiles() { let file1, file2, file3; try { file1 = open('path-to-file1'); } catch (err) { return; } try { file2 = open('path-to-file2'); } catch (err) { // we need to close first file, remember? close(file1); return; } try { file3 = open('path-to-file3'); } catch (err) { // and now we need to close both first and second file close(file1); close(file2); return; } // do some stuff with files // closing files after successfully processing them close(file1); close(file2); close(file3); return; }

Karmaşık görünüyor. İşte burada Go'nun defer devreye giriyor:

 package main import ( "fmt" ) func openFiles() { // Pretending we're opening files fmt.Printf("Opening file 1\n"); defer fmt.Printf("Closing file 1\n"); fmt.Printf("Opening file 2\n"); defer fmt.Printf("Closing file 2\n"); fmt.Printf("Opening file 3\n"); // Pretend we've got an error on file opening // In real products, an error will be returned here. return; } func main() { openFiles() /* Prints: Opening file 1 Opening file 2 Opening file 3 Closing file 2 Closing file 1 */ }

Görüldüğü gibi üçüncü dosyayı açarken hata alırsak defer deyimleri geri dönmeden önce ters sırada yürütüldüğü için diğer dosyalar otomatik olarak kapatılacaktır. Ayrıca, bir fonksiyonun farklı bölümleri yerine dosyanın aynı yerde açılıp kapanması güzel.

Çözüm

Go'daki tüm iyi ve kötü şeylerden bahsetmedim, sadece en iyi ve en kötü şeyleri düşündüklerimden.

Go, mevcut kullanımda gerçekten ilginç programlama dillerinden biridir ve gerçekten potansiyeli vardır. Bize gerçekten harika araçlar ve özellikler sağlıyor. Ancak, orada geliştirilebilecek çok şey var.

Go geliştiricileri olarak bizler bu değişiklikleri uygularsak, topluluğumuza çok fayda sağlayacaktır, çünkü Go ile programlamayı çok daha keyifli hale getirecektir.

Bu arada, Go ile testlerinizi geliştirmeye çalışıyorsanız, Toptaler Gabriel Aszalos'un yazdığı Go Uygulamanızı Test Etme: Doğru Yoldan Başlayın'ı deneyin.

İlgili: İyi Yapılandırılmış Mantık: Bir Golang OOP Eğitimi