İçeriğe geç
academia.sh

Ders 04 / 16

Girinti ve Kod Biçimi

Kod biçiminin üslup tercihi olmaktan çıkarılıp fark maliyetiyle ölçülmesi: aynı içeriğin iki girinti düzeninde ürettiği fark satırları, tarife tablosunun hizalanmış ve düz biçimlerine uygulanan aynı revizyonun fark satırı sayıları ve içerik taşımayan fark satırlarının ayıklanması.

İçindekiler

Önceki derste her fonksiyon tek soyutlama düzeyine indirildi, ama dosyanın kendisi hakkında bir şey söylenmedi: satırların nerede biteceği, hangi bildirimlerin yan yana duracağı, girintinin ne kadar olacağı. Bu kararlar genellikle üslup sayılır ve tartışması zevke bağlanır.

Biçimin ölçülebilir bir sonucu vardır. Kod tabanına yapılan her değişiklik bir fark üretir; farkı bir başkası okur ve onaylar. Aynı içerik değişikliği, farklı biçimlerde farklı sayıda fark satırı üretir ve bu satırların bir kısmı hiçbir bilgi taşımaz. Bu ders biçimi iki sayıya bağlıyor: bir değişikliğin ürettiği fark satırı sayısı ve bunların içinde içerik taşımayanların sayısı.

Girinti Yapının Kendisini Gösterir

Girinti, iç içe geçmiş blokların derinliğini göze taşıyan tek işarettir. Yapı ile girinti ayrı düştüğünde okuyan kişi bloğun nerede bittiğini süslü parantezleri sayarak bulmak zorunda kalır. Aşağıdaki iki dosya aynı sözcük dizisini taşır; yalnız girintileri farklıdır.

mkdir -p duzenli karisik duz hizali
// duzenli/ucret.mjs — girinti yapisal derinlikle uyusuyor
const ASGARI_UCRET = 52;
const AGIRLIK_KADEMELERI = [
  { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 30, kiloBasiUcret: 17 },
];

export function kademeUcreti(agirlikKg, bolgeKatsayisi) {
  const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
  if (kademe === undefined) {
    throw new RangeError("kademe disi agirlik");
  }
  return Math.max(kademe.kiloBasiUcret * agirlikKg * bolgeKatsayisi, ASGARI_UCRET);
}
// karisik/ucret.mjs — ayni icerik, girinti yapiyla uyusmuyor
const ASGARI_UCRET = 52;
const AGIRLIK_KADEMELERI = [
    { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 30, kiloBasiUcret: 17 },
];

export function kademeUcreti(agirlikKg, bolgeKatsayisi) {
    const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
      if (kademe === undefined) {
    throw new RangeError("kademe disi agirlik");
      }
  return Math.max(kademe.kiloBasiUcret * agirlikKg * bolgeKatsayisi, ASGARI_UCRET);
}

İkinci dosyada throw satırı, içinde bulunduğu koşuldan daha az girintili. Bir okuyucu o satıra baktığında koşulun içinde mi dışında mı olduğunu girintiden anlayamaz. Aynı belirsizlik AGIRLIK_KADEMELERI dizisinin ilk ögesinde de var: iki öge aynı düzeyde olduğu hâlde farklı sütunlarda duruyor.

İki dosya arasındaki fark ölçülebilir. Dosyaların ilk satırı dosya adını taşıyan işaret olduğu için karşılaştırmaya girmez.

diff <(tail -n +2 duzenli/ucret.mjs) <(tail -n +2 karisik/ucret.mjs)
3c3
<   { ustSinirKg: 5, kiloBasiUcret: 26 },
---
>     { ustSinirKg: 5, kiloBasiUcret: 26 },
8,9c8,9
<   const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
<   if (kademe === undefined) {
---
>     const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
>       if (kademe === undefined) {
11c11
<   }
---
>       }

Sekiz fark satırı var ve hiçbiri bir davranış değişikliği anlatmıyor. Bu, biçim uyuşmazlığının ilk maliyetidir: girintiyi düzelten kişi, hiçbir içerik değiştirmediği hâlde sekiz satırlık bir fark üretir ve o farkı bir başkası okumak zorunda kalır.

Tarife Tablosunun İki Biçimi

İkinci ölçüm daha somut bir soruyu yanıtlar: aynı içerik değişikliği iki farklı biçimde kaç satır fark üretir? Tarife modülü iki biçimde yazıldı. Birincisinde eşittir işaretleri hizalanmış ve kademe tablosu satır başına iki öge alacak şekilde sıkıştırılmış; ikincisinde bildirimler tek boşluklu ve her kademe kendi satırında.

// hizali/tarife.mjs — hizalanmis bildirimler, sikistirilmis kademe tablosu
export const HACIMSEL_BOLEN              = 3000;
export const ASGARI_UCRET                = 52;
export const SOZLESMELI_MUSTERI_INDIRIMI = 0.15;
export const PARA_BIRIMI                 = "TL";

export const AGIRLIK_KADEMELERI = [
  { ustSinirKg:  1, kiloBasiUcret: 38 }, { ustSinirKg:  5, kiloBasiUcret: 26 },
  { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 },
];
// duz/tarife.mjs — tek bosluklu bildirimler, satir basina bir kademe
export const HACIMSEL_BOLEN = 3000;
export const ASGARI_UCRET = 52;
export const SOZLESMELI_MUSTERI_INDIRIMI = 0.15;
export const PARA_BIRIMI = "TL";

export const AGIRLIK_KADEMELERI = [
  { ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 10, kiloBasiUcret: 21 },
  { ustSinirKg: 30, kiloBasiUcret: 17 },
];

Taşıyıcı tarifeyi gözden geçirdi ve üç şey değişti: beş kilogram kademesinin kilo başı ücreti 26’dan 25’e indi, elli kilograma kadar yeni bir kademe açıldı, SOZLESMELI_MUSTERI_INDIRIMI sabiti SOZLESME_INDIRIMI adına çevrildi. Aynı üç değişiklik iki dosyaya da uygulanır.

// hizali/tarife-yeni.mjs — ayni revizyon, hizalamasi korunmus hali
export const HACIMSEL_BOLEN    = 3000;
export const ASGARI_UCRET      = 52;
export const SOZLESME_INDIRIMI = 0.15;
export const PARA_BIRIMI       = "TL";

export const AGIRLIK_KADEMELERI = [
  { ustSinirKg:  1, kiloBasiUcret: 38 }, { ustSinirKg:  5, kiloBasiUcret: 25 },
  { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 },
  { ustSinirKg: 50, kiloBasiUcret: 14 },
];
// duz/tarife-yeni.mjs — tarife revizyonu uygulanmis hali
export const HACIMSEL_BOLEN = 3000;
export const ASGARI_UCRET = 52;
export const SOZLESME_INDIRIMI = 0.15;
export const PARA_BIRIMI = "TL";

export const AGIRLIK_KADEMELERI = [
  { ustSinirKg: 1, kiloBasiUcret: 38 },
  { ustSinirKg: 5, kiloBasiUcret: 25 },
  { ustSinirKg: 10, kiloBasiUcret: 21 },
  { ustSinirKg: 30, kiloBasiUcret: 17 },
  { ustSinirKg: 50, kiloBasiUcret: 14 },
];

Hizalanmış sürümde ad kısaldığı için hizalama sütunu kaydı ve dört bildirimin dördü de yeniden yazıldı. Düz sürümde yalnız adı değişen bildirim yeniden yazıldı.

Fark Ölçer

Aşağıdaki araç her dosya çifti için farkı alır ve fark satırlarını ikiye ayırır. Çıkan bir satırın boşlukları sadeleştirilmiş hâli giren satırlardan birinde de bulunuyorsa o çift yalnız biçim farkıdır; içerik taşımaz. Kalanlar içerik farkıdır.

// fark-olc.mjs — iki dosya arasindaki fark satirlarinin kaci icerik tasiyor
import { execSync } from "node:child_process";

// Ilk satir dosya adi isaretidir, karsilastirmaya girmez.
const fark = (eski, yeni) =>
  execSync(`diff <(tail -n +2 ${eski}) <(tail -n +2 ${yeni}) || true`, { shell: "/bin/bash" })
    .toString().split("\n");
const icerik = (s) => s.slice(1).replace(/\s+/g, " ").trim();

const CIFTLER = [
  ["duzenli/ucret.mjs", "karisik/ucret.mjs"],
  ["hizali/tarife.mjs", "hizali/tarife-yeni.mjs"],
  ["duz/tarife.mjs", "duz/tarife-yeni.mjs"],
];

for (const [eski, yeni] of CIFTLER) {
  const satirlar = fark(eski, yeni);
  const cikan = satirlar.filter((s) => s.startsWith("< ")).map(icerik);
  const giren = satirlar.filter((s) => s.startsWith("> ")).map(icerik);
  const esli = cikan.filter((s) => giren.includes(s)).length;
  console.log(`${eski.padEnd(18)} -> ${yeni.padEnd(22)} degisen=${cikan.length + giren.length}` +
    `  yalniz bicim=${esli * 2}  icerik=${cikan.length + giren.length - esli * 2}`);
}
duzenli/ucret.mjs  -> karisik/ucret.mjs      degisen=8  yalniz bicim=8  icerik=0
hizali/tarife.mjs  -> hizali/tarife-yeni.mjs degisen=11  yalniz bicim=6  icerik=5
duz/tarife.mjs     -> duz/tarife-yeni.mjs    degisen=5  yalniz bicim=0  icerik=5

Üç satırın anlattığı şey şu. Girinti düzeltmesi sekiz satır fark üretiyor ve sekizi de biçim; içerik farkı sıfır. Tarife revizyonunun içerik farkı iki biçimde de aynı: beş satır. Hizalanmış biçimde bu beş satırın yanına altı satır daha ekleniyor. Aynı bilgi, iki katından fazla fark satırıyla anlatılıyor.

Farkı Okuyan Kişi

Sayıların anlamı, farkı okuyan kişinin işidir. On bir satırlık farkta beş satır bir karar taşır, altı satır taşımaz; okuyan kişi hangisinin hangisi olduğunu önceden bilmez ve hepsini karşılaştırmak zorundadır. Beş satırlık farkta böyle bir ayıklama yoktur.

Sıkıştırılmış tablonun ikinci bir etkisi de var. Hizalanmış sürümün fark çıktısında değişen tablo satırı iki kademe taşıyor: biri değişti, biri değişmedi. Okuyan kişi satırın hangi yerinde değişiklik olduğunu karakter karakter aramak zorunda. Düz sürümde değişen satır tek kademe taşıdığı için karşılaştırma satırın kendisiyle biter.

Buradan biçim kararının ölçütü çıkar: bir satır tek bir bilgi taşımalı ve bir bilginin biçimi komşusuna bağlı olmamalı. Hizalama bu ikinci koşulu bozar; bir adın uzunluğu komşu satırların boşluk sayısını belirlediği için, adı değişen bir bildirim komşularını da değiştirir. Satır başına birden çok öge yerleştirmek birinci koşulu bozar.

Biçim Kararı Bir Kez Verilir

Ölçüm hangi biçimin seçileceğini tam olarak söylemez; girintinin iki mi dört boşluk olduğu bu sayılara yansımaz. Söylediği şey, kararın proje düzeyinde bir kez verilmesi ve dosya dosya yeniden tartışılmamasıdır. Aynı projede iki girinti alışkanlığı bir arada yaşadığında her dosya, üzerinde en son çalışan kişinin alışkanlığına dönüşür ve her dönüşüm içerik taşımayan fark satırı üretir.

Bu yüzden biçim kararı bir tartışma konusu değil, bir yapılandırma dosyası konusudur. Biçimlendirici sınıfından bir araç kuralları kaynağa mekanik olarak uygular; karar bir kez verilir, uygulama kişiye bırakılmaz. Aracın seçtiği biçim kimsenin tercihiyle birebir uyuşmayabilir; ölçülen maliyet, tercih uyuşmazlığının maliyetinden büyüktür.

Özet

  • Biçim kararının ölçüsü, ürettiği fark satırı sayısıdır: her değişiklik bir fark üretir ve farkı bir başkası okur.
  • Yalnız girintisi bozulmuş bir dosya sekiz fark satırı üretti ve sekizinin de içerik farkı sıfırdı; biçim uyuşmazlığı içerik değişmeden maliyet doğurur.
  • Aynı tarife revizyonu düz biçimde beş, hizalanmış biçimde on bir fark satırı üretti; içerik farkı ikisinde de beş satır, aradaki altı satır yalnız hizalamadan geliyor.
  • Bir satır tek bilgi taşımalı ve bir bilginin biçimi komşu satırlara bağlı olmamalı; hizalama ikinci koşulu, satır başına çok öge birinci koşulu bozar.
  • Biçim kararı proje düzeyinde bir kez verilir ve mekanik olarak uygulanır; dosya başına yeniden verilen karar her seferinde içerik taşımayan fark üretir.

Sonraki Adım

Biçim, kodun göze nasıl göründüğünü belirliyor; yapının kendisini değil. Düzgün girintilenmiş bir fonksiyon da okunması güç olabilir: iç içe geçmiş koşullar, birden çok çıkış noktası ve birleşik mantıksal ifadeler girintiyle düzelmez. Bu güçlüğün de bir sayısı var. Sonraki ders bir fonksiyonun karar noktalarını sayan bir ölçer yazar, ücretlendirme fonksiyonunun iki sürümünde çevrimsel karmaşıklığı ve bağımsız yol sayısını karşılaştırır, ardından sayıyı düşüren dönüşümleri uygular.

İ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