İçeriğe geç
academia.sh

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ı, await ile 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat