İçeriğe geç
academia.sh

Ders 17 / 30

Şablon Yöntem

Ücretlendirme adımlarının üç taşıyıcı dosyasında ayrı ayrı sıralanması ile iskeletin tek yere alınmasının karşılaştırılması: en az iki dosyada yinelenen satır sayısı, adım sırası sapmasının ürettiği tutar hatası, yedinci adım eklendiğinde düzenlenen dosya sayısı ve kalıbın bedeli olarak toplam satır artışı ile boş kanca sayısı.

İçindekiler

Üç komut aynı üç adımı aynı sırayla yapıyordu: önceki değeri sakla, yeni değeri yaz, kaydı tut. Aynı tekrar ücretlendirme tarafında daha büyük ölçekte var. Ücret hesabı altı adımdan geçer — ücretli ağırlığı belirle, taban ücreti hesapla, bölge katsayısını uygula, ek hizmet ücretini ekle, indirimi düş, asgari ücreti uygula — ve yurt içi, ekspres, yurt dışı taşıyıcıları için üç dosya bu altı adımı ayrı ayrı sıralar. Adımların sırası her üçünde aynı olmak zorundadır; değişen yalnızca birkaç adımın içeriğidir.

Şablon yöntem (template method) sırayı tek bir yere alır ve değişen adımları dışarıdan gelen kancalara bırakır. Kazanç iki sayıyla ölçülür: yinelenen satır sayısı ve sıraya bir adım eklendiğinde düzenlenen dosya sayısı. Bir üçüncü sayı da var ve o kalıbın asıl gerekçesi: sıra bir kopyada saparsa üretilen tutar yanlış olur.

Yinelenen İskelet

Ortak veri ve bölge katsayıları iki sürümde paylaşılır.

mkdir -p yinelenen sablon
// bolge.mjs — iki surumun paylastigi bolge katsayilari ve gonderi kumesi
export const BOLGE = { "1": 100, "2": 130, "3": 175 };
export const GONDERILER = [
  { kod: "GN-1", agirlik: 2, hacim: 8, bolge: "1", indirim: 0 },
  { kod: "GN-2", agirlik: 6, hacim: 60, bolge: "2", indirim: 10 },
  { kod: "GN-3", agirlik: 14, hacim: 20, bolge: "3", indirim: 0 },
  { kod: "GN-4", agirlik: 1, hacim: 3, bolge: "2", indirim: 40 },
  { kod: "GN-5", agirlik: 22, hacim: 180, bolge: "3", indirim: 25 },
];

Yurt içi taşıyıcının dosyası altı adımı sıralar; ek hizmet ücreti sıfır olduğu için o adım hiç yazılmamış.

// yinelenen/yurtici.mjs — alti adim bu dosyada siralanmis, ek hizmet adimi atlanmis
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 3900;

export function ucretle(gonderi) {
  const agirlik = Math.ceil(gonderi.agirlik);
  let tutar = 2500 + 420 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, ASGARI);
}
// yinelenen/ekspres.mjs — ayni alti adim yeniden siralanmis
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 6500;

export function ucretle(gonderi) {
  const agirlik = Math.ceil(gonderi.agirlik);
  let tutar = 4000 + 640 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += 1500;
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, ASGARI);
}

Üçüncü dosyada son iki adım ters sırada: asgari ücret indirimden önce uygulanmış.

// yinelenen/yurtdisi.mjs — ayni alti adim, son iki adim ters sirada
import { BOLGE } from "../bolge.mjs";

export const ASGARI = 12000;

export function ucretle(gonderi) {
  const agirlik = Math.max(Math.ceil(gonderi.agirlik), Math.ceil(gonderi.hacim / 5));
  let tutar = 6000 + 900 * agirlik;
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += 2200;
  tutar = Math.max(tutar, ASGARI);
  return tutar - Math.round((tutar * gonderi.indirim) / 100);
}

İskeletin Tek Yere Alınması

Şablon sürümünde sıra bir dosyadadır ve değişen dört şey kancadır: ücretli ağırlık, taban ücret, ek hizmet ve asgari ücret.

// sablon/sablon.mjs — degismeyen iskelet, alti adim tek yerde
import { BOLGE } from "../bolge.mjs";

export function ucretle(tarife, gonderi) {
  const agirlik = tarife.ucretliAgirlik(gonderi);
  let tutar = tarife.tabanUcret(agirlik);
  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);
  tutar += tarife.ekHizmet(gonderi);
  tutar -= Math.round((tutar * gonderi.indirim) / 100);
  return Math.max(tutar, tarife.asgari);
}
// sablon/tarifeler.mjs — yalnizca degisen adimlar
const agirlikca = (g) => Math.ceil(g.agirlik);

export const yurtici = {
  ad: "yurtici",
  asgari: 3900,
  ucretliAgirlik: agirlikca,
  tabanUcret: (a) => 2500 + 420 * a,
  ekHizmet: () => 0,
};

export const ekspres = {
  ad: "ekspres",
  asgari: 6500,
  ucretliAgirlik: agirlikca,
  tabanUcret: (a) => 4000 + 640 * a,
  ekHizmet: () => 1500,
};

export const yurtdisi = {
  ad: "yurtdisi",
  asgari: 12000,
  ucretliAgirlik: (g) => Math.max(Math.ceil(g.agirlik), Math.ceil(g.hacim / 5)),
  tabanUcret: (a) => 6000 + 900 * a,
  ekHizmet: () => 2200,
};

Bu, strateji dersindeki tarife nesnesinden bir noktada ayrılır: strateji bütün hesabı devralıyordu, kanca yalnız bir adımı devralır. Sıranın sahibi iskelettir; kanca ne zaman çağrıldığını seçemez.

Sapmanın Ölçülmesi

Sürücü betik on beş hesabı iki sürümde de yapar, sapan sonuçları ve asgari ücretin altına düşen tutarları sayar.

// kosum.mjs — iki surumu ayni gonderilerle kosar, asgari altina dusen tutari sayar
import { GONDERILER } from "./bolge.mjs";
import * as yurtici from "./yinelenen/yurtici.mjs";
import * as ekspres from "./yinelenen/ekspres.mjs";
import * as yurtdisi from "./yinelenen/yurtdisi.mjs";
import { ucretle } from "./sablon/sablon.mjs";
import * as tarifeler from "./sablon/tarifeler.mjs";

const YINELENEN = { yurtici, ekspres, yurtdisi };
let sapma = 0;
let asgariAlti = { yinelenen: 0, sablon: 0 };

for (const ad of ["yurtici", "ekspres", "yurtdisi"]) {
  const satir = [];
  for (const g of GONDERILER) {
    const a = YINELENEN[ad].ucretle(g);
    const b = ucretle(tarifeler[ad], g);
    if (a !== b) sapma += 1;
    if (a < YINELENEN[ad].ASGARI) asgariAlti.yinelenen += 1;
    if (b < tarifeler[ad].asgari) asgariAlti.sablon += 1;
    satir.push(`${g.kod}=${a}/${b}`);
  }
  console.log(`${ad.padEnd(9)} ${satir.join(" ")}`);
}
console.log(`iki surum arasindaki sapma sayisi = ${sapma}`);
console.log(`asgari ucretin altina dusen tutar: yinelenen=${asgariAlti.yinelenen} sablon=${asgariAlti.sablon}`);
yurtici   GN-1=3900/3900 GN-2=5873/5873 GN-3=14665/14665 GN-4=3900/3900 GN-5=15409/15409
ekspres   GN-1=6780/6780 GN-2=10523/10523 GN-3=24180/24180 GN-4=6500/6500 GN-5=24855/24855
yurtdisi  GN-1=12000/12000 GN-2=21636/21636 GN-3=34750/34750 GN-4=7200/12000 GN-5=52050/52050
iki surum arasindaki sapma sayisi = 1
asgari ucretin altina dusen tutar: yinelenen=1 sablon=0

On beş hesabın on dördü aynı; biri sapıyor. Yüzde kırk indirimli GN-4, yurt dışı taşıyıcıda yinelenen sürümde 7200, şablon sürümünde 12000. Asgari ücret 12000 olduğu için yinelenen sürümün ürettiği tutar sözleşmeye aykırıdır. Sapmanın kaynağı bir formül hatası değil, adım sırasıdır: iskelet üç kez yazıldığında üçüncüsünde iki adım yer değiştirmiştir. Şablon sürümünde sıra kancalara açık olmadığı için bu sapmanın yazılabileceği bir yer yok.

Yinelenen Satır ve Yeni Adım

İkinci ölçü statiktir: her sürümde kaç anlamlı satır var ve bunların kaçı en az iki dosyada birden geçiyor.

// yineleme.mjs — iki surumde yinelenen satir sayisini sayar
import { readFileSync, readdirSync } from "node:fs";

const anlamli = (yol) =>
  readFileSync(yol, "utf8")
    .split("\n")
    .map((s) => s.trim())
    .filter((s) => s.length > 0 && s.startsWith("//") === false && s.startsWith("import ") === false);

function say(dizin) {
  const dosyalar = readdirSync(dizin).sort();
  const sayim = new Map();
  for (const d of dosyalar) {
    for (const s of new Set(anlamli(`${dizin}/${d}`))) sayim.set(s, (sayim.get(s) ?? 0) + 1);
  }
  const yinelenen = [...sayim.entries()].filter(([, n]) => n >= 2);
  const toplam = dosyalar.reduce((t, d) => t + anlamli(`${dizin}/${d}`).length, 0);
  return { dosya: dosyalar.length, toplam, yinelenen: yinelenen.length };
}

for (const dizin of ["yinelenen", "sablon"]) {
  const r = say(dizin);
  console.log(`${dizin.padEnd(10)} dosya=${r.dosya}  anlamli satir=${r.toplam}  en az iki dosyada gecen satir=${r.yinelenen}`);
}
yinelenen  dosya=3  anlamli satir=26  en az iki dosyada gecen satir=6
sablon     dosya=2  anlamli satir=30  en az iki dosyada gecen satir=0

Altıya karşı sıfır. Bu altı satır bilgi yinelemesidir: aynı adımın aynı biçimde üç kez yazılması. Şimdi sıraya yedinci bir adım giriyor — bölge katsayısından sonra yüzde altı yakıt ek ücreti.

// adim.mjs — yedinci adimi (yakit ek ucreti) iki surume de ekler
import { cpSync, readFileSync, readdirSync, writeFileSync } from "node:fs";

cpSync("yinelenen", "yinelenen-yeni", { recursive: true });
cpSync("sablon", "sablon-yeni", { recursive: true });

const CAPA = "  tutar = Math.round((tutar * BOLGE[gonderi.bolge]) / 100);";
const YAKIT = "  tutar += Math.round((tutar * 6) / 100);";

const ekle = (yol) => {
  const eski = readFileSync(yol, "utf8");
  const yeni = eski.replace(CAPA, `${CAPA}\n${YAKIT}`);
  writeFileSync(yol, yeni);
  return yeni === eski ? 0 : 1;
};

let yinelenenDuzenlenen = 0;
for (const d of readdirSync("yinelenen-yeni")) yinelenenDuzenlenen += ekle(`yinelenen-yeni/${d}`);
let sablonDuzenlenen = 0;
for (const d of readdirSync("sablon-yeni")) sablonDuzenlenen += ekle(`sablon-yeni/${d}`);

console.log(`yedinci adim icin duzenlenen dosya: yinelenen=${yinelenenDuzenlenen}  sablon=${sablonDuzenlenen}`);

const { GONDERILER } = await import("./bolge.mjs");
const y = await import("./yinelenen-yeni/yurtici.mjs");
const s = await import("./sablon-yeni/sablon.mjs");
const t = await import("./sablon-yeni/tarifeler.mjs");
console.log(`GN-2 yurtici yeni tutar: yinelenen=${y.ucretle(GONDERILER[1])}  sablon=${s.ucretle(t.yurtici, GONDERILER[1])}`);
yedinci adim icin duzenlenen dosya: yinelenen=3  sablon=1
GN-2 yurtici yeni tutar: yinelenen=6226  sablon=6226

Üç dosyaya karşı bir dosya, ve iki sürüm yeni adımdan sonra da aynı tutarı veriyor. Üç dosyanın düzenlenmesi yalnız iş yükü değil, üç ayrı sapma fırsatıdır; yukarıdaki 7200 tam olarak böyle oluşmuştur.

Bedelin Sayımı

Aynı statik ölçüm bedeli de veriyor: anlamlı satır sayısı 26’dan 30’a yükseldi. Şablon yöntem satır sayısını düşürmez; yinelemeyi düşürüp toplamı artırır, çünkü her kanca bir ad ve bir imza ister. İkinci kalem iş yapmayan kancadır.

echo "bos adim (is yapmayan kanca) sayisi: yinelenen=$(grep -h 'tutar += 0;' yinelenen/*.mjs | wc -l | tr -d ' ')  sablon=$(grep -h 'ekHizmet: () => 0' sablon/*.mjs | wc -l | tr -d ' ')"
bos adim (is yapmayan kanca) sayisi: yinelenen=0  sablon=1

Yinelenen sürümde yurt içi taşıyıcı ek hizmet adımını hiç yazmamıştı; şablon sürümünde yazmak zorunda, çünkü iskelet o kancayı çağırıyor. Sıfır döndüren bir kanca, kalıbın istediği biçimsel bedeldir. Üçüncü kalem okuma maliyetidir: yinelenen sürümde bir taşıyıcının tam akışı tek dosyada görünür, şablon sürümünde iskelet bir dosyada ve değişen adımlar başka dosyada durur, yani akışı görmek için iki dosya okunur.

Özet

  • Şablon yöntem değişmeyen adım sırasını tek bir gövdeye alır ve değişen adımları kancalara bırakır; kanca ne zaman çağrıldığını seçemez, sıranın sahibi iskelettir.
  • En az iki dosyada birden geçen satır sayısı 6’dan 0’a indi; yedinci adım eklendiğinde düzenlenen dosya sayısı 3’ten 1’e indi ve iki sürüm aynı tutarı üretmeye devam etti.
  • Sıranın bir kopyada sapması on beş hesaptan birinde yanlış tutar üretti: yüzde kırk indirimli gönderi yurt dışı taşıyıcıda asgari ücretin altına, 12000 yerine 7200’e düştü.
  • Bedel: anlamlı satır sayısı 26’dan 30’a yükseldi ve iş yapmayan kanca sayısı 0’dan 1’e çıktı.
  • Bir taşıyıcının tam ücret akışı yinelenen sürümde tek dosyada görünür, şablon sürümünde iki dosyaya dağılır.

Sonraki Adım

Şablonda sıra sabitti; hangi adımın çalışacağı baştan belliydi. Teslimatın kendisinde ise hangi işlemin geçerli olduğu o anki duruma bağlı. Bir teslimat oluşturulmuş, toplanmış, yolda, dağıtımda, teslim edilmiş ya da iade edilmiş olabilir; toplanmamış bir gönderi dağıtıma çıkamaz, teslim edilmiş bir gönderi iade edilemez. Bu kurallar bugün alan üzerinde bayraklarla ve iç içe koşullarla denetleniyor ve bazı geçersiz geçişler denetimden kaçıyor. Sonraki ders tüm durum–olay çiftlerini tek tek deneyip kabul edilen geçersiz geçiş sayısını ve çevrimsel karmaşıklığı ölçer, sonra durumu nesneye çevirip aynı sayıları yeniden hesaplar.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat