Ders 17 / 17
Hata Ayıklama Araçları
Hata ayıklama arayüzüne bağlanma, kesme noktaları ve koşullu duraklar, gözlem ifadeleri, adımlama ve eşzamansız çağrı yığıtlarının okunması.
İçindekiler
Profilleme nerede zaman harcandığını söyledi; neyin yanlış olduğunu söylemedi. Bunun için programı belirli bir noktada durdurup değişkenlerine bakmak gerekir.
Bu ders tanı araçlarının ikinci yarısını ele alır. Örnekler ölçüm akışının aynı işleme adımı üzerinde kurulacak; kullanılan düzenek, arayüz tabanlı araçların da altında çalışan hata ayıklama arayüzüdür.
Hata Ayıklama Arayüzü
Çalışma zamanı, kendisine bağlanan bir istemcinin programı durdurmasına, değişken okumasına ve adım adım ilerletmesine izin veren bir arayüz sunar. Tarayıcı geliştirici araçları, editör eklentileri ve komut satırı istemcileri aynı arayüzü kullanır.
Bağlanmanın üç yolu vardır. Çalışma zamanı ilgili seçenekle başlatılır ve bir istemci
bağlanır; başlangıçta duraklatan seçenekle başlatılır, böylece ilk satırdan itibaren
izlenir; ya da kodun içine debugger deyimi konur — bir istemci bağlıysa yürütme orada
durur.
Aşağıdaki örneklerde istemci yerine programın kendisi kullanılıyor: aynı arayüze program içinden bağlanılıyor. Bu, arayüz tabanlı araçların hangi ilkelerle çalıştığını doğrudan gösterir.
Kesme Noktası, Koşul ve Gözlem İfadeleri
Kesme noktası (breakpoint), yürütmenin durdurulacağı yerdir. Durak anında iki şey incelenebilir: yerel değişkenlerin değerleri ve çağrı yığıtı.
Her durakta durmak, çok tekrarlı bir akışta işe yaramaz. Koşullu kesme noktası, yalnız verilen ifade doğru olduğunda durur. Aşağıdaki program bunu, durak anında bir ifade değerlendirip koşul sağlanmadığında hemen devam ederek gerçekleştirir.
import { Session } from "node:inspector"; const oturum = new Session(); oturum.connect(); oturum.post("Debugger.enable"); function ifadeyiOku(cerceve, ifade) { let sonuc = null; oturum.post( "Debugger.evaluateOnCallFrame", { callFrameId: cerceve.callFrameId, expression: ifade }, (hata, yanit) => { sonuc = yanit.result.value ?? yanit.result.description; }, ); return sonuc; } oturum.on("Debugger.paused", (olay) => { const cerceveler = olay.params.callFrames; const ust = cerceveler[0]; const deger = ifadeyiOku(ust, "olcum.deger"); if (deger > 20) { console.log("durak:", ust.functionName, "— satır", ust.location.lineNumber + 1); console.log(" gözlem: olcum.istasyon =", ifadeyiOku(ust, "olcum.istasyon")); console.log(" gözlem: olcum.deger =", deger); console.log(" yığıt:", cerceveler.slice(0, 3).map((c) => c.functionName).join(" <- ")); } oturum.post("Debugger.resume"); }); function olcumIsle(olcum) { debugger; return { istasyon: olcum.istasyon, yuvarlanmis: Math.round(olcum.deger) }; } function akisiIsle(olcumler) { return olcumler.map(function tekOlcumu(olcum) { return olcumIsle(olcum); }); } const sonuc = akisiIsle([ { istasyon: "A1", deger: 21.4 }, { istasyon: "B2", deger: 19.8 }, { istasyon: "C3", deger: 23.1 }, ]); console.log("sonuç:", sonuc.map((s) => `${s.istasyon}=${s.yuvarlanmis}`).join(" ")); oturum.disconnect();
durak: olcumIsle — satır 34 gözlem: olcum.istasyon = A1 gözlem: olcum.deger = 21.4 yığıt: olcumIsle <- tekOlcumu <- akisiIsle durak: olcumIsle — satır 34 gözlem: olcum.istasyon = C3 gözlem: olcum.deger = 23.1 yığıt: olcumIsle <- tekOlcumu <- akisiIsle sonuç: A1=21 B2=20 C3=23
Üç ölçümden ikisi koşulu sağladı ve bildirildi; ikinci ölçümde program hiç
duraklamamış gibi devam etti. Yazdırılan satır numarası, kaynak dosyadaki debugger
deyiminin bulunduğu satırdır; kod değişirse bu sayı da değişir.
Gözlem ifadesi (watch expression), durak anında değerlendirilen ifadedir. Yalnız
değişken adı olmak zorunda değildir; olcum.deger > 20 gibi bir ifade de, bir yöntem
çağrısı da değerlendirilebilir. Arayüz tabanlı araçlarda bu ifadeler bir listeye yazılır
ve her durakta yeniden hesaplanır.
Çağrı yığıtı görünümü de aynı durak verisinden gelir: olcumIsle çağrısına tekOlcumu
üzerinden, oraya da akisiIsle üzerinden gelinmiş. Bu zincir, “bu fonksiyon buraya nereden
çağrıldı” sorusunun yanıtıdır.
Adımlama
Durakta üç ilerletme komutu vardır. Üzerinden atlama (step over) bir sonraki satıra geçer; çağrılan fonksiyonların içine girmez. İçine girme (step into) çağrılan fonksiyonun ilk satırında durur. Dışına çıkma (step out) içinde bulunulan fonksiyon dönene kadar çalıştırır.
import { Session } from "node:inspector"; const oturum = new Session(); oturum.connect(); oturum.post("Debugger.enable"); let adim = 0; oturum.on("Debugger.paused", (olay) => { const ust = olay.params.callFrames[0]; console.log("adım", adim, "— satır", ust.location.lineNumber + 1); adim += 1; if (adim < 8) oturum.post("Debugger.stepOver"); else oturum.post("Debugger.resume"); }); function ortalamaAl(olcumler) { debugger; let toplam = 0; for (const olcum of olcumler) { toplam += olcum.deger; } const ortalama = toplam / olcumler.length; return ortalama; } const sonuc = ortalamaAl([ { istasyon: "A1", deger: 21.4 }, { istasyon: "B2", deger: 19.8 }, ]); console.log("ortalama:", sonuc.toFixed(2)); oturum.disconnect();
adım 0 — satır 18 adım 1 — satır 19 adım 2 — satır 20 adım 3 — satır 20 adım 4 — satır 21 adım 5 — satır 20 adım 6 — satır 21 adım 7 — satır 20 ortalama: 20.60
Satır dizisi döngünün yapısını açığa çıkarır: yirminci satır döngü başlığıdır ve her turda iki kez görünür — bir kez yineleyiciden değer almak, bir kez turu bitirmek için. Yirmi birinci satır gövdedir ve iki ölçüm için iki kez çalışır.
Adımlamanın eşzamansız kodda bir sınırı vardır: await ifadesinin üzerinden atlandığında
yürütme o fonksiyondan çıkar ve olay döngüsüne döner. Devamı, ilgili söz sonuçlandığında
başka bir turda çalışır. Bu nedenle eşzamansız akışlarda adımlama yerine, ilgilenilen
noktaya doğrudan kesme noktası koymak daha verimlidir.
Eşzamansız Çağrı Yığıtı
Kursun ilk konusunda gösterildiği gibi, bir geri çağrı boş yığıtta çalışır. Bunun tanı açısından sonucu ağırdır: geri çağrı içinde oluşan bir hatanın yığıtında, o işi kim başlattı bilgisi yoktur.
Çalışma zamanları bu boşluğu eşzamansız çağrı yığıtlarıyla kapatır: await ile birbirine
bağlanan fonksiyonların zinciri korunur ve hata yığıtında async işaretiyle görünür.
function cerceveleriYaz(hata) { const cerceveler = hata.stack .split("\n") .slice(1) .map((satir) => satir.trim()) .filter((satir) => !satir.includes("node:")) .map((satir) => satir.replace(/\s*\(?file:.*$/, "")) .filter((satir) => satir !== "at async"); for (const cerceve of cerceveler) console.log(" ", cerceve); } async function olcumGetir(istasyon) { await new Promise((karar) => setTimeout(karar, 5)); throw new Error(`istasyon yanıt vermedi: ${istasyon}`); } async function olcumOku(istasyon) { return await olcumGetir(istasyon); } async function akisiIsle(istasyonlar) { const sonuclar = []; for (const istasyon of istasyonlar) { sonuclar.push(await olcumOku(istasyon)); } return sonuclar; } function geriCagriylaGetir(istasyon, gericagri) { setTimeout(() => gericagri(new Error(`istasyon yanıt vermedi: ${istasyon}`)), 5); } try { await akisiIsle(["A1"]); } catch (hata) { console.log("await zinciri —", hata.message); cerceveleriYaz(hata); } geriCagriylaGetir("B2", (hata) => { console.log("geri çağrı zinciri —", hata.message); cerceveleriYaz(hata); });
await zinciri — istasyon yanıt vermedi: A1 at olcumGetir at async olcumOku at async akisiIsle geri çağrı zinciri — istasyon yanıt vermedi: B2 at Timeout._onTimeout
Fark çarpıcıdır. await zincirinde hatanın yolu baştan sona görünüyor: hangi istasyon
okunurken, hangi akış işlenirken oluştuğu okunabiliyor. Geri çağrı zincirinde ise yalnız
zamanlayıcı çerçevesi var; isteği kimin başlattığı bilgisi kaybolmuş.
Çıktıdaki dosya yolları ve çalışma zamanına ait iç çerçeveler burada elenerek yazdırıldı; gerçek bir yığıtta bunlar da bulunur ve makineye göre değişir. Arayüz tabanlı araçlar aynı elemeyi “kütüphane kodunu gizle” biçiminde yapar.
Buradan çıkan tasarım kuralı, kursun geri çağrı dersindeki gerekçeye bir yenisini ekler:
sözlerle ve await ile yazılmış kod yalnız daha okunaklı değil, daha tanılanabilirdir.
Yazdırarak Hata Ayıklama
Konsola yazdırmak ilkel görünse de bazı durumlarda tek uygulanabilir yöntemdir: sorun seyrek ortaya çıkıyorsa, üretim ortamındaysa ya da durdurmanın kendisi davranışı değiştiriyorsa.
İki iyileştirme onu ciddi bir araç haline getirir. Birincisi, kayıtların bağlam taşımasıdır: hangi istasyon, hangi deneme, hangi işlem anahtarı. Bağlamsız bir “buraya geldi” satırı, eşzamansız bir akışta hangi isteğe ait olduğunu söylemez.
İkincisi, kayıt noktası (logpoint) kullanmaktır: kesme noktası gibi kurulur, ama durdurmak yerine bir ifade yazdırıp devam eder. Böylece kod değiştirilmeden ve yeniden başlatılmadan gözlem eklenebilir.
Özet
- Hata ayıklama arayüzü, programı durdurma, değişken okuma ve adım adım ilerletme işlemlerini sunar; arayüz tabanlı araçlar da bu arayüzü kullanır.
- Kesme noktası bir koşula bağlanabilir; gözlem ifadeleri durak anında değerlendirilir ve çağrı yığıtı çağrının nereden geldiğini gösterir.
- Adımlama komutları üzerinden atlama, içine girme ve dışına çıkma biçimindedir;
awaitüzerinden atlandığında yürütme olay döngüsüne döner. - Eşzamansız çağrı yığıtı,
awaitile bağlanan zinciri korur; geri çağrı tabanlı kodda bu zincir kaybolur. - Yazdırarak hata ayıklama, kayıtlar bağlam taşıdığında ve kayıt noktaları kullanıldığında ciddi bir araçtır.
Kurs Kapanışı
Kurs tek bir soruyla başladı: tek iş parçacıklı bir dil, beklemeyi nasıl karşılar? Yanıt adım adım kuruldu.
Önce model kuruldu: kullanıcı kodu tek iş parçacığında, sonuna kadar çalışarak yürür; olay döngüsü her turda görev kuyruğundan bir iş alır ve ardından mikro görev kuyruğunu tamamen boşaltır. Bu kural, kurstaki her çıktı sırasının gerekçesiydi.
Sonra soyutlama kuruldu: geri çağrının bıraktığı boşluk — birleştirilememe, hata
yolunun elle taşınması, sözleşme ihlalleri — sözün durum makinesiyle kapandı.
Birleştiriciler beklemenin yapısını verdi, async ve await ise okunuşu düzeltti;
ikisi de sözün üzerine oturan katmanlardı, yeni bir eşzamansızlık düzeneği değil.
Ardından denetim kuruldu: iptal işareti, zaman aşımı ve yeniden deneme kalıpları. Buradaki ayrım kursun en pratik dersidir — beklemeyi kesmek işi kesmez.
Son olarak gözlem kuruldu: erişilebilirlik temelli bellek modeli, anlık görüntü karşılaştırmasıyla sızıntı tanısı, ana iş parçacığı profillemesi ve kesme noktalarıyla durum incelemesi. Ölçüm akışı örneği, tek bir zamanlayıcı geri çağrısından başlayıp iptal edilebilir, zaman aşımlı, dayanıklı ve sızıntı bırakmayan bir akışa dönüştü.
Kalan konu, bu kodun nasıl paketlendiği ve dağıtıldığıdır. Sonraki kurs — Modüller, Araçlar ve Ekosistem — modül sistemlerini, paketlerin çözümlenme biçimini, bağımlılık sürümlemesini ve derleme araçlarını ele alır. Bu kursta birkaç kez modül biçimine değinilmiş ve ayrıntısı oraya bırakılmıştı; sıra oraya geldi.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.