Ders 04 / 16
Girinti ve Kod Biçimi
Kod biçiminin üslup tercihi olmaktan çıkarılıp fark maliyetiyle ölçülmesi: aynı içeriğin iki girinti düzeninde ürettiği fark satırları, tarife tablosunun hizalanmış ve düz biçimlerine uygulanan aynı revizyonun fark satırı sayıları ve içerik taşımayan fark satırlarının ayıklanması.
İçindekiler
Önceki derste her fonksiyon tek soyutlama düzeyine indirildi, ama dosyanın kendisi hakkında bir şey söylenmedi: satırların nerede biteceği, hangi bildirimlerin yan yana duracağı, girintinin ne kadar olacağı. Bu kararlar genellikle üslup sayılır ve tartışması zevke bağlanır.
Biçimin ölçülebilir bir sonucu vardır. Kod tabanına yapılan her değişiklik bir fark üretir; farkı bir başkası okur ve onaylar. Aynı içerik değişikliği, farklı biçimlerde farklı sayıda fark satırı üretir ve bu satırların bir kısmı hiçbir bilgi taşımaz. Bu ders biçimi iki sayıya bağlıyor: bir değişikliğin ürettiği fark satırı sayısı ve bunların içinde içerik taşımayanların sayısı.
Girinti Yapının Kendisini Gösterir
Girinti, iç içe geçmiş blokların derinliğini göze taşıyan tek işarettir. Yapı ile girinti ayrı düştüğünde okuyan kişi bloğun nerede bittiğini süslü parantezleri sayarak bulmak zorunda kalır. Aşağıdaki iki dosya aynı sözcük dizisini taşır; yalnız girintileri farklıdır.
mkdir -p duzenli karisik duz hizali
// duzenli/ucret.mjs — girinti yapisal derinlikle uyusuyor const ASGARI_UCRET = 52; const AGIRLIK_KADEMELERI = [ { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, ]; export function kademeUcreti(agirlikKg, bolgeKatsayisi) { const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg); if (kademe === undefined) { throw new RangeError("kademe disi agirlik"); } return Math.max(kademe.kiloBasiUcret * agirlikKg * bolgeKatsayisi, ASGARI_UCRET); }
// karisik/ucret.mjs — ayni icerik, girinti yapiyla uyusmuyor const ASGARI_UCRET = 52; const AGIRLIK_KADEMELERI = [ { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, ]; export function kademeUcreti(agirlikKg, bolgeKatsayisi) { const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg); if (kademe === undefined) { throw new RangeError("kademe disi agirlik"); } return Math.max(kademe.kiloBasiUcret * agirlikKg * bolgeKatsayisi, ASGARI_UCRET); }
İkinci dosyada throw satırı, içinde bulunduğu koşuldan daha az girintili. Bir okuyucu
o satıra baktığında koşulun içinde mi dışında mı olduğunu girintiden anlayamaz. Aynı
belirsizlik AGIRLIK_KADEMELERI dizisinin ilk ögesinde de var: iki öge aynı düzeyde
olduğu hâlde farklı sütunlarda duruyor.
İki dosya arasındaki fark ölçülebilir. Dosyaların ilk satırı dosya adını taşıyan işaret olduğu için karşılaştırmaya girmez.
diff <(tail -n +2 duzenli/ucret.mjs) <(tail -n +2 karisik/ucret.mjs)
3c3
< { ustSinirKg: 5, kiloBasiUcret: 26 },
---
> { ustSinirKg: 5, kiloBasiUcret: 26 },
8,9c8,9
< const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
< if (kademe === undefined) {
---
> const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
> if (kademe === undefined) {
11c11
< }
---
> }
Sekiz fark satırı var ve hiçbiri bir davranış değişikliği anlatmıyor. Bu, biçim uyuşmazlığının ilk maliyetidir: girintiyi düzelten kişi, hiçbir içerik değiştirmediği hâlde sekiz satırlık bir fark üretir ve o farkı bir başkası okumak zorunda kalır.
Tarife Tablosunun İki Biçimi
İkinci ölçüm daha somut bir soruyu yanıtlar: aynı içerik değişikliği iki farklı biçimde kaç satır fark üretir? Tarife modülü iki biçimde yazıldı. Birincisinde eşittir işaretleri hizalanmış ve kademe tablosu satır başına iki öge alacak şekilde sıkıştırılmış; ikincisinde bildirimler tek boşluklu ve her kademe kendi satırında.
// hizali/tarife.mjs — hizalanmis bildirimler, sikistirilmis kademe tablosu export const HACIMSEL_BOLEN = 3000; export const ASGARI_UCRET = 52; export const SOZLESMELI_MUSTERI_INDIRIMI = 0.15; export const PARA_BIRIMI = "TL"; export const AGIRLIK_KADEMELERI = [ { ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, ];
// duz/tarife.mjs — tek bosluklu bildirimler, satir basina bir kademe export const HACIMSEL_BOLEN = 3000; export const ASGARI_UCRET = 52; export const SOZLESMELI_MUSTERI_INDIRIMI = 0.15; export const PARA_BIRIMI = "TL"; export const AGIRLIK_KADEMELERI = [ { ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, ];
Taşıyıcı tarifeyi gözden geçirdi ve üç şey değişti: beş kilogram kademesinin kilo başı
ücreti 26’dan 25’e indi, elli kilograma kadar yeni bir kademe açıldı,
SOZLESMELI_MUSTERI_INDIRIMI sabiti SOZLESME_INDIRIMI adına çevrildi. Aynı üç
değişiklik iki dosyaya da uygulanır.
// hizali/tarife-yeni.mjs — ayni revizyon, hizalamasi korunmus hali export const HACIMSEL_BOLEN = 3000; export const ASGARI_UCRET = 52; export const SOZLESME_INDIRIMI = 0.15; export const PARA_BIRIMI = "TL"; export const AGIRLIK_KADEMELERI = [ { ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 25 }, { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, { ustSinirKg: 50, kiloBasiUcret: 14 }, ];
// duz/tarife-yeni.mjs — tarife revizyonu uygulanmis hali export const HACIMSEL_BOLEN = 3000; export const ASGARI_UCRET = 52; export const SOZLESME_INDIRIMI = 0.15; export const PARA_BIRIMI = "TL"; export const AGIRLIK_KADEMELERI = [ { ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 25 }, { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }, { ustSinirKg: 50, kiloBasiUcret: 14 }, ];
Hizalanmış sürümde ad kısaldığı için hizalama sütunu kaydı ve dört bildirimin dördü de yeniden yazıldı. Düz sürümde yalnız adı değişen bildirim yeniden yazıldı.
Fark Ölçer
Aşağıdaki araç her dosya çifti için farkı alır ve fark satırlarını ikiye ayırır. Çıkan bir satırın boşlukları sadeleştirilmiş hâli giren satırlardan birinde de bulunuyorsa o çift yalnız biçim farkıdır; içerik taşımaz. Kalanlar içerik farkıdır.
// fark-olc.mjs — iki dosya arasindaki fark satirlarinin kaci icerik tasiyor import { execSync } from "node:child_process"; // Ilk satir dosya adi isaretidir, karsilastirmaya girmez. const fark = (eski, yeni) => execSync(`diff <(tail -n +2 ${eski}) <(tail -n +2 ${yeni}) || true`, { shell: "/bin/bash" }) .toString().split("\n"); const icerik = (s) => s.slice(1).replace(/\s+/g, " ").trim(); const CIFTLER = [ ["duzenli/ucret.mjs", "karisik/ucret.mjs"], ["hizali/tarife.mjs", "hizali/tarife-yeni.mjs"], ["duz/tarife.mjs", "duz/tarife-yeni.mjs"], ]; for (const [eski, yeni] of CIFTLER) { const satirlar = fark(eski, yeni); const cikan = satirlar.filter((s) => s.startsWith("< ")).map(icerik); const giren = satirlar.filter((s) => s.startsWith("> ")).map(icerik); const esli = cikan.filter((s) => giren.includes(s)).length; console.log(`${eski.padEnd(18)} -> ${yeni.padEnd(22)} degisen=${cikan.length + giren.length}` + ` yalniz bicim=${esli * 2} icerik=${cikan.length + giren.length - esli * 2}`); }
duzenli/ucret.mjs -> karisik/ucret.mjs degisen=8 yalniz bicim=8 icerik=0 hizali/tarife.mjs -> hizali/tarife-yeni.mjs degisen=11 yalniz bicim=6 icerik=5 duz/tarife.mjs -> duz/tarife-yeni.mjs degisen=5 yalniz bicim=0 icerik=5
Üç satırın anlattığı şey şu. Girinti düzeltmesi sekiz satır fark üretiyor ve sekizi de biçim; içerik farkı sıfır. Tarife revizyonunun içerik farkı iki biçimde de aynı: beş satır. Hizalanmış biçimde bu beş satırın yanına altı satır daha ekleniyor. Aynı bilgi, iki katından fazla fark satırıyla anlatılıyor.
Farkı Okuyan Kişi
Sayıların anlamı, farkı okuyan kişinin işidir. On bir satırlık farkta beş satır bir karar taşır, altı satır taşımaz; okuyan kişi hangisinin hangisi olduğunu önceden bilmez ve hepsini karşılaştırmak zorundadır. Beş satırlık farkta böyle bir ayıklama yoktur.
Sıkıştırılmış tablonun ikinci bir etkisi de var. Hizalanmış sürümün fark çıktısında değişen tablo satırı iki kademe taşıyor: biri değişti, biri değişmedi. Okuyan kişi satırın hangi yerinde değişiklik olduğunu karakter karakter aramak zorunda. Düz sürümde değişen satır tek kademe taşıdığı için karşılaştırma satırın kendisiyle biter.
Buradan biçim kararının ölçütü çıkar: bir satır tek bir bilgi taşımalı ve bir bilginin biçimi komşusuna bağlı olmamalı. Hizalama bu ikinci koşulu bozar; bir adın uzunluğu komşu satırların boşluk sayısını belirlediği için, adı değişen bir bildirim komşularını da değiştirir. Satır başına birden çok öge yerleştirmek birinci koşulu bozar.
Biçim Kararı Bir Kez Verilir
Ölçüm hangi biçimin seçileceğini tam olarak söylemez; girintinin iki mi dört boşluk olduğu bu sayılara yansımaz. Söylediği şey, kararın proje düzeyinde bir kez verilmesi ve dosya dosya yeniden tartışılmamasıdır. Aynı projede iki girinti alışkanlığı bir arada yaşadığında her dosya, üzerinde en son çalışan kişinin alışkanlığına dönüşür ve her dönüşüm içerik taşımayan fark satırı üretir.
Bu yüzden biçim kararı bir tartışma konusu değil, bir yapılandırma dosyası konusudur. Biçimlendirici sınıfından bir araç kuralları kaynağa mekanik olarak uygular; karar bir kez verilir, uygulama kişiye bırakılmaz. Aracın seçtiği biçim kimsenin tercihiyle birebir uyuşmayabilir; ölçülen maliyet, tercih uyuşmazlığının maliyetinden büyüktür.
Özet
- Biçim kararının ölçüsü, ürettiği fark satırı sayısıdır: her değişiklik bir fark üretir ve farkı bir başkası okur.
- Yalnız girintisi bozulmuş bir dosya sekiz fark satırı üretti ve sekizinin de içerik farkı sıfırdı; biçim uyuşmazlığı içerik değişmeden maliyet doğurur.
- Aynı tarife revizyonu düz biçimde beş, hizalanmış biçimde on bir fark satırı üretti; içerik farkı ikisinde de beş satır, aradaki altı satır yalnız hizalamadan geliyor.
- Bir satır tek bilgi taşımalı ve bir bilginin biçimi komşu satırlara bağlı olmamalı; hizalama ikinci koşulu, satır başına çok öge birinci koşulu bozar.
- Biçim kararı proje düzeyinde bir kez verilir ve mekanik olarak uygulanır; dosya başına yeniden verilen karar her seferinde içerik taşımayan fark üretir.
Sonraki Adım
Biçim, kodun göze nasıl göründüğünü belirliyor; yapının kendisini değil. Düzgün girintilenmiş bir fonksiyon da okunması güç olabilir: iç içe geçmiş koşullar, birden çok çıkış noktası ve birleşik mantıksal ifadeler girintiyle düzelmez. Bu güçlüğün de bir sayısı var. Sonraki ders bir fonksiyonun karar noktalarını sayan bir ölçer yazar, ücretlendirme fonksiyonunun iki sürümünde çevrimsel karmaşıklığı ve bağımsız yol sayısını karşılaştırır, ardından sayıyı düşüren dönüşümleri uygular.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.