2 puan yazan GN⁺ 2025-03-12 | 1 yorum | WhatsApp'ta paylaş
  • Kod mantıksal olarak çok iyi olsa bile uzun süre okunması zor olabilir; bu yazı bu yorgunluğun kaynağını gözle görülen karmaşıklıkta arıyor
  • Halstead Complexity Metrics, operatör ve operand sayılarını sayarak Volume ve Difficulty hesaplar; aynı davranışta bile daha çok değişken ve operatör içeren uygulamayı daha zor kabul eder
  • SonarSource’un Cognitive Complexity metriği, kısaltılmış söz dizimleri, doğrusal akışın kesilmesi ve iç içe kontrol akışları merkezinde koşulların, döngülerin, istisna işlemenin, mantıksal operatör birleşimlerinin, özyinelemenin ve gotonun yükünü değerlendirir
  • Değişkenler tarafında değişken gölgeleme (variable shadowing), benzer adlar, uzun değişken yaşam aralıkları ve alışılmadık kullanım kalıpları, okurun veri akışını izleme maliyetini artırır
  • Küçük fonksiyonlar, tanıdık kalıplar, uzun zincirlerin gruplanması, basit koşul ifadeleri, sınırlı goto, sığ iç içelik, ayırt edilebilir değişken adları ve kısa değişken yaşam aralıkları; dil ve biçimlendirmeden bağımsız uygulanabilir okunabilirlik ölçütleri hâline gelir

Kod okunabilirliği metriklerinin sınırları ve hedefi

  • Kod okunabilirliği için yaygın kullanılan ve üzerinde uzlaşılmış tek bir metrik yok
  • Başvurulabilecek kaynaklar, pratikte yaygın kullanılmayan akademik makaleler ve görüşlere yakın metinler olduğundan, kod incelemelerinde doğrudan kullanılabilecek daha somut bir tartışma aracına ihtiyaç vardı
  • Amaç yeni bir metrik icat etmek değil, kodun kolay okunup okunmadığını konuşurken herkesin kullanabileceği görsel kalıpları toplamaktı
  • İncelenen ölçüm ve fikirlerin şu koşulları sağlaması istendi
    • Kaynak kod parçalarına veya tek bir fonksiyona uygulanabilir olması
    • Cyclomatic Complexity gibi uygulanan algoritmanın kendisinden ayrıştırılması zor olan temel karmaşıklığa odaklanmakla sınırlı kalmaması
    • Değişken adı uzunluğu, boşluklar, girintileme, parantez yerleşimi gibi yüzeysel stil unsurlarında kalmaması

Halstead Complexity Metrics

  • Maurice Halstead, 1970’lerin sonlarında kaynak kodun ampirik ölçümlerini oluşturmak için Halstead Complexity Metrics’i önerdi
  • Bu metrik dil ve platformlardan bağımsız uygulanabilir; uygulanan algoritmanın kendisinden çok kodun hangi biçimde yazıldığına odaklanır
  • Temel ölçümler operatörler ve operandlar üzerinden dört sayıdır
    • Benzersiz operatör sayısı n1
    • Benzersiz operand sayısı n2
    • Toplam operatör sayısı N1
    • Toplam operand sayısı N2
  • Halstead bunlara dayanarak programın length, volume, difficulty gibi ilgili metriklerini oluşturdu ve hatta uygulamada yer alan hata sayısını tahmin eden değerlere ulaşmaya çalıştı
  • Sezgisel olarak operatör sayısı arttıkça dikkate alınması gereken potansiyel etkileşimler artar; operand sayısı arttıkça veri akışı olasılıklarını anlamak zorlaşır
  • JavaScript örneği

    • Aynı tek-çift belirleme fonksiyonunda bile if ve return kullanan basit bir uygulamada operatör ve operand sayısı daha azdır
    • 4 benzersiz operatör, 7 toplam operatör
    • 5 benzersiz operand, 6 toplam operand
    • Volume 33.30, Difficulty 2.50
    • Dizi, Number, karşılaştırma ifadesi ve indeks kullanan uygulamada daha fazla operatör ve operand vardır
    • 7 benzersiz operatör, 10 toplam operatör
    • 9 benzersiz operand, 12 toplam operand
    • Volume 71.35, Difficulty 3.75
    • İlk uygulama gözle bakıldığında da daha basittir; Halstead’in Volume ve Difficulty değerleri de bunu destekler
    • Dezavantajı, her dilde neyin operator ve operand sayılacağının açık olmamasıdır; ölçüm için belirli bir araç veya uygulama seçip tutarlı kullanmak daha iyidir
  • Halstead’den çıkarılan pratik kalıplar

    • Fonksiyon ne kadar küçük ve değişken sayısı ne kadar azsa genel olarak okunması o kadar kolaydır
    • Dile özgü operatörler veya söz dizimi şekeri okura ek yük getirir; bu yüzden aşırı kullanmamak daha iyidir
    • map, reduce, filter gibi fonksiyonel bileşenler, lambda’lar, iteratörler ve comprehension’lar uzun zincirler hâlinde bağlandığında kısa görünse bile okunabilirlik düşebilir
    • Bu tür uzun zincirler JavaScript ve Rust’ta veya Python’da itertools’a yoğun biçimde dalmış kodlarda daha sık görülebilir

Cognitive Complexity’nin gördüğü okuma zorluğu

  • SonarSource tarafından oluşturulan Cognitive Complexity, okuma zorluğunu daha doğru yakalamaya yönelik bir metriktir
  • Bu metriğin temel fikri üç noktadan oluşur
    • İfadeleri birleştiren kısaltılmış söz dizimleri zorluğu azaltır
    • Doğrusal akıştan her sapma zorluğu artırır
    • İç içe kontrol akışları zorluğu artırır
  • Adının bilimsel veya nesnel bir metrik gibi duyulduğuna dair eleştiriler olsa da pratikte işe yarayan bir sezgisel kural olarak görülebilir
  • Kısaltılmış söz dizimlerinde yoğunluk sorunu

    • if (a != null) { myObj = a.myObj; } biçimi yerine MyObj myObj = a?.myObj; gibi kısaltılmış söz dizimi daha kısadır ve daha az okuma süresi gerektirir
    • Ancak iki kod gerçekte tamamen aynı olmayabilir
    • İlkinde myObj, a.myObj veya null olur
    • İkincisinde myObj, a.myObj veya undefined olur
    • TypeScript veya Rust gibi güçlü tip denetimi olan diller de yalnızca eksik kalma olasılığını azaltır; her durumun doğru ele alınacağını garanti etmez
    • Düz JavaScript gibi tip denetimi desteğinin zayıf olduğu yerlerde bu tür köşe durumlarının ele alınmama olasılığı daha yüksektir
    • Kısaltılmış söz dizimleri yazması ve okuması kolay olabilir, ancak kısalık ve yoğunluk arasında bir ödünleşim vardır
  • Doğrusal akışı bozan unsurlar

    • Koşul içermeyen doğrusal kod, koşullu koda göre gözle taraması daha kolaydır
    • Cognitive Complexity, koşullar, döngüler ve gotonun yanı sıra koşullu makroları, try/except bloklarını, mantıksal operatör dizilerini ve özyinelemeyi de zorluk artırıcı unsurlar olarak görür
    • switch tek bir grup olarak hesaplanır, ancak else-if zincirlerinde her ek else-if daha zor kabul edilir
    • Çünkü else-if her dalda birden fazla karşılaştırma yapabilir
    • Bununla birlikte switchteki fall-through ve eksik break de okuma zorluğunu artırabilir
    • Koşul ifadesi içinde aynı mantıksal operatörün art arda kullanılmasıyla &&, ||, ! operatörlerinin karıştırılması farklı zorluk seviyeleri yaratır
    • debug || verbose || consoleMode basit bir koşul ifadesidir
    • debug || (verbose && consoleMode) operatör karışımı nedeniyle daha zor okunur
    • debug || !(verbose && consoleMode) olumsuzlama da içerdiği için daha karmaşık hâle gelir
  • İstisna işleme ve goto

    • try/catch, Cognitive Complexity’de zorluğu artırır; ancak birden fazla catch bloğu tek bir catchten daha zor sayılmaz, try ve finally ise yok sayılır
    • İstisna fırlatma eyleminin kendisi de okuma maliyeti yaratabilir
    • İstisna işleme fonksiyon sınırlarını aştığında ilgili fonksiyonların karmaşıklığı birbirine dolaşır
    • Okur, ilgili istisnanın nerede yakalandığını bulmak zorunda kalır
    • goto genellikle zorluğu artıran bir unsur olarak hesaplanır
    • Ancak hata koşulunda kaynakları serbest bırakıp fonksiyondan çıkmak için kullanılan goto out veya goto done biçimini bazı uzmanlar yararlı görebilir
    • Buna karşılık continue veya break ile ifade edilemeyecek şekilde döngü sınırlarının dışına çıkan goto, okurun yeni bir kontrol akışını yeniden kurmasını gerektirdiğinden okuma yükü yüksektir

İç içelik ve fonksiyon biçimi

  • Koşul ifadelerinin kendisi okumayı zorlaştırıyorsa, iç içe koşullar daha da zordur
  • Cognitive Complexity, koşul ve döngülerin kendi puanına ek olarak her iç içelik seviyesi için ek zorluk verir
  • Bu fikir “Level of Indentation” veya “Bumpy Road” adlarıyla da anılır
  • İç içelik 2 seviyeyi aştığında özellikle okunması zorlaşır; erken dönüşlerle iç içeliği azaltan kod daha düz okunur
  • Bu metrik fonksiyon uzunluğunu doğrudan yansıtmaz, ancak diğer koşullar aynıysa uzun fonksiyon kısa fonksiyona göre daha fazla okuma çabası gerektirir

Değişken adları, yaşam aralıkları ve tanıdık kalıplar

  • Ayırt edilebilir ve açıklayıcı adlar

    • Açıklayıcı adlar kodun ne yapmaya çalıştığını anlamak için önemlidir; yinelenen veya şifre gibi adlar ters etki yapar
    • Değişken gölgeleme (variable shadowing) tehlikelidir
    • Okurun hangi değişkenin kullanıldığını ayırt etmek için kapsam kurallarını düşünmek zorunda kaldığı durumlardan kaçınılmalıdır
    • Görsel olarak benzer tanımlayıcılardan da kaçınılmalıdır
    • i ve j, item ve items gibi gözle kolayca karıştırılabilecek adlar hatalara yol açabilir
    • Bir fonksiyon içinde node, _node, thisNode gibi aynı değişken adının çeşitli varyasyonlarını kullanan kod, bug üretmeye yatkın bir biçimdir
  • Kısa değişken yaşam aralığı

    • Canlı değişken analizi (live variable analysis), bir değişkenin ilk kullanıldığı noktadan son kullanılabileceği noktaya kadar olan aralığa bakar
    • Değişken yaşam aralığı uzunsa okur daha fazla değişkeni ve olası değeri zihninde tutmak zorunda kalır
    • Tüm değişkenleri fonksiyonun en başında tanımlamak yerine gerçek kullanımın hemen öncesinde tanımlamak yaşam aralığını kısaltabilir
    • En kötü durum, bir değişkenin birden fazla fonksiyon boyunca canlı kalıp birçok yerde kullanılmasıdır
    • Birden fazla değişkenin yaşam aralığı fiilen aynıysa bir nesne daha uygun olabilir
    • Nesne uygun değilse okurun değerleri anlamak için okuması gereken fonksiyon ve satır sayısını en aza indirmek daha iyidir
  • Uzun zincirler ve ara değişkenler

    • Fonksiyonel programlama stili değişken yaşam aralıklarını kısaltır, ancak çok uzun zincirler veya iç içe callback’ler okuma yükü oluşturabilir
    • Uzun fonksiyon zincirlerini küçük gruplara ayırmak ve iyi adlandırılmış ara değişkenler veya yardımcı fonksiyonlar kullanmak okurun bilişsel yükünü azaltabilir
    • Ara değişken kullanılan sürüm biraz daha az verimli olabilir
    • Performans araçları ilgili satırın gerçekten darboğaz olduğunu söylemediği sürece bu tür mikro verimlilik farkları önemli değildir
  • Tanıdık kod kalıplarının yeniden kullanımı

    • Tanıdık kod ve değişken biçimlerini yeniden kullanmak, okurun zaten bildiği kalıpları tanımasını sağladığı için okumayı daha az yorucu hâle getirir
    • Bu, Principle of Least Surprise ile bağlantılıdır
    • Kod tabanı içinde koşul yazma biçimi gibi tekrarlanan biçimleri tutarlı tutmak daha iyidir
    • Bir kalıptan sapmak gerektiğinde fark, değişken adı veya yorumla görünür kılınabilir
    • Bu düşünce sonuna kadar götürüldüğünde, tekrarlanan kalıpları okurun yeniden tanımasına gerek kalmaması için şablon fonksiyonlar veya generic fonksiyonlar kullanma yönüne varır

Okunabilirliği artıran 8 görsel kalıp

  • Line/Operator/Operand count: Daha küçük fonksiyonlar ve daha az değişken ile operatör daha kolay okunur
  • Novelty: Fonksiyon biçimlerinde, operatörlerde ve söz dizimi şekerinde yenilikten kaçınıp kod tabanının ortak kalıplarını yeniden kullanmak
  • Grouping: Uzun fonksiyon zincirleri, iteratörler ve comprehension’larda mantıksal grupları yardımcı fonksiyonlar veya ara değişkenlerle bölmek
  • Conditional simplicity: Koşul ifadelerini olabildiğince kısa tutmak; tek bir koşul içinde farklı mantıksal operatörleri karıştırmak yerine aynı operatör dizilerini tercih etmek
  • Gotos: Belirli bir hata işleme kalıbını izlemiyorsa ve alternatifler daha kötü değilse goto kullanmamak
  • Nesting: İç içe mantığı ve büyük girinti değişikliklerini en aza indirmek; derin iç içelik gerekiyorsa bunu büyük bir fonksiyonun içine gömmek yerine ayrı fonksiyona ayırmak
  • Variable distinction: Açıklayıcı ve görsel olarak ayırt edilebilir değişken adları kullanmak, değişken gölgelemekten kaçınmak
  • Variable liveness: Değişken yaşam aralığını kısa tutmak ve özellikle fonksiyon sınırlarını aşan uzun yaşam aralıklarına dikkat etmek

Gerçek kod tabanlarında ortaya çıkan sorun

  • Zihinsel yorgunluğu büyük ölçüde artıran kod tabanı aynı anda birçok anti-pattern içeriyordu
  • Somut olarak uzun fonksiyonlar, çeşitli dil bileşenlerinin karışımı ve yardımcı fonksiyonlara ayrılması gereken çok sayıda fonksiyon zinciri vardı
  • Sonuç olarak büyük fonksiyonların içinde iç içelik karmaşıklığı ve uzun değişken yaşam aralıkları birlikte arttı
  • Kodun ve yazarlarının kalitesi yüksek olmasına rağmen bir veya daha fazla kritik bug bulundu
  • Bunlardan biri kolay görülebilecek bir bug’dı; ancak uzun ve karmaşık bir fonksiyonun ortasında yer aldığı ve akıl yürütmesi zor olduğu için gözden kaçmış olma olasılığı yüksekti
  • Bir ay sonra kendi kodunuzu en çok okuma olasılığı olan kişi yine siz olduğunuzdan, okunması kolay bir biçimde yazmak doğrudan pratik iş maliyetiyle bağlantılıdır

1 yorum

 
GN⁺ 2025-03-12
Hacker News yorumları
  • Makalede map/reduce/filter zincirleri uzun ya da birden fazlaysa okunabilirliği bozduğu söylenmiş; bu, diğer argümanlardan hiç doğal biçimde çıkmıyor
    Alışık olunmadığı için kötü denmesine dair sık rastlanan bir şikâyetin araya sıkıştırılmış hali gibi duruyor; biraz alışınca çoğu zaman alternatiflerinden çok daha kolay okunup yazılıyor
    Örneğin books.filter(book => book.pageCount > 1000).map(book => book.author).distinct() ifadesinden daha okunur bir kod önermenin zor olduğunu düşünüyorum
    Karmaşıklık metrikleri açısından da böyle bir kod neredeyse tüm diğer yöntemlerden daha iyi; fonksiyonel programlamanın temelleri en azından öğrenilmeli. Monad anlatmaya kadar gerek yok ama map ve filterı gelişigüzel küçümsemeyecek kadar aşina olunmalı

    • İfade gereksiz yere sert hissettiriyor ama alıntılanan cümlenin kastettiği örnek böyle kısa bir zincir değildi
      Yaklaşık 5 çağrının arka arkaya geldiği zincirlerden itibaren okumanın zorlaşmaya başladığını düşünüyordu; makaledeki örnek de o uzunluktaydı
      “multiple” ise iç içe zincirlerin olduğu ya da üzerinde işlem yapılan tipin değiştiği durumları kastediyordu; böyle durumlarda okuma hızı düşüyor
      Fonksiyonel bileşenler zarif olabilir ama aşırı da kullanılabilir
    • Örnek, tek bir liste üzerinde kavramsal olarak basit bir filtreden ibaret
      Zincir çok uzayıp koşullar karmaşıklaştığında ve liste/değişken sayısı arttığında, tek seferde kavramak zorlaşır
      Prosedürel döngülerde ara sonuçlara ad verebilirsiniz; bu adlar sayesinde önceki işlemi unutup bir sonraki adıma odaklanabilirsiniz
    • SELECT DISTINCT author FROM books WHERE pageCount > 1000;
    • Örnek fena değil ama asıl şikâyetin çok uzun zincirlere daha yakın olduğunu düşünüyorum
      Kişisel olarak ara sonuçlara ad vermek için zinciri bölüyorum; değişken adlarını yorum gibi kullanmak gibi
      var longBooks = books.filter(book => book.pageCount > 1000)
      var authorsOfLongBooks = longBooks.map(book => book.author).distinct()
    • İnsanların paylaştığı SQL çözümleri de iyi ama Prolog’da şöyle de yazılabilir
      ?- setof(Author, Book^Pages^(book_author(Book, Author), book_pages(Book, Pages), Pages > 1000), Authors).
      Prolog veritabanı yapısına bağlı olarak daha kısa da olabilir
      ?- setof(Author, Pages^(book(_, Author, Pages), Pages > 1000), Authors).
  • İyi kodun temelde epey nitel ve edebi bir yönü olduğunu düşünüyorum
    Matematiksel düşünmeye alışık olup nicel cevaplar isteyen programcılar ya da akademidekiler çoğu zaman bundan rahatsız oluyor
    Dostoyevsky ve Wodehouse’u ikisini de severim; ikisi de harikadır ama yöntemleri çok farklıdır
    Kodlama o kadar açık uçlu bir alan olmasa da, nitelik olarak tamamen farklı hissettiren iyi kod tabanlarıyla çalıştım; yeni bir yazarın üslubuna alışmak gibi, bir kod tabanının tarzını da “sezmek” çoğu zaman zaman alıyor

    • %100 katılıyorum. Programlama konusunda aldığım en iyi iltifatlardan biri “kodun hikâye gibi okunuyor” sözüdür
      Dosyayı yukarıdan aşağı okuyunca anlatı doğal biçimde ilerlesin diye fonksiyon sırasını belirlediğim ve okuyucuya seslenir gibi deklaratif bir uygulama kurduğum anlamına geliyordu
      Saf fonksiyonel programlama paradigmasını izliyorum; bu yaklaşımın daha anlatısal bir stile iyi uyduğunu düşünüyorum
      Fonksiyonların bağımlılıkları/girdileri argümanlarla ya da diğer saf fonksiyonlarla sınırlı, çıktıları da yalnızca dönüş tipinde yer aldığı için karmaşıklığı adım adım göstermek kolaylaşıyor
      Gizli durum gibi karmaşıklıklar barındıran diğer paradigmaların aksine, ironik biçimde matematiksel olarak en kesin paradigmanın aynı zamanda daha anlatısal bir stile de en iyi uyduğunu düşünüyorum
    • Bir fonksiyonun üst düzey amacını okuyup anlamak 5 saniyeden uzun sürüyorsa, bunun kötü kod olduğunu düşünüyorum
      Görünüşü önemli değil; uzun geliştirme deneyimi olmadan makul bir sürede fonksiyonun ne yaptığını anlayamıyorsanız, o basbayağı kötü koddur
    • Zarif kabul edilen birçok sözdizimi kalıbı gerçekte matematikten bile daha az anlaşılır geliyor
      Örneğin makaledeki üçlü operatör return n % 2 === 0 ? 'Even' : 'Odd'; insan beynine tersinden okunuyormuş gibi geliyor; bir derleyicinin sözdizimi ağacını işlemesine insandan daha uygun
      Bir insan matematikçi n mod 2 = 0 ise 'Even', değilse 'Odd' gibi parçalı fonksiyon biçiminde yazardı; bu çok daha nettir
    • Bu yüzden kod incelemesi önemli
      Yeni ekip üyeleri geldiğinde tutarlı bir stili öğrenmelerini sağlar, ekibin stilini de makul ölçüde tutarlı tutar
      .editorconfig hakkındaki yorum da dikkate değer: https://news.ycombinator.com/item?id=43333011
      Pull request’lerde stil ayrıntıları üzerine tartışmayı azaltır
    • Belki de Literate Programming’in yaygınlaşmamış olmasının nedeni budur
  • Yazı iyi, ama kod okurken zihni en çok yoran unsur olan değişkenliği gözden kaçırdığını düşünüyorum
    Bir metodu okurken bir değişkenin anlamını tam bir kez “sabitleyebilmek” ve geri kalanını çıkarırken onu aynı tutabilmek büyük bir nimet
    Bir metodu anlama düzeyi %0’dan %100’e monoton biçimde artmalı; belirli bir iterasyonda döngü gövdesinin akümülatörü nasıl değiştirdiğini karıştırıp metodu kafada baştan başlatmak gerekmemeli
    GOTO’nun zararlı olmasının gerçek nedeni de burada. Mesele metot içinde kafadaki komut işaretçisini taşımak zor olduğu için değil; GOTO varken değişken değişkenlerin durumunu bilmenin zor olması

    • Katılmıyorum. Kodun modellediği soyut bir bilgi uzayı vardır ve kafadaki komut işaretçisi bu uzayın içinde hareket etmelidir
      Hem değişken hem de değişmez değişkenler bu harekete yardımcı da olabilir, engel de olabilir; bu, kodun o uzaya ne kadar temiz karşılık geldiğine bağlıdır
      Değişmez değişkenlerin, değerin değişeceği ya da yanlış anlaşılmaya yol açacak şekilde değişeceği konusunda endişelenmemek gibi küçük bir taktik avantajı var; ama deneyimime göre bunu “her zaman değişmezlik kullan” kuralı yapacak kadar büyük değil
      Bazen değişkenlik, o bilgi uzayını çok daha temiz ifade etmeyi sağlar
    • Toplam karmaşıklık yalnızca bilinen bir başlangıç noktasından komut işaretçisini hareket ettirme meselesi değildir
      Çağrı noktasından değil de çağrılan tarafın bakış açısından bakınca, biri belirli bir satıra atlayabildiğinde ondan önce ne olmuştu geriye dönük izleyemezsiniz. Her yerden gelmiş olabileceği için yerel analiz değil, küresel program analizi gerekir
      Değişkenlik GOTO karmaşıklığının gerçek kaynağı olsaydı if deyimleri ve for döngüleri de aynı soruna sahip olmalıydı
      Değişkenlik ve durumun doğrudan karmaşıklık yarattığına katılıyorum, ama GOTO’nun tamamen farklı ve çok daha zararlı bir kategori olduğunu düşünüyorum
  • Kişisel olarak sevmediğim kalıp, if içinde hemen döndürüp geri kalanını örtük varsayılan yol olarak bırakma biçimi
    if (n % 2 === 0) return "Even"; return "Odd"; daha kısa, ama if ... return "Even"; else return "Odd"; tarafını çok daha fazla tercih ediyorum
    Nedeni, ilkinin asimetrik bir his vermesi. "Even" ve "Odd" simetrik seçenekler olduğu için else olan taraf daha sezgisel

    • return (n % 2 === 0) ? "Even" : "Odd"; gibi yazmak, en az boilerplate içerdiği için en okunabilir olanı
      Üçlü operatörü olan bir dilde bunu kolayca tanımak gerekir
    • Guard clause/erken dönüş, geliştiricinin odağını asıl normal akıştan çok fonksiyonun davranışını daraltma yönüne kaydırma eğiliminde
      Deneyimime göre else ek iç içe geçme yaratır ve normal akışın anlık kapsamının ötesindeki sınır koşullarını ya da değişkenleri değerlendirmeyi sürdürmeye kolayca iter. Tüm o bağlamı elde tutmak gerekir
    • Kişisel olarak öndeki biçimi daha çok tercih ediyorum
      Fonksiyon adının hemen altında, bir seviye girintide dönüşü görsel olarak görebiliyorsunuz ve erken çıkış yoksa garantili bir sonuç var hissi veriyor
      Dönüş daha aşağıya gömülü olunca bir şeyler tuhaf hissettiriyor
    • return "Odd"; satırının iften ayrı görünmesi için boş satır koyardım; dil izin veriyorsa if gövdesine süslü parantez de eklerdim
      elsee izin verdiğim durumlar da var ama genelde yan etki olduğunda; çoğunlukla ortadan kaldırana kadar refactor edince daha net oluyor
      Karmaşık kodun önem ya da çalıştırma maliyeti sırasına göre dışarı çıkan bir guard dizisine dönüşmesi sık görülür ve gerçek fonksiyon/metot mantığını sonlandırma koşullarından ayırır
    • Asimetri, kodu continuation/callback stiline ya da daha karmaşık veri yapısı mutasyonu biçimine refactor edince belirginleşir
      İlk biçim aşağı doğru akıp ikinci komut kümesini çalıştırır. return kontrol akışını kesen özel bir operatör olduğu için, ilk biçimin genel kontrol akışı iki durumun tamlığını doğru taşımaz
      İdiomatik Rust’ta, metot kontrol akışını bozan istisnai bir durum olmadıkça return kullanılmaz; ikinci örnek de genellikle return deyimi olmadan daha sık görülür
      Python’da da genellikle hatalı argüman ya da durumda başta erken dönüş yapılır, kuyruk konumundaki dönüş ise gerçek dönüş değeri olur
      Bu gelenekler nedeniyle tam bir if-else yapısını bozmak, girintili returnü istisnai bir durum gibi gösterir. Bu geleneği izlerseniz return deyimleri, kontrol akışını kesen durumlar dışında doğal olarak gereksiz tekrar gibi görünür ve Rust geleneği anlaşılır hale gelir. Her dilde return, break ile eşdeğer bir deyimdir
  • Belki sadece ben böyle düşünüyorum, ama TypeScript bazen kodu okumayı zorlaştırabiliyor
    Veri modelini bir ölçüde “atomik” tutup geliştiriciler tipleri gerçekten bildirme ve belgeleme konusunda özenli olursa sorun yok
    Ama utility type’larla tipler tiplerden türetilmeye başladığında ve açık tipleri atlayıp tip çıkarımına yaslanınca işler hızla çözülmeye başlıyor
    4-5 katmanlı tip dolaylı başvurusu gibi derin bir yığında bir alanın nereden geldiğini izlemek çok zorlaşıyor. Bir kısmı çıkarım, bir kısmı açık; bir kısmı türetilmiş tip, alan alias’ları da işin içinde
    Büyük veri modellerinde ve derin çağrı yığınlarında function checkDogs(dogs: Dog[]) { ... } gibi dönüş tipi atlanmış bir biçim tamamen kullanışsız ve gerçekten insanı çıldırtıyor

    • Fonksiyonların muhtemelen çıktı tipini açıkça belirtmesi gerektiğini düşünüyorum
      Başlıca nedeni, o fonksiyondan dönen tüm yolların bu tipe uymaya zorlanması
      Yeni bir koşul eklenirken başka bir daldan biraz farklı bir tip döndürülüp regresyon oluştuğunu çok gördüm
      Öte yandan değişken bildirimlerine tip eklemenin büyük bir değeri olduğunu düşünmüyorum
      Örnekte const checkedDoggos = checkDogs([]) gayet iyi; checkedDoggosun fonksiyon tipini miras almasına izin verilmeli
      Linter’ın const checkedDoggos: DogBreedAndSize[] = checkDogs([]) yazmayı zorunlu kıldığı bir codebase ile uğraşıyorum; oldukça gülünç ve pek bir değeri yok
    • TypeScript’te dönüş tipinin bir kısmı çıkarımla gelse bile, hiç tip bilgisi olmayan JavaScript’e kıyasla az da olsa tip bilgisi olmasını tercih ederim
      JavaScript’te emin olamadığınız için stack içinde yukarı aşağı gidip bunu zihninizde tutmanız gerekir
    • Benim prensibim, yalnızca TypeScript derleyicisi bağırdığında tip eklemek
  • “Az değişkenli küçük fonksiyonlar genelde daha kolay okunur” sözüne temkinli yaklaşmak gerekir
    Okunabilirlik tartışmasının yalnızca mikro okunabilirliğe odaklanmasından hoşlanmıyorum. Bu durumda, makro okunabilirlikten çok mikro okunabilirliğin daha önemli olduğu yönündeki yanlış varsayım altında kodun aşırı küçük parçalara bölünmesi kolaylaşıyor
    Bu tür bir dogmalaşma, ormanı görmeyip yalnızca ağaçlara bakan programcılar üretir; aşırı verimsiz ya da hata ayıklaması zor kodlara yol açar
    APL ailesi diller bunun karşı ucunda yer alıyor, ama gerçek optimum noktanın ortalarda bir yerde olduğunu ve kişiden kişiye büyük ölçüde değişeceğini düşünüyorum

    • Özellikle birden çok dosya işin içine girince kesinlikle bir orta nokta var
      Alışık olmadığınız bir kodda tanıma gitmeyi 3-4 kez yapınca bile insan zorlanıyor; sorun bende de olabilir ama çoğu kişinin benden çok daha iyi olduğunu hayal etmek zor
      .NET kültüründe, özellikle “clean architecture”ta bu sorun şaşırtıcı düzeyde. Bir özelliği değiştirmek ya da bir sorunun izini sürmek istediğinizde iş 4 katmana ve 15 dosyaya yayılmış oluyor; bazı dosyaların %60’tan fazlası yalnızca anahtar kelimelerden ibaret
      Sınırın nerede çizilmesi gerektiğini bilmiyorum, ama her 5 satırda bir yukarı aşağı kaydırmayı gerektirecek kadar parçalanmış kod yerine, diğer önerilere uyan ve sırayla okunabilen tek bir uzun fonksiyonu genelde tercih ederim
      Tipler/sınıflar için de aynı şey geçerli; yalnızca bu DTO’da kullanılan 4 değerli bir enum’u ille de ayrı bir dosyaya koymaya gerek yok
  • İlginç bir yazı ama tatmin edici değil
    Sonuçlara fazla hızlı atlıyor ve yeniden kişisel tercihe dönüyor. Birçok tercihe katılsam da yazının kendisi açıkça tercihin ötesine geçmeye çalışan bir metindi
    “Dile özgü operatörlerden veya sözdizimsel şekerden kaçının; okura yük getirir” iddiası metriklerden çıkmıyor. Bir fonksiyonda birbirinden farklı 3 operatör varsa ve dile özgü tek bir operatör bu üçünün yerini bir kerede alıyorsa, fonksiyonun “çabası” azalır
    map/reduce/filter gibi bileşenler de doğru kullanıldığında başka operatörlerin yerini alıp “hacmi” azaltabilir; yani durum iki yöne de gidebilir
    ?. örneği, JavaScript’in okunması zor dil tasarımına işaret eden oldukça dile özgü bir teşhis gibi görünüyor. Birçok dilde null ile undefined ayrı olmadığı için buna genellikle null-safe operator denir
    “Variable shadowing korkunçtur” ile “uzun yaşam süresi daha fazla değişkeni zihinde tutmayı gerektirir” ifadeleri birbiriyle çelişebilir
    Bazı bağlamlarda variable shadowing’i çok severim; çünkü önceki örneği erişilebilir bırakmak yerine kapsamdan çıkarır

  • VS Code için Highlight adlı harika bir eklenti var
    Kullanıcı tanımlı regex’lerle koda farklı renkler uygulayabiliyor; yaygın kullanım muhtemelen //TODO’ları sarı yapmak olur
    Ben logları soluk göstermek için kullanıyorum; çünkü her yere log koyunca görsel gürültü çok artıyor
    Bakımını yaptığım kütüphane this.logger?.info('Some logs here'); gibi loglar kullanıyor; bunlara 0.4 opaklık uygulayıp arka plana itiyorum
    Yine de görünüyorlar, ama ilk bakışta asıl iş mantığı daha öne çıkıyor
    Ayar şu şekilde değiştirilebilir: "highlight.regexes": { "((?:this\\.)?(?:_)?logger(?:\\?)?.(debug|error|info|warn)[^\\)]*\\)\\;)": { "regexFlags": "gmi", "decorations": [{ "opacity": "0.4" }] } }
    https://marketplace.visualstudio.com/items?itemName=fabiospa...

  • Uzun fonksiyon zincirlerini veya callback’leri küçük gruplara bölüp iyi adlandırılmış değişkenler kullanmanın biraz daha az verimli olduğu görüşüne katılmıyorum
    İki sürüm de aynı derecede verimli olabilir
    Her iki durumda da aynı nesneler ayrılır, heap’te saklanır ve garbage collection’a konu olur. Verim farkı derleyiciye bağlıdır
    İkinci sürümde derleyicinin, her değişkenin yalnızca tanımlandıktan hemen sonra kullanıldığını görüp bu nesneleri zincir çağrıda olduğu gibi kapsam dışına çıkarabilmesi gerekir

    • Katılıyorum. Derlemeden sonra, dönüş değerine bir ad verilmiş olması derleyicinin umurunda olmayacaktır büyük ihtimalle
      Elbette değişken tipinin çıkarıma bırakıldığı varsayımıyla
      Gerçekte göreceğiniz maliyet, ara değeri debugger’da görmek için açıkça somutlaştırdığınız durumlarda ortaya çıkar. Örneğin listeye çevirirseniz kaçınılabilecek bir allocation oluşur ve bu maliyet getirir
  • “Okunabilirliği” nicelleştirme girişimi iyi. Bu tür yaklaşımlara çok daha fazla ihtiyaç var
    Şu anda en yaygın okunabilirlik tanımı “benim kolay okuduğum şey”e yakın geliyor
    Çok sayıda insana kod gösterip, o kodun ne yaptığını açıklayan cümleyi seçmelerini isteyerek süre tutarsanız okunabilirliğin gerçek boyutlarını bulmak mümkün olabilir
    En çok kişinin en kısa ortalama sürede doğru bildiği sorular, gerçek dünyada okunması kolay kod örnekleri olur; daha da önemlisi, gerçekten okunması zor pratikleri belirlemeye yardımcı olabilir
    Yanıt verenler muhtemelen “programlama deneyimi”, “X paradigmasını anlıyor mu” gibi eksenlerde kümelenecektir; modalar değiştikçe sonuçlar da zaman içinde kayabilir

    • Temel zorluklardan biri, kod okumayı öğreniyor olmamız
      Neyi okuyup yazmayı öğrendiğimiz, neyi okunması kolay hissettiğimizi şekillendirir
      Ne yapmaya çalıştığınız, kiminle çalıştığınız, kodlamadan önce neleri yapabildiğiniz, başka hangi dilleri bildiğiniz gibi birçok etken rol oynar
      Kolay kazanımları topladıktan sonra, örneğin değişken adlarını keyfi, alakasız veya yanıltıcı koymamak düzeyinin ötesine geçtiğinizde, birçok “okunabilirlik” meselesi eninde sonunda uzlaşma oluşturmaya dair olabilir
      Birlikte çalışmak istediğiniz belirli programcı grubunun ötesine geçen tek bir doğru cevap olmayabilir
    • Pek değerli olduğunu düşünmüyorum
      Kod okunabilirliği dil okunabilirliğine benzer; çoğunlukla o dili iyi bilmeyen kişiler için sorundur ve zaman ayırınca çözülür
      Programlamanın asıl sorunu kod karmaşıklığıdır ve bu, tek tek kod parçalarının metrikleriyle değerlendirilemez
      Sorun, fonksiyon gövdesindeki uygulama seçimlerinden çok fonksiyonlar arasındaki ilişkidedir
    • Bu yaklaşım fazla tek boyutlu
      Kodun ne yaptığını anlamak çoğu zaman kolaydır; zor olan o kodu değiştirmek veya özellik eklemektir
      Bu zorluk, birden çok soyutlama katmanının birbirine nasıl bağlandığını gizlemesinden kaynaklanır