Ders 01 / 16
Anlamlı Adlar
Adlandırmanın ölçülebilir bir tasarım kararı olarak ele alınması: aynı gönderi ücreti hesabının kısaltılmış ve niyeti açıklayan iki sürümünde çağrı yerindeki opak argümanların sayılması, kısaltmaların kaç aday karşılığa çözümlendiğinin çıkarılması ve ad uzunluğunun kapsamla ilişkisi.
İçindekiler
Bir dil müfredatı tamamlandığında elde edilen yetenek şudur: istenen davranışı üreten kod yazmak. JavaScript ve TypeScript kursları bunu sağladı; testler geçiyorsa program doğrudur. Bu müfredat farklı bir soruyla başlıyor. Aynı davranışı üreten iki program arasında bir tercih yapmak gerekirse, tercih neye dayanır?
Soru kaçınılmazdır, çünkü kod bir kez yazılıp bırakılmaz. Bir gönderi ücretlendirme kitaplığına altı ay sonra yeni bir bölge katsayısı eklenir, bir yıl sonra hacimsel ağırlık böleni değişir. Bu değişiklikleri yapan kişi, kodu önce okur. Okuma maliyeti tasarımın asıl ölçüsüdür ve “daha okunabilir” gibi bir iddia, sayıya bağlanmadığı sürece tartışmayı bitirmez. Bu ders adlandırmayı iki sayıya bağlıyor: çağrı yerinde ne söylediği anlaşılmayan argüman sayısı ve bir kısaltmanın kaç farklı alan sözcüğüne çözümlenebildiği.
Kurs boyunca tek bir kitaplık geliştirilecek: gönderi ücretlendirme ve yönlendirme. İlk işlevi, bir gönderinin ağırlığından, hacminden ve bölgesinden ücretini hesaplamak.
Ölçüt: Çağrı Yerinde Cevaplanabilen Soru
Bir adın taşıdığı bilgi, o adın kullanıldığı yerde ölçülür. Bir çağrı satırına bakan kişi şu soruları sorar: bu argüman hangi parametreyi dolduruyor, değeri ne anlama geliyor, sonuç neyin ücreti? Cevap için başka bir dosyayı açmak gerekiyorsa ad bilgi taşımıyordur.
Bunu mekanik hâle getirmek için bir argümanı etiketli sayalım: çağrı metninin kendisi hangi parametreye karşılık geldiğini söylüyorsa (adlandırılmış bir alan ya da en az üç harfli bir değişken adı) etiketlidir. Değilse opaktır: çıplak bir sayı, bir doğruluk değeri ya da tek harfli bir ad. Opak argüman sayısı, çağrı yerinde cevaplanamayan soru sayısının alt sınırıdır.
Kısaltılmış Sürüm
İlk sürüm çalışıyor ve testleri geçiyor. Hacimsel ağırlığı hesaplar, ücretlendirilen ağırlığı bulur, kademeyi seçer, bölge katsayısını uygular, indirimi düşer ve asgari ücretin altına inmez.
mkdir -p surum-a surum-b
// surum-a/veri.mjs — ornek gonderi ve tarife export const g = { a: 2.4, e: 30, by: 24, y: 18 }; export const t = { k: [{ s: 1, u: 38 }, { s: 5, u: 26 }, { s: 10, u: 21 }, { s: Infinity, u: 17 }], bk: { 1: 1, 2: 1.35, 3: 1.8 }, as: 45 };
// surum-a/ucret.mjs — gonderi ucreti, kisaltilmis adlarla export function hsp(g, t, b, x) { const d = (g.e * g.by * g.y) / 3000; const a = Math.max(g.a, d); const k = t.k.find((i) => a <= i.s); const u = k.u * a * t.bk[b]; return Math.max(u * (1 - x), t.as); }
// surum-a/cagri.mjs — cagri yeri import { hsp } from "./ucret.mjs"; import { g, t } from "./veri.mjs"; console.log(hsp(g, t, 2, 0.12).toFixed(2));
Çağrı satırındaki dört argümandan hiçbiri kendini açıklamıyor. 2 bir bölge kodu mu, bir
kademe indisi mi, bir adet mi? 0.12 bir indirim oranı mı, bir vergi oranı mı? Cevap
ucret.mjs dosyasında; okuyan kişi ikinci dosyayı açmak zorunda.
Adlandırılmış Sürüm
İkinci sürüm aynı aritmetiği yapar. Değişen tek şey adlar, parametre biçimi ve sayıların nereden geldiği.
// surum-b/veri.mjs — ayni veri, alan adlariyla export const BOLGE = { AYNI_SEHIR: 1, KOMSU_BOLGE: 2, UZAK_BOLGE: 3 }; export const SOZLESMELI_MUSTERI_INDIRIMI = 0.12; export const ornekGonderi = { agirlikKg: 2.4, enCm: 30, boyCm: 24, yukseklikCm: 18 }; export const yurticiTarife = { agirlikKademeleri: [{ ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 }, { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: Infinity, kiloBasiUcret: 17 }], bolgeKatsayilari: { 1: 1, 2: 1.35, 3: 1.8 }, asgariUcret: 45, };
// surum-b/ucret.mjs — ayni hesap, niyeti aciklayan adlarla const HACIMSEL_BOLEN = 3000; export function gonderiUcretiHesapla({ gonderi, tarife, bolgeKodu, indirimOrani }) { const hacimselAgirlikKg = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN; const ucretlendirilenAgirlikKg = Math.max(gonderi.agirlikKg, hacimselAgirlikKg); const kademe = tarife.agirlikKademeleri.find((k) => ucretlendirilenAgirlikKg <= k.ustSinirKg); const bolgeliUcret = kademe.kiloBasiUcret * ucretlendirilenAgirlikKg * tarife.bolgeKatsayilari[bolgeKodu]; return Math.max(bolgeliUcret * (1 - indirimOrani), tarife.asgariUcret); }
// surum-b/cagri.mjs — cagri yeri import { gonderiUcretiHesapla } from "./ucret.mjs"; import { ornekGonderi, yurticiTarife, BOLGE, SOZLESMELI_MUSTERI_INDIRIMI } from "./veri.mjs"; console.log(gonderiUcretiHesapla({ gonderi: ornekGonderi, tarife: yurticiTarife, bolgeKodu: BOLGE.KOMSU_BOLGE, indirimOrani: SOZLESMELI_MUSTERI_INDIRIMI }).toFixed(2));
İki sürümün aynı sonucu ürettiği doğrulanır.
node surum-a/cagri.mjs node surum-b/cagri.mjs
133.44 133.44
Davranış aynı. Fark yalnızca okuma maliyetinde ve bu maliyet sayılabilir.
Ölçüm
Aşağıdaki araç iki şeyi sayar. Birincisi, verilen çağrı yerindeki opak argümanları. İkincisi, gerçekleştirimdeki iki harfe kadar kısa adları alıp her birinin alan sözlüğündeki kaç sözcüğe çözümlenebileceğini — yani kısaltmanın belirsizliğini.
// ad-olcer.mjs — cagri yerindeki opak argumanlari ve kisaltmalarin belirsizligini sayar import { readFileSync } from "node:fs"; // Kitapligin alan sozlugu: adlarin tasimasi beklenen sozcukler. const SOZLUK = ["agirlik", "asgari", "bolen", "bolge", "desi", "gonderi", "hacim", "indirim", "kademe", "katsayi", "kilo", "oran", "sinir", "tarife", "ucret"]; // Bir arguman, cagri yerinde hangi parametreyi doldurdugunu kendisi soyluyorsa etiketlidir. const etiketli = (arg) => /^[A-Za-z_$][\w$]{2,}\s*:/.test(arg) || /^[A-Za-z_$][\w$]{2,}$/.test(arg); function opakSay(dosya, fnAdi) { const govde = readFileSync(dosya, "utf8"); const cagri = govde.match(new RegExp(`\\b${fnAdi}\\(([^()]*)\\)`)); const args = cagri[1].replace(/[{}]/g, "").split(",").map((s) => s.trim()).filter(Boolean); return { toplam: args.length, opak: args.filter((a) => !etiketli(a)).length }; } function kisaltmalar(dosya) { const govde = readFileSync(dosya, "utf8").replace(/^\/\/.*$/gm, ""); const adlar = [...new Set(govde.match(/[A-Za-z_$][\w$]*/g))].filter((a) => a.length <= 2); return adlar.map((ad) => ({ ad, adaylar: SOZLUK.filter((s) => s.startsWith(ad[0].toLowerCase())), })); } for (const [dosya, fn] of [["surum-a/cagri.mjs", "hsp"], ["surum-b/cagri.mjs", "gonderiUcretiHesapla"]]) { const s = opakSay(dosya, fn); console.log(`${dosya.padEnd(20)} ${fn.padEnd(21)} arguman=${s.toplam} opak=${s.opak}`); } console.log("\nkisaltma aday sayisi adaylar"); const kisa = kisaltmalar("surum-a/ucret.mjs"); for (const k of kisa) console.log(`${k.ad.padEnd(9)} ${String(k.adaylar.length).padEnd(12)} ${k.adaylar.join(", ") || "-"}`); const tek = kisa.filter((k) => k.adaylar.length === 1).length; console.log(`\nsurum-a kisa ad sayisi = ${kisa.length}, tek adayla cozumlenen = ${tek}`); console.log(`surum-b kisa ad sayisi = ${kisaltmalar("surum-b/ucret.mjs").length}`);
node ad-olcer.mjs
surum-a/cagri.mjs hsp arguman=4 opak=4 surum-b/cagri.mjs gonderiUcretiHesapla arguman=4 opak=0 kisaltma aday sayisi adaylar g 1 gonderi t 1 tarife b 2 bolen, bolge x 0 - d 1 desi e 0 - by 2 bolen, bolge y 0 - a 2 agirlik, asgari k 3 kademe, katsayi, kilo i 1 indirim s 1 sinir u 1 ucret bk 2 bolen, bolge as 2 agirlik, asgari surum-a kisa ad sayisi = 15, tek adayla cozumlenen = 6 surum-b kisa ad sayisi = 1
Dört opak argüman sıfıra indi. Kısaltma tablosu ikinci sonucu veriyor: on beş kısa addan
yalnız altısı tek bir alan sözcüğüne çözümleniyor. Üçü (x, e, y) hiçbir sözcüğe
karşılık gelmiyor; okuyan kişi bunları ancak kodu izleyerek çözebilir. Beşi birden çok
aday taşıyor: a hem ağırlık hem asgari, k hem kademe hem katsayı hem kilo olabilir.
Gerçekleştirimde a ağırlığı, as asgari ücreti tutuyor — iki farklı kavram bir harf
farkıyla ayrılmış.
Adlandırılmış sürümde tek bir kısa ad kaldı: find çağrısına verilen k parametresi. Bu
kalıntı rastlantı değil; sonraki bölümün konusu.
Ad Uzunluğu Kapsama Bağlıdır
Kısa ad kendiliğinden kusur değildir. Ölçüt, adın yaşadığı kapsamdır. Bir satırlık bir
geri çağırma içindeki k ile modül düzeyinde dışa aktarılan t arasındaki fark, okuyanın
tanımı bulmak için gezmesi gereken mesafedir. Birincisinde tanım aynı satırda; ikincisinde
tanım başka bir dosyada.
Buradan pratik bir kural çıkar: adın uzunluğu, adın görünür olduğu kod miktarıyla birlikte
artar. Döngü sayacı i üç satırlık bir döngüde savunulabilir, dışa aktarılan bir sabit
olarak savunulamaz. Aynı gerekçe tersine de işler: dar kapsamda gereksiz uzun ad
(gecerliGonderininHacimselAgirligiDegeri) bilgi eklemeden satırı uzatır.
Adın Vermesi Gereken Bilgi
Ölçüm ne kadarının eksik olduğunu söyler, hangi bilginin eklenmesi gerektiğini söylemez. Bunun için dört ölçüt işe yarar.
Birim ve tür adın içinde durur. agirlikKg ile agirlik arasındaki fark, gramla
kilogramı karıştıran bir hatanın derleme öncesinde görünmesidir. enCm, ustSinirKg,
indirimOrani adlarının sonundaki ekler bu işi yapar; indirimOrani ile indirimTutari
birbirinin yerine geçemez.
Ad yanlış bilgi vermemelidir. bolgeListesi adlı bir değişkenin gerçekte bir eşleme
tutması, hiç ad vermemekten kötüdür: okuyan kişi yanlış bir varsayımla ilerler. Yanlış
adın maliyeti, hata bulunana kadar yapılan bütün yorumlardır.
Kavram başına tek sözcük. Aynı kitaplıkta getTarife, tarifeAl ve tarifeGetir
birlikte bulunuyorsa okuyan kişi üç farklı işlem olduğunu varsayar. Sözlük kurs boyunca
sabittir: bir gönderinin ücretlendirilen ağırlığı her yerde ucretlendirilenAgirlikKg
adıyla geçer.
Aranabilirlik. 3000 sayısı kod tabanında yüzlerce yerde geçer; HACIMSEL_BOLEN bir
kez geçer. Sayının değişmesi gerektiğinde aranacak şey addır. Çıplak sayıların isimsiz
bırakılması bu yüzden yalnız okuma değil, değiştirme maliyetidir.
Özet
- Adlandırma bir üslup tercihi değil, ölçülebilir bir karardır: ölçüt, çağrı yerinde cevaplanamayan soruların alt sınırı olan opak argüman sayısıdır.
- Kısaltılmış sürümde dört argümanın dördü opaktı; adlandırılmış sürümde hiçbiri opak değil. İki sürüm aynı sonucu (133.44) üretiyor, fark yalnızca okuma maliyetinde.
- Kısaltma çözümleme tablosu ikinci ölçüyü verdi: on beş kısa addan altısı tek bir alan sözcüğüne çözümleniyor, üçü hiçbirine, beşi birden çoğuna.
- Ad uzunluğu adın yaşadığı kapsamla birlikte artar; bir satırlık geri çağırmadaki kısa ad ile dışa aktarılan kısa ad aynı şey değildir.
- İyi ad birimi ve türü taşır, yanlış bilgi vermez, kavram başına tek sözcük kullanır ve aranabilir; çıplak sayıya ad vermek okuma değil değiştirme maliyetini düşürür.
Sonraki Adım
Adlandırılmış sürümde bir şey dikkat çekiyor: HACIMSEL_BOLEN sabiti bir yorum satırına
ihtiyaç duymadı, çünkü adı ne olduğunu söylüyor. Kısaltılmış sürümde aynı bilgi ancak bir
yorumla verilebilirdi. Bu gözlem genelleşir mi? Bir yorumun varlığı, kodun söyleyemediği
bir şeyi söylemek zorunda kalmasının işareti midir? Sonraki ders bu soruyu ölçerek yanıtlar:
yorumların ne kadarı ada çevrilebilir, çevrilemeyenler hangileridir ve yorumla kodun
birbirinden ayrı düşmesi hangi noktada sessiz bir yanlışa dönüşür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.