Ders 11 / 16
Tutarlılık
Aynı işin projede tek biçimde yapılmasının ölçülmesi: üç ayrı işi üç ayrı biçimde yapan bir pakette biçim sayısını bulan tarama, tekilleştirmeden sonra ölçümün tekrarlanması ve biçim birleştirmenin çıktılarda ve hata davranışında yol açtığı değişikliklerin sayılması.
İçindekiler
Aktör düzeninde her kararın bir sahibi oldu. Kitaplıkta yine de rahatsız edici bir şey
kalıyor: bir dosyada tutar toFixed ile yazılıyor, başka bir dosyada elle yuvarlanıp
başına para birimi ekleniyor; bir yerde bulunamama hata atarak, başka bir yerde boş değer
dönerek bildiriliyor.
Bunların hiçbiri yanlış değil. Üçü de çalışıyor, üçü de sınamalardan geçiyor. Yalnız farklılar ve farklılığın bedeli okuyanın üzerinde birikir: her dosyada aynı sorunun cevabı yeniden aranır. Bu ders farklılığı sayıya bağlıyor — aynı iş kaç ayrı biçimde yapılıyor.
Ölçüt: İş Başına Biçim Sayısı
Bir kod tabanında tekrarlanan işler vardır: para yazmak, bulunamama durumunu bildirmek, bir modülü dışa açmak. Her iş için o kod tabanında kullanılan farklı yazım biçimlerinin sayısı, tutarlılığın ölçüsüdür. Hedef her zaman birdir; hangi biçim olduğu ikinci sorudur.
Sayı doğrudan bir maliyete karşılık gelir. Bir iş üç biçimde yapılıyorsa, o işe yeni bir yerde ihtiyaç duyan kişi üç örnek görür ve hangisini izleyeceğine karar vermek zorunda kalır. Kararı verirken çoğunlukla en son baktığı dosyayı örnek alır; böylece biçim sayısı azalmaz, dağılım rastlantısallaşır.
Üç İş, Üç Ayrı Biçim
Paketteki üç modül üç ayrı iş yapıyor: ücret satırı, etiket satırı, günlük toplam. Üçü de tutar yazıyor, üçü de bulunamama durumunu bildiriyor, üçü de bir şey dışa açıyor.
mkdir -p karisik tekil
// karisik/ucret.mjs — ucret satiri export function ucretSatiri(gonderi, tarife) { const kademe = tarife.kademeler.find((k) => gonderi.agirlikKg <= k.ustSinirKg); if (kademe === undefined) throw new RangeError("kademe bulunamadi"); return `${gonderi.kod} ${(kademe.kiloBasiUcret * gonderi.agirlikKg).toFixed(2)} TL`; }
// karisik/etiket.mjs — etiket satiri export const etiketSatiri = (gonderi, bolgeAdlari) => { const bolge = bolgeAdlari[gonderi.bolgeKodu]; if (bolge === undefined) return null; return `${gonderi.kod} ${bolge} ${gonderi.beyanDegeri.toFixed(2)}TL`; };
// karisik/rapor.mjs — gunluk toplam function gunlukToplam(gonderiler, gun) { const gunun = gonderiler.filter((g) => g.gun === gun); if (gunun.length === 0) return { hata: "o gune ait gonderi yok" }; const toplam = Math.round(gunun.reduce((t, g) => t + g.ucret, 0) * 100) / 100; return `${gun}. gun TL ${toplam}`; } export { gunlukToplam };
Üç dosya birbirinden bağımsız yazılmış gibi duruyor ve öyle de olabilir: her biri farklı bir zamanda, farklı bir kişi tarafından, o günkü alışkanlıkla yazılmıştır. Sonuç, aynı paketin içinde üç ayrı yazım geleneğidir.
Tekilleştirilmiş Sürüm
İkinci sürümde her iş için tek bir biçim seçildi: tutar toFixed(2) ile yazılıp para
birimi bir boşlukla ekleniyor, bulunamama bir alan hatası atarak bildiriliyor, dışa açma
bildirimin başında yapılıyor.
// tekil/ucret.mjs — ucret satiri export function ucretSatiri(gonderi, tarife) { const kademe = tarife.kademeler.find((k) => gonderi.agirlikKg <= k.ustSinirKg); if (kademe === undefined) throw new RangeError("kademe bulunamadi"); return `${gonderi.kod} ${(kademe.kiloBasiUcret * gonderi.agirlikKg).toFixed(2)} TL`; }
// tekil/etiket.mjs — etiket satiri export function etiketSatiri(gonderi, bolgeAdlari) { const bolge = bolgeAdlari[gonderi.bolgeKodu]; if (bolge === undefined) throw new RangeError("bolge bulunamadi"); return `${gonderi.kod} ${bolge} ${gonderi.beyanDegeri.toFixed(2)} TL`; }
// tekil/rapor.mjs — gunluk toplam export function gunlukToplam(gonderiler, gun) { const gunun = gonderiler.filter((g) => g.gun === gun); if (gunun.length === 0) throw new RangeError("o gune ait gonderi yok"); return `${gun}. gun ${gunun.reduce((t, g) => t + g.ucret, 0).toFixed(2)} TL`; }
Biçim Tarayıcı
Tarayıcı her iş için tanınacak biçimlerin örüntülerini taşır, dizindeki dosyalarda hangi biçimlerin göründüğünü bulur ve iş başına kaç ayrı biçim kullanıldığını sayar.
// bicim-tara.mjs — ayni isin kac ayri bicimde yapildigini sayar import { readFileSync, readdirSync } from "node:fs"; const ISLER = { "para bicimi": [["toFixed + bosluk + TL", /toFixed\(2\)\} TL/], ["toFixed + bitisik TL", /toFixed\(2\)\}TL/], ["Math.round + onek TL", /Math\.round\(/]], "bulunamama": [["hata atma", /throw new \w*Error/], ["bos deger", /return null/], ["hata nesnesi", /return \{ hata:/]], "disa aktarma": [["export function", /^export function /m], ["export const ok", /^export const \w+ = \(/m], ["sonda export bloku", /^export \{/m]], }; for (const dizin of process.argv.slice(2)) { const dosyalar = readdirSync(dizin).filter((a) => a.endsWith(".mjs")).sort(); const metin = dosyalar.map((a) => [a, readFileSync(`${dizin}/${a}`, "utf8")]); console.log(dizin); let toplam = 0; for (const [is, bicimler] of Object.entries(ISLER)) { const bulunan = bicimler .map(([ad, oruntu]) => [ad, metin.filter(([, m]) => oruntu.test(m)).map(([d]) => d)]) .filter(([, dosyalar]) => dosyalar.length > 0); toplam += bulunan.length; console.log(` ${is.padEnd(14)} bicim=${bulunan.length}`); for (const [ad, d] of bulunan) console.log(` ${ad.padEnd(22)} ${d.join(", ")}`); } console.log(` toplam bicim=${toplam} (is basina 1 hedefleniyor, hedef=${Object.keys(ISLER).length})`); }
node bicim-tara.mjs karisik tekil
karisik
para bicimi bicim=3
toFixed + bosluk + TL ucret.mjs
toFixed + bitisik TL etiket.mjs
Math.round + onek TL rapor.mjs
bulunamama bicim=3
hata atma ucret.mjs
bos deger etiket.mjs
hata nesnesi rapor.mjs
disa aktarma bicim=3
export function ucret.mjs
export const ok etiket.mjs
sonda export bloku rapor.mjs
toplam bicim=9 (is basina 1 hedefleniyor, hedef=3)
tekil
para bicimi bicim=1
toFixed + bosluk + TL etiket.mjs, rapor.mjs, ucret.mjs
bulunamama bicim=1
hata atma etiket.mjs, rapor.mjs, ucret.mjs
disa aktarma bicim=1
export function etiket.mjs, rapor.mjs, ucret.mjs
toplam bicim=3 (is basina 1 hedefleniyor, hedef=3)
Toplam biçim sayısı dokuzdan üçe indi ve üç, iş sayısına eşit — yani her iş tek biçimde yapılıyor. Dosya listeleri de tersine döndü: önce her biçimin tek bir dosyası vardı, şimdi tek biçimin üç dosyası var.
Tekilleştirmenin Bedeli
Biçim birleştirmek serbest bir işlem değildir. Para biçimini birleştirmek çıktı metnini, bulunamama biçimini birleştirmek hata davranışını değiştirir. Değişikliklerin sayılması gerekir.
// karsilastir.mjs — tekillestirmenin ciktilara ve bulunamama davranisina etkisi import { ucretSatiri as kUcret } from "./karisik/ucret.mjs"; import { etiketSatiri as kEtiket } from "./karisik/etiket.mjs"; import { gunlukToplam as kToplam } from "./karisik/rapor.mjs"; import { ucretSatiri as tUcret } from "./tekil/ucret.mjs"; import { etiketSatiri as tEtiket } from "./tekil/etiket.mjs"; import { gunlukToplam as tToplam } from "./tekil/rapor.mjs"; const TARIFE = { kademeler: [{ ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }] }; const BOLGELER = { 1: "Ayni Sehir", 2: "Komsu Bolge" }; const GONDERI = { kod: "GN-4172", agirlikKg: 2.4, bolgeKodu: 2, beyanDegeri: 1500, gun: 3, ucret: 62.4 }; const LISTE = [GONDERI, { ...GONDERI, kod: "GN-4173", ucret: 51.2 }]; const dene = (fn) => { try { const sonuc = fn(); return typeof sonuc === "object" && sonuc !== null ? JSON.stringify(sonuc) : String(sonuc); } catch (hata) { return `${hata.constructor.name}: ${hata.message}`; } }; const DURUMLAR = [ ["ucret", () => kUcret(GONDERI, TARIFE), () => tUcret(GONDERI, TARIFE)], ["etiket", () => kEtiket(GONDERI, BOLGELER), () => tEtiket(GONDERI, BOLGELER)], ["toplam", () => kToplam(LISTE, 3), () => tToplam(LISTE, 3)], ["etiket/yok", () => kEtiket({ ...GONDERI, bolgeKodu: 9 }, BOLGELER), () => tEtiket({ ...GONDERI, bolgeKodu: 9 }, BOLGELER)], ["toplam/yok", () => kToplam(LISTE, 7), () => tToplam(LISTE, 7)], ]; for (const [ad, karisik, tekil] of DURUMLAR) { const a = dene(karisik), b = dene(tekil); console.log(`${ad.padEnd(11)} ${a === b ? "ayni " : "degisti"} ${a.padEnd(34)} -> ${b}`); }
ucret ayni GN-4172 62.40 TL -> GN-4172 62.40 TL
etiket degisti GN-4172 Komsu Bolge 1500.00TL -> GN-4172 Komsu Bolge 1500.00 TL
toplam degisti 3. gun TL 113.6 -> 3. gun 113.60 TL
etiket/yok degisti null -> RangeError: bolge bulunamadi
toplam/yok degisti {"hata":"o gune ait gonderi yok"} -> RangeError: o gune ait gonderi yok
Beş durumun dördü değişti. Üçü sunum kararıdır: para birimi eki, ondalık hane sayısı ve sıralama artık her yerde aynı. Biri davranış kararıdır: iki fonksiyon artık boş değer ya da hata nesnesi yerine hata atıyor ve bunları çağıran her yer buna göre güncellenmelidir.
Bu, tutarlılık çalışmasının gerçek maliyetidir. Ölçüm işi mekanikleştirir ama kararı
vermez; tarayıcı kaç biçim olduğunu söyler, hangisinin standart olacağını söylemez.
3. gun TL 113.6 satırının 3. gun 113.60 TL olması bir seçimdir ve seçim yapıldıktan
sonra tek başına geri alınamaz.
Standardı Belirlemek
Standardın hangisi olacağına dair üç ölçüt işe yarar.
Yaygınlık. Kod tabanında zaten en çok kullanılan biçim, en az değişiklikle standartlaştırılabilendir. Tarayıcının dosya listesi bu bilgiyi doğrudan veriyor.
Dışarıya bakan yüzeyle uyum. Bir biçim kullanıcıya ya da başka bir sisteme görünüyorsa, onun beklentisi iç tercihten önce gelir. Para biçimi fatura çıktısında görünüyorsa standardı belirleyen şey muhasebenin biçimidir.
Yanlış kullanıma dirençlilik. Bulunamama örneğinde hata atmak, boş değer döndürmekten daha dirençlidir; önceki derslerden birinde ölçülmüştü — boş değer, hatayı üretildiği yerden uzağa taşıyor.
Ölçütlerin hiçbiri “daha güzel”i içermez. Tutarlılık tartışmasının çıkmaza girdiği yer, biçimlerin estetik olarak karşılaştırılmasıdır; oysa asıl kazanç biçimin tek olmasından gelir, hangisi olduğundan değil. Karar verildikten sonra iş mekanikleşir: tarayıcı kod tabanında çalıştırılır, biçim sayısı birden büyük olan her iş listelenir.
Tekilleştirilmiş sürümde bir şey hâlâ eksik. Üç dosya aynı para biçimini üç kez yazıyor; biçim tek ama yazım üç yerde tekrarlanıyor. Bunlar ayrı sorunlardır: biri aynı işin farklı biçimlerde yapılması, öteki aynı bilginin birden çok yerde durmasıdır.
Özet
- Tutarlılığın ölçüsü, bir işin kod tabanında kaç ayrı biçimde yapıldığıdır; hedef her zaman birdir, hangi biçim olduğu ikinci sorudur.
- Üç modüllü pakette para biçimi, bulunamama bildirimi ve dışa aktarma üçer biçimdeydi; toplam dokuz biçim üçe indi ve iş sayısına eşitlendi.
- Tekilleştirme bedelsiz değil: beş durumun dördü değişti; üçü sunum kararı, biri hata davranışı kararıydı ve çağıran her yeri ilgilendirir.
- Standardı seçerken yaygınlık, dışarıya bakan yüzeyle uyum ve yanlış kullanıma dirençlilik ölçütleri kullanılır; estetik karşılaştırma tartışmayı bitirmez.
- Aynı biçimin tek olması ile o biçimin tek yerde yazılması ayrı sorunlardır; ikincisi tekilleştirmeden sonra da yerinde duruyor.
Sonraki Adım
Bu konudaki kararların hepsi yereldi: bir adın ne söylediği, bir imzanın neyi kabul ettiği, bir modülün neyi dışa açtığı, bir dosyanın kime hizmet ettiği, bir işin hangi biçimde yapıldığı. Her biri tek bir dosyaya bakılarak verilebilir ve tek bir dosyaya bakılarak denetlenebilir.
Kod tabanı büyüdükçe bu ölçeğe sığmayan bir maliyet doğar. Aynı bilgi birden çok yerde durmaya başlar ve bir tanesi güncellendiğinde ötekiler geride kalır. Bugün gerekmeyen bir esneklik bugünden kurulur ve gerekmediği ortaya çıkana kadar her değişikliğin üzerine binen bir yük olur. Bir kararın değiştirilmesi tek bir dosyaya değil, kaç yere dokunduğu önceden bilinmeyen bir kümeye yayılır. Sonraki konu bu üç maliyeti ele alıyor ve yinelemenin türlerini ayırt etmekle başlıyor: aynı bilginin iki yerde durması ile iki parçanın tesadüfen birbirine benzemesi aynı şey değildir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.