Ders 13 / 16
Basit Tut ve Sık Yeniden Düzenle
Aynı ücretlendirme gereksiniminin tek adımda tasarlanmış bir çözümle ve beş küçük adımla evrilen bir çözümle karşılanması, her adımda değişen satır sayısının ölçülmesi ve kırmızı bir testin işaret ettiği şüpheli bölgenin karşılaştırılması.
İçindekiler
Önceki derste iki değişiklik yapıldı: bir tablo tek dosyaya taşındı, bir fonksiyon ikiye ayrıldı. İkisi de küçüktü ve ikisi de mevcut davranışı korudu. Bu ders, adımın küçük olmasının neyi ucuzlattığını sayıyla gösterir.
İlkenin adı basit tut (keep it simple): bir gereksinim, onu karşılayan en az yapıyla karşılanır; eksik kalan yapı sonradan, gerçekten gerektiğinde eklenir. İlkeye ikinci bir cümle eşlik eder — sık yeniden düzenle. Az yapıyla ilerlemek, biriken yapıyı düzenleme işini erteleme hakkı vermez; düzenleme her adımın parçasıdır.
Test odaklı geliştirme konusunda adım boyu geri bildirim süresi açısından ele alınmıştı: kırmızıdan yeşile geçen turun uzunluğu ölçülmüştü. Burada aynı büyüklük başka bir açıdan ölçülür: bir test kırmızıya döndüğünde hatanın hangi satır kümesinde arandığı.
Gereksinim
Gönderi ücretlendirme kitaplığına dört kurallı bir ücret hesabı ekleniyor.
- Taban ücret, ağırlık kademesinden okunur.
- Taban ücret gönderim bölgesinin katsayısıyla çarpılır.
- Sonuç asgari ücretin altına düşemez.
- Hacim ağırlığı gerçek ağırlıktan büyükse kademe hacim ağırlığından okunur.
Kademe tablosu önceki derste kurulmuştu ve değişmeden kullanılıyor.
// tarife.mjs — onceki dersten devralinan kademe tablosu export const KADEME = [ { ustGram: 1000, kurus: 4990, ad: 'kucuk' }, { ustGram: 5000, kurus: 7490, ad: 'orta' }, { ustGram: 10000, kurus: 9900, ad: 'buyuk' }, { ustGram: 20000, kurus: 12900, ad: 'cok-buyuk' }, { ustGram: Infinity, kurus: 24900, ad: 'agir' }, ]; export function kademe(gram) { return KADEME.find((k) => gram <= k.ustGram); }
Tek Adımda Tam Tasarım
Dört kural birden okunduğunda akla gelen ilk çözüm hepsini tek yazımda karşılamaktır.
// ucret.mjs — tek adimda yazilmis tam surum import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; const ASGARI_KURUS = 6990; export function gonderiUcreti(g) { const hacimGram = Math.ceil((g.en * g.boy * g.yukseklik) / 3); const etkinGram = Math.max(g.gram, hacimGram); const taban = kademe(etkinGram).kurus; const asgarili = Math.max(taban, ASGARI_KURUS); return Math.round(asgarili * BOLGE_KATSAYI[g.bolge]); }
Dört kuralın dördü de birer testle sabitleniyor.
// ucret.test.mjs — dort kuralin tamami import { test } from 'node:test'; import assert from 'node:assert/strict'; import { gonderiUcreti } from './ucret.mjs'; const kutu = { en: 20, boy: 15, yukseklik: 10 }; test('kademe ucreti agirliktan okunur', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B1' }), 7490); }); test('bolge katsayisi taban ucreti carpar', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B3' }), 11984); }); test('asgari ucret bolge carpimindan sonra uygulanir', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 500, bolge: 'B2' }), 6990); }); test('hacim agirligi gercek agirliktan buyukse o kullanilir', () => { assert.equal(gonderiUcreti({ en: 40, boy: 30, yukseklik: 30, gram: 500, bolge: 'B2' }), 16125); });
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'ucret.mjs satir sayisi: %s\n' "$(wc -l < ucret.mjs | tr -d ' ')"
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar not ok 3 - asgari ucret bolge carpimindan sonra uygulanir ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir # tests 4 # pass 3 # fail 1 ucret.mjs satir sayisi: 13
Üçüncü test kırmızı: asgari ücret bölge çarpımından önce uygulanmış, dolayısıyla asgari tutar da bölge katsayısıyla çarpılmış. Hatanın kendisi tek satırlık, ama koşumun işaret ettiği şüpheli bölge dosyanın tamamı: dört kural aynı yazımda eklendiği için testin hangi kural yüzünden düştüğü koşumdan okunamaz. Şüpheli satır sayısı 13.
Aynı Gereksinim, Beş Adım
Aynı dört kural, her biri kendi testiyle kapanan adımlara bölünüyor. İlk adım yalnız birinci kuralı karşılıyor.
// ucret.mjs — adim 1: yalniz agirlik kademesi import { kademe } from './tarife.mjs'; export function gonderiUcreti(g) { return kademe(g.gram).kurus; }
: > onceki.mjs cat > ucret.test.mjs <<'SON' // ucret.test.mjs — adim 1 import { test } from 'node:test'; import assert from 'node:assert/strict'; import { gonderiUcreti } from './ucret.mjs'; const kutu = { en: 20, boy: 15, yukseklik: 10 }; test('kademe ucreti agirliktan okunur', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B1' }), 7490); }); SON node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')" cp ucret.mjs onceki.mjs
ok 1 - kademe ucreti agirliktan okunur # tests 1 # pass 1 # fail 0 bu adimda degisen satir: 6
İkinci adım bölge katsayısını ekliyor. Değişen tek şey çarpım.
// ucret.mjs — adim 2: bolge katsayisi import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; export function gonderiUcreti(g) { return Math.round(kademe(g.gram).kurus * BOLGE_KATSAYI[g.bolge]); }
cat >> ucret.test.mjs <<'SON' test('bolge katsayisi taban ucreti carpar', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B3' }), 11984); }); SON node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')" cp ucret.mjs onceki.mjs
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar # tests 2 # pass 2 # fail 0 bu adimda degisen satir: 6
Üçüncü adım asgari ücreti ekliyor ve bu adımda tam olarak tek adımlık tasarımdaki karar verilmiş oluyor: asgari ücret çarpımdan önce mi sonra mı uygulanacak? İlk deneme öncekini seçiyor.
// ucret.mjs — adim 3: asgari ucret (ilk deneme) import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; const ASGARI_KURUS = 6990; export function gonderiUcreti(g) { const taban = Math.max(kademe(g.gram).kurus, ASGARI_KURUS); return Math.round(taban * BOLGE_KATSAYI[g.bolge]); }
cat >> ucret.test.mjs <<'SON' test('asgari ucret bolge carpimindan sonra uygulanir', () => { assert.equal(gonderiUcreti({ ...kutu, gram: 500, bolge: 'B2' }), 6990); }); SON node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')"
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar not ok 3 - asgari ucret bolge carpimindan sonra uygulanir # tests 3 # pass 2 # fail 1 bu adimda degisen satir: 6
Kırmızıya dönen test aynı, hata aynı. Değişen şey aramanın nerede yapılacağı: bu adımda değişen satır sayısı 6. Tek adımlık tasarımda aynı kırmızı, 13 satırlık bir dosyanın tamamını şüpheli bırakıyordu ve o 13 satır içinde geçen dört kural birbirine karışmış durumdaydı. Burada şüpheli bölge yalnız asgari ücret satırlarıdır.
// ucret.mjs — adim 3: asgari ucret carpimdan sonra import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; const ASGARI_KURUS = 6990; export function gonderiUcreti(g) { const bolgeli = Math.round(kademe(g.gram).kurus * BOLGE_KATSAYI[g.bolge]); return Math.max(bolgeli, ASGARI_KURUS); }
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')" cp ucret.mjs onceki.mjs
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar ok 3 - asgari ucret bolge carpimindan sonra uygulanir # tests 3 # pass 3 # fail 0 bu adimda degisen satir: 6
Dördüncü adım hacim ağırlığını ekliyor.
// ucret.mjs — adim 4: hacim agirligi import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; const ASGARI_KURUS = 6990; export function gonderiUcreti(g) { const hacimGram = Math.ceil((g.en * g.boy * g.yukseklik) / 3); const etkinGram = Math.max(g.gram, hacimGram); const bolgeli = Math.round(kademe(etkinGram).kurus * BOLGE_KATSAYI[g.bolge]); return Math.max(bolgeli, ASGARI_KURUS); }
cat >> ucret.test.mjs <<'SON' test('hacim agirligi gercek agirliktan buyukse o kullanilir', () => { assert.equal(gonderiUcreti({ en: 40, boy: 30, yukseklik: 30, gram: 500, bolge: 'B2' }), 16125); }); SON node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')" printf 'gonderiUcreti govdesi: %s satir\n' "$(awk '/^export function gonderiUcreti/,/^}/' ucret.mjs | wc -l | tr -d ' ')" cp ucret.mjs onceki.mjs
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar ok 3 - asgari ucret bolge carpimindan sonra uygulanir ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir # tests 4 # pass 4 # fail 0 bu adimda degisen satir: 6 gonderiUcreti govdesi: 6 satir
Beşinci Adım: Yeniden Düzenleme
Dört kural karşılandı ve testler yeşil. İlkenin ikinci yarısı burada devreye girer: biriken yapı, bir sonraki gereksinim gelmeden önce düzenlenir. Ücret fonksiyonu iki ayrı soyutlama düzeyini taşıyor — ağırlığın nasıl belirlendiği ile ücretin nasıl hesaplandığı. Ağırlık belirleme kendi adını alıyor.
// ucret.mjs — adim 5: etkin agirlik ayiklandi import { kademe } from './tarife.mjs'; const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 }; const ASGARI_KURUS = 6990; export function etkinAgirlik(g) { return Math.max(g.gram, Math.ceil((g.en * g.boy * g.yukseklik) / 3)); } export function gonderiUcreti(g) { const bolgeli = Math.round(kademe(etkinAgirlik(g)).kurus * BOLGE_KATSAYI[g.bolge]); return Math.max(bolgeli, ASGARI_KURUS); }
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))' printf 'bu adimda degisen satir: %s\n' "$(diff onceki.mjs ucret.mjs | grep -c '^[<>]')" printf 'gonderiUcreti govdesi: %s satir\n' "$(awk '/^export function gonderiUcreti/,/^}/' ucret.mjs | wc -l | tr -d ' ')"
ok 1 - kademe ucreti agirliktan okunur ok 2 - bolge katsayisi taban ucreti carpar ok 3 - asgari ucret bolge carpimindan sonra uygulanir ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir # tests 4 # pass 4 # fail 0 bu adimda degisen satir: 10 gonderiUcreti govdesi: 4 satir
Bu adımda hiçbir kural eklenmedi, hiçbir test değişmedi ve değişen satır sayısı önceki adımlardan büyük çıktı. Yeniden düzenleme adımlarında bu olağandır: ölçü değişen satır sayısı değil, koşumun yeşil kalmasıdır. Ücret fonksiyonunun gövdesi altı satırdan dört satıra indi ve ağırlık belirleme kuralı artık kendi adıyla ayrıca sınanabilir hâle geldi.
Adım Boyunun Ölçüsü
İki yol aynı dosyada bitti; ölçülen büyüklükler farklı.
| Ölçüt | Tek adım | Beş adım |
|---|---|---|
| Koşum sayısı | 1 | 6 |
| Yeşil biten koşum | 0 | 5 |
| Kırmızıda şüpheli satır | 13 | 6 |
| Bir adımda değişen satır | 13 | 6 (yeniden düzenlemede 10) |
Tek adımlık yolun tek koşumu kırmızı bitti ve hatanın hangi kuralda olduğu koşumdan okunamadı; kırmızı olan test asgari ücret testiydi ama asgari ücret satırı, hacim ağırlığı ve bölge katsayısı satırlarıyla aynı yazımda eklenmişti. Beş adımlık yolda kırmızı tek bir kuralın eklendiği adımda göründü.
Ölçünün alan karşılığı şudur: bir gereksinim ne kadar çok kural içeriyorsa, hepsini tek yazımda karşılamanın maliyeti kural sayısıyla değil, kuralların birbirine göre sıraları sayısıyla artar. Dört kuralın uygulanma sırası için birden fazla seçenek vardır ve tek yazımda bunların hepsi aynı anda seçilir. Adımlara bölmek bu seçimleri birer birer yapılır hâle getirir.
Basit tut ilkesinin sık yapılan yanlış okunuşu, “yapıyı hiç kurma” biçimindedir. Beşinci adım bunun tersini gösterir: yapı kurulur, ama gereksinim geldikten sonra ve testler yeşilken kurulur. İlkenin yasakladığı şey yapı değil, gereksinimin önüne geçen yapıdır.
Özet
- Basit tut ilkesi bir gereksinimi karşılayan en az yapıyla ilerlemeyi, sık yeniden düzenleme ise biriken yapıyı her adımın sonunda düzenlemeyi söyler.
- Aynı dört kural tek yazımda karşılandığında tek koşum kırmızı bitti ve şüpheli bölge 13 satırdı; beş adıma bölündüğünde aynı hata 6 satırlık bir adımda göründü.
- Adımlara bölmek kuralların uygulanma sırasına ilişkin kararları birer birer alınır hâle getirir; tek yazımda bu kararların hepsi aynı anda ve gerekçesiz alınır.
- Yeniden düzenleme adımında değişen satır sayısı büyüyebilir; o adımın ölçüsü satır sayısı değil, testlerin yeşil kalmasıdır.
- İlke yapıyı yasaklamaz; gereksinimin önüne geçen yapıyı yasaklar.
Sonraki Adım
Bu derste her adım, gelmiş bir gereksinime karşılık geldi. Gelmemiş bir gereksinim için yazılan yapının ne kadara mal olduğu ölçülmedi. Sonraki ders ücret hesabına bugünden bir eklenti noktası ve bir yapılandırma katmanı ekler, bunların kaç dallanma ve kaç çalışmayan yol ürettiğini sayar, sonra gerçekten gelen gereksinimin bu yapıya uymadığını gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.