İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat