- Go dilinde hata işlemenin ayrıntılı ve uzun yazılması uzun süredir kullanıcı şikayetlerinin üst sıralarında yer alıyor
- Çeşitli sözdizimsel iyileştirme önerileri (
check/handle, try, ? operatörü vb.) tartışıldı ve denendi, ancak toplulukta yeterli uzlaşı oluşmadığı için hepsi reddedildi
- Dil değişikliklerinin kod, araçlar, belgeler vb. üzerinde geniş kapsamlı etkisi ve Go’nun kendine özgü sadelik ilkesini koruma yaklaşımı başlıca değerlendirme noktaları oldu
- Mevcut yaklaşımın açıklığı, hata ayıklama kolaylığı ve bazı kullanıcıların bunu tercih etmesi nedeniyle, sözdizimsel bir değişiklik getirmek için güçlü bir gerekçe bulunmuyor
- Öngörülebilir gelecekte hata işleme sözdiziminde bir değişiklik planlanmıyor ve ilgili önerilerin tamamı ek araştırma olmadan kapatılacak
Go’da hata işleme ayrıntılılığı sorununun gündeme gelişi
- Go ile ilgili eski şikayetlerden biri, hata işleme sözdiziminin gereğinden fazla uzun ve ayrıntılı olması
- Özellikle
if err != nil gibi kalıplar kod içinde tekrar tekrar görülüyor
- Birden fazla API çağrısı gerektiren programlarda bu durum daha belirgin hale geliyor ve kimi zaman gerçek mantıktan çok hata işleme kodu yazılıyor
- Yıllık kullanıcı anketlerinde bu şikayet sürekli üst sıralarda yer alıyor
Toplulukla istişare ve ilk öneriler
- Go ekibi topluluk geri bildirimine önem verdiği için hata işleme iyileştirmelerini uzun süredir araştırıyor
- 2018’deki Go 2 proje tartışmalarında Russ Cox, hata işleme sorununun özünü resmî olarak ortaya koydu
- Marcel van Lohuizen tarafından önerilen
check ve handle mekanizması gündeme geldi
- Benzer dillerle karşılaştırmalar ve çeşitli alternatiflerin değerlendirilmesi de buna dahildi
- Bu yaklaşım kodu gerçekten daha kısa hale getiriyordu, ancak artan karmaşıklık nedeniyle benimsenmedi
try önerisi ve sonrası
- 2019’da çok daha sade bir
try yerleşik işlevi önerildi
check işlevini kodla sağlıyor, handle kısmını dışarıda bırakıyordu
- Bu öneri kontrol akışını gizlediği gerekçesiyle eleştirildi ve topluluktan gelen tepkiyle geri çekildi
- Bu deneyim, yeterli geri bildirim alınmadan hazırlanmış olgun önerilerin riskli olduğunu gösterdi
- Büyük ölçekli değişiklik önerilerinde tasarımın erken aşamalarında daha geniş görüş toplamanın önemli olduğu anlaşıldı
Ek girişimler ve çeşitli öneriler
- Toplulukta çok sayıda varyasyon ve alternatif hata işleme yöntemi önerilmeye devam etti
- Ian Lance Taylor’ın umbrella issue’su ile durum özetlendi; Go Wiki ve bloglarda örnekler toplanmayı sürdürdü
- 2024’te Rust’tan alınan
? operatörünün uygulanması önerildi
- Küçük ölçekli kullanılabilirlik testlerinde sezgisel bulunduğuna dair geri bildirim alındı, ancak yine farklı görüşler nedeniyle uzlaşı sağlanamadı
Tartışmanın tıkanması ve sonuç
- Resmî ve gayriresmî olarak 3’ten fazla öneri, topluluk tarafında ise yüzlerce öneri olmasına rağmen yeterli ortak anlayış/uzlaşı oluşmadığı için hepsi reddedildi
- Go içindeki mimar grubu bile yön konusunda fikir birliğine sahip değil
- Koşullar değişene ya da güçlü bir ortak görüş oluşana kadar hata işleme sözdizimini değiştirme girişimlerinin durdurulmasına karar verildi
Mevcut yaklaşımın korunmasını savunan başlıca gerekçeler
- Dilin ilk tasarımında sözdizimsel şeker eklenmiş olsaydı bugün bu kadar tartışma olmayabilirdi, ancak şu anda 15 yıldır kullanılan bir yaklaşım etrafında oluşmuş bir ekosistem var
- Yeni bir sözdizimi getirmek kaçınılmaz olarak eski ve yeni kullanıcılar arasında kod stili farkı ve tutarlılığın bozulması riskini taşıyor
- Bu durum Go’nun tasarım felsefesiyle (aynı işi birden çok yolla yapmamak) ve sadelik/tutarlılık önceliğiyle de uyumlu
- Kısa değişken tanımında (
:=) yeniden bildirim izni de hata işleme nedeniyle ortaya çıkan ikincil bir değişimdi
- Açık hata işleme sözdizimi (
if üzerinden) kod okumada, hata ayıklamada ve breakpoint yerleştirmede sezgisel avantajlar sağlıyor
- Dil değişiklikleri, gerçek değişiklik kapsamı (kod, belgeler, araçlar vb.) ve maliyet açısından da büyük yük oluşturuyor
Alternatif iyileştirmeler ve gelecekteki yön
- Standart kütüphanenin yeteneklerini artırmak (örneğin
cmp.Or eklenmesi) bazı tekrar eden kodları azaltabilir
- IDE ve geliştirme araçlarındaki kod katlama, otomatik tamamlama, LLM kullanımı vb. sayesinde bu ayrıntılılık pratikte bir ölçüde aşılabiliyor
- Başlıca Go kullanıcı gruplarında (örneğin Google Cloud Next etkinliğine katılanlar) dil değişikliği gerekliliğine olumsuz bakan görüşler daha baskın
- Go kullanımı arttıkça ayrıntılılık sorunu pratikte daha az hissediliyor
Sözdizimsel iyileştirme gereğini destekleyen gerekçeler
- Kullanıcı geri bildirimlerine bakıldığında hâlâ hata işleme sözdiziminin iyileştirilmesi talebi var
- Yalnızca karakter sayısını azaltmakla kalmayıp açıklığı artıran bir hata işleme sözdizimi, kod kalitesi ve güvenliğine katkı sağlayabilir
- Basit hata kontrolünden ziyade gerçekten işlevsel hata işleme biçimleri üzerine daha ayrıntılı araştırma yapılması gerekiyor
Nihai sonuç ve bundan sonraki politika
- Şu ana kadar anlamlı bir uzlaşı veya somut bir değişiklik çıkmadığı kabul edilerek, öngörülebilir gelecekte hata işleme için sözdizimsel dil değişikliklerine ilişkin tüm tartışma ve önerilerin durdurulduğu ilan edildi
- Önceki tartışma ve araştırma süreci, dolaylı olarak Go ekosistemi ve süreçlerinin gelişimine katkı sağladı
- İleride daha net bir problem tanımı ve uzlaşı oluşursa tartışma yeniden başlayabilir
- Şimdilik odak noktası, yeni denemelerden çok Go’nun sağlamlığını ve sadeliğini korumak olacak
1 yorum
Hacker News yorumu
Go ekibinin kolayca başka alternatifler seçebileceğini öne sürmek istiyorsanız, lütfen Go2ErrorHandlingFeedback wiki ve GitHub issue araması bağlantılarına mutlaka bakın. Önerilen fikirlerin neredeyse tamamı zaten ciddi biçimde tartışıldı ve Go ekibinin şeffaf yaklaşımını takdir eden biri olarak her gün Go kullanmaktan büyük keyif alıyorum
Taslak tasarım belgesi C++, Rust ve Swift’ten bahsediyor ama benim aradığım Haskell/Scala/OCaml gibi fonksiyonel dillerdeki do-notation/for-comprehensions/monadic-let tarzı yaklaşımları bulmak zor. Go ekibi dil tasarımının ustaları gibi görünüyor ama iş hata işlemeye gelince, parametreli çok biçimliliği olmayan Java benzeri statik tip sınırlarına çarpıp çözüm üretemiyor gibiler. Bence bu, dilin temel tasarımından kaynaklanan bir sorun
Akıllı ve deneyimli insanların yazdığı belgeler olmasına rağmen, Haskell’in Maybe/Either monad’ı ve bind operatörü (do-notation) gibi çözümlerin hiçbir yerde anılmaması çok şaşırtıcı. Oysa bunlar ne zor ne de ukalaca şeyler; hataları güvenli biçimde iletmek için son derece zarif ve kendini kanıtlamış yöntemler. Go topluluğunun bunu neden benimsemediğini bilmiyorum. Bu sayfanın varlığına minnettarım ama bu kadar bilinen bir çözümün atlanmasını anlamak zor
Neredeyse her dil daha iyi çeşitli yaklaşımlar sunuyor; bu yüzden neden sadece Go’da bu sorun bu kadar öne çıkıyor diye merak ediyorum. Mesele sadece uzlaşma eksikliği mi, yoksa Go’ya özgü başka dillerdeki çözümleri uygunsuz kılan bir özellik mi var, bunu merak ediyorum
Go eleştirilerinde sık görülen bir şey, nispeten uzman olmayan kişilerin Go geliştiricilerinin dillerden kendilerinden daha az anladığını varsayma eğilimi. Oysa Go geliştiricileri çoğu durumda çok daha deneyimli ve çok daha bilgili. Uzman olmayanlar, daha çok özelliği olan dillerin otomatik olarak daha iyi olduğunu düşünüyor ama asıl önemli olanın genel dengeyi iyi kurmak olduğunu gözden kaçırıyor
Go’nun yeni dil özellikleri ekleme konusunda temkinli, muhafazakâr yaklaşımının kullanıcılara fayda sağladığını düşünüyorum. Swift tarafında özellik değişimi o kadar fazla ki öğrenmesi zorlaşıyor ve en yeni Mac’lerde bile bazen tek bir basit proje dahi derlenmiyor. Anahtar kelimeler sürekli artıp değiştiği için Swift’in sürekliliği zayıf; buna karşılık Go’nun gücü istikrarı
Bir keresinde bir Go fonksiyonunda, içteki fonksiyondan hata gelmesi beklenen istisnai bir durum vardı; içteki fonksiyon hata vermezse bu kez dış fonksiyonun hata döndürmesi gerekiyordu. Bu alışılmadık yapıda
if err == nilile dallanmak zorundaydım ama alışkanlıklaif err != nilyazdım ve sürekli kullandığım kalıba fazla alışmış olduğum için hatayı bulmam uzun sürdü. Sık kullanılanif err != nilile nadir kullanılanif err == nilarasındaki sözdizimsel fark dil düzeyinde desteklenseydi, bu tür hatalar azalabilirdi diye düşünüyorumif err == nilyazdığım her yere kalıbı vurgulamak için// invertedyorumu ekliyorum. Dilde bunun otomatik ele alınması güzel olurdu ama şimdilik bu şekilde ayrımı daha görünür kılabiliyorumif err == nil { return ... }kalıbı kodda daha da tuhaf görünebilir. Mevcut Go hata işleme tarzının açık ve okunması kolay olduğu için birçok kişinin bunu tercih ettiğini düşünüyorumif fruit != "Apple"gibi kalıplarda da ortaya çıkabildiğinden, bunun özünde sadece hata işlemeye değil, genel durum dallanmasına ilişkin bir mesele olarak görülmesi gerektiği söyleniyor. Sonuçta hata da diğer durum değerleri gibi ele alınıyorif err != nilifadesini özel bir sembol gibi render edip arka planda doğal biçimde kaybolacak şekilde daha az görünür kılmak, buna karşılık farklı yazılmışif err == nilifadesini öne çıkarmak suretiyle editör düzeyinde hataların önüne geçmek mümkün olabilirif err … {gibi bir kalıbı kısaltılmış göstererek okunabilirliği artırmak da bir öneri olabilirGo’nun açık hata işleme yaklaşımını seviyorum. Bir fonksiyonu ya her zaman başarılı olan (
minimal error) ya da başarısız olabilen bir yapı olarak basitçe anlıyorum. Başarısız olabilecek bir fonksiyonun, bir sonraki adıma geçmeden önce mutlaka ele alınması gerekir. Birçok dilde exception mekanizması yüzünden hata oluştuğunda catch edilene kadar çağrı yığınında fırlatılıyor; bu da çoğu zaman yalnızca hatanın nerede oluştuğunu söylüyor ama gerçek anlamda pek ipucu vermiyor. Go’da şu seçenekler açıkça mevcut: 1) hatayı yok saymak 2) hata oluşunca hemen dönmek 3) hatayı wrap ederek faydalı bilgi eklemek 4) belirli bir hatayı yorumlayıp ona göre dallanmak (ör. 404’e çevirmek). Go2’deResult<Value, Failure>tipi ya da daha somut ve numaralanabilir hata tipleri görmek isterdim. Go 1 ile geriye dönük uyumluluk için bunun Go 2’de gelmesi daha uygun olur diye düşünüyorumBaşta Go’nun hata işleme tarzını pek sevmiyordum ama errors-are-values blog yazısını okuyup
panic(err)kullanımını yerinde uygulamaya başladıktan sonra aksine bundan çok memnun kalmaya başladım. Üst kodun doğrudan ele almaması gereken anormal durumlarda panic kullanmak, kod içindeki dağınık hata dallarını büyük ölçüde azaltmamı sağladı. Bu hata yönetimi yaklaşımı iş hayatında gerçekten çok yardımcı oldu@operatörüyle çağrı noktasında hata bastırma mümkün; bash’ta da-egibi hata yönetim teknikleri var deniyortry/catch/finallyakışını ilk gördüğümde çok yenilikçi gelmişti ama artık Go’daki gibi daha basit mantığı tercih ediyorum. Kod satırı sayısının yüksek olması bile, akışın daha açık olması açısından avantaj olabilirGerçek hataları işleyince ayrıntı fazlalığının hemen arka plana düştüğü söyleniyor ama elle stack trace üretmenin gerçekten “işleme” sayılıp sayılmayacağı sorgulanıyor. Go’nun tanımına göre exception da bir işleme biçimi değil mi? diye esprili bir itiraz var
Bu yazının Go hata işlemedeki sorunu sadece “sözdizimi çok ayrıntılı” diye ele almasını sevmiyorum. Bence asıl sorunlar şunlar: 1) hataların sessizce atlanması ya da yanlışlıkla yok sayılmasının kolay olması 2) fonksiyon sonuçlarını değer gibi kolayca aktarma veya saklamanın mümkün olmaması 3)
errors.Isgibi iç içe hataların tip sistemiyle uyumsuz görünmesi 4) hataya göre switch yapmanın zor olması 5) standart kütüphanede sentinel value kullanımının yaygın olması 6) generics ile uyumsuzluk yüzünden paket ihtiyacı doğmasıElixir’de (ve Erlang’da) fonksiyonlar genellikle
{:ok, result}veya{:error, description}tuple’ı döndürür. Elixir’ninwithsözdizimi sayesinde hata işleme blok sonunda toplanabildiği için okunabilirlik çok daha iyi olur. Go’ya dawithbenzeri bir yapı gelirse, yalnızca hatanilolduğunda yürütmenin sürmesi ve en altta bir hata işleyici bloğu bulunması sayesinde kod daha okunur hale gelebilirRust tarzının neden doğrudan izlenmediğini anlamıyorum. Özellikle artık generics varken benzer bir şeyin hızla uygulanabilmesi gerekir. Rust’ın
?operatörü kullanışlı olsa da bunun hata görmezden gelmeyi teşvik ettiğini söyleyen eleştiriye katılmıyorum. Gerçekte Go’da hata dönüş değerlerini yok saymak çoğu zaman derleyici hatası bile üretmiyor. Rust tarzında olduğu gibiResulttipi dönüşünü zorunlu kılmak ancak hataları önleyebilir. Eğer mesele kolaylık adına tartışmalı olmaktaysa, o zamanpanicde yasaklanmalı değil mi diyen sert bir görüş varResultgetirememesinin nedeni olarak, sum type olmaması ve her tip için zero value gerektiren tuhaf tasarımı gösteriliyor?operatörü gibi kolaylık sağlayan bir özelliğin “artık wrap edilmiş hata kullanmayacağız” anlamına geldiği iddiasına karşı, tam tersine böyle bir özelliğin wrapping’i teşvik edecek şekilde de tasarlanabileceği söyleniyorRust’taki gibi kutucuk işaretlercesine özellik benimsemeyi tartışmak yerine, bir dilin bütünsel tutarlılık içinde tasarlanması gerektiğini düşünüyorum. Bir özellik listesindeki maddeleri tek tek tamamlamak, o özelliğin gerçekten dilin doğasına uyduğu anlamına gelmez