Ders 13 / 14
Statik Çözümleme ve Biçimlendirme
Ayrıştırma düzeyindeki denetimin sınırı, kural kümesi ve şiddet düzeyleri, uyarı bastırmanın disiplini, biçimlendirmenin çözümlemeden ayrılması ve yinelemeye dayanıklılık.
İçindekiler
Buraya kadarki araçlar kodu dönüştürdü: modülleri birleştirdi, sözdizimi indirgedi, konum eşlemesi üretti. Bu ders, kodu dönüştürmeyen bir araç sınıfını ele alır — kaynağı okuyup kural kümesine göre yargılayan statik çözümleme (static analysis) araçlarını.
Kabuk Programlama kursunda aynı araç sınıfı kabuk betikleri için tanıtılmıştı; ölçütler ve disiplin aynıdır. Burada dilin kendi özellikleri ve paket düzeyindeki yerleşim eklenir.
Ayrıştırma Düzeyinde Denetim
En temel denetim, kaynağın ayrıştırılabilir olup olmadığıdır. Çalışma zamanları bunu kod çalıştırmadan yapan bir seçenek sunar:
// dosya: bozuk.js function olc(metin) { return metin.split(/\s+/.length; }
$ node --check bozuk.js 2>&1 | sed "s|$(pwd -P)/||" | head -6
bozuk.js:3
return metin.split(/\s+/.length;
^^^^^^
SyntaxError: missing ) after argument list
at wrapSafe (node:internal/modules/cjs/loader:1804:18)
Çıkış kodu sıfırdan farklıdır; bu, denetimin bir betiğe konabileceği anlamına gelir. İletinin son satırındaki iç çerçeveler çalışma zamanının sürümüne göre değişir; ilk dört satır kararlıdır.
Bu denetimin sınırı, ayrıştırmanın kendisidir. Ayrıştırılabilen her kaynak geçer — anlamı ne olursa olsun:
// dosya: supheli.js function esikAsti(sayi, esik) { if (sayi = esik) { return true; } return false; } console.log(esikAsti(3, 5));
$ node --check supheli.js && echo "sozdizim denetimi: gecti" sozdizim denetimi: gecti $ node supheli.js true
Koşulda karşılaştırma değil atama yapıldı. Program ayrıştırıldı, çalıştı ve yanlış yanıt verdi: 3, 5’i aşmıyor. Dil bu yazımı geçerli sayar çünkü atama bir ifadedir ve değer üretir. Yakalanması için ayrıştırmanın ötesinde bir yargı gerekir — kural kümesi burada devreye girer.
Kural Kümesi ve Şiddet Düzeyleri
Bir çözümleyici, kaynağı ayrıştırıp elde ettiği yapı üzerinde kurallar çalıştırır. Her kural bir örüntü arar, bulduğunda bir bulgu üretir ve bulgunun bir şiddet düzeyi (severity level) olur.
Kuralların nasıl işlediğini görmek için küçük bir çözümleyici yeterlidir. Aşağıdaki gerçekleştirim satır tabanlıdır; gerçek araçlar soyut sözdizim ağacı üzerinde çalışır, ama kural, şiddet ve bastırma yapısı aynıdır.
// dosya: cozumle.mjs import { readFile } from 'node:fs/promises'; const KURALLAR = [ { ad: 'gevsek-esitlik', siddet: 'hata', sina: (s) => /[^=!<>]==[^=]|[^!]!=[^=]/.test(s), ileti: 'gevşek karşılaştırma yerine katı karşılaştırma kullanın' }, { ad: 'bos-yakalama', siddet: 'hata', sina: (s) => /catch\s*(\([^)]*\))?\s*\{\s*\}/.test(s), ileti: 'boş yakalama bloğu hatayı gizler' }, { ad: 'konsol', siddet: 'uyari', sina: (s) => /console\.(log|debug)\(/.test(s), ileti: 'kütüphane kodunda doğrudan konsol yazımı' }, ]; const YOKSAY = /\/\/\s*cozumleme-yoksay:\s*([\w-]+)/; export async function cozumle(dosya) { const satirlar = (await readFile(dosya, 'utf8')).split('\n'); const bulgular = []; satirlar.forEach((satir, i) => { const yoksayilan = satir.match(YOKSAY)?.[1]; for (const kural of KURALLAR) { if (kural.ad === yoksayilan) continue; if (kural.sina(satir)) { bulgular.push({ dosya, satir: i + 1, ...kural }); } } }); return bulgular; } const bulgular = await cozumle(process.argv[2]); for (const b of bulgular) { console.log(`${b.dosya}:${b.satir} ${b.siddet.padEnd(6)} ${b.ad.padEnd(16)} ${b.ileti}`); } const hataVar = bulgular.some((b) => b.siddet === 'hata'); console.log(`${bulgular.length} bulgu`); process.exit(hataVar ? 1 : 0);
Ölçüm kütüphanesinin istatistik modülü, üç kuralı da tetikleyecek biçimde yazılmış olsun:
// dosya: src/istatistik.mjs export function ortalamaUzunluk(sozcukler) { if (sozcukler.length == 0) return 0; return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length; } export function enUzunSozcuk(sozcukler) { console.log('enUzunSozcuk çağrıldı'); // cozumleme-yoksay: konsol try { return sozcukler.reduce((en, s) => (s.length > en.length ? s : en), ''); } catch {} }
$ node cozumle.mjs src/istatistik.mjs; echo "cikis kodu: $?" src/istatistik.mjs:3 hata gevsek-esitlik gevşek karşılaştırma yerine katı karşılaştırma kullanın src/istatistik.mjs:11 hata bos-yakalama boş yakalama bloğu hatayı gizler 2 bulgu cikis kodu: 1
Yedinci satırdaki konsol yazımı bulgu üretmedi: satır sonundaki yorum o kuralı bastırdı. Çıkış kodu, Kabuk Programlama kursundaki çıkış kodu sözleşmesinin aynısını izler — sıfırdan farklı kod, çağıran betiğin işlemi durdurmasını sağlar.
Kuralların Sınıflanması
Her kural aynı ağırlıkta değildir. Üç sınıf ayırt edilir:
Doğruluk kuralları. Kod büyük olasılıkla yanlış davranır: kullanılmayan atama, koşulda atama, erişilemez kod, sözü beklemeden bırakma. Bunlar hata düzeyinde işaretlenmeye değerdir.
Şüphe kuralları. Kod yanlış olmayabilir ama örüntü kusur belirtisidir: boş yakalama, karşılaştırma yerine dönüştürme, geniş kapsamlı değişken. Uyarı düzeyi uygundur; bilinçli kullanımlar için bastırma gerekir.
Biçim kuralları. Davranışa etkisi yoktur: girinti, tırnak türü, noktalı virgül. Bunlar çözümleyicinin işi değildir; bir sonraki bölümün konusudur.
Ayrımın pratik önemi, hata düzeyinin bir maliyet taşımasıdır: hata düzeyindeki her kural, işlem akışını durdurma yetkisi alır. Bu yetki biçim tercihlerine verildiğinde, ekip çözümleyiciyi bir engel olarak görmeye başlar ve bastırma yorumları çoğalır.
Bastırma Disiplini
Bastırma yorumu gerekli bir düzenektir: hiçbir kural kümesi tüm meşru kullanımları öngöremez. Kötüye kullanımı da aynı ölçüde kolaydır.
İşleyen üç kural vardır. Bastırma dar olmalıdır: tüm dosya değil tek satır, tüm kurallar değil tek kural. Bastırma gerekçeli olmalıdır: yanındaki yorum neden bastırıldığını söylemelidir. Bastırma sayılabilir olmalıdır: kaç bastırma olduğu ölçülüyorsa, artışı bir sinyaldir.
Bir kural sürekli bastırılıyorsa sorun kuralın kendisindedir. O kural ya projeye uygun değildir ve kapatılmalıdır, ya da yanlış tanımlanmıştır ve daraltılmalıdır.
Biçimlendirme Ayrı Bir İştir
Biçimlendirici (formatter), kodun anlamını değiştirmeden yazımını tek bir düzene sokan araçtır. Çözümleyiciden iki noktada ayrılır: bulgu bildirmez, doğrudan yazar; ve doğru olanı değil, tek olanı seçer.
Doğru biçimlendiricinin taşıması gereken özellik yinelemeye dayanıklılıktır (idempotent): bir kez uygulanmış çıktıya yeniden uygulandığında hiçbir şey değişmemelidir. Bu özellik olmadan araç, her çalıştırmada değişiklik üretir ve sürüm denetimini gürültüye boğar.
// dosya: bicimle.mjs import { readFile, writeFile } from 'node:fs/promises'; export function bicimle(kaynak) { const satirlar = kaynak .replace(/\t/g, ' ') .split('\n') .map((s) => s.replace(/\s+$/, '')); while (satirlar.length > 0 && satirlar.at(-1) === '') satirlar.pop(); return satirlar.join('\n') + '\n'; } const dosya = process.argv[2]; const once = await readFile(dosya, 'utf8'); const birKez = bicimle(once); const ikiKez = bicimle(birKez); await writeFile(dosya, birKez); console.log('değişti mi:', once !== birKez); console.log('idempotent mi:', birKez === ikiKez);
Girdi, sekme karakteri ve satır sonu boşluğu içeren bir dosya olsun. cat -et çağrısı
sekmeleri ^I, satır sonlarını $ ile gösterir:
$ printf 'export function olc(metin) {\n\treturn metin.split(/\\s+/).length; \n}\n' > bicimsiz.mjs
$ cat -et bicimsiz.mjs
export function olc(metin) {$
^Ireturn metin.split(/\s+/).length; $
}$
$ node bicimle.mjs bicimsiz.mjs
değişti mi: true
idempotent mi: true
$ cat -et bicimsiz.mjs
export function olc(metin) {$
return metin.split(/\s+/).length;$
}$
$ node bicimle.mjs bicimsiz.mjs
değişti mi: false
idempotent mi: true
İkinci çalıştırma hiçbir şey değiştirmedi. Örnek biçimlendirici yalnızca iki dönüşüm yapar; gerçek araçlar kaynağı ayrıştırıp ağaçtan yeniden yazar ve satır uzunluğu, parantez yerleşimi, alan hizalaması gibi kararları da verir. Yeniden yazma yaklaşımının sonucu, çıktının girdinin özgün yazımından tümüyle bağımsız olmasıdır.
Ayrımın gerekçesi bir maliyet hesabıdır. Biçim tartışmaları inceleme süresinin ölçülebilir bir bölümünü tüketir ve teknik bir sonuca bağlanmaz. Kararın araca devredilmesi bu süreyi sıfırlar. Bunun karşılığında ekip, tek tek biçim tercihlerinden vazgeçer.
Otomasyon ve Sınırlar
İki aracın da işe yaraması, çalıştırılmalarına bağlıdır. Yerleşme sırası genellikle şudur: düzenleyicide kayıtta biçimlendirme, işleme öncesi denetim, yapı hattında son denetim. Son halka olmadan diğerleri isteğe bağlı kalır; yalnızca son halka varsa geri bildirim gecikir.
Statik çözümlemenin sınırı ilkesel bir sınırdır: kural kümesi, kodun ne yaptığını değil nasıl yazıldığını bilir. Yanlış bulgular kaçınılmazdır ve kaçırılan kusurlar da öyle. Çözümleyici sınamaların yerine geçmez; ikisi farklı kusur sınıflarını yakalar. Bir çözümleyicinin temiz raporu, kodun doğru olduğunu değil, bilinen kusur örüntülerinden kaçındığını söyler.
Özet
- Sözdizim denetimi ayrıştırılabilirliği sınar ve kod çalıştırmadan sonuç verir; anlamsal kusurları — koşulda atama gibi — yakalamaz.
- Statik çözümleyici, ayrıştırılmış kaynak üzerinde kural kümesi çalıştırır; her bulgu bir şiddet düzeyi taşır ve çıkış kodu işlem akışını durdurabilir.
- Kurallar doğruluk, şüphe ve biçim olarak ayrılır; hata düzeyi yalnızca doğruluk kurallarına verilir.
- Bastırma dar, gerekçeli ve sayılabilir olmalıdır; sürekli bastırılan bir kural yanlış tanımlanmıştır.
- Biçimlendirici anlamı değiştirmeden tek bir yazım düzeni uygular ve yinelemeye dayanıklı olmalıdır; kararın araca devredilmesi inceleme süresini biçim tartışmalarından kurtarır.
- Statik çözümleme sınamaların yerine geçmez; temiz rapor doğruluğu değil, bilinen kusur örüntülerinin yokluğunu bildirir.
Sonraki Adım
Bu kursta kurulan araçların hepsi komut satırından çağrılan programlardır: paketleyici, çözümleyici, biçimlendirici, sınama koşucusu. Her geliştiricinin bunları aynı seçeneklerle çağırması bir bellek işi olmamalıdır. Kursun son dersi, bu çağrıların projede nasıl adlandırılıp saklandığını ve yeniden üretilebilir bir komut kümesinin nasıl kurulduğunu ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.