İçeriğe geç
academia.sh

Ders 10 / 16

Kodun Aktöre Göre Düzenlenmesi

Kodun teknik katmana göre değil değişim nedenine göre gruplanmasının ölçülmesi: aynı ücret paketinin iki düzeninde bir dosyada kaç aktörün kararının tanımlı olduğu, her aktörün kaç dosyaya yayıldığı ve tek bir muhasebe değişikliğinin iki düzende kaç dosyaya dokunduğu.

İçindekiler

Görünürlük kararı bir modülün dışarıya ne verdiğini belirliyor. Bir soru daha kalıyor: bu modüller hangi ölçüte göre ayrıldı? Ücret hesabı bir dosyada, biçimlendirme başka bir dosyada; ayrım teknik görünüyor ve makul.

Bir kod tabanını asıl zorlayan şey, farklı kişilerin farklı nedenlerle aynı dosyaya dokunmasıdır. Muhasebenin istediği bir yuvarlama değişikliği ile operasyonun istediği bir kademe değişikliği aynı dosyada buluşuyorsa, iki karar birbirini bekler; biri diğerinin sınamasını bozar. Bu ders, bir dosyanın kaç ayrı aktöre hizmet ettiğini sayıyor.

Aktör Nedir

Aktör, bir değişiklik isteğinin kaynağıdır: bir rol, bir birim, bir paydaş. Ücretlendirme kitaplığında üçü var.

  • Operasyon, taşımanın nasıl yapıldığına karar verir: ağırlık kademeleri, hacimsel bölen, aktarma sayısı, teslim süresi.
  • Muhasebe, paranın nasıl sayıldığına karar verir: vergi oranı, yuvarlama kuralı, fatura satırının biçimi.
  • Müşteri iletişimi, müşteriye ne söylendiğine karar verir: etiket metni, teslim mesajı.

Üçü birbirinden bağımsız değişir. Vergi oranı değiştiğinde ağırlık kademelerinin değişmesi için bir neden yoktur. Ölçüt buradan çıkar: bir dosya tek bir aktörün isteğiyle değişmelidir.

Katmana Göre Düzen

Birinci düzen dosyaları teknik işlevine göre ayırıyor: hesap, akış, biçim. Bu ayrım Veri Erişim Katmanı ve İş Mantığı kursunda kurulan katman sorumluluklarına benzer görünür, ama orada ayrımı belirleyen şey bağımlılık yönüydü; burada yalnız işin türü.

mkdir -p katman aktor
// katman/hesap.mjs — operasyonun kademe kurallari ile muhasebenin vergi kurallari bir arada
export const HACIMSEL_BOLEN = 3000;
export const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];
export const KDV_ORANI = 0.2;

export function ucretlendirilenAgirlikKg(gonderi) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  return Math.max(gonderi.agirlikKg, hacimsel);
}

export function kademeSec(kg) {
  return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg);
}

export function netUcret(gonderi) {
  const kg = ucretlendirilenAgirlikKg(gonderi);
  return kademeSec(kg).kiloBasiUcret * kg;
}

export function yuvarla(tutar) {
  return Math.round(tutar * 100) / 100;
}

export function kdv(net) {
  return yuvarla(net * KDV_ORANI);
}
// katman/akis.mjs — operasyonun aktarma kurali ile musterinin teslim mesaji bir arada
export function aktarmaSayisi(gonderi) {
  return gonderi.bolgeKodu > 2 ? 2 : 1;
}

export function teslimGunu(gonderi) {
  return 1 + aktarmaSayisi(gonderi);
}

export function teslimMesaji(gonderi) {
  return `${gonderi.kod} kargonuz ${teslimGunu(gonderi)} gun icinde teslim edilecek`;
}
// katman/bicim.mjs — muhasebenin fatura bicimi ile musterinin etiket metni bir arada
import { netUcret, kdv, yuvarla, ucretlendirilenAgirlikKg } from "./hesap.mjs";
import { aktarmaSayisi } from "./akis.mjs";

export function faturaSatiri(gonderi) {
  const net = yuvarla(netUcret(gonderi));
  const vergi = kdv(netUcret(gonderi));
  return `${gonderi.kod} net=${net.toFixed(2)} kdv=${vergi.toFixed(2)} toplam=${yuvarla(net + vergi).toFixed(2)}`;
}

export function etiketMetni(gonderi) {
  return `${gonderi.kod} ${ucretlendirilenAgirlikKg(gonderi).toFixed(2)}kg ${aktarmaSayisi(gonderi)} aktarma`;
}

Her dosya kendi içinde tutarlı. Yine de hesap.mjs hem kademeleri hem vergiyi, bicim.mjs hem faturayı hem etiketi, akis.mjs hem aktarmayı hem müşteri mesajını taşıyor.

Aktöre Göre Düzen

İkinci düzen aynı fonksiyonları aktörlerine göre topluyor.

// aktor/operasyon.mjs — kademe, hacim ve aktarma kararlari
export const HACIMSEL_BOLEN = 3000;
export const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];

export function ucretlendirilenAgirlikKg(gonderi) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  return Math.max(gonderi.agirlikKg, hacimsel);
}

export function kademeSec(kg) {
  return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg);
}

export function netUcret(gonderi) {
  const kg = ucretlendirilenAgirlikKg(gonderi);
  return kademeSec(kg).kiloBasiUcret * kg;
}

export function aktarmaSayisi(gonderi) {
  return gonderi.bolgeKodu > 2 ? 2 : 1;
}

export function teslimGunu(gonderi) {
  return 1 + aktarmaSayisi(gonderi);
}
// aktor/muhasebe.mjs — vergi, yuvarlama ve fatura bicimi
import { netUcret } from "./operasyon.mjs";

export const KDV_ORANI = 0.2;

export function yuvarla(tutar) {
  return Math.round(tutar * 100) / 100;
}

export function kdv(net) {
  return yuvarla(net * KDV_ORANI);
}

export function faturaSatiri(gonderi) {
  const net = yuvarla(netUcret(gonderi));
  const vergi = kdv(netUcret(gonderi));
  return `${gonderi.kod} net=${net.toFixed(2)} kdv=${vergi.toFixed(2)} toplam=${yuvarla(net + vergi).toFixed(2)}`;
}
// aktor/musteri.mjs — musteriye gorunen metinler
import { ucretlendirilenAgirlikKg, aktarmaSayisi, teslimGunu } from "./operasyon.mjs";

export function etiketMetni(gonderi) {
  return `${gonderi.kod} ${ucretlendirilenAgirlikKg(gonderi).toFixed(2)}kg ${aktarmaSayisi(gonderi)} aktarma`;
}

export function teslimMesaji(gonderi) {
  return `${gonderi.kod} kargonuz ${teslimGunu(gonderi)} gun icinde teslim edilecek`;
}
// karsilastir.mjs — iki duzenin ayni ciktiyi urettigi dogrulanir
import { faturaSatiri as kFatura, etiketMetni as kEtiket } from "./katman/bicim.mjs";
import { teslimMesaji as kMesaj } from "./katman/akis.mjs";
import { faturaSatiri as aFatura } from "./aktor/muhasebe.mjs";
import { etiketMetni as aEtiket, teslimMesaji as aMesaj } from "./aktor/musteri.mjs";

const gonderi = { kod: "GN-4172", agirlikKg: 2.4, enCm: 30, boyCm: 24, yukseklikCm: 18, bolgeKodu: 3 };
for (const [katman, aktor] of [[kFatura, aFatura], [kEtiket, aEtiket], [kMesaj, aMesaj]]) {
  const a = katman(gonderi), b = aktor(gonderi);
  console.log(`${a === b ? "ayni  " : "FARKLI"} ${a}`);
}
ayni   GN-4172 net=112.32 kdv=22.46 toplam=134.78
ayni   GN-4172 4.32kg 2 aktarma
ayni   GN-4172 kargonuz 3 gun icinde teslim edilecek

Ölçüm: Bir Dosyada Kaç Aktör

Ölçer her aktörün sözcük dağarcığını alır ve her dosyada hangi aktörlerin kararlarının tanımlı olduğuna bakar. İthal edilen bir ad o dosyanın kararı değildir; yalnız tanımlar sayılır.

// aktor-olc.mjs — her dosyada hangi aktorun kararlari tanimli, her aktor kac dosyaya yayilmis
import { readFileSync, readdirSync } from "node:fs";

const AKTOR = {
  operasyon: ["HACIMSEL_BOLEN", "AGIRLIK_KADEMELERI", "ucretlendirilenAgirlikKg", "kademeSec",
    "netUcret", "aktarmaSayisi", "teslimGunu"],
  muhasebe: ["KDV_ORANI", "yuvarla", "kdv", "faturaSatiri"],
  musteri: ["etiketMetni", "teslimMesaji"],
};

// Yalnizca tanimlar sayilir; baska bir dosyadan ithal edilen ad o dosyanin karari degildir.
const tanimlar = (m) => [...m.matchAll(/^export\s+(?:const|function|class)\s+(\w+)/gm)].map((e) => e[1]);

for (const dizin of process.argv.slice(2)) {
  const dosyalar = readdirSync(dizin).filter((a) => a.endsWith(".mjs")).sort();
  const yayilim = Object.fromEntries(Object.keys(AKTOR).map((a) => [a, 0]));
  let cakisan = 0;
  console.log(dizin);
  for (const dosya of dosyalar) {
    const adlar = new Set(tanimlar(readFileSync(`${dizin}/${dosya}`, "utf8")));
    const sahipler = Object.entries(AKTOR)
      .filter(([, sozcukler]) => sozcukler.some((s) => adlar.has(s))).map(([a]) => a);
    for (const a of sahipler) yayilim[a] += 1;
    if (sahipler.length > 1) cakisan += 1;
    console.log(`  ${dosya.padEnd(14)} aktor=${sahipler.length}  [${sahipler.join(", ")}]`);
  }
  const dokum = Object.entries(yayilim).map(([a, n]) => `${a}=${n}`).join("  ");
  console.log(`  cakisan dosya=${cakisan}   aktor basina dosya: ${dokum}`);
}
node aktor-olc.mjs katman aktor
katman
  akis.mjs       aktor=2  [operasyon, musteri]
  bicim.mjs      aktor=2  [muhasebe, musteri]
  hesap.mjs      aktor=2  [operasyon, muhasebe]
  cakisan dosya=3   aktor basina dosya: operasyon=2  muhasebe=2  musteri=2
aktor
  muhasebe.mjs   aktor=1  [muhasebe]
  musteri.mjs    aktor=1  [musteri]
  operasyon.mjs  aktor=1  [operasyon]
  cakisan dosya=0   aktor basina dosya: operasyon=1  muhasebe=1  musteri=1

Katman düzeninde üç dosyanın üçü de çakışma noktası; her biri iki ayrı aktörün isteğiyle değişebilir. Aktör düzeninde çakışma yok. İkinci sayı da aynı şeyi tersinden söylüyor: katman düzeninde her aktörün kararları iki dosyaya yayılmış, aktör düzeninde bir dosyada toplanmış.

Gerçek Bir Değişiklik

Sayıların iddiası bir istekle sınanır. Muhasebe iki şey değiştiriyor: katma değer vergisi oranı yüzde yirmiden yüzde ona iniyor ve tutarlar kuruşa değil tam liraya yuvarlanıyor. İkinci karar fatura satırının biçimini de etkiliyor, çünkü kuruş hanesi artık anlamsız.

cp -r katman katman-yeni && cp -r aktor aktor-yeni
// katman-yeni/hesap.mjs — vergi orani ve yuvarlama kurali degisti
export const HACIMSEL_BOLEN = 3000;
export const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];
export const KDV_ORANI = 0.1;

export function ucretlendirilenAgirlikKg(gonderi) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  return Math.max(gonderi.agirlikKg, hacimsel);
}

export function kademeSec(kg) {
  return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg);
}

export function netUcret(gonderi) {
  const kg = ucretlendirilenAgirlikKg(gonderi);
  return kademeSec(kg).kiloBasiUcret * kg;
}

export function yuvarla(tutar) {
  return Math.round(tutar);
}

export function kdv(net) {
  return yuvarla(net * KDV_ORANI);
}
// katman-yeni/bicim.mjs — fatura satirindan kurus hanesi kalkti
import { netUcret, kdv, yuvarla, ucretlendirilenAgirlikKg } from "./hesap.mjs";
import { aktarmaSayisi } from "./akis.mjs";

export function faturaSatiri(gonderi) {
  const net = yuvarla(netUcret(gonderi));
  const vergi = kdv(netUcret(gonderi));
  return `${gonderi.kod} net=${net.toFixed(0)} kdv=${vergi.toFixed(0)} toplam=${yuvarla(net + vergi).toFixed(0)}`;
}

export function etiketMetni(gonderi) {
  return `${gonderi.kod} ${ucretlendirilenAgirlikKg(gonderi).toFixed(2)}kg ${aktarmaSayisi(gonderi)} aktarma`;
}
// aktor-yeni/muhasebe.mjs — ayni iki karar, tek dosyada
import { netUcret } from "./operasyon.mjs";

export const KDV_ORANI = 0.1;

export function yuvarla(tutar) {
  return Math.round(tutar);
}

export function kdv(net) {
  return yuvarla(net * KDV_ORANI);
}

export function faturaSatiri(gonderi) {
  const net = yuvarla(netUcret(gonderi));
  const vergi = kdv(netUcret(gonderi));
  return `${gonderi.kod} net=${net.toFixed(0)} kdv=${vergi.toFixed(0)} toplam=${yuvarla(net + vergi).toFixed(0)}`;
}
for d in katman aktor; do
  n=0
  for f in "$d"/*.mjs; do
    diff -q <(tail -n +2 "$f") <(tail -n +2 "$d-yeni/$(basename "$f")") > /dev/null || n=$((n + 1))
  done
  echo "$d duzeni: muhasebe degisikligi $n dosyaya dokundu"
done
katman duzeni: muhasebe degisikligi 2 dosyaya dokundu
aktor duzeni: muhasebe degisikligi 1 dosyaya dokundu

Katman düzeninde bicim.mjs de değişti ve o dosyada müşteri iletişiminin sahip olduğu etiketMetni de duruyor. Muhasebenin isteğiyle açılan bir değişiklik, müşteri iletişiminin dosyasına dokunmuş oldu. Aktör düzeninde musteri.mjs hiç açılmadı.

Etiket metnindeki ağırlığın iki haneli kalması da bunu gösteriyor: kuruş kararı paraya aitti, ağırlığa değil. Katman düzeninde iki biçim aynı dosyada durduğu için ayrımı yapan şey dosya değil, değişikliği yapan kişinin dikkatidir.

Ayrımın Sınırı

Aktöre göre gruplama, teknik ayrımların yerine geçmez; onlarla dik açıda durur. Bir uygulamada hem katman ayrımı (bağımlılık yönünü belirler) hem aktör ayrımı (değişim nedenini belirler) bulunur. İkisi çakıştığında seçim, hangi ayrımın daha sık değiştiğine göre yapılır: yılda üç kez vergi değişiyor ve yılda bir kez veri kaynağı değişiyorsa, üstteki bölme aktöre göre olur.

İkinci sınır, aktörlerin de zamanla değişmesidir. Muhasebe ile fiyatlandırma bugün aynı birimse muhasebe.mjs ikisini birden taşır; ayrıldıkları gün dosya da bölünür. Ölçüm bu durumu erken gösterir: bir dosyanın aktör sayısı ikiye çıktığında bölme zamanı gelmiştir.

Üçüncü sınır, ölçünün sözlüğe bağlı olmasıdır. Aktör sözlüğü elle yazılır ve yanlış yazılırsa ölçüm de yanlış çıkar. Sözlüğü doğru tutan şey koddan değil, değişiklik isteklerinin geldiği yerden okunur.

Özet

  • Aktör, bir değişiklik isteğinin kaynağıdır; ölçüt, bir dosyanın tek bir aktörün isteğiyle değişmesidir.
  • Katman düzeninde üç dosyanın üçü de iki aktörün kararını taşıyordu; aktör düzeninde çakışan dosya sayısı sıfır.
  • Aktör başına dosya sayısı ikiden bire indi; her aktörün kararları tek bir yerde toplandı.
  • Vergi oranı ve yuvarlama değişikliği katman düzeninde iki, aktör düzeninde bir dosyaya dokundu; katman düzeninde dokunulan ikinci dosya müşteri iletişiminin metnini de taşıyordu.
  • Aktör ayrımı katman ayrımının yerine geçmez; ikisi çakıştığında daha sık değişen ayrım üstte durur ve bir dosyanın aktör sayısı ikiye çıktığında bölme zamanı gelmiştir.

Sonraki Adım

Aktör düzeninde faturaSatiri bir dosyaya taşındı, ama kitaplıkta hâlâ aynı işin birden çok biçimde yapıldığı yerler var: bir yerde tutar toFixed ile, başka bir yerde elle biçimlendiriliyor; bir yerde hata atılıyor, başka bir yerde boş sonuç dönüyor. Bunların hiçbiri yanlış değil, yalnız farklı. Farklılığın da bir bedeli var ve sayılabilir. Sonraki ders aynı işin kaç ayrı biçimde yapıldığını bulan bir tarama yazar, tekilleştirme sonrası ölçümü tekrarlar.

İ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