Ders 10 / 13
Küçük Adımlarla İlerleme
Aynı kural kümesine iki farklı adım boyuyla ulaşan yolların ölçülmesi, geri bildirim noktası sayısı ile hata arama alanının ilişkisi ve son yeşil noktaya dönüş maliyeti.
İçindekiler
Önceki derste döngü iki kez döndü ve her turda tek bir kural ele alındı. Bu bir zorunluluk değil, bir seçimdi: aynı iş, dört kuralı birden kapsayan tek bir turla da yürütülebilirdi. Adım boyu test odaklı geliştirmenin en çok tartışılan ve en az ölçülen değişkenidir.
Bu ders adım boyunu ölçülebilir bir şeye bağlar. Aynı hedefe iki farklı yoldan gidilir ve her durakta iki nicelik kaydedilir: kaç test düşüyor ve son duraktan bu yana kaç satır değişti. İkinci nicelik önemlidir, çünkü bir test düştüğünde hatanın aranacağı alan tam olarak odur.
Hedef: Dört Kural
Yenileme kuralları dörde çıktı. Yenileme son tarihi bugünden itibaren üye türünün ödünç süresi kadar ileri alır ve yenileme sayacını artırır. Rezerve edilmiş bir kitap yenilenemez. Bir ödünç en çok iki kez yenilenebilir. Süresi geçmiş bir ödünç yenilenemez; son gününde yapılan yenileme ise kabul edilir.
// yenileme.mjs — dort kuralin tamami export const KURALLAR = { ogrenci: { oduncGun: 28 }, uye: { oduncGun: 14 } }; export const EN_COK_YENILEME = 2; export const SINIR_ILETISI = `en cok ${EN_COK_YENILEME} kez yenilenebilir`; export function yenile(kayit, bugun) { if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez'); if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error(SINIR_ILETISI); if (bugun > kayit.sonGun) throw new Error('gecikmis odunc yenilenemez'); return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, yenilemeSayisi: kayit.yenilemeSayisi + 1, }; }
Dört kural yedi teste karşılık gelir. Sınır durumu — son gününde yapılan yenileme — ayrı bir test olarak duruyor, çünkü karşılaştırma işlecinin yanlış seçilmesi tam orada görünür.
// yenileme.test.mjs — dort kuralin yedi testi import { test } from 'node:test'; import assert from 'node:assert/strict'; import { yenile } from './yenileme.mjs'; const kayit = (ek = {}) => ({ uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028, yenilemeSayisi: 0, rezerveEdildi: false, ...ek, }); test('ogrenci yenilemesi bugunden yirmi sekiz gun ileri alir', () => { assert.equal(yenile(kayit(), 1020).sonGun, 1048); }); test('standart uye yenilemesi bugunden on dort gun ileri alir', () => { assert.equal(yenile(kayit({ uyeTuru: 'uye' }), 1020).sonGun, 1034); }); test('yenileme sayaci bir artar', () => { assert.equal(yenile(kayit(), 1020).yenilemeSayisi, 1); }); test('rezerve edilmis kitap yenilenemez', () => { assert.throws(() => yenile(kayit({ rezerveEdildi: true }), 1020), /rezerve edilmis/); }); test('en cok iki kez yenilenebilir', () => { assert.throws(() => yenile(kayit({ yenilemeSayisi: 2 }), 1020), /en cok 2 kez/); }); test('gecikmis odunc yenilenemez', () => { assert.throws(() => yenile(kayit(), 1029), /gecikmis odunc/); }); test('son gunde yapilan yenileme kabul edilir', () => { assert.equal(yenile(kayit(), 1028).sonGun, 1056); });
node --test --test-reporter=tap yenileme.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci yenilemesi bugunden yirmi sekiz gun ileri alir ok 2 - standart uye yenilemesi bugunden on dort gun ileri alir ok 3 - yenileme sayaci bir artar ok 4 - rezerve edilmis kitap yenilenemez ok 5 - en cok iki kez yenilenebilir ok 6 - gecikmis odunc yenilenemez ok 7 - son gunde yapilan yenileme kabul edilir # tests 7 # pass 7 # fail 0
Bu, varış noktasıdır. Soru oraya nasıl gidildiğidir.
İki Yolun Ölçümü
Aşağıdaki betik yolun aradaki duraklarını gerçekleştirim sürümleri olarak tutar. Büyük adım yolunda iki durak vardır: ilk kuralın yazıldığı sürüm ve tamamlanmış sürüm. Küçük adım yolunda beş durak vardır ve her durak bir kural ekler. Aynı yedi denetim her durakta çalıştırılır; ayrıca ardışık iki durak arasında kaç gövde satırının değiştiği sayılır.
// adim-boyu.mjs — ayni hedefe iki farkli adim boyuyla giden yolun olcumu import assert from 'node:assert/strict'; import { yenile, KURALLAR, EN_COK_YENILEME, SINIR_ILETISI } from './yenileme.mjs'; function S1(kayit, bugun) { return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, }; } function S2(kayit, bugun) { return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, yenilemeSayisi: kayit.yenilemeSayisi + 1, }; } function S3(kayit, bugun) { if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez'); return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, yenilemeSayisi: kayit.yenilemeSayisi + 1, }; } function S4(kayit, bugun) { if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez'); if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error(SINIR_ILETISI); return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, yenilemeSayisi: kayit.yenilemeSayisi + 1, }; } function S5Hatali(kayit, bugun) { if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez'); if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error(SINIR_ILETISI); if (bugun >= kayit.sonGun) throw new Error('gecikmis odunc yenilenemez'); return { ...kayit, sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun, yenilemeSayisi: kayit.yenilemeSayisi + 1, }; } const kayit = (ek = {}) => ({ uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028, yenilemeSayisi: 0, rezerveEdildi: false, ...ek, }); const TESTLER = { 'ogrenci suresi': (y) => assert.equal(y(kayit(), 1020).sonGun, 1048), 'uye suresi': (y) => assert.equal(y(kayit({ uyeTuru: 'uye' }), 1020).sonGun, 1034), 'sayac artar': (y) => assert.equal(y(kayit(), 1020).yenilemeSayisi, 1), 'rezerve reddi': (y) => assert.throws(() => y(kayit({ rezerveEdildi: true }), 1020), /rezerve edilmis/), 'yenileme siniri': (y) => assert.throws(() => y(kayit({ yenilemeSayisi: 2 }), 1020), /en cok 2 kez/), 'gecikme reddi': (y) => assert.throws(() => y(kayit(), 1029), /gecikmis odunc/), 'son gun kabulu': (y) => assert.equal(y(kayit(), 1028).sonGun, 1056), }; const TOPLAM = Object.keys(TESTLER).length; function dusenler(surum) { return Object.entries(TESTLER).filter(([, t]) => { try { t(surum); return false; } catch { return true; } }).map(([ad]) => ad); } // Imza satiri sayilmaz; yalniz govde satirlari karsilastirilir. function degisenSatir(eski, yeni) { const govde = (f) => f.toString().split('\n').slice(1).map((s) => s.trim()).filter(Boolean); const kalan = govde(eski); let eklenen = 0; for (const satir of govde(yeni)) { const i = kalan.indexOf(satir); if (i === -1) eklenen += 1; else kalan.splice(i, 1); } return eklenen + kalan.length; } const YOLLAR = { 'buyuk adim': [S1, yenile], 'kucuk adim': [S1, S2, S3, S4, yenile] }; for (const [ad, duraklar] of Object.entries(YOLLAR)) { console.log(ad); let enBuyuk = 0; duraklar.forEach((surum, i) => { const fark = i === 0 ? 0 : degisenSatir(duraklar[i - 1], surum); enBuyuk = Math.max(enBuyuk, fark); console.log(` durak ${i + 1} dusen ${dusenler(surum).length}/${TOPLAM} degisen satir ${fark}`); }); console.log(` geri bildirim noktasi ${duraklar.length}, en buyuk arama alani ${enBuyuk} satir`); } const hataliFark = { 'buyuk adim': degisenSatir(S1, S5Hatali), 'kucuk adim': degisenSatir(S4, S5Hatali) }; console.log(`son adimda yanlis islec yazildiginda dusen test: ${dusenler(S5Hatali).join(', ')}`); for (const [ad, fark] of Object.entries(hataliFark)) { console.log(` ${ad}: hatanin aranacagi alan ${fark} satir`); }
buyuk adim durak 1 dusen 4/7 degisen satir 0 durak 2 dusen 0/7 degisen satir 4 geri bildirim noktasi 2, en buyuk arama alani 4 satir kucuk adim durak 1 dusen 4/7 degisen satir 0 durak 2 dusen 3/7 degisen satir 1 durak 3 dusen 2/7 degisen satir 1 durak 4 dusen 1/7 degisen satir 1 durak 5 dusen 0/7 degisen satir 1 geri bildirim noktasi 5, en buyuk arama alani 1 satir son adimda yanlis islec yazildiginda dusen test: son gun kabulu buyuk adim: hatanin aranacagi alan 4 satir kucuk adim: hatanin aranacagi alan 1 satir
Ölçümün Okunması
İki yol da aynı yere varıyor ve varış noktasında iki tablo da yedi testi geçiyor. Fark yolda.
Büyük adım yolunda düşen test sayısı dörtten sıfıra bir sıçramada iniyor. Bu sıçramanın içinde dört satırlık bir yazım var ve dört satırın hiçbiri tek başına doğrulanmadı. Yazılanların dördü de doğruysa sorun yoktur; biri yanlışsa hangi satırın hangi testi düşürdüğü, ancak elle ayıklanarak bulunur.
Küçük adım yolunda düşen test sayısı birer birer iniyor: dört, üç, iki, bir, sıfır. Her inişin karşılığı tek satırlık bir yazımdır. Bu, ölçümün asıl bulgusudur — arama alanı dört satırdan bir satıra iner.
Son üç satır bunu doğrudan gösteriyor. Gecikme denetimindeki karşılaştırma işleci yanlış seçildiğinde — büyüktür yerine büyük eşit — yalnız bir test düşüyor: son gününde yapılan yenilemenin kabulü. Düşen test iki yolda da aynıdır. Farklı olan, o testin işaret ettiği alandır: büyük adımda dört satır, küçük adımda bir satır.
Buradan geri bildirim süresi kavramı çıkar. Geri bildirim süresi, bir yazım hatasının yapıldığı an ile fark edildiği an arasındaki mesafedir; birimi saniye değil, o aralıkta yazılmış satır sayısıdır. Adım boyu küçüldükçe bu mesafe kısalır.
Son Yeşil Noktaya Dönüş
Küçük adımın ikinci kazancı geri dönüş maliyetidir. Bir yazım beklenmedik biçimde karmaşıklaşırsa en ucuz çözüm çoğu zaman onu atıp yeniden başlamaktır. Bu kararın maliyeti, atılacak işin büyüklüğüdür.
Büyük adım yolunda son yeşil nokta birinci duraktır: geri dönüş, dört satırın tamamını atmak demektir. Küçük adım yolunda son yeşil nokta her zaman bir satır geridedir. Aynı şey, düşen testin yorumlanmasında da geçerlidir: küçük adımda “az önce yazdığım satır” her zaman tek bir cümledir.
Bunun bir bedeli vardır ve tabloda görünüyor: küçük adım yolunda beş geri bildirim noktası var, büyük adım yolunda iki. Her nokta bir koşum demektir. Koşum ucuzsa — dördüncü derste ölçülen anlamda hızlı ve bağımsız testlerse — bu bedel önemsizdir. Koşum pahalıysa adım boyu kendiliğinden büyür ve geri bildirim süresi uzar. İki dersin bağı budur: test takımının hızı, geliştirme döngüsünün adım boyunu belirler.
Adım Boyunun Seçimi
Adım boyu sabit bir kural değil, belirsizliğe göre ayarlanan bir değişkendir. Yazılacak şey tanıdıksa büyük adım ucuzdur ve ara duraklar zaman kaybıdır. Yazılacak şey belirsizse — yeni bir alan, tanınmayan bir arayüz, karmaşık bir sınır durumu — adım küçülür.
Pratikte ölçüt şudur: bir adımın sonunda düşen test sayısı beklenenden farklı çıkıyorsa adım fazla büyüktür. Yukarıdaki küçük adım tablosunda her durak tam olarak bir testi yeşile çeviriyor; bu, adım boyunun kuralın tanesine oturduğunu gösterir.
Özet
- Adım boyu, iki geri bildirim noktası arasında yazılan satır sayısıyla ölçülür ve bir hatanın aranacağı alan tam olarak o sayıdır.
- Ölçümde büyük adım yolu düşen test sayısını dörtten sıfıra bir sıçramada indirdi; küçük adım yolu birer birer indirdi.
- Aynı yazım hatası iki yolda da tek bir testi düşürdü, ama arama alanı büyük adımda dört, küçük adımda bir satırdı.
- Küçük adımda son yeşil nokta her zaman bir satır geridedir; geri dönüş kararının maliyeti bu mesafedir.
- Küçük adımın bedeli daha çok koşumdur; test takımı yavaşsa adım boyu kendiliğinden büyür ve geri bildirim süresi uzar.
Sonraki Adım
Bu dersteki yedi test, kuralları ayrı ayrı cümlelerle anlattı ve her cümle bir kod bloğu oldu. Kurallar çoğaldığında bu yazım biçimi tekrar üretir: aynı düzenek, aynı çağrı, yalnız girdi ve beklenen sonuç değişir. Kuralın kendisi ise koda gömülü kalır ve kütüphane görevlisiyle konuşurken okunamaz. Sonraki ders kuralları örnek tablolarına taşır, o tablolar için bir biçim tanımlar ve tabloyu koşulabilir teste çeviren bir çözümleyici yazar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.