1 puan yazan GN⁺ 2023-11-16 | 1 yorum | WhatsApp'ta paylaş
  • Koşullu dallanmayı çağıran tarafa çekmek ve yinelemeyi toplu işlem tarafına itmek olmak üzere iki kod yapılandırma kuralı temel noktadır
  • if yukarı taşındığında önkoşulları türlerle veya assert’lerle zorunlu kılmak kolaylaşır; karmaşık kontrol akışı tek bir yerde toplanarak yinelenen koşulları ve ölü dalları bulmak kolaylaşır
  • Bir enum oluşturup hemen ardından tekrar match etmek, aynı koşulu dallanma, veri yapısı ve yeniden dallanma olarak tekrar eder; bu yüzden foo(x) ve bar(y) çağrılarına sadeleştirme payı vardır
  • for aşağı indirildiğinde tek tek nesneler üzerinde yinelemek yerine toplu işlem temel birim olur; başlangıç maliyetinin amorti edilmesi, işlem sırasının değiştirilmesi, vektörleştirme ve struct-of-array optimizasyonları mümkün hale gelir
  • Koşulu döngünün dışına almak hot loop’taki dallanmaları azaltabilir ve TigerBeetle’da olduğu gibi kontrol düzlemindeki karar verme maliyetini veri düzlemindeki toplu işlemle amorti eden yapılara da uzanır

if’i yukarı taşımak

  • Bir fonksiyonun içindeki if koşulu, önce çağırana taşınıp taşınamayacağı açısından bakılması gereken bir şeydir
    • Örnekte, Option<Walrus> alıp None ise dönen bir fonksiyon yerine doğrudan Walrus alan bir fonksiyonun daha iyi bir biçim olduğu kabul edilir
    • Önkoşulu fonksiyonun içinde kontrol edip “hiçbir şey yapmamak” yerine, çağıranın bunu kontrol etmesi ve tür ya da assert ile geçerli olmasını zorunlu kılması daha açıktır
  • Önkoşul denetimini yukarı taşıyan refactoring, çağrı yolu boyunca yayılabilir ve sonuçta toplam denetim sayısını azaltabilir
  • Kontrol akışı ve if, karmaşıklığı artırır ve kolayca hataların nedeni olabilir
    • if yukarı taşındığında karmaşık dallanma mantığı tek bir fonksiyonda toplanır; asıl iş ise doğrusal alt rutinlere iner
    • Karmaşık kontrol akışı, dosyanın geneline dağılmış haldeyken değil, tek ekrandaki tek bir fonksiyondayken yinelenen ve ölü koşulları fark etmek daha kolaydır
  • dissolving enum” refactoring’i, dallanmayı veri yapısına dönüştürüp sonra tekrar dallanma desenini ortadan kaldırma yöntemidir
    • f() koşula göre E::Foo(x) veya E::Bar(y) oluşturuyor, g(e) de tekrar match ile foo(x) veya bar(y) çağırıyorsa aynı koşul birden fazla biçimde tekrarlanır
    • Koşul main() içine çekildiğinde if condition { foo(x) } else { bar(y) } biçimine sadeleşir

for’u aşağı indirmek

  • Veri odaklı bakışta programlar ve hot path’ler çoğunlukla çok sayıda nesneyle uğraştığı için, nesnelerin toplu işlenmesini tanıtıp toplu işlemi varsayılan durum haline getiririz
    • Tek tek nesneler için skaler sürüm, toplu işlemin özel bir durumu olur
    • for walrus in walruses { frobnicate(walrus) } yerine frobnicate_batch(walruses) daha iyi bir biçimdir
  • Başlıca fayda performanstır
    • Tüm toplu işi bir seferde ele almak başlangıç maliyetini amorti edebilir
    • İşlem sırasını esnek biçimde değiştirmek ve tüm varlıkların önce tek bir alanını işleyen vektörleştirme ya da struct-of-array yaklaşımı da mümkün olur
    • Uç bir örnek olarak Vectorized Interpreters Talk bağlantısı verilir
  • İlginç bir örnek olarak FFT tabanlı polinom çarpımı geçer
    • Polinomları birden fazla noktada aynı anda değerlendirmek, tek tek nokta değerlendirmesini defalarca yapmaktan daha hızlı olabilir
  • if ve for kuralları birlikte uygulanabilir
    • İyi biçim, if condition dışında dallara göre for walrus in walruses çalıştıran yapıdır
    • Kötü biçim, for döngüsünün içinde her seferinde if condition değerlendiren yapıdır
    • Koşulu yeniden değerlendirmekten kaçınabilir, hot loop’tan dallanmayı kaldırabilir ve vektörleştirmeyi mümkün kılabilir
  • Bu desen hem mikro hem de makro düzeyde geçerlidir
    • TigerBeetle’ın mimarisi, veri düzleminde nesne topluluklarını aynı anda işleyerek kontrol düzleminin karar verme maliyetini amorti eden bir yapıdır
    • for tavsiyesinin ana motivasyonu performanstır; ancak jQuery gibi koleksiyon düzeyinde çalışan yaklaşımlar ifade gücüne de yardımcı olabilir

1 yorum

 
GN⁺ 2023-11-16
Hacker News görüşleri
  • Başta bu yazının bu kadar tepki almasına şaşırmıştım; ama bunun veri odaklı tasarım tavsiyesi olduğunu hatırlayınca anladım
    Bu forumdaki birçok kişi gibi ben de çoğunlukla iş amaçlı web uygulamaları geliştiriyorum ve bu bağlamda bu tavsiye saçmalık gibi görünebilir
    Günlük işinizde komut önbelleğini dert etmiyorsanız, bu tavsiyeyi genel olarak göz ardı edebileceğiniz yönündeki sezgisel kural doğru görünüyor
    Bu tavsiyenin ne zaman önem kazandığına dair bir fikir edinmek istiyorsanız Mike Acton’ın “Typical C++ Bullshit” konuşmasına bakmak iyi olur; bu yazı da sanki onun içeriğini daha anlaşılır biçimde toparlamış gibi
    Casey Muratori’nin meseleye bakışına epey katılıyorum; ancak çoğu iş yazılımında performanstan çok değiştirilebilirlik ve doğruluk, yani “zaman içinde programlama” optimize edilmeli

    • Bu tavsiyenin nesne yönelimli programlama için de hâlâ oldukça uygulanabilir olduğunu düşünüyorum
      Geliştiriciler karmaşık iş mantığını sınıf içindeki küçük private metotlara bölerek DRY hâle getirmeye çalışıyor; “if’i yukarı taşı” yaklaşımı ise dallanma mantığının birden fazla metoda dağılmasını engelliyor
      “for’u aşağı indir” de önemli. Pek çok çağrı akışı sonunda pahalı veritabanı sorgularıyla biter ve üst seviyedeki döngünün alt seviyede birden çok DB çağrısı oluşturması yaygındır
      Bu yineleme çoğu zaman SQL’in where koşulu ya da join ile değiştirilebilir; toplama ve filtreleme işlemlerini de çok sayıda nesneyi çekip üzerinde dönmektense DAO’ya yakın bir yere itmek, DB’ye yakın noktada optimize etmeyi kolaylaştırır
      Ancak tüm tasarım ilkelerinde olduğu gibi bunu da dogma gibi uygulamamak, bilinçli karar vermek gerekir
    • Veri odaklı tasarım insanları hep kışkırtıyor gibi. Bunun, mevcut baskın nesne yönelimli yaklaşımların büyük kısmının yanlış olduğu imasını taşımasından kaynaklandığını düşünüyorum
      Değiştirilebilirlik ile performansı birbirinin karşıtı olarak görmek üzücü. Gerçekte ikisinin çatıştığına dair ikna edici bir kanıt henüz görmedim
    • Değiştirilebilirlik, doğruluk ve performans birbirini dışlamaz; aksine çoğu zaman birlikte hareket eder
      Performansın en önemli yanlarından biri küçük tutmaktır. Küçük kod, küçük veri yapıları, daha az çalıştırılan komut sayısı esastır; “komut önbelleğini düşünmek” de özünde küçük kod yazmaktır
      Kod küçüldükçe bug’ların girebileceği alan azalır, tek seferde anlaşılması kolaylaşır ve test kapsamı sağlamak da kolaylaştığı için doğruluk açısından avantajlıdır. Değiştirilebilirlikte de kod küçüldükçe değişiklik küçülür
      Elbette paralelleştirme, önbellekleme ve düşük seviye optimizasyon gibi kodu karmaşıklaştıran optimizasyonlar da vardır; ama bunlar performans optimizasyonunun yalnızca bir parçasıdır ve performansı ciddiye alan bir programcı profil çıkarma ve analiz yapmadan bunlara girişmez
      if/for ile for/if işlevsel olarak çok farklı olmasa da biri daha hızlı olabilir; maliyetsiz biçimde doğru olanı kullanabiliyorsanız özellikle yavaş olanı seçmek için bir neden yoktur
    • John Ousterhout’u dinlerseniz, pratikte değiştirilebilirlik ve doğruluk performansla ilk düşünüldüğü kadar çatışmaz
      Basit programların çağrı yığını kısadır; karmaşık ve pahalı soyutlamalardan kaçındıkları için daha az bellek kullanma ve daha hızlı çalışma eğilimindedirler
      En üst düzey performans hedeflendiğinde program karmaşıklaşabilir; fakat gerçek sadelik çoğu zaman makul performansı beraberinde getirir
      “for’u aşağı indirmek” daha çok veri odaklı bir tavsiyedir; “if’i yukarı taşımak” ise programı daha basit hâle getirmeye daha yakındır. Daha doğrusu kaynak kod yerelliğini artırmaktan söz eder ve dallanma mantığını tek yerde toplamak esastır: https://loup-vaillant.fr/articles/source-of-readability
    • İş uygulamalarında da bu iki tavsiyenin epey uygulanabilir olduğunu düşünüyorum
      Özellikle “döngüyü aşağı indir” CRUD uygulamalarında güçlüdür. Oluşturma ve güncellemeleri mümkün olduğunca toplu işlemek, çoğu zaman CPU darboğazından çok daha büyük zaman kazancı sağlar
      items.map(insertToDb/postToServer) ile insertToDb/postToServer(items) arasındaki fark neredeyse her durumda birkaç basamaklık olur
      Bu tür optimizasyonlarla iş sürelerinin saniye/dakika ölçeğinden milisaniye ölçeğine indiğini gördüm; çoğu zaman API de daha temiz oluyor, loglar da daha okunur hâle geliyor
  • Deneyimim arttıkça, fazla sayıda programcının küçük ölçekteki “güzel kod”a önem verdiğini ama tüm kod tabanı tasarımına yeterince önem vermediğini hissediyorum
    Kısa ve iyi cilalanmış fonksiyonlar güzel, ama bu tür yazılar PR’larda veya tartışmalarda verimsiz bisiklet kulübesi tartışmalarına dönüşebiliyor
    Bir fonksiyonun biraz dağınık olup olmadığı, if ve for’un nereye konduğu, map ve filter kullanılıp kullanılmadığı; fonksiyon adı uygunsa, arayüz ve tipler ifade gücü yüksekse, amaç netse, dokümantasyonu varsa ve yan etkiler aşırı kullanılmıyorsa beni pek ilgilendirmiyor

    • “if’i yukarı taşı” demek bisiklet kulübesi tartışması değil; aksine, senin de dediğin gibi karar verilmesi gereken mimari bir tercihe daha yakın
      Bir girdi geldiğinde doğrulamanın nerede yapılacağını düşünürsek, bu sezgisel kurala göre girdinin alındığı en üst noktada doğrulamak gerekir
      Bu, kodu anlamayı da kolaylaştırır; ispat perspektifinden de ön koşulların yukarı doğru yayılması gerektiği için teknik olarak da gereklidir
      İlk tavsiye kesinlikle önemsiz bir tartışma değil; ikincisi ise biraz daha muğlak
    • Güvenlik alanında çalışırken FAANG ölçütleriyle L7~L9 seviyesinde birçok teknik lider ve mimarla karşılaştım; “doğru tasarım”a en çok takılanlar genelde 5~10 yıl deneyimli kişiler oluyordu
      Junior’ların kendi ayağına sıkmasını engellemeye çalışırlar, ama karmaşıklığın maliyeti ve kodun uzun vadede nasıl değiştiğine dair sezgileri çoğu zaman henüz yeterli değildir
      Buna karşılık 20 yılı aşınca, çoğu teknik unsurdan çok basitliğe daha fazla değer vermeye başlarsınız
      “Ne kadar erken dallanmalıyım?”, “Bu kodun gerçekten yapması gereken şey ne?” gibi sorular uzun vadede en değerli cevapları verme eğilimindedir
      Soyutlama ve kapsüllemeyle ilgili sorular ise daha çok yukarıda bahsedilen türden tartışmalara sürüklenir; “büyük resme” bakan insanların kodlarında en çok güvenlik sorunu çıkmasının nedeni de, aslında o iyi tasarlanmış kod tabanının ne yaptığını iyi anlamamalarıdır
    • Bu tür önemsiz gibi görünen ilkeler önceden belirlenmişse, PR’larda ve tartışmalarda aynı tip yıpratıcı tartışmalar azalabilir
      Veri odaklı tasarımın iyi uyduğu performansa duyarlı yazılımlarda, bu küçük ölçekli şeylere daha fazla dikkat etmek gerekir. Çünkü derleyici optimizasyonları bu şekilde çalışır
      O zaman oyunun kuralları değişir. İfadelerin anlamı önem kazanır, kendini açıklayan kod daha anlamlı hale gelir, kod yorumları yalnızca çıkarım yapmak için kalır ve “dokümantasyon” daha çok spesifikasyon ve kullanıcı kılavuzuna yaklaşır
    • Tersini de çok gördüm. Aşırı tasarlanmış katedraller tasarlamayı sevip algoritmaların düşük seviyeli verimliliğini düşünmeyen de çok kişi var
    • Bunun bir nedeni, programlama dillerinde fonksiyonlardan çok daha büyük bileşenleri ya da fonksiyonlardan farklı şekilde bağlanan bileşenleri anlamlı biçimde ifade etme yollarının yetersiz olması
      En fazla fonksiyonları gruplayabiliyoruz; sonrasında gerisini kendiniz halletmek zorundasınız
  • “if’i yukarı taşıma”nın bir dezavantajı, fonksiyon tanımı içinde ön koşulların ve son koşulların doğrudan görünmemesi ve her çağrı noktasında kontrol edilmek zorunda olunmasıdır
    Birden çok kişinin yer aldığı büyük projelerde, böyle bir fonksiyon amaçlanan bağlamın dışında yeniden kullanılıp hatalara yol açabilir
    Sözleşme framework’ü kullanırsanız bu çözülebilir, ama koşulları hem sözleşmede hem kodda iki kez yazmanız gerekir; bağımlı tiplerde de durum aynıdır
    Belirli bir bağlama ait kod bölgelerini etiketlemek ve yalnızca o bağlam içinde çağrılabilen fonksiyonlar tanımlamak ilginç bir yaklaşım
    Python’da @requires_context("VALIDATED_XY") gibi bir dekoratör ve validated_xy context manager ile fonksiyonun yalnızca doğrulanmış bir bölgede çağrılmasını sağlayabilirsiniz
    Runtime bu bağlamın anlamını bilmez; ama araçlar ve testlerle, istenen bağlamın yalnızca belirli koşullar sağlandığında kurulacağı şekilde tasarım yapılabilir
    Haskell gibi dillerde identity monad benzeri şeylerle tip seviyesinde zorunlu kılınabilir; tip seviyesinde zorunlu kılma olmasa bile “unsafe” kod bölgelerini korumanın ilginç bir yolu olabilir

    • Yazarın argümanının ikinci kısmını kaçırmış gibisiniz. Söylenen şey “ön koşul kontrolünü çağırana itebilir ve bunu tiple zorunlu kılabilirsiniz
      Dolayısıyla ön koşul hâlâ fonksiyon tanımında doğrudan görünür; yalnızca if deyimi olarak değil, tip imzasının bir parçası olarak ortaya çıkar
      Bu, yazıda kullanılan Rust’ta yaygın bir örüntüdür ve if kontrolünden farklı olarak runtime’da değil compile-time’da doğrulanan katı bir ön koşuldur; karşılanmazsa program derlenmez
    • Python’da da bunu tiplerle ifade edebilirsiniz
      Örneğin do_something(position: ValidatedPosition) şeklinde alır, normal bir Position’ı doğruladıktan sonra ValidatedPosition’a çevirip geçirirsiniz
      Pratikte doğrulamayı ValidatedPosition constructor’ı içinde kapsüllersiniz, ama asıl nokta şu: Position’ı olduğu gibi geçirmeye çalışırsanız mypy size yanlış tip verdiğinizi söyler
      Python’un tip denetimi Rust kadar kapsamlı değil, ama aktarılan verinin uygun şekilde işlendiğini garanti etmek istediğimizde giderek daha kullanışlı hale geliyor
    • “if’i yukarı taşı” tavsiyesi ön koşul kontrolüyle değil, doğru kod yolunu seçmekle ilgilidir
      Ön koşulu olan bir fonksiyon varsa elbette fonksiyonun başında assertion koyabilirsiniz. Örneğin Java tip sistemi null’a izin verdiği için nesne gerektiren bir fonksiyon null gelirse exception fırlatmalıdır
      Her çağrı noktası, fonksiyonu yalnızca ön koşul doğru olduğunda çağırmaktan sorumludur. Bu, ön koşulun tanımı gereği zaten böyledir
      Ön koşulu ihlal ederek fonksiyonu çağırmak çağıranın hatasıdır. Fonksiyon içinde bunu kontrol edip tanımsız davranışı önleyen kod gerekebilir; ancak bu tür assertion’larla programın gerçek kontrol akışını ayırmak gerekir ve yazı ikincisiyle ilgilidir
    • Bu örnekteki fn frobnicate(walrus: Walrus) için, sahip olunan bir Walrus olmayan bir şeyi geçirirseniz program derlenmez
      Generic olsa bile argümanın trait bound’ları sağlaması gerekir; fonksiyon içinde argümanın nasıl kullanıldığına bağlı olarak derleyici, fonksiyon tanımı için gerekli bound’ları ister
    • public ve private’ın amacı da bir ölçüde kodu belirli bağlama göre etiketlemek değil miydi diye düşünüyorum
      Ya da public, private ve .NET ekosistemindeki protected’ın kapsadığı alanları kesen daha özel semantiklere ihtiyaç olabilir
  • İlk örnek if ve for yüzünden değil, başka bir nedenle kötü
    Genel olarak bir şeyin “container”ı varsa, fonksiyonu container’a değil, onun içindeki domain düzeyi “nesne”ye yazmak daha iyidir
    Clojure’da agent kullanırken agent’a yönelik fonksiyonlar yazılmaz; agent’ın taşıyabileceği nesneye yönelik fonksiyonlar yazılır
    Elixir’de de çekirdek domain fonksiyonları PID’ye değil, içerideki domain veri yapısına göre çalışır; gerektiğinde GenServer çağrısı ona devredilir
    Bu, daha esnek olur ve çekirdek domain olan “Walrus’u frobnicate etmek” ile uygulama kaygısı olan “Walrus olabilir de olmayabilir de; varsa frobnicate etmek” arasını daha temiz ayırır

    • Verilen tavsiye, doğrulama mantığını fazla yukarı taşıma ihtimali taşıyor gibi görünüyor
      Erken doğrulama yapmak iyi, ama fonksiyonun garip bir hatayla aniden patlaması yerine doğrulama hatasını açıkça vermesi de önemli
      Haskell bu sorunu newtype ile çözer; “uygun doğrulamanın zaten yapıldığını onaylayan şeffaf bir container” sunar
      İnsanlara gerçekten vurgulamak istediğim tavsiye “üzgün if”leri tercih etmeleri. Normal yolu kat kat iç içe geçirmektense, hatalı koşulları en başta tek tek kontrol edip hemen düzeltmek ya da durmak neredeyse her zaman daha okunabilir ve bakımı daha kolay koddur
      İnsanların beklenen duruma odaklanma eğilimi vardır, ama kod bunun tersine önce istisnai durumlara odaklanmayı ister. Her if zihinsel yük yaratır; dış sistemlerden bilgi almak ya da hata nedeniyle erken çıkmak gerekiyorsa, tespit ve işleme yan yana olduğunda bu yükü hemen bırakabilirsiniz
    • İlk örneğin walrus.frobnicate() olsaydı daha iyi olacağı kastediliyorsa, bu yazarın anlatmak istediği özden çok sözdizimi tercihi meselesi gibi geliyor
  • Bunun kötü tavsiye olduğunu söyleyecek değilim, ama mutlaka iyi tavsiye de değil
    Seçilen dilin Rust olması epey şey söylüyor. Güçlü tip sistemi, başka dillerde gereken birçok savunmacı programlamanın önüne geçer
    Bir C programcısı fonksiyona geçirilen pointer’ın geçerliliğini kontrol etmeyip NULL dereference’a yol açıyorsa, böyle biriyle ekip olmak istemem
    Bu yüzden en azından bazı ifler kesinlikle aşağıda olmalı; hataların yukarı doğru iyi yayıldığı bir yaklaşım da iyi olur
    for konusunda o kadar güçlü hissetmiyorum, ama C’de dizi argümanlarının pointer’a decay etmesi nedeniyle yinelemenin de aşağıda değil yukarıda olması gerektiğini düşünüyorum. Dizinin oluşturulduğu fonksiyonda uzunluğa güvenebilirsiniz, ama argüman olarak geçirilen fonksiyonda güvenemezsiniz

    • C arayüzleri beklentileri dikkatle belgelemeli ve yalnızca o kadarını kontrol etmeli
      Dokümantasyon güçlü tip sisteminin yerini almalı; runtime kontrolleri değil
      NULL kontrolleri ve diğer savunmacı önlemlerle dolu kod çok daha zor okunur
      Kütüphane sınırlarında daha savunmacı kontroller gerektiği söylenebilir; bu yazı da tam olarak bu kontrolleri yukarı itmeyi söylüyor
      Güvenlik açısından kritik kod farklı olabilir, ama çoğu durumda kazara NULL dereference’ların testler, sanitizer’lar ve fuzzing ile yakalanması yeterlidir
    • C’de NULL çoğu zaman geçerli ama dereference edilemeyen bir pointer değeridir
      Geçersiz pointer değerini kontrol etmek için olası sayısız hatalı değerin tümünü kontrol etmeniz gerekir; yalnızca NULL’a bakıyorsanız aslında geçersiz değeri kontrol etmiş olmazsınız
      Bir fonksiyonun önkoşulu “p parametresi NULL olamaz” ise kontrol edebilirsiniz. Ama önkoşul “p geçerli bir pointer olmalı” ise uygun assertion koşulunu bulmada bol şans
    • Benim sezgisel kuralım şu: Tip sistemi hatalı değerleri engelleyemiyorsa, bunu runtime’da engelleme sorumluluğu vardır
      Son zamanlarda çok T-SQL kullanıyorum; parametreleri ya da değişkenleri NOT NULL olarak tanımlayamıyorsunuz
      Bu yüzden makul olan en erken noktada, genelde stored procedure’ün en başında NULL kontrolü yapmak iyi olur
      Aksi halde NULL, çağrı katmanlarının derinlerine beklenmedik biçimde yayılıp daha belirsiz sorunlar yaratabilir
      Neyse ki tablo verileri NOT NULL olarak tanımlanabildiği için bu tür bug’lar genelde veriyi bozmaz, ama erken yakalamak çok daha kolaydır
      Ancak bazı parametre değerlerine göre DB’ye yazan bir mantık varsa ve o değer beklenmedik şekilde NULL ise, yanlış değer yazılabilir ya da gereken değer hiç yazılmayabilir; bu da fiilen veri bozulmasıdır
      Dolayısıyla cevap savunmacı programlamadır
  • Uygun bağlam olmadan bakınca bu tavsiye oldukça tuhaf, hatta belki kötü tavsiye
    Döngüler ve if ifadelerinin ikisi de kontrol akışı işlemleridir; bu yüzden yazıdaki bazı argümanlar pek anlamlı değil
    En güçlü gerekçe performans gibi görünüyor, ama performans genelde en son ilgilenilecek konu olmalı; özellikle de sezgisel bir tavsiyeyse bence daha da öyle
    Ne yazık ki yazar bunu bir slogana dönüştürmüş; umarım yayılmaz

    • Performans bir argüman olabilir ama güçlü bir argüman değil
      Yazarın örneğindeki gibi iyileştirilmiş kod yazılabiliyorsa, koşul döngü çalışırken sabittir. Her seferinde pahalı bir koşul değerlendirmiyorsanız branch prediction çoğunu halleder
      Koşul const değerlerden oluşan bir boolean expression ise compiler’ın bunu anlaması da mümkün
  • Yazının tamamının şöyle bir örnekle devam edeceğini sanmıştım: koşula göre walrusları ayırıp frobnicate_batch(fwalrus) ve transmogrify_batch(twalrus) çağırmak
    Ama gerçekte tek bir koşulla tüm döngüyü iki dala saran bir örneğe gitti

  • Yazılım mühendisliği geçmişinden gelen programcıların bunu sık sık yanlış yapması şaşırtıcı
    Ben programlamaya bilim alanında başladım; orada böyle şeyleri düşünmek kesinlikle gerekliydi
    Döngü sırasını yanlış seçmek bile bir simülasyonun 1 saat yerine 1 hafta sürmesine yol açabilir
    Bu geçmiş sayesinde for ve if sırasını uygun şekilde ayarlayıp küçük optimizasyonları içgüdüsel olarak yapıyorum; böyle yapılmamış kod bana düpedüz yanlış görünüyor

  • Bu tür genel kuralların gerçek koda gerçekten uygulanıp uygulanamayacağından emin değilim.
    Bu tür kurallar çoğu zaman yanlış yere konmuş bir dogma gibi görünüyor; blog yazısı bunun bir sezgisel kural olduğunu söyleyerek başlasa bile genç programcılar bunu her zaman öyle algılamıyor.
    Birkaç hafta önce YouTube bana sürekli “I'm a never-nester” adlı bir video öneriyordu; sanırım asla if iç içe yazılmaması gerektiğini savunuyordu ve epey gülünçtü.
    Yazıdaki örnekte olduğu gibi, for içinde if condition bulunan kodu “kötü” diye işaretlemişti; ama pratikte bu şekilde yazılmış kodların çoğunda condition, walrus'a bağlı olduğu için if yukarı taşınamaz.
    Eğer taşınabiliyorsa, aynı koşulun her döngüde yeniden değerlendirildiği o kadar barizdir ki programcılar bunu doğal olarak kaçınmaya eğilimlidir.
    Ama bir junior ya da öğrenci dogma gibi duyulan bir tavsiyeyi okursa, onu katı biçimde uygulamaya çalışıp daha kötü kod üretebilir.

    • “Asla iç içe yazma” konusunda özellikle dogmatik değilim, ama söz diziminin izin verdiği varsayımıyla match (condition_a, condition_b) kullanarak dört durumu ayırmanın iç içe iflerden daha kötü olduğu bir durumu deneyimsel olarak görmedim.
    • Bence bu, aksine ifin “yukarı taşınması” gereken iyi bir örnek.
      Kodun amacı koşula göre walrus üzerinde belirli bir işlem yapmak; aslında if, polimorfizm ve tip sistemi yerine kullanılıyor.
      Walrus neden farklı senaryolarda çağrılması gereken iki ayrı fonksiyona sahip olsun? Tek bir fonksiyon ve iki tip olsun, doğru tipi geçirmek yetmez mi?
      Mevcut yapıda bile, yukarıda koşula göre işlem fonksiyonunu seçip aşağıdaki döngüde seçilen fonksiyonun her seferinde çağrılmasını sağlayabilirsiniz.
      Kararı mümkün olduğunca erken verirseniz bunu kodun geneline saçmanız gerekmez. Kodun iç kısmı dallanma olmadan her seferinde aynı işi yapar; çıktı ise yalnızca yapılandırma grafiği üzerinden değişir.
      Elbette bu yeni bir fikir değil; 15 yıl önce bile zaten eski bir fikirdi: https://www.youtube.com/watch?v=4F72VULWFvc
    • “GOOD” refaktörü yalnızca koşul walrus'a bağlı olmadığında çalışır ve bu gerçeği açıkça ortaya koymaya yardımcı olur.
      Burada “for'u aşağı indir” kuralını yeniden uygularsak, if condition içinde frobnicate_batch(walruses) ya da transmogrify_batch(walruses) çağıran bir biçime dönüşür.
    • Böyle yazıların var olmasını iyi buluyorum.
      Birkaç kez karşılaştığım ama düzgün ifade edemediğim bir sorunu biri kelimelere dökmüş; zihinde tutmaya değer iyi bir model gibi görünüyor.
      Aynı zamanda bu tür eleştiriler de değerli. Dogmatik bir junior geliştiricinin ikisini birlikte okuyup biraz daha nüanslı karar verebilmesini umarım.
    • Tüm “sezgisel kurallar” ve “en iyi uygulamalar”dan öğrenilmesi gereken asıl şey, arkalarındaki nedendir.
      Programcılar özellikle bunları kopyalayıp mekanik biçimde uygulamamalı.
      Sezgisel kuralları ve en iyi uygulamaları körü körüne uygulamak iyi bir fikir olmayabilir; nihayetinde her şey “duruma bağlıdır”.
  • Bu tür sezgisel kuralların içinde genelde bir bilgelik kırıntısı vardır, ama çoğunlukla yeni programcılarda yeniden dogmadan arındırılması gereken bir şey yaratır.
    Fazla katı takip etmeye çalışınca işlerin daha kötüye gittiği durumlar her zaman çoktur; “bu tavsiyeyi ne zaman dinlememen gerektiğini bil” aslında işin temel zorluğudur.

    • Dogmadan arındırma zaten gereklidir.
      Bu kuralların üzerinde oynamak için faydalı olduğunu düşünüyorum. Benimseyip deneyin, sonuna kadar zorlayın, bir gün ya da bir yıl kadar tersine çevirin, nereye kadar gittiğini görün.
      Sınırları öğrenmek de sürekli üzerine yazılan bir parşömenin üstüne eklenen bir başka malzemedir.
    • Bu yazı, daha büyük bir derleme içinde bir koan olarak yararlı olabilir.
      O koanların bazıları birbiriyle çelişmelidir.