İçeriğe geç
academia.sh

Ders 16 / 21

Hata Yönetimi

`try`/`catch`/`finally` akışı, `throw` deyimi, yerleşik hata tipleri, hata nesnesinin alanları, neden zinciri ve hata ile dönüş değeri arasındaki seçim.

İçindekiler

Önceki derslerde try/catch yapısı hata iletilerini göstermek için kullanıldı ama tanımlanmadı. Bu ders yapıyı tamamlar: blokların akışı, hata fırlatma, yerleşik hata tipleri ve — en önemlisi — bir sorunun hata mı yoksa dönüş değeri mi olarak bildirileceği kararı.

Kod Çalıştırma Yolları dersinde hata nesnesinin ad, ileti ve yığıt izi taşıdığı gösterilmişti. Burada bu nesneleri kendimiz üreteceğiz.

Blokların Akışı

try bloğu izlenen kodu içerir. Hata oluşursa akış catch bloğuna atlar. finally bloğu, hata olsun olmasın çalışır.

function dene(deger) {
  try {
    console.log("1 try basi");
    if (deger < 0) throw new Error("negatif deger");
    console.log("2 try sonu");
    return "try donusu";
  } catch (hata) {
    console.log("3 catch:", hata.message);
    return "catch donusu";
  } finally {
    console.log("4 finally");
  }
}
console.log(dene(1));
console.log("---");
console.log(dene(-1));
1 try basi
2 try sonu
4 finally
try donusu
---
1 try basi
3 catch: negatif deger
4 finally
catch donusu

İki gözlem önemlidir. Birincisi, hata oluştuğunda try bloğunun kalan satırları çalışmaz; akış doğrudan catch’e geçer. İkincisi, finally bloğu return deyiminden sonra ama fonksiyon gerçekten dönmeden önce çalışır. Bu yüzden temizlik işleri oraya yazılır: açılan bir kaynağın kapatılması, ayrılan bir kilidin bırakılması.

finally içinde return yazmak, dönüş değerini ezer:

function ezer() {
  try {
    return "try";
  } finally {
    return "finally";
  }
}
console.log(ezer());
finally

Aynı davranış fırlatılan hatayı da yutar. Bu yüzden finally bloğu yalnızca temizlik içerir; akışı yönlendiren bir deyim taşımaz.

Hata nesnesine ihtiyaç yoksa yakalama bağı yazılmayabilir:

function gecerliJson(metin) {
  try {
    JSON.parse(metin);
    return true;
  } catch {
    return false;
  }
}
console.log(gecerliJson('{"a":1}'), gecerliJson("{bozuk}"));
true false

Yerleşik Hata Tipleri

Standart birkaç hata tipi tanımlar ve dilin kendi ürettiği hatalar bunlardan biridir:

const denemeler = [
  () => null.x,
  () => bilinmeyenAd,
  () => ([].length = -1),
  () => (1234).toFixed(200),
];
for (const d of denemeler) {
  try {
    d();
  } catch (hata) {
    console.log(hata.name + ": " + hata.message);
  }
}
TypeError: Cannot read properties of null (reading 'x')
ReferenceError: bilinmeyenAd is not defined
RangeError: Invalid array length
RangeError: toFixed() digits argument must be between 0 and 100

TypeError bir değerin beklenen türde olmadığını, ReferenceError bir adın bulunamadığını, RangeError bir değerin izin verilen aralığın dışında olduğunu bildirir. SyntaxError ise ayrıştırma sırasında üretilir; ilk derste görüldüğü gibi programı hiç başlatmaz — tek istisnası, metin ayrıştıran yerleşik yöntemlerin çalışma zamanında ürettiği durumdur.

Hata adları standarttır; iletiler değildir. İleti metinleri çalışma zamanları arasında ve sürümler arasında değişir. Bu yüzden kodda ileti metnine göre dallanılmaz; ada, tipe veya kendi eklediğimiz bir alana bakılır.

Hata Fırlatmak

throw deyimi herhangi bir değeri fırlatabilir. Fırlatılan şeyin hata nesnesi olması zorunlu değildir, ama olmalıdır:

try {
  throw "duz metin";
} catch (hata) {
  console.log(typeof hata, hata);
  console.log("name:", hata.name, "| stack:", typeof hata.stack);
}

try {
  throw new Error("hata nesnesi");
} catch (hata) {
  console.log(typeof hata, hata instanceof Error);
  console.log("name:", hata.name, "| stack:", typeof hata.stack);
}
string duz metin
name: undefined | stack: undefined
object true
name: Error | stack: string

Metin fırlatıldığında ad ve yığıt izi yoktur; hatanın nereden geldiği kaybolur. Kursun kuralı: fırlatılan her değer bir hata nesnesidir.

Alt katmandaki bir hatayı sararken özgün hata korunmalıdır. Hata nesnesi bunun için bir neden (cause) alanı taşıyabilir:

function sicaklikCevir(metin) {
  const deger = Number(metin);
  if (Number.isNaN(deger)) {
    throw new Error(`sayiya cevrilemedi: ${JSON.stringify(metin)}`);
  }
  return deger;
}

function kaydiOku(satir) {
  const [istasyon, sicaklikMetni] = satir.split(";");
  try {
    return { istasyon, sicaklik: sicaklikCevir(sicaklikMetni) };
  } catch (hata) {
    throw new Error(`kayit okunamadi: ${istasyon}`, { cause: hata });
  }
}

console.log(kaydiOku("A1;21.5"));
try {
  kaydiOku("B2;abc");
} catch (hata) {
  console.log(hata.message);
  console.log("neden:", hata.cause.message);
}
{ istasyon: 'A1', sicaklik: 21.5 }
kayit okunamadi: B2
neden: sayiya cevrilemedi: "abc"

Üst katman kendi bağlamını ekledi (hangi kayıt), alt katmanın bilgisi (neden) kayboldu sayılmadı. Bu, hata iletilerini bağlamla zenginleştirmenin standart yoludur.

Kendi Hata Tipini İşaretlemek

Bir hatayı kendi ürettiğimizi ayırt edebilmek gerekir; aksi halde catch bloğu, kodun kendi hatalarıyla birlikte beklenmedik programlama hatalarını da yutar.

En yalın yol, hata nesnesine ad ve bir kod alanı eklemektir:

function olcumHatasi(kod, ileti) {
  const hata = new Error(ileti);
  hata.name = "OlcumHatasi";
  hata.kod = kod;
  return hata;
}

const h = olcumHatasi("BOS_ALAN", "sicaklik alani bos");
console.log(h.name, "|", h.kod, "|", h.message, "|", h instanceof Error);
OlcumHatasi | BOS_ALAN | sicaklik alani bos | true

Kalıtımla adlandırılmış hata sınıfları tanımlamak — sınıf sözdizimi kullanarak — bir sonraki kursun konusudur. Bu kursta ad ve kod alanı yeterlidir.

Ölçüm Betiğine Uygulama

Bozuk veriye verilen yanıt artık bir sözleşmeye bağlanabilir: her satır ya kabul edilir ya da gerekçesiyle reddedilir; hiçbir satır sessizce yanlış değere dönüşmez.

const KOD_BOS = "BOS_ALAN";
const KOD_SAYI = "SAYI_DEGIL";

function olcumHatasi(kod, ileti) {
  const hata = new Error(ileti);
  hata.name = "OlcumHatasi";
  hata.kod = kod;
  return hata;
}

function sicaklikOku(metin) {
  if (metin.trim() === "") {
    throw olcumHatasi(KOD_BOS, "sicaklik alani bos");
  }
  const deger = Number(metin);
  if (Number.isNaN(deger)) {
    throw olcumHatasi(KOD_SAYI, `sicaklik sayiya cevrilemedi: ${JSON.stringify(metin)}`);
  }
  return deger;
}

const satirlar = ["A1;21.5", "A2;", "B1;abc", "B2;0"];
const kabul = [];
const red = [];

for (const satir of satirlar) {
  const [istasyon, alan] = satir.split(";");
  try {
    kabul.push({ istasyon, sicaklik: sicaklikOku(alan) });
  } catch (hata) {
    if (hata.name !== "OlcumHatasi") throw hata;
    red.push({ istasyon, kod: hata.kod, ileti: hata.message });
  }
}

console.log("kabul:", kabul);
console.log("red:", red);
kabul: [ { istasyon: 'A1', sicaklik: 21.5 }, { istasyon: 'B2', sicaklik: 0 } ]
red: [
  { istasyon: 'A2', kod: 'BOS_ALAN', ileti: 'sicaklik alani bos' },
  {
    istasyon: 'B1',
    kod: 'SAYI_DEGIL',
    ileti: 'sicaklik sayiya cevrilemedi: "abc"'
  }
]

Üç karar bu betiği önceki sürümlerinden ayırıyor.

Birincisi, B2 kaydındaki 0 değeri kabul edildi. Doğruluk Değerleri dersinde kurulan ayrım korunuyor: sıfır geçerli bir ölçümdür.

İkincisi, yakalama bloğu kendi hatasını tanıyor. hata.name !== "OlcumHatasi" denetimi, beklenmedik bir hatayı yeniden fırlatıyor. Bu satır olmasaydı, örneğin satir.split çağrısında oluşacak bir TypeError de “reddedilen kayıt” sayılır ve programlama hatası veri hatası gibi görünürdü.

Üçüncüsü, red listesi gerekçe taşıyor. Kaç kaydın neden reddedildiği çıktıdan okunabiliyor.

Hata mı, Dönüş Değeri mi

Her olağandışı durum hata değildir. Ölçüt şudur: çağıran taraf durumu öngörebiliyorsa dönüş değeri, öngöremiyorsa hata uygundur.

Boş bir listenin ortalamasını istemek öngörülebilir bir durumdur; Fonksiyon Bildirimi ve İfadesi dersindeki ortalama fonksiyonu bu yüzden null döndürdü. Buna karşılık, sayısal olması gereken bir alanda harf bulunması verinin sözleşmesinin ihlalidir; hata uygundur.

İki uygulama da yukarıdaki betikte bir arada bulunuyor: sicaklikOku hata fırlatıyor, çağıran döngü hatayı yakalayıp bir veri yapısına — red listesine — çeviriyor. Sınır, sorunun nerede ele alınabileceğine göre çizilir.

Hatalar akış denetimi için kullanılmaz. Bir döngüden çıkmak, bir değeri atlamak veya bir koşulu bildirmek için hata fırlatmak, hem maliyetli hem de okunması güç bir kalıptır; break, continue ve dönüş değerleri bu iş içindir.

Özet

  • try bloğunda hata oluşursa kalan satırlar çalışmaz; finally her durumda ve return değerlendirildikten sonra çalışır.
  • finally içinde return veya throw yazmak, dönüş değerini ve hatayı ezer; blok yalnızca temizlik içerir.
  • Hata adları standarttır, iletiler değildir; dallanma ileti metnine göre yapılmaz.
  • Fırlatılan her değer hata nesnesi olmalıdır; aksi halde ad ve yığıt izi bulunmaz.
  • Sarmalanan hatalar cause alanıyla korunur; kendi hataları ad ve kod alanıyla işaretlenir ve tanınmayan hatalar yeniden fırlatılır.
  • Öngörülebilir durum dönüş değeriyle, sözleşme ihlali hatayla bildirilir.

Sonraki Adım

Denetim akışı ve fonksiyonlar tamamlandı. Bu konudaki örneklerde filter, map, split ve nesne yayma gibi yapılar açıklanmadan kullanıldı. Sonraki konu bunları tanımlayacak: dizilerin oluşturma ve dönüştürme yöntemleri, nesne erişim kısayolları, şablon dizgileri, JSON dönüşümleri ve tarih–zaman gösterimi. Ölçüm listesi orada metinden okunup metne geri yazılan tam bir veri kümesine dönüşecek.

İ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