İçeriğe geç
academia.sh

Ders 18 / 30

Durum

Teslimat yaşam döngüsünün beş bayrakla ve iç içe koşullarla tutulması ile her durumun kendi geçişlerini bilen nesnelere çevrilmesinin karşılaştırılması: otuz durum-olay çiftinde kabul edilen geçersiz geçiş sayısı, çevrimsel karmaşıklık, temsil edilebilir birleşim sayısı, yeni durum eklendiğinde değişen satır sayısı ve kalıbın bedeli olarak geçiş hedefi hatasının gecikmeli görünmesi.

İçindekiler

Ş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ıdır. 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. Bugün bu kurallar beş bayrakla ve iç içe koşullarla denetleniyor.

Durum makinesi kavramı Yazılım Kalitesi ve Test müfredatında bir kez kurulmuştu: hata yaşam döngüsü orada durum ve geçiş tablosuyla anlatılmış, geçişler test durumu üretmek için kullanılmıştı. Burada yeni olan makinenin kendisi değil, nesnelerle ifade edilişi. Durum (state) kalıbı her durumu bir nesne yapar ve o nesneye yalnızca kendi izin verdiği geçişleri bildirir. Ölçülecek sayılar kabul edilen geçersiz geçiş sayısı, çevrimsel karmaşıklık ve temsil edilebilir bayrak birleşimi sayısıdır.

Bayraklarla Tutulan Yaşam Döngüsü

Beş bayrak ve beş olay var. Her olay kendi koşulunu kendisi kurar; durumun adı bayrakların sıralı denetiminden türetilir.

mkdir -p bayrak durum
// bayrak/teslimat.mjs — durum bes bayrakta, gecisler ic ice kosullarda
export const teslimat = (kod) => {
  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false };
  const durumAdi = () =>
    d.iade ? "iade" : d.teslim ? "teslim" : d.dagitimda ? "dagitimda" : d.yolda ? "yolda" : d.toplandi ? "toplandi" : "olusturuldu";
  return {
    kod,
    durumAdi,
    olay(ad) {
      if (ad === "topla") {
        if (d.toplandi === false && d.teslim === false) {
          d.toplandi = true;
          return true;
        }
        return false;
      }
      if (ad === "yolaCik") {
        if (d.toplandi && d.yolda === false) {
          d.yolda = true;
          return true;
        }
        return false;
      }
      if (ad === "dagitimaVer") {
        if (d.yolda && d.dagitimda === false) {
          d.dagitimda = true;
          return true;
        }
        return false;
      }
      if (ad === "teslimEt") {
        if (d.yolda && d.teslim === false && d.iade === false) {
          d.teslim = true;
          return true;
        }
        return false;
      }
      if (ad === "iadeEt") {
        if (d.yolda && d.iade === false) {
          d.iade = true;
          return true;
        }
        return false;
      }
      return false;
    },
  };
};

Durumun Nesneleştirilmesi

İkinci sürümde her durum bir nesnedir ve kendi geçiş kümesini taşır. Geçiş kümesi olay adından sonraki durumun adına giden bir eşlemedir.

// durum/durumlar.mjs — her durum yalnizca kendi gecislerini bilir
const durum = (ad, gecisler) => ({
  ad,
  izin: (olay) => Object.hasOwn(gecisler, olay),
  sonraki: (olay) => gecisler[olay],
});

export const DURUMLAR = {
  olusturuldu: durum("olusturuldu", { topla: "toplandi" }),
  toplandi: durum("toplandi", { yolaCik: "yolda" }),
  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade" }),
  dagitimda: durum("dagitimda", { teslimEt: "teslim", iadeEt: "iade" }),
  teslim: durum("teslim", {}),
  iade: durum("iade", {}),
};
// durum/teslimat.mjs — gecis karari durum nesnesine sorulur
import { DURUMLAR } from "./durumlar.mjs";

export const teslimat = (kod) => {
  let su = DURUMLAR.olusturuldu;
  return {
    kod,
    durumAdi: () => su.ad,
    olay(ad) {
      if (su.izin(ad) === false) return false;
      su = DURUMLAR[su.sonraki(ad)];
      return true;
    },
  };
};

Teslimat nesnesi artık hangi olayın hangi durumda geçerli olduğunu bilmiyor; kararı elindeki durum nesnesine soruyor. Bu, sorma–söyle ilkesinin durum makinesindeki karşılığıdır: bilgi kararın verildiği yerdedir.

Otuz Çiftin Tek Tek Denenmesi

Ölçüm, altı durumun her birinde beş olayın da denenmesiyle yapılır: otuz çift. Beklenti tablosu ve her duruma götüren yol ayrı bir dosyada durur.

// tablo.mjs — gecerli gecis tablosu ve her duruma goturen yol
export const OLAYLAR = ["topla", "yolaCik", "dagitimaVer", "teslimEt", "iadeEt"];
export const YOL = {
  olusturuldu: [],
  toplandi: ["topla"],
  yolda: ["topla", "yolaCik"],
  dagitimda: ["topla", "yolaCik", "dagitimaVer"],
  teslim: ["topla", "yolaCik", "dagitimaVer", "teslimEt"],
  iade: ["topla", "yolaCik", "iadeEt"],
};
export const GECERLI = {
  olusturuldu: { topla: "toplandi" },
  toplandi: { yolaCik: "yolda" },
  yolda: { dagitimaVer: "dagitimda", iadeEt: "iade" },
  dagitimda: { teslimEt: "teslim", iadeEt: "iade" },
  teslim: {},
  iade: {},
};
// kosum.mjs — otuz durum-olay ciftini iki surumde de dener
import { teslimat as bayrakTeslimat } from "./bayrak/teslimat.mjs";
import { teslimat as durumTeslimat } from "./durum/teslimat.mjs";
import { GECERLI, OLAYLAR, YOL } from "./tablo.mjs";

function dene(uret, etiket) {
  let kabulEdilenGecersiz = 0;
  let reddedilenGecerli = 0;
  const satirlar = [];
  for (const [durumAdi, yol] of Object.entries(YOL)) {
    const isaret = [];
    for (const olay of OLAYLAR) {
      const t = uret("TS-1");
      for (const o of yol) t.olay(o);
      const kabul = t.olay(olay);
      const gecerli = Object.hasOwn(GECERLI[durumAdi], olay);
      if (kabul && gecerli === false) kabulEdilenGecersiz += 1;
      if (kabul === false && gecerli) reddedilenGecerli += 1;
      isaret.push(kabul === gecerli ? "." : kabul ? "K" : "R");
    }
    satirlar.push(`  ${durumAdi.padEnd(12)} ${isaret.join(" ")}`);
  }
  console.log(`${etiket} (${OLAYLAR.join(" ")}):`);
  for (const s of satirlar) console.log(s);
  console.log(`  ${etiket.padEnd(12)} kabul edilen gecersiz=${kabulEdilenGecersiz}  reddedilen gecerli=${reddedilenGecerli}`);
}

dene(bayrakTeslimat, "bayrak");
dene(durumTeslimat, "durum");
bayrak (topla yolaCik dagitimaVer teslimEt iadeEt):
  olusturuldu  . . . . .
  toplandi     . . . . .
  yolda        . . . K .
  dagitimda    . . . . .
  teslim       . . . . K
  iade         . . K . .
  bayrak       kabul edilen gecersiz=3  reddedilen gecerli=0
durum (topla yolaCik dagitimaVer teslimEt iadeEt):
  olusturuldu  . . . . .
  toplandi     . . . . .
  yolda        . . . . .
  dagitimda    . . . . .
  teslim       . . . . .
  iade         . . . . .
  durum        kabul edilen gecersiz=0  reddedilen gecerli=0

Üçe karşı sıfır. Bayrak sürümü hiçbir geçerli geçişi reddetmiyor, yani kabaca bozuk değil; fazla izin veriyor. Üç K işareti üç somut hatadır: dağıtıma çıkmamış bir gönderi teslim edilebiliyor, teslim edilmiş bir gönderi iade edilebiliyor, iade edilmiş bir gönderi dağıtıma verilebiliyor. Üçünün kaynağı aynı: her olay kendi koşulunu kendisi kuruyor ve hiçbiri “şu anda hangi durumdayım” sorusunu tek bir yerden sormuyor.

Karmaşıklık ve Temsil Uzayı

// karmasiklik.mjs — karar noktasi sayarak cevrimsel karmasiklik verir
import { readFileSync, readdirSync } from "node:fs";

const KARAR = /\bif\b|&&|\|\||\?|\bcase\b|\bwhile\b|\bfor\b/g;

for (const dizin of ["bayrak", "durum"]) {
  let karar = 0;
  let satir = 0;
  const dosyalar = readdirSync(dizin).sort();
  for (const d of dosyalar) {
    const metin = readFileSync(`${dizin}/${d}`, "utf8").replace(/^\/\/.*$/gm, "");
    karar += (metin.match(KARAR) ?? []).length;
    satir += metin.split("\n").filter((s) => s.trim().length > 0).length;
  }
  console.log(`${dizin.padEnd(7)} dosya=${dosyalar.length}  satir=${satir}  karar noktasi=${karar}  cevrimsel karmasiklik=${karar + 1}`);
}
bayrak  dosya=1  satir=47  karar noktasi=21  cevrimsel karmasiklik=22
durum   dosya=2  satir=26  karar noktasi=1  cevrimsel karmasiklik=2

Yirmi ikiye karşı iki. Fark, kararların koddan veriye taşınmasından gelir: geçiş kuralları artık dallanma değil, eşleme girdisidir. İkinci sayı bunun neden hataya yer bırakmadığını gösterir.

// bedel.mjs — temsil uzayi ve gecis hedefi yanlis yazildiginda ortaya cikan gecikme
import { cpSync, readFileSync, writeFileSync } from "node:fs";
import { DURUMLAR } from "./durum/durumlar.mjs";

const BAYRAK = 5;
console.log(`bayrak: temsil edilebilir birlesim=${2 ** BAYRAK}  gecerli durum=${Object.keys(DURUMLAR).length}`);
console.log(`durum : temsil edilebilir birlesim=${Object.keys(DURUMLAR).length}  gecerli durum=${Object.keys(DURUMLAR).length}`);

cpSync("durum", "durum-hatali", { recursive: true });
writeFileSync(
  "durum-hatali/durumlar.mjs",
  readFileSync("durum/durumlar.mjs", "utf8").replace('dagitimaVer: "dagitimda"', 'dagitimaVer: "dagitimdaa"'),
);

const t = (await import("./durum-hatali/teslimat.mjs")).teslimat("TS-3");
let olayNo = 0;
for (const o of ["topla", "yolaCik", "dagitimaVer", "teslimEt"]) {
  olayNo += 1;
  try {
    const kabul = t.olay(o);
    console.log(`olay ${olayNo} ${o.padEnd(12)} kabul=${kabul ? 1 : 0}`);
  } catch (e) {
    console.log(`olay ${olayNo} ${o.padEnd(12)} ${e.constructor.name} firlatildi`);
    break;
  }
}
bayrak: temsil edilebilir birlesim=32  gecerli durum=6
durum : temsil edilebilir birlesim=6  gecerli durum=6
olay 1 topla        kabul=1
olay 2 yolaCik      kabul=1
olay 3 dagitimaVer  kabul=1
olay 4 teslimEt     TypeError firlatildi

Otuz ikiye karşı altı. Beş bağımsız bayrak otuz iki birleşim üretir ve bunların yirmi altısı hiçbir gerçek duruma karşılık gelmez; yolda olmadan dağıtımda olmak ya da hem teslim hem iade olmak temsil edilebilir. Durum nesnesi tek bir değişken tuttuğu için temsil uzayı geçerli durum kümesine eşittir.

Çıktının son dört satırı bedelin ilk kalemidir. Geçiş hedefi bir harf yanlış yazıldığında dagitimaVer olayı kabul edilir, hata ancak bir sonraki olayda ortaya çıkar. Geçişler veri olduğu için hedef adları kod değildir ve yanlış ad yazıldığı yerde değil, kullanıldığı yerde patlar. Bedelin ikinci kalemi dosya sayısıdır: bir dosyaya karşı iki dosya, ve bir olayın kabul edilip edilmediğini anlamak için ikisini de okumak gerekir.

Yeni Durum Eklendiğinde

Adres teyidi bekleyen gönderiler için beklemede durumu ve iki yeni olay isteniyor: yoldaki gönderi beklet ile bekletiliyor, devamEt ile yeniden yola çıkıyor. Betik değişikliği iki sürüme de uygular, değişen satırları sayar ve aynı olay dizisini ikisinde de koşar.

// yenidurum.mjs — "beklemede" durumunu iki surume de ekler ve degisimi olcer
import { cpSync, readFileSync, writeFileSync } from "node:fs";

cpSync("bayrak", "bayrak-yeni", { recursive: true });
cpSync("durum", "durum-yeni", { recursive: true });

const degistir = (yol, arayan, yeni) => {
  const eski = readFileSync(yol, "utf8");
  writeFileSync(yol, eski.replace(arayan, yeni));
};

degistir(
  "bayrak-yeni/teslimat.mjs",
  "  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false };",
  "  const d = { toplandi: false, yolda: false, dagitimda: false, teslim: false, iade: false, beklemede: false };",
);
degistir(
  "bayrak-yeni/teslimat.mjs",
  "    d.iade ? \"iade\" : d.teslim ? \"teslim\" : d.dagitimda ? \"dagitimda\" : d.yolda ? \"yolda\" : d.toplandi ? \"toplandi\" : \"olusturuldu\";",
  "    d.iade ? \"iade\" : d.teslim ? \"teslim\" : d.beklemede ? \"beklemede\" : d.dagitimda ? \"dagitimda\" : d.yolda ? \"yolda\" : d.toplandi ? \"toplandi\" : \"olusturuldu\";",
);
degistir(
  "bayrak-yeni/teslimat.mjs",
  '      if (ad === "dagitimaVer") {\n        if (d.yolda && d.dagitimda === false) {',
  `      if (ad === "beklet") {
        if (d.yolda && d.beklemede === false && d.dagitimda === false) {
          d.beklemede = true;
          return true;
        }
        return false;
      }
      if (ad === "devamEt") {
        if (d.beklemede) {
          d.beklemede = false;
          return true;
        }
        return false;
      }
      if (ad === "dagitimaVer") {
        if (d.yolda && d.dagitimda === false && d.beklemede === false) {`,
);

degistir(
  "durum-yeni/durumlar.mjs",
  '  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade" }),',
  `  yolda: durum("yolda", { dagitimaVer: "dagitimda", iadeEt: "iade", beklet: "beklemede" }),
  beklemede: durum("beklemede", { devamEt: "yolda", iadeEt: "iade" }),`,
);

const fark = (a, b) => {
  const x = readFileSync(a, "utf8").split("\n");
  const y = readFileSync(b, "utf8").split("\n");
  const kume = new Set(x.map((s) => s.trim()));
  return y.filter((s) => s.trim().length > 0 && kume.has(s.trim()) === false).length;
};

console.log(`bayrak: eklenen/degisen satir=${fark("bayrak/teslimat.mjs", "bayrak-yeni/teslimat.mjs")}`);
console.log(`durum : eklenen/degisen satir=${fark("durum/durumlar.mjs", "durum-yeni/durumlar.mjs")}`);

const b = (await import("./bayrak-yeni/teslimat.mjs")).teslimat("TS-2");
const c = (await import("./durum-yeni/teslimat.mjs")).teslimat("TS-2");
for (const t of [b, c]) {
  const iz = [];
  for (const o of ["topla", "yolaCik", "beklet", "dagitimaVer", "devamEt", "dagitimaVer", "teslimEt"]) {
    iz.push(`${o}=${t.olay(o) ? "1" : "0"}`);
  }
  console.log(`${t.durumAdi().padEnd(10)} ${iz.join(" ")}`);
}
bayrak: eklenen/degisen satir=9
durum : eklenen/degisen satir=2
teslim     topla=1 yolaCik=1 beklet=1 dagitimaVer=0 devamEt=1 dagitimaVer=1 teslimEt=1
teslim     topla=1 yolaCik=1 beklet=1 dagitimaVer=0 devamEt=1 dagitimaVer=1 teslimEt=1

Dokuz satıra karşı iki satır, ve iki sürüm aynı olay dizisinde aynı kabul dizisini ve aynı son durumu veriyor. Dokuz satırın bir kısmı var olan bir koşulun içine giriyor — dagitimaVer koşuluna üçüncü bir denetim eklendi — yani yeni durum eski geçişlerin davranışını da riske atıyor. Durum sürümünde eski geçiş satırlarına dokunulmadı.

Özet

  • Durum kalıbı her durumu kendi geçiş kümesini bilen bir nesne yapar; teslimat nesnesi hangi olayın geçerli olduğunu bilmez, elindeki durum nesnesine sorar.
  • Otuz durum-olay çiftinin denenmesinde bayrak sürümü 3 geçersiz geçişi kabul etti, durum sürümü 0; iki sürüm de hiçbir geçerli geçişi reddetmedi.
  • Çevrimsel karmaşıklık 22’den 2’ye indi; temsil edilebilir birleşim sayısı 32’den 6’ya indi, yani 26 anlamsız bayrak birleşimi ortadan kalktı.
  • Yeni bir durum ve iki olay eklendiğinde bayrak sürümünde 9, durum sürümünde 2 satır değişti; iki sürüm aynı olay dizisinde aynı sonucu verdi.
  • Bedel: dosya sayısı 1’den 2’ye çıktı ve geçiş hedefi yanlış yazıldığında hata yazıldığı olayda değil bir sonraki olayda ortaya çıktı.

Sonraki Adım

Durum nesnesi bir soruyu tek bir yerden yanıtlıyor: bu olay burada geçerli mi. Kitaplığın ücret düzeltmeleri tarafında ise soru tek bir yere sorulamıyor. Bir gönderiye sözleşmeli müşteri indirimi, hacim indirimi, kampanya indirimi ve elle girilen düzeltme sırayla uygulanabilir; kuralların bir kısmı gönderiye uymaz ve atlanır, bir kısmı uygulandıktan sonra zinciri keser. Bugün bu kurallar tek gövdede iç içe koşullarla duruyor ve sıraları gövdenin içine gömülü. Sonraki ders bu gövdenin karar noktalarını sayar, kural sırası değiştiğinde düzenlenen satırları ölçer, sonra isteği işleyiciler arasında aktaran bir zincir kurup 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