Ders 11 / 30
Cephe
Karmaşık alt sistemin sadeleştirilmesi: altı adımlı gönderi kabul akışını istemcilerin kendisinin sıraladığı düzen ile tek giriş noktası arkasına alan düzenin doğrudan bağ, ithal kapanışı ve bilinen adım sayısıyla karşılaştırılması, bir adım atlandığında ortaya çıkan farkın ölçülmesi ve akışa yeni adım eklendiğinde düzenlenen dosyaların sayılması.
İçindekiler
Dekoratör zincirini kuran kod, hangi eklerin hangi sırayla uygulanacağını bilmek zorundaydı. Tek bir çağrı için bu katlanılır bir bilgidir. Kitaplığın tamamı böyle çalıştığında ise istemci sıralı adımların hepsini öğrenmek zorunda kalır.
Gönderi kabulü altı adımdır: adresi düzelt, bölgeyi çöz, taban ücreti hesapla, ekleri uygula, taşıyıcıyı seç, teslim tarihini bul. Sıra keyfi değildir — bölge düzeltilmiş posta kodundan çıkar, ücret bölgeye bağlıdır, taşıyıcı kapsamı yine bölgeye. İki istemci bu altı adımı ayrı ayrı biliyorsa aynı sıra iki yerde durur ve bir adım atlandığında hata çalışma zamanında, yanlış bir tutar biçiminde çıkar. Cephe (facade) alt sistemin önüne tek bir giriş noktası koyar. Ölçüler: istemcinin doğrudan bağ sayısı, ithal kapanışı boyutu ve sıralı adım bilgisinin kaç dosyada bulunduğu.
Problem: Sıra Bilgisi İstemcide
Alt sistem altı küçük modüldür; her biri tek bir işi yapar ve ötekileri tanımaz.
// alt/adres.mjs — alt sistem 1: adresi normallestirir export const adresiDuzelt = (adres) => ({ postaKodu: String(adres.postaKodu ?? "").replace(/\s+/g, ""), il: String(adres.il ?? "").trim(), });
// alt/bolge.mjs — alt sistem 2: posta kodundan ucret bolgesi const POSTA_BOLGE = { 34: "yakin", "06": "orta", 35: "orta", 65: "uzak" }; export const bolgeBul = (postaKodu) => POSTA_BOLGE[postaKodu.slice(0, 2)] ?? "bilinmeyen";
// alt/tarife.mjs — alt sistem 3: agirlik kademesi ve bolge katsayisi const KADEME = [[1, 4990], [5, 8490], [15, 14990], [30, 24990]]; const KATSAYI = { yakin: 100, orta: 118, uzak: 145, bilinmeyen: 165 }; export const tabanUcret = (agirlik, bolge) => { const k = KADEME.find(([ust]) => agirlik <= ust) ?? [0, 24990]; return Math.round((k[1] * KATSAYI[bolge]) / 100); };
// alt/ekler.mjs — alt sistem 4: sigorta ve vergi ekleri export const ekleriUygula = (tutar, secenekler, deger) => { const sigortali = secenekler.sigorta === true ? Math.max(500, Math.round(deger * 0.005)) : 0; return Math.round((tutar + sigortali) * (secenekler.vergi === true ? 1.2 : 1)); };
// alt/tasiyici.mjs — alt sistem 5: bolgeye gore en ucuz tasiyici const CARPAN = { kara: 1, hava: 1.35, deniz: 0.9 }; const KAPSAM = { yakin: ["kara", "hava", "deniz"], orta: ["kara", "hava"], uzak: ["kara"], bilinmeyen: ["kara"] }; export const enUcuzTasiyici = (tutar, bolge) => KAPSAM[bolge] .map((t) => ({ tasiyici: t, tutar: Math.round(tutar * CARPAN[t]) })) .sort((a, b) => a.tutar - b.tutar)[0];
// alt/teslim.mjs — alt sistem 6: bolgeye gore teslim tarihi const GUN = { yakin: 1, orta: 2, uzak: 4, bilinmeyen: 5 }; export const teslimTarihi = (baslangic, bolge) => { const t = new Date(`${baslangic}T00:00:00Z`); t.setUTCDate(t.getUTCDate() + GUN[bolge]); return t.toISOString().slice(0, 10); };
// istekler.mjs — iki duzenin de isledigi istekler export const ISTEKLER = [ { kod: "G-1", agirlik: 0.8, deger: 120000, adres: { postaKodu: " 34100 ", il: " Istanbul " }, secenekler: { sigorta: true, vergi: true }, tarih: "2026-03-02" }, { kod: "G-2", agirlik: 12, deger: 40000, adres: { postaKodu: "06500", il: "Ankara" }, secenekler: { vergi: true }, tarih: "2026-03-02" }, { kod: "G-3", agirlik: 26, deger: 900000, adres: { postaKodu: "65100", il: "Van" }, secenekler: {}, tarih: "2026-03-30" }, ];
İlk düzende iki istemci altı modülü kendisi sıralar. İkinci istemci farklı bir sonuç döndürüyor ama aynı altı adımı baştan tekrarlıyor.
// dogrudan/kabul.mjs — istemci alti modulu kendisi sirayla cagirir import { adresiDuzelt } from "../alt/adres.mjs"; import { bolgeBul } from "../alt/bolge.mjs"; import { tabanUcret } from "../alt/tarife.mjs"; import { ekleriUygula } from "../alt/ekler.mjs"; import { enUcuzTasiyici } from "../alt/tasiyici.mjs"; import { teslimTarihi } from "../alt/teslim.mjs"; export function kabulEt(istek) { const adres = adresiDuzelt(istek.adres); const bolge = bolgeBul(adres.postaKodu); const taban = tabanUcret(istek.agirlik, bolge); const tutar = ekleriUygula(taban, istek.secenekler, istek.deger); const secim = enUcuzTasiyici(tutar, bolge); return { kod: istek.kod, bolge, ...secim, tarih: teslimTarihi(istek.tarih, bolge) }; }
// dogrudan/teklif.mjs — ikinci istemci ayni sirayi bastan tekrar eder import { adresiDuzelt } from "../alt/adres.mjs"; import { bolgeBul } from "../alt/bolge.mjs"; import { tabanUcret } from "../alt/tarife.mjs"; import { ekleriUygula } from "../alt/ekler.mjs"; import { enUcuzTasiyici } from "../alt/tasiyici.mjs"; import { teslimTarihi } from "../alt/teslim.mjs"; export function teklifVer(istek) { const adres = adresiDuzelt(istek.adres); const bolge = bolgeBul(adres.postaKodu); const taban = tabanUcret(istek.agirlik, bolge); const tutar = ekleriUygula(taban, istek.secenekler, istek.deger); const secim = enUcuzTasiyici(tutar, bolge); return { tutar: secim.tutar, tarih: teslimTarihi(istek.tarih, bolge) }; }
Çözüm: Alt Sistemin Önünde Tek Giriş
Cephe alt sistemi gizlemez, ona giden tek bir yol açar. İçinde iş kuralı yoktur; yalnız sıra ve iki dış işlem vardır.
// cephe/kabul-cephesi.mjs — cephe: alt sistemin sirasini bilen tek yer, dis yuzu iki islem import { adresiDuzelt } from "../alt/adres.mjs"; import { bolgeBul } from "../alt/bolge.mjs"; import { tabanUcret } from "../alt/tarife.mjs"; import { ekleriUygula } from "../alt/ekler.mjs"; import { enUcuzTasiyici } from "../alt/tasiyici.mjs"; import { teslimTarihi } from "../alt/teslim.mjs"; const hesapla = (istek) => { const adres = adresiDuzelt(istek.adres); const bolge = bolgeBul(adres.postaKodu); const taban = tabanUcret(istek.agirlik, bolge); const tutar = ekleriUygula(taban, istek.secenekler, istek.deger); return { bolge, secim: enUcuzTasiyici(tutar, bolge), tarih: teslimTarihi(istek.tarih, bolge) }; }; export function kabulEt(istek) { const s = hesapla(istek); return { kod: istek.kod, bolge: s.bolge, ...s.secim, tarih: s.tarih }; } export function teklifVer(istek) { const s = hesapla(istek); return { tutar: s.secim.tutar, tarih: s.tarih }; }
// cephe/kabul.mjs — ayni istemci, tek bag import { kabulEt as cepheKabul } from "./kabul-cephesi.mjs"; export const kabulEt = (istek) => cepheKabul(istek);
// cephe/teklif.mjs — ikinci istemci, tek bag import { teklifVer as cepheTeklif } from "./kabul-cephesi.mjs"; export const teklifVer = (istek) => cepheTeklif(istek);
// kosum.mjs — iki duzen ayni kabul kaydini ve ayni teklifi uretiyor mu import { ISTEKLER } from "./istekler.mjs"; import { kabulEt as dogrudanKabul } from "./dogrudan/kabul.mjs"; import { teklifVer as dogrudanTeklif } from "./dogrudan/teklif.mjs"; import { kabulEt as cepheKabul } from "./cephe/kabul.mjs"; import { teklifVer as cepheTeklif } from "./cephe/teklif.mjs"; let ayrilan = 0; for (const istek of ISTEKLER) { const a = JSON.stringify([dogrudanKabul(istek), dogrudanTeklif(istek)]); const b = JSON.stringify([cepheKabul(istek), cepheTeklif(istek)]); if (a !== b) ayrilan += 1; console.log(`${istek.kod} ${JSON.stringify(dogrudanKabul(istek))}`); } console.log(`ayrilan sonuc = ${ayrilan} / ${ISTEKLER.length}`);
G-1 {"kod":"G-1","bolge":"yakin","tasiyici":"deniz","tutar":6037,"tarih":"2026-03-03"}
G-2 {"kod":"G-2","bolge":"orta","tasiyici":"kara","tutar":21226,"tarih":"2026-03-04"}
G-3 {"kod":"G-3","bolge":"uzak","tasiyici":"kara","tutar":36236,"tarih":"2026-04-03"}
ayrilan sonuc = 0 / 3
Bağın ve Kapanışın Ölçülmesi
Ölçüm Tasarım İlkeleri kursundaki iki ayrı ölçüyü birlikte kullanıyor: doğrudan bağ sayısı (bir dosyanın kendi ithal satırları) ve ithal kapanışı boyutu (o dosyanın dolaylı olarak bağlandığı bütün dosyalar).
// grafik.mjs — istemcilerin dogrudan bag sayisi, ithal kapanisi ve adim sirasini bilen dosyalar import { readdirSync, readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; const ADIM = ["adresiDuzelt", "bolgeBul", "tabanUcret", "ekleriUygula", "enUcuzTasiyici", "teslimTarihi"]; const ithaller = (yol) => [...readFileSync(yol, "utf8").matchAll(/from "(\.[^"]+)"/g)] .map((m) => resolve(dirname(yol), m[1])); const kapanis = (yol, gorulen = new Set()) => { for (const c of ithaller(yol)) if (!gorulen.has(c)) { gorulen.add(c); kapanis(c, gorulen); } return gorulen; }; for (const yol of ["dogrudan/kabul.mjs", "dogrudan/teklif.mjs", "cephe/kabul.mjs", "cephe/teklif.mjs"]) { const metin = readFileSync(yol, "utf8"); const adim = ADIM.filter((a) => metin.includes(a)).length; console.log(`${yol.padEnd(20)} dogrudan bag=${ithaller(yol).length} ithal kapanisi=${kapanis(yol).size} ` + `bildigi adim=${adim}/6`); } const yuzey = (dosyalar) => dosyalar .reduce((t, y) => t + (readFileSync(y, "utf8").match(/^export /gm) ?? []).length, 0); console.log(`alt sistem yuzeyi = ${yuzey(readdirSync("alt").map((d) => `alt/${d}`))} islev, ` + `cephe yuzeyi = ${yuzey(["cephe/kabul-cephesi.mjs"])} islem`);
dogrudan/kabul.mjs dogrudan bag=6 ithal kapanisi=6 bildigi adim=6/6 dogrudan/teklif.mjs dogrudan bag=6 ithal kapanisi=6 bildigi adim=6/6 cephe/kabul.mjs dogrudan bag=1 ithal kapanisi=7 bildigi adim=0/6 cephe/teklif.mjs dogrudan bag=1 ithal kapanisi=7 bildigi adim=0/6 alt sistem yuzeyi = 6 islev, cephe yuzeyi = 2 islem
Üç sayı üç ayrı şey söylüyor. Doğrudan bağ 6’dan 1’e indi. Bildiği adım 6/6’dan 0/6’ya indi: sıra bilgisi iki istemciden çıkıp tek dosyada toplandı. Buna karşılık ithal kapanışı küçülmedi, 6’dan 7’ye çıktı — cephe bir dosya olarak kapanışa eklendi. Cephe derleme bağımlılığını azaltmaz; azalttığı şey istemcinin bilmek zorunda olduğu ad ve sıra sayısıdır. Kapanışın gerçekten küçülmesi için alt sistemin bir kısmının hiç kullanılmaması gerekirdi.
Son satır cephenin yüzeyini veriyor: alt sistem 6 işlev sunuyor, cephe 2 işlem. Bu daralma kazanç olduğu kadar bedeldir; aşağıda ölçülüyor.
Bir Adım Atlanınca
Sıra bilgisinin istemcide durmasının maliyeti, atlanan bir adımın hata vermeden yanlış sonuç üretmesidir.
// atlanan.mjs — dogrudan duzende ilk adim atlanirsa ne olur import { ISTEKLER } from "./istekler.mjs"; import { kabulEt } from "./dogrudan/kabul.mjs"; import { bolgeBul } from "./alt/bolge.mjs"; import { tabanUcret } from "./alt/tarife.mjs"; import { ekleriUygula } from "./alt/ekler.mjs"; import { enUcuzTasiyici } from "./alt/tasiyici.mjs"; import { teslimTarihi } from "./alt/teslim.mjs"; // Ayni sira, tek eksikle: adresiDuzelt cagrilmadi. function atlayarakKabul(istek) { const bolge = bolgeBul(istek.adres.postaKodu); const taban = tabanUcret(istek.agirlik, bolge); const tutar = ekleriUygula(taban, istek.secenekler, istek.deger); const secim = enUcuzTasiyici(tutar, bolge); return { kod: istek.kod, bolge, ...secim, tarih: teslimTarihi(istek.tarih, bolge) }; } const g = ISTEKLER[0]; const dogru = kabulEt(g), eksik = atlayarakKabul(g); console.log(`tam sira : ${JSON.stringify(dogru)}`); console.log(`adim atlandi: ${JSON.stringify(eksik)}`); const gun = (Date.parse(eksik.tarih) - Date.parse(dogru.tarih)) / 86400000; console.log(`tutar farki = ${eksik.tutar - dogru.tutar} kurus, teslim farki = ${gun} gun`);
tam sira : {"kod":"G-1","bolge":"yakin","tasiyici":"deniz","tutar":6037,"tarih":"2026-03-03"}
adim atlandi: {"kod":"G-1","bolge":"bilinmeyen","tasiyici":"kara","tutar":10601,"tarih":"2026-03-07"}
tutar farki = 4564 kurus, teslim farki = 4 gun
Posta kodundaki tek bir boşluk yüzünden bölge “bilinmeyen” oldu; ücret 4564 kuruş yüksek, teslim tarihi 4 gün ileri çıktı ve hiçbir hata atılmadı. Cephe düzeninde bu hata istemcide yapılamaz, çünkü istemcinin çağırdığı adım yoktur.
Akışa Yeni Adım Girince
Yeni gereksinim yasak madde denetimidir ve akışın ortasına, bölge çözümünden hemen sonra girer.
mkdir -p yeni && cp -r alt dogrudan cephe istekler.mjs kosum.mjs grafik.mjs yeni/ ls yeni/alt
adres.mjs bolge.mjs ekler.mjs tarife.mjs tasiyici.mjs teslim.mjs
// yeni/alt/denetim.mjs — alt sistem 7: yasak madde denetimi const YASAK = new Set(["yanici", "asindirici"]); export const yasakMi = (istek) => (istek.icerik ?? []).some((i) => YASAK.has(i));
cd yeni sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \ -e 's#^ const bolge = bolgeBul(adres.postaKodu);#&\n if (yasakMi(istek)) return { kod: istek.kod, red: "yasak madde" };#' dogrudan/kabul.mjs sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \ -e 's#^ const bolge = bolgeBul(adres.postaKodu);#&\n if (yasakMi(istek)) return { red: "yasak madde" };#' dogrudan/teklif.mjs sed -i.y -e 's#^import { tabanUcret } from "../alt/tarife.mjs";#&\nimport { yasakMi } from "../alt/denetim.mjs";#' \ -e 's#^ const bolge = bolgeBul(adres.postaKodu);#&\n if (yasakMi(istek)) return { red: "yasak madde" };#' \ -e 's#^ return { kod: istek.kod, bolge: s.bolge, ...s.secim, tarih: s.tarih };# if (s.red !== undefined) return { kod: istek.kod, red: s.red };\n&#' \ -e 's#^ return { tutar: s.secim.tutar, tarih: s.tarih };# if (s.red !== undefined) return { red: s.red };\n&#' cephe/kabul-cephesi.mjs sed -i.y 's# { kod: "G-3", agirlik: 26# { kod: "G-4", agirlik: 2, deger: 5000, adres: { postaKodu: "34100", il: "Istanbul" },\n secenekler: {}, tarih: "2026-03-02", icerik: ["yanici"] },\n { kod: "G-3", agirlik: 26#' istekler.mjs rm -f dogrudan/*.y cephe/*.y *.y node kosum.mjs | tail -2 node grafik.mjs | head -2 for d in dogrudan cephe; do echo "$d duzenlenen: $(diff -rq "../$d" "$d" | grep '^Files ' | sed 's#.*/\([a-z-]*\.mjs\).*#\1#' | tr '\n' ' ')" done
G-3 {"kod":"G-3","bolge":"uzak","tasiyici":"kara","tutar":36236,"tarih":"2026-04-03"}
ayrilan sonuc = 0 / 4
dogrudan/kabul.mjs dogrudan bag=7 ithal kapanisi=7 bildigi adim=6/6
dogrudan/teklif.mjs dogrudan bag=7 ithal kapanisi=7 bildigi adim=6/6
dogrudan duzenlenen: kabul.mjs teklif.mjs
cephe duzenlenen: kabul-cephesi.mjs
Doğrudan düzende iki istemcinin ikisi de düzenlendi ve doğrudan bağ sayısı 6’dan 7’ye çıktı. Cephe düzeninde yalnız cephe düzenlendi; iki istemci dosyası da olduğu gibi kaldı ve bağ sayıları 1’de durdu. Dört isteğin dördünde iki düzen aynı yanıtı verdi — yasak madde taşıyan G-4 için ikisi de reddetti. İstemci sayısı arttıkça fark doğrusal büyür: on istemci olsa doğrudan düzende on dosya düzenlenecek, cephe düzeninde yine bir.
Bedel ve Ne Zaman Uygulanmayacağı
Cephenin bedeli üç maddedir. Birincisi bir dosya ve bir dolaylılık basamağıdır; ithal kapanışı da bir büyüdü. İkincisi yüzeyin daralmasıdır: alt sistemin 6 işlevine karşı cephe 2 işlem sunuyor. Cephenin geçirmediği bir yetenek — yalnız bölgeyi çözmek, taşıyıcıyı seçmeden ücret hesaplamak — istemciye kapalıdır. Kalıp alt sistemi kapatmaz, alt sistem modülleri kullanılabilir olarak kalır; ama istemci alt sisteme doğrudan gittiği anda cephenin sağladığı sıra güvencesi de biter.
Üçüncüsü en sık rastlanan bozulmadır: cephe iş kuralı biriktirmeye başlarsa. Buradaki cephede tek bir karar yok — yalnız çağrı sırası var. Cepheye indirim eşiği, red kuralı ya da tarife seçimi yazıldığı anda dosya alt sistemin yedinci modülü olur ve hiçbir yerde ölçülmeyen bir sorumluluk yığını büyür. Kalıbın sınırı şudur: cephe sıra bilir, kural bilmez.
İki durumda uygulanmaz. Alt sistem tek modül ve tek çağrıdan oluşuyorsa sıra bilgisi yoktur; cephe yalnız yeniden adlandırma yapar. İstemcilerin her biri alt sistemin farklı bir alt kümesini farklı bir sırayla kullanıyorsa tek bir cephe onların hiçbirine yetmez; o durumda cepheye eklenen her seçenek bir bayrak parametresi olarak birikir ve Temiz Kod kursunda ölçülen bayrak maliyeti geri döner.
Özet
- Cephenin çözdüğü problem, sıralı çağrı bilgisinin her istemcide tekrarlanmasıdır; çözüm alt sistemin önüne sırayı bilen tek bir giriş noktası koymaktır.
- Doğrudan bağ sayısı istemci başına 6’dan 1’e, bilinen adım sayısı 6/6’dan 0/6’ya indi; üç isteğin sonuçları iki düzende de aynı kaldı.
- İthal kapanışı 6’dan 7’ye çıktı: cephe derleme bağımlılığını azaltmaz, istemcinin bilmesi gereken ad ve sıra sayısını azaltır.
- Doğrudan düzende bir adım atlandığında hata atılmadı; ücret 4564 kuruş, teslim tarihi 4 gün saptı. Akışa yedinci adım girince doğrudan düzende iki istemci de düzenlendi, cephe düzeninde yalnız cephe düzenlendi.
- Bedel bir dosya, bir dolaylılık basamağı ve yüzeyin 6 işlevden 2 işleme daralmasıdır; cephe sıra bilir, kural bilmez — kural biriktiği anda alt sistemin bir modülüne dönüşür.
Sonraki Adım
Buraya kadarki dört kalıp nesneleri birleştirmenin farklı biçimlerini kurdu ve hepsinde ölçü dosya, tip ya da bağ sayısıydı. Bir sonraki problem başka bir kaynağı ölçer: belleği. Ücret hesabı sırasında her gönderi için bir tarife nesnesi kuruluyorsa, yüz binlerce gönderilik bir toplu işlemde aynı kademe tablosu yüz binlerce kez kopyalanır. Oysa tablonun içeriği gönderiye göre değişmez; gönderiye göre değişen şey yalnız ağırlık ve adrestir. Sonraki ders nesne sayısını sayar, değişmeyen bilgiyi paylaşılan bir nesnede toplayan sinek siklet kalıbını yazar ve kazancı hem nesne sayısıyla hem de ortama bağlı olduğu işaretlenmiş bir yığın ölçümüyle gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.