3 puan yazan GN⁺ 2023-09-17 | 1 yorum | WhatsApp'ta paylaş
  • Soyutlama seviyesini ayırmak için kodu küçük fonksiyonlara bölmek yerine, yukarıdan aşağıya akan doğrusal kodun genel akışı takip etmeyi daha kolaylaştırdığı savunuluyor
  • Fonksiyon çıkarımıyla yukarıdan aşağıya (top-down) bir yapı kurulduğunda, bake ve bakePizza gibi adları benzer fonksiyonlar arasında gidip gelerek kontrol etmek gerekebiliyor
  • Fırının önceden ısıtılmasının nerede yapıldığı ya da pizzayı iki kez çevirdiğinizde ne olacağı gibi konularda, küçük fonksiyonlar niyeti göstermesine rağmen gerçek davranışı gizleyebilir
  • Doğrusal koda adım adım yorumlar eklendiğinde, dolaylı başvuruları artırmadan işin niyetini açıklayabildiği için ek soyutlamadan daha okunaklı olabilir
  • Yalnızca bir kez kullanılan küçük fonksiyonları çıkarmak doğrusallık kaybına yol açar; örnekteki fırın oluşturma biçiminde olduğu gibi, gerçek kodda performans sorunlarını bile ortaya çıkarabilir

Fonksiyon çıkarmaktan daha önemli olan durumlar: doğrusallık

  • Google Testing Blog örneği, iki farklı createPizza uygulamasını karşılaştırıyor ve sağdaki uygulamanın soyutlama seviyelerini karıştırmadığı için daha okunaklı ve yukarıdan aşağıya (top-down) olduğu görüşünü savunuyor
  • Karşı görüşe göre ise soldaki uygulamada daha önemli olan şey, ekran üzerinde yukarıdan aşağıya doğrusal olarak okunabilen kod olması
    • Sağdaki uygulamada tüm davranışı anlamak için birden fazla küçük fonksiyon tanımına gitmek gerekiyor
    • Sunum biçimi nedeniyle sağdaki kodun bir kısmı gizlendiğinden, iki uygulama benzer boyutta görünüyor; oysa gerçekte sağdaki daha uzun
  • Fonksiyon çıkarmak, yalnızca isimlere bakarak davranışı yeterince anlamayı zorlaştırabilir
    • Hem bake hem de bakePizza olduğunda, hangisinin fırını ısıttığını hemen anlamak zor
    • Aynı pizzayı iki kez çevirdiğinizde işlemin idempotent olup olmadığını ya da sonucu bozup bozmadığını anlamak için iç uygulamaya bakmak gerekir

Yorum eklenmiş doğrusal kod ve fırın örneği

  • Soldaki doğrusal koda sağdaki fonksiyon adlarının yorum olarak eklendiği sürüm, en okunaklı biçim olarak değerlendiriliyor
    • Prepare pizza, Add toppings, Heat oven, Bake pizza, Box and slice gibi yorumlar her adımın niyetini ortaya koyuyor
    • Okunabilirlik, ek soyutlama katmanları ve dolaylı başvurulardan değil, o anda yapılan işi doğru şekilde anlatmaktan geliyor
  • Sonuç, yalnızca bir kez kullanılan küçük fonksiyonların doğrusal koddan çıkarılmaması gerektiği yönüne daha yakın
    • Küçük fonksiyon çıkarmanın faydasının doğrusallık kaybını telafi etmediği düşünülüyor
  • Örnekteki fırın işleyişi de yapısal olarak tuhaf bulunuyor
    • Fırının önceden ısıtılması kendi başına tamamlanmış bir davranış olduğundan, bunun fırının bir metodu olması daha uygun
    • Tek bir pizza yapılırken her seferinde yeni bir fırın oluşturulup önceden ısıtılması, gerçekçi kullanım biçimiyle uyuşmuyor
    • Gerçek kodda da bu tür yapılar görülebiliyor ve bazen performans sorunlarına yol açabiliyor
  • Fırın, createPizza içinde yeni oluşturulmak yerine büyük olasılıkla parametre olarak alınmalı
    • Fırını sağlamak, çağıranın sorumluluğuna daha yakın
    • Akış pizzayı kutuya koymaksa, pizza yerine kutuyu döndüren bir arayüz daha doğal olabilir

1 yorum

 
GN⁺ 2023-09-17
Hacker News yorumları
  • Bu bir stil meselesi; yemek yapmak gibi, tuzun fazlası da azı da yemeği mahveder.
    Umarım burada kimse 1000 satırlık bir tanrı fonksiyonu önermiyordur; fonksiyon başına en fazla 5 satır da okunaklı değildir. Nereden bölüneceği muhakeme, iyi bir sezgi ve yineleme gerektirir. İlk denenen soyutlama pek iyi olmadı diye soyutlamadan vazgeçmek gerekmez; birkaç kez refactor edince iş alanına iyi uyan sınıflar ve API’ler ortaya çıkabilir.
    Aynı zamanda soyutlama konusunda fazla aceleci olmamak ya da birkaç satır tekrar yüzünden ölümcül yara almış gibi davranmamak gerekir. Erken soyutlama, birlikte evrilmesi gerekmeyen kodları kolayca birbirine bağlar. Yalnızca tek bir yerden çağrılan bir fonksiyonu çıkarıp bir iş birimini gizlemek algoritmayı temizleştirebilir; özellikle boilerplate’i ya da iş mantığıyla DB bağlantısı yönetimi gibi altyapı kaygılarının karışımını gizlerken yararlıdır. Ancak dikkatli kullanılmalı; aynı soyutlama düzeyinde olması gereken adımları parçalamaktan kaçınmak iyi olur.

    • Asıl mesele bu. Acemi geliştiriciler devasa fonksiyonlar yazma eğilimindedir; Clean Code gibi kitapları ilk kez okuyan hevesli geliştiriciler ise her şeyi birkaç satırlık milyon tane fonksiyona bölmeye çalışır.
      Birlikte çalıştığım biri, “okunması kolay” olduğu gerekçesiyle tüm boolean koşulları fonksiyona çıkarır, “yorumlar kötüdür” gerekçesiyle hiç yorum yazmazdı. O kitabı sevmiyorum; çünkü bu tür kötü tavsiyeleri körü körüne izleyen fanatikler üretiyor.
    • 1000 satırlık bir tanrı fonksiyonu neden olmaz? Bunun daha kötü olduğunu kim söyledi, hangi araştırma bu sonuca vardı?
      Bazen alanın kendisi 1000 satırlık bir tanrı fonksiyonu gerektirebilir; mantık ve işler tek yerde toplandığı için 20 adet 50 satırlık fonksiyondan çok daha okunaklı olabilir. Sonuçta bütünü anlamak için o 20 fonksiyonun hepsini okumak gerekir; ayrıca biri bunların bazılarını yeniden kullanmaya kalkıp onları asıl işte olmayan 2-3 gereksinime uyacak şekilde ayarlayabilir ve belirli bir mantığı alakasız kullanım senaryolarına bağlayabilir.
      Eğer o fonksiyon saf bir fonksiyonsa, 1000 satır da olsa 10000 satır da olsa fark etmez; hâlâ makul olduğunu düşünüyorum.
    • Yemek benzetmesinden gidersek, birine yemek yapmayı anlatırken bir noktada fond eklemek gerekiyorsa, fond nasıl yapılır kısmını ayrı bir bölümde açıklamak mantıklıdır. Fond bağımsız bir şeydir ve yemekle temas ettiği tek bir nokta olduğu için dışarı almak iyi, hatta yararlı olabilir.
      Yemek tarifleri de zaten oldukça soyutlanmıştır. “Soğanı hafifçe sotele” dendiğinde, soğanı nasıl doğrayacağını ve hafifçe sotelemenin algoritmasını zaten bildiğin varsayılır. Her şeyi inline yazarsan okunamaz hale gelir.
      Kod da benzer. Soyutlamayı katı biçimde dışlarsan, dilin izin verdiği en düşük düzeye inersin; bu kesinlikle okunması kolay kod değildir. Örneğin Python’ın decode metodu yerine Unicode çözmeyi elle yapmaya kalkarsan, programın gerçekte ne yaptığını anlamak çok zorlaşır. Dil basit ve iyi doğrulanmış bir soyutlama sunduğu için kimse bunu yapmaz; peki kendi basit ve iyi doğrulanmış soyutlamanı oluşturup bunu iş mantığının genelinde kullanmaktan farkı nedir?
      Zor olan, kimsenin tekrar dokunmasına gerek kalmayacak kadar iyi seçilmiş bir soyutlama oluşturmaktır.
    • Şirket ekibine sık verdiğim geri bildirim, bir adım geri çekilip daha büyük problem alanına bakmaları ve bunların zorunlu olarak mı aynı, yoksa tesadüfen mi aynı olduğunu düşünmeleridir.
      Şu an kod satırları benzer görünüyor diye gelecekte de aynı olmaları ya da aynı tutulmaları gerekmez. “Kod neredeyse tekrar ediyor” diye iki farklı kullanım senaryosunu zorla birleştirirseniz, zamanla hiçbir şeyi soyutlayamayan bir soyutlamaya dönüşmesi kolaydır.
      Kullanım senaryoları fazla ayrışırsa, uygulama ya mantığın büyük kısmını çağıran tarafa iter ya da farkları flag’lerle dışa açıp içeride iki farklı uygulamayı yan yana tutar. İlki sığ bir soyutlamadır ve değeri düşüktür; ikincisi ise iki bağımsız uygulamadan daha az nettir.
    • İyi yapılandırılmış 1000 satırlık bir fonksiyonu, küçük yüzlerce fonksiyondan oluşan kötü bir spagettiye her zaman tercih ederim.
  • Örnek kod fazla basit olduğu için doğrusal kodun daha okunur olması elbette doğal, ama bu fikir iyi ölçeklenmiyor
    Yeniden kullanılabilirlik ya da birim test edilebilirliği de düşünülmeli; tüm kodu tek bir fonksiyona koyarsanız, okunan kod bloğuyla ilgili de olabilecek ilgisiz de olabilecek tüm yerel değişkenler kapsam içinde olur ve akıl yürütmek zorlaşabilir
    Yine de deneyimsiz olduğum dönemlere dönüp bakınca, gayet düzgün doğrusal kodu fazla modülerleştirip oradan oraya zıplamayı gerektiren, bakımı daha zor koda dönüştürdüğüm çok oldu. İlk yazılan hâlin, o an kafamdaki düşünce akışına daha yakın olması ve okurun da muhtemelen onu o şekilde yorumlama olasılığının yüksek olması gibi bir avantajı var. Aşırı refactoring bunu ortadan kaldırabilir
    Sonuçta programlama zanaate daha yakın; duruma uygun seçimi yapmada deneyim yardımcı olur

    • İş yerinde en iyi inceleme alan fonksiyonlarımdan biri 2000 satırlık bir canavardı ve 9 ayrı değişken kapsamını adım gibi kullanan doğrusal bir stile sahipti
      Amacı tek bir şeydi. Uygulamanın bir köşesinde, bir platformda kullanılan tekil HTML sayfalarını, başka bir platformun native hissini taklit eden bir carousel'e dönüştürme işiydi ve o platform ile uygulamanın o alanına son derece özeldi
      9 kapsamın her birini ayrı fonksiyon yapabilirdim, ama o zaman geliştiricilerin bunları yeniden kullanmak istemesi muhtemeldi. Her adımda, önceki adımda olanlara dair ince varsayımlar vardı; bunları ayrı fonksiyonlara dönüştürmek için varsayımları yeniden gözden geçirmek, genelleştirmek ve her metodun bağımsız çalıştığını doğrulamak gerekirdi. Neredeyse başka hiçbir yerde gerekmeyecek kod için böyle bir maliyete katlanmanın anlamı yoktu
      Debug etmek daha zor değildi, uçtan uca testleri de vardı ve ara aşama durumu fonksiyonun dışına sızmıyordu. Hatta zaman içinde 2 başka geliştirici değişikliklere katkı verdi, iyi çalıştı ve yazması da hızlıydı
      Doğrusal kod iyi ölçeklenir ve problemi çözer. Her zaman istediğiniz biçim olmayabilir, ama sanıldığından çok daha fazla durumda hayatı çok daha kolaylaştırır
      İlk kez 2000 satırlık canavarı gördüklerinde tepki iyi değildi; ama 5 dakika bakınca gerçek bir kusur bulmak zordu ve birkaç test olduğunda geriye, gerçekleşmeyen korkulardan başka bir şey kalmıyordu
    • Fonksiyonlara bölünmüş kodun ölçeklendiğine dair kanıt nerede? Toplam kod karmaşıklığı büyüdükçe, onlarca fonksiyona parçalanıp okunamaz hâle gelme durumu da birlikte büyür
      Bir noktada o onlarca fonksiyonun yalnızca belirli bir sırayla çağrılması gerektiğini ve her birinin yalnızca bir kez kullanıldığını fark edersiniz. Sonuçta, birinin o fonksiyonları işe yarar şekilde kullanabilmesi için sihirli kombinasyon sırasını bilmesini zorunlu kılmış olursunuz
    • “Fikir ölçeklenmiyor” demek yanlış; “programlama bir zanaattir, deneyim duruma göre karar vermeye yardımcı olur” demek doğru
      Devasa doğrusal bir fonksiyonun çoğu zaman daha okunur ve tercih edilir olmasının temel nedeni, birden çok kavram ve ilişkiyi bağlam değiştirmeden tek parça hâlinde aynı anda zihinde tutabilmeyi sağlayarak anlamayı kolaylaştırmasıdır. Bunun uç bir savunucusu K dilinin mucidi Arthur Whitney'dir; tek ekrana olabildiğince çok şey sığdırmak için çok özlü ve başkalarına neredeyse anlaşılmaz görünen kod yazar
      Kişisel bir örnek olarak, iş mantığının büyük bir switch ifadesinde yer aldığı devasa Windows mesaj işleme fonksiyonu, yani WndProc, mesaj handler'larını ayrı fonksiyonlara bölen Visual C++ sürümüne kıyasla okumak, anlamak ve debug etmek açısından çok daha kolaydı
      Bir başka örnek de mikrodenetleyici örnek kodlarında ADC kullanımının tek dosyada tamamen yer aldığı bir sürüm ile main.c, config.c, interrupts.c, timer.c gibi birden çok dosyaya bölünmüş bir sürüm olmasıydı; 200 satır bile değildi ama ikincisini bağlam geçişleri yüzünden anlamak zordu
    • İnsanların yeniden kullanılmayacak doğrusal kodu alışkanlıkla ayrı fonksiyonlara çokça çıkardığını sık gördüm
      Bu kod parçaları genellikle sınıfın private fonksiyonları olur ve durum taşır. private fonksiyon oldukları için fiilen test etmek de zordur
      Artık yalnızca bir kez çağrılan ve genellikle yan etkili durumu değiştiren bir sürü private fonksiyonunuz olur. Çağıran kodla hemen yan yanaysa basit durumlarda hâlâ okunabilir, ama zamanla biri çağıran fonksiyon ile çıkarılmış fonksiyonun arasına başka fonksiyon ekler
      O zaman çağrı grafiğine bakmadıkça ya da sınıf dosyasında arama yapmadıkça nereden çağrıldığını bilmediğiniz kod parçaları, farklı yan etkili durumları değiştirmeye başlar
      Kodu doğrusal olmayan hâle getirecekseniz, dil destekliyorsa çıkarılmış private fonksiyonu en azından çağıran fonksiyonun iç fonksiyonu yapmayı düşünmenizi isterim. Böylece başka yerden çağrılmadığı açık olur
      Gerçek kod tabanlarında bu da ya o ya bu meselesi değil; ikisini birleştirip okunur ve bakımı yapılabilir bir biçime getirme sanatı gibidir
    • Fonksiyon gerçekten doğrusal ise uzun fonksiyonlar o kadar da kötü değildir. Ama gerçek örnek doğrusal değil; içinde birden çok dal var
      İnsanlar tüm bu dalları test edecek mi? Yoksa sadece bir pizza ekleyen bir test yazıp kabaca çalışıyor mu diye mi bakacak? Dışarıdan birden çok dalı test etmek genellikle zahmetlidir ve küçük, özelleşmiş fonksiyonları test etmekten daha uğraştırıcıdır; bu yüzden ikincisi daha olası görünüyor
  • “Doğrusal kod ölçeklenmez” sözü aslında bunun tam tersidir. Büyük kod tabanlarında asıl kâbusa dönüşen şey, derinlemesine iç içe geçmiş çağrı yığınlarına sahip küçük ve özlü fonksiyonlardır.
    Yeni kodun nereye ekleneceği net değildir; kodun çağrılabileceği tüm yolları izlemek gerektiği için değişikliklerin etkisini anlamanın zorluğu üstel biçimde artar ve yinelenen alt rutinler de ortaya çıkar.
    Vakaların %99’unda iyi bir soyutlama yapmış olmazsınız; bu yüzden doğrusal kod yazmak daha iyidir. Şüpheli fonksiyon semantiklerine kıyasla kopyala/yapıştırı tercih ederim.

    • Bir başka risk de şudur: print_table() eklerseniz, biri onu bulup kendi kodunda kullanır ve kendi kullanım senaryosuna göre çıktıyı ayarlayan küçük bir bayrak ekler.
      12 ay sonra şöyle bir şeye dönüşür:
      print_table(
      rows,
      headers = None,
      is_unicode = False,
      left_align = False,
      align = [],
      remove_emoji = None,
      max_width = 80,
      potato_mode = 7,
      _debug_frontend = not FLAGS.dont_debug,
      ellipsis_for = 0,
      no_print = False,
      )
    • Bu, okunabilirlik sorununu açıklıyor; özünde de okunabilirliğin ölçeklenebilirliğe zarar verdiğini söylemiş oluyor.
      Okunabilirliğin ölçeklenebilirliği etkileyebileceği gerçeğini bir kenara bırakıp iki kavramı birbirine dik kabul edersek, doğrusal kod modüler kod kadar iyi ölçeklenmez. Bu ikili ayrımı bilmek ve duruma göre dikkate almak değerli.
      Yine de hâlâ katılmıyorum. Küçük fonksiyon saf fonksiyon ise okunabilirlik sorunu yaratmaz. Bu, duruma dokunmadığı anlamına gelir; koda mantık enjekte etmemeli, bağımlılık enjeksiyonunu ve fonksiyonları başka fonksiyonlara geçirmeyi açıkça en aza indirmelidir.
      Yalnızca veri aktaran saf fonksiyonlardan bir pipeline kurarsanız kod hem okunabilir hem de genişletilebilir olur. Tasarım kusurları yüzünden mantığı yeniden yazmak zorunda kalma ihtimali çok azalır; saf fonksiyonları birleştirdiğinizde kod Lego gibi olur. Refactoring de mevcut ilkel öğeleri yeniden düzenleyip yeniden birleştirmeye daha çok benzer.
  • Örnek kod, en azından pizza metaforunu anlamlı biçimde korumaya çalışsaydı ya da düşük seviyeli Go kodu olmasaydı daha az dikkat dağıtıcı olurdu.
    prepare bir fonksiyon adı olarak berbat. Deneyimli bir Gopher muhtemelen NewPizzaFromOrder gibi bir ad verirdi.
    addToppingsi ayrı bir fonksiyon olarak tutmak için bir neden göremiyorum. Mutlaka gerekiyorsa şahsen bunu Pizza üzerinde func (p *Pizza) WithToppings(topping ...Topping) *Pizza { /* ... */ } gibi bir metot yapardım. Gerçek pizza değiştirilebilir olduğu için metot alıcıyı değiştirir.
    Her pizza pişirdiğinizde neden yeni bir fırın örneği oluşturulduğunu da anlamıyorum. Mevcut bir fırınla başlayıp oven.Preheat() yapmalı ve oven.Bake(pizza) çağrılmalı. Hatta daha ileri gidip oven.Preheat()in, .Bake()i açığa çıkaran yeni bir Oven tipi döndürmesini sağlayarak ön ısıtma yapmadan pişirme hatasını derleme aşamasında engelleyebilirsiniz. Başka bir yerde Baker arayüzü olabilir ve ön ısıtmanın pek önemli olmadığı, buna ihtiyaç duymayan bir ToasterOven implementasyonu da bulunabilir.
    Kodu değiştirmesem bile bildirim sırasını beklenen akışa göre yeniden düzenlerdim. Böylece birbirini çağıran fonksiyonlara göz atarken sayfada aşağı yukarı zıplamak gerekmez.
    Zamanım olmadığı için burada duruyorum, ama bu kod “hangisi daha okunabilir” tartışmasını başlatmak için bile zaten çok kötü bir örnek.

  • John Carmack de neredeyse aynı şeyi söyledi ve o zamandan beri bunu uyguluyorum. Doğrusal kod yürütme sırasını izlediği için doğal olarak okunması kolaydır ve göz sıçramalarını en aza indirir.
    Bazı kodların yeniden kullanım için doğrusal olmaması gerekir; o zaman yürütme bir grafa dönüşür. Kod graf yapısındaki yeniden kullanımdan yararlanmıyorsa, tek bir kenarın yeterli olduğu yerde düğüm eklemeye gerek yoktur.
    http://number-none.com/blow/blog/programming/2014/09/26/carm...

    • Carmack’in söylediği ama orijinal metinde olmayan nokta şu: yan etkisiz mantığı ayrı bir fonksiyona çıkarabiliyorsanız, bu genellikle iyi bir fikirdir.
      Bu durumda soldaki kod pizza.Toppings = get_pizza_toppings(order.kind) gibi olsaydı, ana fonksiyonda pizzayı değiştirme odağı kalacağı için bence daha iyi olurdu.
  • Doğrusal kodun daha okunabilir olduğuna bir ölçüde katılıyorum, ama bu tek başına onu iyi bir kod pratiği yapmaz.
    İyi doğrusal kodun daha okunabilir olduğunu düşünüyorum, fakat sürdürülebilirlik ve test edilebilirlik çok daha zayıf oluyor. Onlarca yıllık deneyimim var ve CS öğrencileri için dış değerlendirme de yapıyorum; yıllar boyunca pratikte gördüğüm iyi uygulamalar içinde kesin olan tek şey fonksiyonları küçük tutmak oldu.
    Soyutlamayı özellikle sevdiğim de yok, kod tekrarından mutlaka kaçınılması gerektiğini de düşünmüyorum; ama mümkün olduğunca tek amaca yakın fonksiyonlar yazarsanız gelecekteki siz size teşekkür eder.
    Örnekteki gibi bir kod 10 yıl boyunca production’da çalışırsa her bölüm değişecektir. Şansınız varsa yorumlar da güncellenir, ama çoğu zaman öyle olmaz. Birim testleri de büyüyüp yönetmesi zorlaştıkça giderek özensizleşir; birileri, değişiklikle açıkça ilişkili görünmeyen test kısımlarını düzeltmeyi unutabilir. Kodun kendisi de zamanla daha az okunur hâle gelmeye çok yatkındır. Bu niyetten ya da beceriksizlikten değil, zaman baskısı gibi insani nedenlerden olur.
    Mükemmel bir dünyada kaygıları ayırmaya gerek olmazdı; ama kusurlu bir dünyada yaşıyoruz ve fonksiyonlar ne kadar küçük, sorumlulukları ne kadar az olursa zaman içinde bu kusurlarla baş etmek o kadar kolaylaşır.

    • Doğru, test etmek daha az kolay; ama buradaki örnek belirli bir sırayla yapılması gereken durum değişiklikleri.
      Bir nesneyi belirli durumların akışı içinden geçiriyorsanız, bunu parçalara ayırıp geçişleri tiplerle belirtmek ya da tek büyük bir fonksiyon olarak yazmak bence daha iyi. Örneğin bakePizza, RawPizza alıp BakedPizza döndürürse çağrı sırasını derleme zamanında zorunlu kılabilirsiniz.
      Okunabilirlik, doğruluk ve test edilebilirlik nedeniyle ilkini tercih ederim; ancak çoğu programlama dilinde bir nesnenin tipini değiştirmek için yeni bir nesne oluşturmak gerekir ve bunun çalışma zamanı maliyeti vardır. Sıcak bir kod yolundaysa yerinde değiştirme makuldür; bu durumda da tek bir doğrusal fonksiyonda tutmak daha iyidir.
    • Yakın zamanda Sussman’ın Software Design for Flexibility kitabını okumaya başladım; bu konuyla doğrudan örtüşüyor.
      https://mitpress.mit.edu/9780262045490/
  • John Carmack’in ilgili e-postası: http://number-none.com/blow/blog/programming/2014/09/26/carm...
    Tartışma: https://news.ycombinator.com/item?id=12120752

  • Kesinlikle katılıyorum. Eskiden karşı kamptaydım.
    Buradaki temel gerilim, bir taraftaki davranışın yerelliği ile diğer taraftaki üst düzey “içindekiler tablosu” görünümünü net biçimde gösterme arzusu arasında. Okunabilir kodda yerellik daha önemlidir. Yazıda söylendiği gibi, içindekiler tablosu bakışı bölüm yorumlarıyla yeterince açık hâle getirilebilir.
    Doğrusal kodu tercih etmek için daha önemli bir neden de var. Tüm kod tabanında gezinirken “parçalar”, yani fonksiyonlar, sınıflar ya da dilin dayattığı birimler kabaca iş kullanım senaryolarına karşılık geliyorsa bu çok daha kolaydır. Aksi hâlde arama alanı fazla büyür ve bütünü parçalardan kendiniz yeniden kurmanız gerekir. Kod yapısının bu işi sizin yerinize yapması gerekir.
    Birden fazla “şey”in hepsi tek bir işle, örneğin kayıt olma ya da satın alma ile ilgiliyse kodda da tek yerde durmaları daha iyidir. Bulmak ve değiştirmek çok daha kolay olur. Yalnızca yeniden kullanım gerektiğinde alt fonksiyonlara bölünmeli; sırf düzenleme amacıyla bölünmemeli.
    [0] https://htmx.org/essays/locality-of-behaviour/

    • Ben ters yöne gittim. Eskiden doğrusal kod tarafındaydım, şimdi ise daha fazla fonksiyon tarafındayım.
      En büyük neden durum. Fonksiyon uzadıkça yerel değişkenlerin kapsamı genişler. Fonksiyonun herhangi bir yerinde herhangi bir değişken değiştirilebilir ve veri akışı hemen açık olmaz. Fonksiyon sayısı fazla olduğunda kapsam küçük kalır ve veri akışı daha açık hâle gelir.
      Yan etki olarak girintileme de azalır.
      Bununla birlikte aşırı küçük fonksiyonları sevmiyorum; çünkü asıl işin nerede yapıldığını bulmak zorlaşır.
    • “Yalnızca yeniden kullanım gerektiğinde alt fonksiyonlara bölün, sırf düzenleme amacıyla bölmeyin” diyorsan testleri nasıl yapacaksın? Kafada tutulması gereken durumu azaltmayı ne yapacaksın? Kaynakların serbest bırakılması? Değişiklik etkisini anlamak?
      Sırayla çalışması gereken, yeniden kullanılamayan 10 adımı olan ve her adımı 100 satır süren bir gün sonu işlemi düşün. Her adım bir öncekiyle benzer ama aynı olmayan verileri kullanıyor. Gerçekten 1000 satırlık tek bir fonksiyonu mu seçersin?
  • İkisi de doğrusal okunuyor. Küçük fonksiyonların çıkarıldığı sürümde sayfanın üstünde bir içindekiler tablosu var ve adımlar arasındaki veri akışını özetliyor. Tamamını okuyacaksanız çekici bir okuma sırası gibi görünüyor.
    Ancak bu okunabilirliği korumak için adımların sırası değiştiğinde fonksiyonların konumunu da taşımak gerekir. private fonksiyonlarsa ve yalnızca içindekiler tablosundan çağrılıyorlarsa sorun yok. Fakat hiçbir şey sıranın korunmasını zorunlu kılmıyor; genel okuma akışını düşünmeyi de zorunlu kılmıyor.
    Fonksiyon yeniden kullanılmaya başladığında çoğu zaman artık doğrusal hâle getirilemez. Bazen insanlar pes edip alfabetik sıralar ya da tamamen rastgele hâle gelir.

  • Deneyimlerime göre bir kişi koda ne kadar aşinaysa, kodu küçük fonksiyonların içine itmenin doğru yol olduğunu o kadar düşünür.
    Zaten o kodun zihinsel modelini kurmuş olduğu için, onun gözünde en temiz uygulama satır sayısı çok az olan uygulamadır.
    Ama sıradaki kişi geldiğinde, aynı zihinsel modeli başlangıçtaki bağlam olmadan kurmak için oradan oraya gidip gelirken kafasındaki stack’i push/pop yapmak zorunda kalır; bu da çok daha zordur.

    • Kod anlamlıysa böyle olmaz. Zarif soyutlamalara, ince arayüzlere ve uygun dokümantasyona sahip iyi yazılmış kodda o kadar çok gidip gelmeye gerek kalmaz.
      Örneğin kullandığınız dilin standart kütüphane kaynak kodunu ne sıklıkla okursunuz? Neredeyse hiç okumazsınız; genelde metot imzasına bakar, biraz karmaşıksa ya da yeniyse dokümantasyonu okursunuz.
      Bir arayüzün özü, metodun nasıl uygulandığıyla değil, ne yaptığıyla ilgilenmenizi sağlamaktır. Bu da bağlam, adlandırma ve dokümantasyonun birleşimiyle açıklanır. Ama birçok geliştirici bunu anlamaz ya da umursamaz; bu yüzden doğrusal ya da modüler olması fark etmeksizin anlamsız kod yazar.
      Örneğin bir servis sınıfında bir metodu çağırıp bir veri almanız, başka bir metotla başka bir veri almanız, üçüncü bir metotla da önceki ikisiyle birleştirilmesi gereken veriyi almanız gerekiyorsa, o servisin anlamı nedir? İç karmaşıklığı tamamen dışarıya açmış olursunuz.
      Küçük metotları zorunlu kılalım demiyorum. Yalnızca bir kez çağrılan, çok spesifik bir iş yapan ve doğru sırayla çağrılması gereken 5 satırlık 20 fonksiyonun bir anlamı yoktur. Bu temiz kod değil, daha çok cargo cult programming’e yakındır.
      Önemli olan, hem yeni ekip üyeleri hem de deneyimli ekip üyeleri için anlamlı olan, akıl yürütmesi kolay ve karmaşıklığı uygun yerde gizleyecek şekilde doğru soyutlamayı yapmaktır. Kolay değil ama mümkün.
    • Katılmıyorum; ama aşağıdan yukarı okuyup düşünen insanlarla yukarıdan aşağı düşünen insanlar arasında bir fark olabilir.
      Oğlum yeterince zeki olmasına rağmen okulda zorlanıyordu; birkaç uzmandan biri, okulun genel olarak aşağıdan yukarı öğrettiğini, ama oğlumun çok belirgin biçimde yukarıdan aşağı öğrenen biri olduğunu açıklamıştı. Ayrıntılara girmeden önce önce genel çerçeveye ihtiyaç duyuyor; başka insanlar ise önce ayrıntıları kavrayıp sonra genel çerçeveyi birleştirmek zorunda. Okul genelde ikinci gruba göre öğretir.
      Programcılar arasında da benzer bir fark olabilir.
    • “Sıradaki kişi oradan oraya gitmek zorunda kalır” ifadesi ancak o kişi kodu okuyamadığında geçerlidir. Kod, en azından başlangıçta, yazıldığı gibi okunmalıdır; çalıştırıldığı sıraya göre okumaya çalışmak yanlış yaklaşımdır.
      Önceki geliştirici BakePizza fonksiyonunu yazdıysa pizzanın düzgün pişeceğini varsayıp sonraki satıra geçebilirsiniz. Restoranın nasıl işlediğini anlamaya çalışırken fırın sıcaklığı gibi ayrıntılara takılırsanız, restoranın nasıl döndüğünü de anlayamaz, doğru fırın sıcaklığını da unutursunuz.
    • Bu yüzden projectional code editor gibi daha iyi araçlara ihtiyacımız var.
      Editörde fonksiyonları geçici olarak inline eden bir toggle olmalı. Artık ileri geri gitmeye gerek kalmaz.