Kod okunabilirliğini düşüren görsel karmaşıklık kalıpları (2023)
(seeinglogic.com)- 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
- Benzersiz operatör sayısı
- 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
ifvereturnkullanan 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
Volume33.30,Difficulty2.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
Volume71.35,Difficulty3.75- İlk uygulama gözle bakıldığında da daha basittir; Halstead’in
VolumeveDifficultydeğ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
- Aynı tek-çift belirleme fonksiyonunda bile
-
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,filtergibi 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 yerineMyObj 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.myObjveyanullolur - İkincisinde
myObj,a.myObjveyaundefinedolur - 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/exceptbloklarını, mantıksal operatör dizilerini ve özyinelemeyi de zorluk artırıcı unsurlar olarak görür switchtek bir grup olarak hesaplanır, ancakelse-ifzincirlerinde her ekelse-ifdaha zor kabul edilir- Çünkü
else-ifher dalda birden fazla karşılaştırma yapabilir - Bununla birlikte
switchteki fall-through ve eksikbreakde 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 || consoleModebasit bir koşul ifadesidirdebug || (verbose && consoleMode)operatör karışımı nedeniyle daha zor okunurdebug || !(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 fazlacatchbloğu tek bircatchten daha zor sayılmaz,tryvefinallyise 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
gotogenellikle 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 outveyagoto donebiçimini bazı uzmanlar yararlı görebilir - Buna karşılık
continueveyabreakile ifade edilemeyecek şekilde döngü sınırlarının dışına çıkangoto, 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
ivej,itemveitemsgibi gözle kolayca karıştırılabilecek adlar hatalara yol açabilir- Bir fonksiyon içinde
node,_node,thisNodegibi 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
gotokullanmamak - 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
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üyorumKarmaşı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
mapvefilterı gelişigüzel küçümsemeyecek kadar aşina olunmalı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
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;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()?- 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
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
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
Ö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 uygunBir insan matematikçi
n mod 2 = 0ise'Even', değilse'Odd'gibi parçalı fonksiyon biçiminde yazardı; bu çok daha nettirYeni ekip üyeleri geldiğinde tutarlı bir stili öğrenmelerini sağlar, ekibin stilini de makul ölçüde tutarlı tutar
.editorconfighakkındaki yorum da dikkate değer: https://news.ycombinator.com/item?id=43333011Pull request’lerde stil ayrıntıları üzerine tartışmayı azaltır
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ı
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
Ç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ı
ifdeyimleri vefordö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,
ifiçinde hemen döndürüp geri kalanını örtük varsayılan yol olarak bırakma biçimiif (n % 2 === 0) return "Even"; return "Odd";daha kısa, amaif ... return "Even"; else return "Odd";tarafını çok daha fazla tercih ediyorumNedeni, ilkinin asimetrik bir his vermesi.
"Even"ve"Odd"simetrik seçenekler olduğu içinelseolan taraf daha sezgiselreturn (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
Deneyimime göre
elseek 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 gerekirFonksiyon 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ıniften ayrı görünmesi için boş satır koyardım; dil izin veriyorsaifgövdesine süslü parantez de eklerdimelsee izin verdiğim durumlar da var ama genelde yan etki olduğunda; çoğunlukla ortadan kaldırana kadar refactor edince daha net oluyorKarmaşı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
İlk biçim aşağı doğru akıp ikinci komut kümesini çalıştırır.
returnkontrol 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
returnkullanılmaz; ikinci örnek de genellikle return deyimi olmadan daha sık görülürPython’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-elseyapısını bozmak, girintilireturnü istisnai bir durum gibi gösterir. Bu geleneği izlersenizreturndeyimleri, 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 dildereturn,breakile eşdeğer bir deyimdirBelki 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ıyorBaş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 verilmeliLinter’ı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 yokJavaScript’te emin olamadığınız için stack içinde yukarı aşağı gidip bunu zihninizde tutmanız gerekir
“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
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/filtergibi 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 dildenullileundefinedayrı 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 olurBen 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 itiyorumYine 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
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
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
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
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