Grug Brain Geliştiricisi (2022)
(grugbrain.dev)- Uzun yıllardır programlama yapan “küçük beyinli” bir geliştiricinin deneyimleri üzerinden, yazılım geliştirmedeki en büyük düşmanın karmaşıklık olduğu ve bunu azaltma yaklaşımının pratik çalışmanın merkezinde yer alması gerektiği vurgulanıyor
- Karmaşıklığı engellemenin temel silahı “hayır” diyebilmek; uzlaşma gerektiğinde ise 80/20 çözümü, prototip, küçük refaktörler ve geç soyutlama ile gerçekçi çözümler aranıyor
- Testler, kod bir ölçüde oturduktan sonra entegrasyon testleri merkeze alınarak; birim testleri ve az sayıdaki uçtan uca test destekleyici olarak kullanılarak yazılmalı, hatalar ise önce regresyon testiyle yeniden üretilip sonra düzeltilmeli
- Araçlar, tip sistemi, loglama, debugger ve basit API’ler geliştiricinin hafıza ve akıl yürütme yükünü azaltır; ancak aşırı generics, callback’ler, mikroservisler ve frontend framework’leri karmaşıklığı artırabilir
- Mevcut koda ve kurumsal süreçlere alçakgönüllülükle yaklaşılmalı, anlaşılmayan kod gelişigüzel silinmemeli ve “bu çok karmaşık” denebilen bir kültür gerekli
Karmaşıklık geliştiricinin ebedi düşmanıdır
- Yazılım geliştirmedeki en tehlikeli düşman karmaşıklıktır
- Karmaşıklık kod tabanına yavaş yavaş sızar ve bir yerde yapılan değişikliğin alakasız görünen başka yerleri de bozmasına neden olur
- Grug bunu görünmez bir “şeytan”a benzetir; geliştiricinin doğrudan göremediği ya da kolayca alt edemediği bir varlık olarak ele alır
- Karmaşıklık iyi niyetli geliştiriciler ya da proje yöneticileri aracılığıyla da içeri girebilir
- Özellikler, soyutlamalar ve süreçler arttıkça kodu anlamak zorlaşır
- Grug, bazen karmaşıklığı içeri alan kişinin kendisi olduğunu da kabul eder
“hayır” ve “tamam”ın kullanım biçimi
- Karmaşıklığı engelleyen en güçlü silah “hayır”dır
- Gereksiz özellikler yapılmaz
- Gereksiz soyutlamalar oluşturulmaz
- Ancak “hayır”, iyi bir mühendislik tavsiyesi olsa da kariyer açısından her zaman avantajlı değildir
- “evet” demek daha fazla ödül ya da yöneticilik pozisyonuna götürebilir
- Yine de geliştirici olarak kendine karşı dürüst olmak için “hayır” önemlidir
- Uzlaşma gerektiğinde “tamam” denir ve ardından 80/20 çözümü aranır
- 80/20 solution, kodun %20’siyle gereksinimlerin %80’ini karşılamayı hedefleyen bir yaklaşımdır
- Tüm süslü özellikleri içermese de değerin çoğunu sunabilir ve karmaşıklığı bastırabilir
- Bazen proje yöneticisine tüm uygulama ayrıntılarını anlatmadan sorunu basit bir yolla çözmek daha iyi olabilir
Kodun yapılandırılması ve refaktör
- Projenin başında uygulama çok erken bölünmemelidir
- Başlangıçta sistemin şekli henüz belirsizdir ve ne inşa edildiği de tam olarak anlaşılmış değildir
- Zaman geçtikçe iyi bir kesim noktası (cut point) ortaya çıkar
- İyi bir kesim noktasının sistemin geri kalanıyla arayüzü dardır
- Az sayıda fonksiyon ya da soyutlama ile iç karmaşıklığı gizler
- Grug bunu karmaşıklık şeytanını modülün içine hapsetmeye benzetir
- Büyük beyinli bir geliştirici proje başında çok sayıda soyutlama oluşturmaya çalıştığında, zararı azaltacak bir yaklaşım gerekir
- Bu çaba, UML diyagramları gibi doğrudan koda zarar vermeyen çıktılara yönlendirilebilir
- “Yarın çalışan bir demo” istemek, insanları hızla gerçekten çalışan kod ve gerçeklikle yüzleştirir
- Bu demo yaklaşımına prototip denebilir
- Refaktör, projenin sonlarına doğru kod oturduktan sonra faydalıdır
- Refaktör ne kadar büyükse başarısız olma ihtimali o kadar artar
- Sistem çalışır durumda tutulurken küçük adımlarla ilerlemek tercih edilir
- Uçtan uca testler refaktör sırasında can simidi olabilir, ancak bozulduklarında nedenini anlamak zor olabilir
- Aşırı soyutlama refaktörün ve sistemin başarısızlığına yol açabilir
- J2EE aşırı soyutlamaya örnek olarak verilir
- OSGi’nin benimsenmesi karmaşıklığı azaltma girişimiydi, ancak tersine daha güçlü bir karmaşıklık yarattı ve yıllar süren yeniden çalışma gerektirdi
Test stratejisi
- Testler çok zaman kazandırır, ancak “her zaman önce test” yaklaşımına şüpheyle bakılır
- Alanı henüz anlamamışken neyin test edilmesi gerektiğini bilmek zordur
- Grug, prototipten sonra kod oturmaya başladığında testlerin çoğunu yazmayı tercih eder
- Testleri daha sonra yazmak da disiplin gerektirir
- “Benim makinemde çalışıyor” diyerek test atlanmamalıdır
- Başka bir makinede ya da gelecekte aynı makinede çalışacağının garantisi yoktur
- Birim testleri, entegrasyon testleri ve uçtan uca testlerin rolleri ayrılır
- unit tests projenin başında yardımcı olabilir, ancak uygulama değişiklikleriyle kolayca bozulabilir ve refaktörü zorlaştırabilir
- end to end testler tüm sistem davranışını gösterir, ancak bozulduğunda nedeni bulmak zordur ve sık bozulursa görmezden gelinebilir
- integration tests, sistemin doğruluğunu kontrol edecek kadar yüksek seviyede ve debugger ile nedeni izlenebilecek kadar düşük seviyede olduğu için “tam kararında” noktaya yakındır
- İdeal test bileşimi şu şekildedir
- Başlangıçta bazı birim testleri
- Kesim noktaları ortaya çıkıp sistem kararlı hale geldikçe güçlü entegrasyon testleri
- Yalnızca en yaygın UI işlevlerini ve önemli edge case’leri kapsayan küçük ve iyi yönetilen uçtan uca testler
- Mocking nadiren kullanılır; gerektiğinde de daha çok sistem sınırı gibi büyük birimlerde tercih edilir
- Hata düzeltirken istisna olarak önce regresyon testi ile hata yeniden üretilir, sonra düzeltilir
Süreç, çevik yaklaşım ve mevcut koda bakış
- Agile en kötüsü değildir, ama yalnızca iyi olarak da görülemez
- Geliştirme organizasyonu yöntemi olarak bir ölçüde işe yarayabilir
- Her başarısızlıkta “Agile doğru uygulanmadı” diyen çevik uzmanlarına karşı dikkatli olunmalıdır
- Başarı için prototip, araçlar ve iyi geliştiricileri işe almak daha önemlidir
- Agile süreçler yardımcı olabilir, ancak fazla ciddiye alınırsa zararlı olabilir
- no silver club, yani tüm yazılım sorunlarını çözecek tek bir sihirli çözüm yoktur
- Chesterton’s Fence, mevcut kodun kaldırılmasına karşı bir uyarı olarak kullanılır
- Bir çitin ne işe yaradığını bilmiyorsan önce onu anlamalısın; hemen kaldırmamalısın
- Çirkin kod bile bugün çalışıyorsa saygıyı hak eder
- Özellikle büyük sistemlerde önce anlamak, sonra iyileştirmek gerekir
- Testler, belirli bir “çit”in neden var olduğuna dair ipucu verebilir
Araçlar ve tip sistemi
- Araçlar, geliştiricinin doğrudan hatırlaması ve akıl yürütmesi gereken yükü azaltır
- Yeni bir ortama girildiğinde, çevredeki araçları öğrenmeye zaman ayırmak üretkenliği artırabilir
- Dokümantasyon yoksa bunu anlamak için diğer geliştiricilere sormak gerekebilir
- IDE’deki kod tamamlama, tüm API’leri ezberleme zorunluluğunu ortadan kaldırır
- Java ile programlamanın kod tamamlama olmadan neredeyse imkânsız olduğu söylenir
- İyi bir debugger son derece önemlidir
- Koşullu breakpoint, expression evaluation ve stack gezintisi gibi özellikler derinlemesine öğrenilmelidir
- Yeni geliştiriciler için debugger öğrenmenin bilgisayar hakkında üniversite derslerinden daha fazlasını öğretebileceği düşünülür
- Tip sisteminin en büyük değeri, “nokta (.) tuşuna bastığında yapılabilecek şeylerin görünmesi”dir
- Tip doğruluğu da iyidir, ancak Grug için araç desteği ve kod tamamlama daha değerlidir
- Aşırı tip soyutlamaları ya da generics gerçek iş kodunu zorlaştırabilir
- Generics’i çoğunlukla container class’larla sınırlı kullanmak tercih edilir
İfadeler, DRY ve sorumlulukların ayrılması
- Kısa koddan çok debug edilmesi kolay kod tercih edilir
- Karmaşık koşul ifadelerini tek satıra yazmak yerine ara değişkenlere bölmek, her ifadenin sonucunu ve anlamını görmeyi kolaylaştırır
- Satır sayısı artsa da koşulları anlamak ve debug etmek kolaylaşır
- DRY iyi bir tavsiyedir, ancak denge gerekir
- Basit ve açık tekrarlar, karmaşık callback’lerden, closure’lardan ve nesne modellerinden daha iyi olabilir
- Tekrarı kaldırmaya çalışırken karmaşıklık artıyorsa bu zarardır
- Separation of Concern konusunda daha eleştirel bir tutum vardır
- Web geliştirmedeki tipik örnek CSS, HTML ve JavaScript’in ayrılmasıdır
- Grug alternatif olarak locality of behavior yaklaşımını tercih eder
- İlgili kodu, etkilediği şeye yakın tutarsan o şeye bakarken ne yaptığını hemen anlayabilirsin
- Closure’lar, koleksiyon işlemlerini soyutlamak gibi doğru kullanım alanlarında faydalıdır
- Ancak tuz, tip sistemi ve generics gibi, azı yeterli; fazlası zararlıdır
- JavaScript’teki “callback hell”, closure’ların aşırı kullanımına örnek olarak verilir
Loglama, eşzamanlılık ve optimizasyon
- Loglama, özellikle bulut dağıtım ortamlarında çok önemlidir
- Ana mantık dallarının her birine log bırakılır
- İstek birden fazla makineye yayılıyorsa, tüm loglara request ID eklenerek bunların ilişkilendirilmesi sağlanır
- Mümkünse log seviyesi dinamik olarak ayarlanır
- Mümkünse kullanıcı bazında log seviyesi de ayarlanır
- Java loglama kütüphaneleri karmaşık olabilir, ancak loglama altyapısına düzgün yatırım yapmak sonradan büyük karşılık verir
- Eşzamanlılık korkulması gereken bir şeydir
- Mümkün olduğunca stateless web request handler’ları ve bağımsız remote job queue’lar gibi basit modeller kullanılır
- optimistic concurrency web tarafında iyi çalışan bir yaklaşım olarak görülür
- thread local variable daha çok framework kodu yazarken ara sıra kullanılır
- Java’daki ConcurrentHashMap gibi eşzamanlı veri yapıları da yine dikkatle kullanılmalıdır
- Optimizasyona ancak gerçek performans profili elde edildiğinde başlanmalıdır
- “premature optimization is the root of all evil” sözüne katılır
- Gerçek darboğaz beklenenden farklı olabilir
- Yalnızca CPU’ya bakılmamalıdır; ağ çağrıları milyonlarca CPU döngüsüne denk gelebilir, bu yüzden mümkünse azaltılmalıdır
API, parsing ve visitor pattern
- İyi bir API, geliştiricinin fazla düşünmesini gerektirmez
- Kötü API’ler, uygulama iç yapısı ya da alan modeli merkezli tasarlandığında veya aşırı soyut olduğunda ortaya çıkar
- Basit durumlar için basit API, karmaşık durumlar için daha karmaşık API sunan katmanlı yaklaşım tercih edilir
- Nesne yönelimli API’lerde, işlevlerin hedef nesnenin üzerinde olması daha iyi görülür
- Java’da bir listeyi filtrelemek için önce Stream’e çevirip sonra tekrar List’e toplama akışı kötü örnek olarak verilir
- Sık yapılan bir işlem olan
filter()ın listede bulunması ve yine list döndürmesi gerektiği savunulur
- Parser’larda recursive descent eğlenceli ve zarif bir yöntem olarak görülür
- Parser generator’lar anlaşılması ve debug edilmesi zor olduğu için eleştirilir
- Gerçek üretim parser’larının neredeyse her zaman recursive descent olduğunu iddia eder
- Bob Nystrom’un Crafting Interpreters kitabı önerilir
- Çevrimiçi olarak ücretsiz okunabilir, ancak kitabın satın alınması da tavsiye edilir
- Ancak visitor pattern bir tuzak olarak tanımlanır
- Visitor pattern için değerlendirme kısaca “kötü”dür
Frontend, mikroservisler ve trendler
- Mikroservisler, “bir sistemi doğru bölmenin zaten en zor sorununa bir de ağ çağrıları eklemek” anlamına geldiği için risklidir
- Frontend geliştirmede karmaşıklık özellikle güçlüdür
- Basit form kaydetme ya da broşür sitesi için bile SPA kütüphaneleri, GraphQL JSON API’leri ve HTTP backend’lerin kullanılmasını eleştirir
- Frontend ile backend ayrıldığında karmaşıklığın iki ayrı barınağı oluştuğunu söyler
- Grug, karmaşıklığı azaltmak için htmx ve hyperscript oluşturduğunu belirtir
- Basit HTML’yi korumayı ve çok fazla JavaScript kullanmamayı tercih eder
- React’in iş bulma ve belirli tür uygulamalar için daha iyi olabileceğini de kabul eder
- Geliştirmede çok fazla trend vardır; özellikle de frontend tarafında
- Backend’in daha sıkıcı hale geldiğini ve birçok kötü fikrin zaten denenmiş olduğunu söyler
- Devrim niteliğindeki yeni yaklaşımlara bir tutam tuzla, yani temkinli yaklaşılması gerektiğini belirtir
- Birçok fikir zaten en az bir kez denenmiştir ve geri dönüştürülmüş kötü fikirler zamanı boşa harcayabilir
Korku ve impostor sendromu
- Kıdemli bir geliştiricinin açıkça “bu çok karmaşık” diyebilmesi olumludur
- Geliştiriciler, Fear Of Looking Dumb(FOLD) yüzünden anlamadıklarını söylemekte zorlanır
- Kıdemli biri bunu önce kabul ederse, junior geliştiriciler de karmaşıklığı ve eksik anlayışlarını dile getirebilir
- FOLD, özellikle genç geliştiricilerde karmaşıklığın güç kazanmasının başlıca kaynaklarından biridir
- Mizah duygusunun ve geçmiş başarısızlıkları hatırlamanın yardımcı olduğu düşünülür
- Impostor sendromu da yazılım geliştirmede yaygındır
- Grug, kendisini her şeyi kontrol ediyormuş gibi hissetmekle ne yaptığını bilmiyormuş gibi hissetmek arasında gördüğünü söyler
- htmx ve _hyperscript’in açık kaynak başarısı olsa da hâlâ hata yapmaktan ve başarısız olmaktan korktuğunu anlatır
- Herkes impostor sendromu yaşıyorsa, o halde kimsenin gerçekten sahtekâr olmadığı şeklinde düşünmek daha iyidir
Önerilen okumalar ve sonuç
- Önerilen okumalar
- Son sonuç basittir
- Karmaşıklık çok, çok kötüdür
1 yorum
Hacker News yorumları
Profesör Carson bu yorumları görüyorsa, bugüne kadarki tüm katkıları için içtenlikle teşekkür etmek isterim.
Üniversitede neden HTMX öğrendiğimizi, sizin neden bu kadar heyecanlandığınızı o zaman anlamamıştım; ama birkaç yıl geçince şimdi anlıyorum. Mesele tamamen HTML over the wire imiş.
Staff Ruby on Rails Engineer olarak çalışırken Hotwire’daki çalışmalarınızı gördüm; zaman zaman Hacker News’te görünmenizi ya da GitHub’da Hotwire geliştiricileriyle konuşmanızı görmek de harikaydı. Programlama topluluğunun ışığı gibi biri olarak büyük saygı ve minnet görüyorsunuz.
“İyi bir debugger parlak bir taş kadar değerlidir, hatta aslında ondan daha değerlidir” sözüne katılıyorum.
Hem küçük startup’larda hem de ‘elit’ büyük teknoloji ekiplerinde bulundum; ama ekipte debugger kullanan kişi neredeyse hep yalnızca bendim. Gerçek dünyada, en azından web teknolojileri tarafında, çoğu kişi
printifadeleriyle debug ediyor gibi görünüyor.Test çalışırken ilginç bir kod satırında durup oraya kadar gelen çağrı yığınını görmek, kodu kafada ileri doğru çalıştırmaktan çok daha kolay. Genç bir grug için bu beceri küçük bir süper güçtür; mümkünse kendi codebase’inizde çalışır hale getirmek için zaman ayırmaya değer.
Stack trace’e ya da bir iki değişkenin değerine bakmanın ötesinde debugger kullanımını pek yapmadıklarını; karmaşık veri yapıları ve kontrol akışının ayrıntılarına saplanmanın kolay olduğunu, kritik noktalara çıktı ifadeleri ve kendi kendini doğrulayan kod koymanın daha üretken olduğunu düşündüklerini söylemişlerdi.
Ben de genel olarak katılıyorum. Yaptığım işlerin neredeyse tamamında hipotez-log-çalıştır döngüsü cevaba çok daha hızlı ulaştırıyor. Kodu kafamda çalıştırmaya çalışmıyorum; kodun nasıl çalıştığına dair zaten bir çalışma modelim oluyor, o model doğruysa nasıl bir çıktı gelmesi gerektiğini biliyorum ve yanlış çıktıyla gerçek durumu hızla sezebiliyorum.
[0] The unreasonable effectiveness of print debugging (349 points, 354 comments) April 2021 https://news.ycombinator.com/item?id=26925570
Mikroservis mesh mimarisinde yerelde bir şeyi düzgün çalıştırmak bile zor; test ortamları da çoğu zaman step-through debugger bağlanabilecek şekilde yapılandırılmamış oluyor. Sonuçta geriye sadece
printdebugging kalıyor; loglama sisteminin kendisi sorunluysa ya da program logları flush etmeden ölürse onu bile kullanamıyorsunuz.Çok geçmeden mutlaka gerekiyor. Ama iş birliği sırasında debugger kullanmayı baştan hiç bilmeyen insanları görmek beni şaşırtmıştı. “Oraya breakpoint koyun”, “şimdi fonksiyonun içine girip değişken durumuna bakalım”, “onu geçin” dediğimde her seferinde boş bakışlarla karşılaşıyorum.
Daha doğrusu
printifadeleri ekleyip silmek yerine, kalıcı olarak bırakacağım loglama kodu ekliyorum. Önemli arayüzlerde genellikle INFO seviyesinde fonksiyona giriş/çıkışı ve parametre değerlerini kaydetmekle başlarım; sistemi kullandıkça daha yakından bakılması gereken yerler görürsem daha ayrıntılı loglar eklerim.Log formatına da epey emek veririm. Dağıtık sistemlerde çalışırken her log satırının önekini tam doğru ayarlamak çok işe yarıyordu. Node ID, pid ve timestamp’i sabit genişlikte koyup tüm küme loglarını indirip sıralayınca, birden fazla node’un davranışı tek bir dosyada interleave edilmiş şekilde görünüyordu.
Ama debugger kullandığım library veya framework’ün derinliklerine girdiği anda kayboluyorum ve bundan hoşlanmıyorum. O framework ya da library, on binlerce insan-saatlik emeğin biriktiği bir şey; benim seviyemi epey aşmış gibi geliyor.
Burada cevher gibi çok cümle var ama mikroservisler hakkındaki şu cümle en sevdiğim: “grug, zeki insanların sistemi doğru biçimde bölmenin en zor problemini alıp üstüne bir de neden ağ çağrıları eklediğini merak eder”
Beş kadar formu olan önemsiz bir web uygulamasını seve seve “mikroservislere” bölerler, aynı veritabanını paylaştırırlar, API yönetim katmanı, büyük (megabaytlık) toplu işler için kuyruk, e-posta bildirim sistemi, kendi yaptıkları gözlemlenebilirlik platformu eklerler; sonra da “böylesi daha kolay” diyerek basit bir web formunu SPA’ya çevirirler
Artık “mimari” ve “pattern”lerin işe yaramaz geliştiriciler için bir istihdam yaratma programı olduğunu anlıyorum. Yoksa muhtemelen sokakta “sandviç verirsen JavaScript yazarım” yazılı bir pankart tutuyor olurlardı
Onlar için API çağrısı olarak dışa açılmıyorsa, anlayamayacakları ya da yeniden kullanamayacakları opak bir kod yığınından ibaret
Birden fazla paket içeride birlikte çalışırken kod tabanının geri kalanına yalnızca küçük bir API açabilir. Bunun ağ olması, modülleri callback ya da davranış değil yalnızca veri alıp vermeye zorlar ve arayüzleri geriye uyumlu biçimde geliştirmeye baskılar. Böylece farklı modülleri farklı zamanlarda “hot reload” etseniz bile patlamayacak hâle getirebilirsiniz
Bunların çoğunun gerçek bir ağ hop’u olmadan da elde edilebileceğini sanıyorum ama ciddi bir deneme henüz görmedim
K8S gibi orkestratörler olmadan çalıştırılamıyorlar; kurulum ve işletimleri zor olduğu için yönetilen bulut satışına elverişliler. Daha fazla ağ bant genişliği ve CPU kullandırıyorlar; ikisi de faturalandırılıyor
Uygulama içinde karmaşık ya da büyük state’i paylaşmayı/sürdürmeyi zorlaştırıp, yerine yönetilen veritabanı veya event queue servisleri kullandırıyorlar. Monolit olsaydı kuyruk ya da channel kullanırdınız; mikroservislerde ise Kafka gibi dev bir canavarı istersiniz
Yerelde çalıştırmak da zorlaşıyor; bulut geliştirme ortamı gerekiyor ve birden fazla geliştirme/test ortamı bulundurmanız gerekebiliyor. Belirli bir bulutun networking yaklaşımı gibi özelliklerine bağımlı hâle geliyorsunuz, bu da bulut bağımlılığını artırıyor
Bulutun IT maliyetlerini düşüreceği iddiasıyla satıldığı zamanları hatırlar mısınız bilmiyorum. Komikti; 2000’lerden beri bunun saçmalık olduğunu ve sonunda tüm maliyetleri artıracağını düşünüyordum
Çünkü bu yöntemi yönetmek daha kolay; teknik bir karardan çok geliştirme organizasyonu biçimine yakın. Alternatif monorepo ise, şahsen onun daha kötü olduğunu düşünüyorum
“Karmaşıklık ile bir Tyrannosaurus’a karşı 1:1 arasında seçim yapacaksa grug Tyrannosaurus’u seçer. En azından grug Tyrannosaurus’u görebilir”
Bu cümleyi en az haftada bir kez düşünüyorum
Bu grug şeffaf Tyrannosaurus’la 1:1’de ve lanetlenmiş
Bu yazının değerlerinden biri, daha incelikli ve karmaşık işler yapabilen birinin deneyim gereği bunlardan bilerek kaçınmaya çalışmasında yatıyor
İnceliğe ve daha yüksek soyutlamalara ihtiyaç duyulan zaman ve yer elbette var. Ama grug felsefesi, bu işleri yapmanın kendi başına içkin bir değeri olmadığını söylüyor ve bu oldukça makul bir tavsiye gibi görünüyor
Yapay zeka yardımının da tutarlı, sıradan ve veri merkezli kodda daha etkili olduğunu gördüm. Duruma göre değişebilir
Acemi geliştirici basit kod yazar, orta seviye geliştirici karmaşık kod yazar, uzman geliştirici yeniden basit kod yazar
Hangi dersi herkesin zaten izlemiş sayılabileceği duruma göre değişir
Modern yazılım geliştirmenin ironilerinden biri, “sonunda zaman kazandıracak” diye düşünerek karmaşıklık eklememizdir.
Bazen bu doğrudur ve gerçekten zaman kazandırır; ama her zaman böyle değildir, hatta çoğu zaman olmayabilir.
DRY bazen aceleci soyutlamalara yol açar. “Bu desen başka yerlerde de kullanılacak, ortak kısmı çıkarayım” diye düşündüğünüz anda karmaşıklık şeytanı içeri girer.
Mümkün olduğunca çok hatayı derleme aşamasında yakalamak isteriz; ama bunun için derleyicinin gerçekte ne yapmaya çalıştığımızı daha fazla bilmesi gerekir ve sonunda anlaşılabilirliği kemiren karmaşık tipler oluştururuz.
Boilerplate’ten kaçınmak için karmaşık makrolar ya da DSL’ler yaparız; ama sızdıran soyutlamalar yasası yüzünden gerçek implementasyonu bilmemiz gereken an geldiğinde kafamız patlar.
Tüm bu örneklerin zorluğu, bazen gerçekten iyi fikirler olmalarıdır. Karmaşıklığı ne zaman eklemenin sadeleştirme sayılacağını ayırt edebilmek, iyi bir yazılım mühendisinin işareti diye düşünüyorum.
İş mantığı ideal olarak tek bir yerde tanımlanmalı; ama diğer şeyler gerekirse yinelenebilir ve bu kendi başına kötü değildir.
DRY’ı dengelemek için “3 kuralı”nı da vurgularım. Kopyala/yapıştır kod üç kereye kadar sorun değildir; ondan sonra soyutlamayı düşünmeye değer. Elbette her duruma uyan bir pratik kural yoktur ve o sezgiyi öğretmek zordur.
Genellikle Flask gibi Sinatra tarzı framework’lerle web uygulamaları yapar, URL desenlerine yanıt veren fonksiyonlar yazarım. Tek bir “ekran”, birlikte çalışan bir veya daha fazla fonksiyon ve ilgili HTML şablonlarından oluşabilir.
Uygulama veritabanı bağlantısı, dosya konumları, HTML header/footer gibi şeyleri ayarlıyorsa, bunun dışında ekranlar arasında neredeyse hiç bağ yoktur. Yeni bir ekrana ihtiyaç olursa mevcut bir ekranı kopyalayıp değiştirirsiniz ya da bir LLM’den ekran veya endpoint oluşturmasını isteyebilirsiniz; sonuç kötüyse yeniden yaptırırsınız.
Eski iş yerimde ML eğitim setleri oluşturan Themis adlı bir framework geliştiriyorduk; mikroservisler, React, Docker vb. kullanıyordu. Gerçek ihtiyaç, sürekli yeni görevler eklemek ve her görev için basit ama çok optimize edilmiş ekranları hızlıca yapmaktı. 20 bin karar vermeniz gerekiyorsa tek tek tıklamak bile zorken, dört tıklama 80 bine çıkar ve insan vazgeçer.
O zamanki yapıda JAXB uygulamasının API endpoint’ini ve monolitik React uygulamasının component’ını yazıp TypeScript·Docker·javac’ın 20 dakika çalışmasını beklemek gerekiyordu. Şansınız varsa açılırdı, yoksa baştan başlardınız.
Themis eleştirisini yazıp yeni görevlerin hızlı geliştirilmesini hedefleyerek Nemesis’i tasarladım; ama eski iş yerimde seçilmeyen yol oldu. Yine de Nemesis ve ben o zamandan beri milyonlarca görev örneğini işliyoruz.
Tek bir yerde kullanılan helper’ı çıkarmanız gerektiği anlamına gelmez. Mantık tek bir fonksiyona, sınıfa, dosyaya vb. yığılmış olsa bile kopyalanmamışsa hâlâ DRY’dır.
Aceleci soyutlama gerçekten vardır. CS derslerinin genelde bunu yapmayı öğretmesi de yardımcı olmuyor. Yeni birine MySQL veritabanı verirseniz ilk yapmaya çalışacağı şey MySQL’i soyutlamak olabilir.
Karmaşık ya da basit olması fark etmez; sonuç değer katmıyorsa önemli değildir. Önce çıkardığınızdan daha fazla değer katmaya odaklanın, karmaşıklığı sonra dert edin.
LLM’leri kullanmayı sevdiğim yollardan biri, bu denemeyi verip şu anda uğraştığım konu hakkında grug-brained developer personasıyla yorum yapmasını istemek. Stres atmaya iyi geliyor.
Prompt’a “Bu denemedeki Grug Brained Developer gibi davran” gibi bir şeyle başlamak yeterli mi, merak ediyorum.
“Karmaşıklık çok kötüdür” gerçekten doğru bir söz
Yazılım mühendisliği yaptığım bunca yıl boyunca, bu düşünce her durumda istikrarlı biçimde doğru çıkan az sayıdaki ilkeden biri oldu. Bazı sorunlar doğası gereği karmaşıktır; ama o zaman bile zaman ayırıp en basit çözüme ulaşmak çok daha iyidir
En etkili işlerim, önceki yaklaşımı sorgulayıp cesurca basitleştirdikten sonra ortaya çıktı. Biraz potansiyel esneklik kaybedebilirsiniz, ama çoğu zaman ihtiyaç duyduğumu sandığım kadar esnekliğe aslında ihtiyaç yoktu
Örneğin, oldukça iyi ve ajan gibi davranan LLM’ler ortaya çıktıktan sonra, kırılgan ve hata ayıklaması zor, aşırı karmaşık TypeScript tiplerinden kaçınıp spesifikasyon gibi kod yazıyor, ardından LLM’den buna dayalı kodu statik olarak üretmesini istiyorum
Projenin ESLint bağımlılığı sürüm güncellemelerinden sonra sürekli bozuluyordu; birçok kural yanlış pozitifleri önleyecek kadar incelikli değildi ve TypeScript ile VSCode’da düzgün biçimde bakımını yapmak da karmaşıktı. Biome.js’ye geçince daha basit ve yeterince etkili oldu, ama son zamanlarda onda da hatalar çıkıyor. Yine de linting’in olsa iyi olacak bir şey olduğunu, aşırı zaman harcayıp bakılması gereken bir şey olmadığını fark ettim; bu yüzden build araç zincirinden çıkardım ve VSCode’da da her zaman açık tutmam gerekmiyor. Ara sıra Biome çalıştırıp yalnızca kod stilini ve formatı kontrol etmek yeterli
Proje için özel bir veri migration aracı yaparken, ileri yönlü migration’ların gerekli olduğunu ama geri yönlü migration’ları uygulamak için harcanacak zaman ve karmaşıklığa değmeyeceğini düşündüm. Veri içeren bir veritabanını rollback etmek gerekirse yedekten geri yüklenebilir; veri yoksa ya da production DB değilse sürümlendirilmiş başlangıç script’iyle temiz bir durumdan başlanabilir
Üçüncüsünün nasıl daha basit olduğundan da pek emin değilim. Matematiksel bir zihinle birebir ve örten bir uzay kolayca oluşturulabilir. Geri yönlü migration’ı başka yollarla taklit etmek daha zor da olabilir. Elbette ayrıntılara bağlı, bu yüzden genel bir kural değil
Zeki Rich, complect’in şeyleri birbirine bağlamak olduğunu söylemişti; buna katılıyorum. Rich karmaşıklığın kötü olduğunu söyledi, ama buna katılmıyorum. Bir şeyleri birbirine bağlamak gereklidir. Birbirleriyle bağlantılı olmazlarsa sorunları çözemezsiniz
Bu yazının 2022 tarihli olduğuna inanamıyorum
10 yıl önce okumuş olduğumu ve o zaman bile artık klasik sayıldığını rahatlıkla söyleyebilirmişim gibi geliyor
Üzücü ama doğru: “yes” demeyi öğrendikten sonra başarısız olunca suçu başka bir grug’a atmayı öğrenmek ideal kariyer tavsiyesi
Kurumsal dünyaya ilk girdiğimde bunun doğru olmadığını, sadece teknik ekibin iletişiminin yetersiz olduğunu düşünmüştüm. Yanılmışım ve grug haklıymış