Ders 15 / 20
Hata Yönetimi Stratejisi
İşletimsel hata ile programcı hatasının ayrımı, hata nedenlerinin zincirlenmesi, eşzamansız hataların yakalanamayan biçimleri ve son çare kancalarının doğru kullanımı.
İçindekiler
Önceki ders, yapılandırma hatasının sürecin açılmasını engellemesi gerektiğini söyledi. Çalışma sırasında ortaya çıkan her hata aynı davranışı hak etmez: bir isteğin bozuk gövdesi hizmeti kapatmamalı, ama bellekte bozulmuş bir durum onu kapatmalıdır.
Bu dersin sorusu şudur: hangi hata yakalanır, hangisi sürecin sonlanmasına yol açar? Ayrım tek bir ölçüte dayanır ve stratejinin tamamı ondan çıkar.
İki Hata Sınıfı
İşletimsel hata, doğru yazılmış bir programın çalışma koşullarında karşılaşmayı beklediği durumdur: dosya yok, ağ ulaşılamıyor, istemci geçersiz veri gönderdi, disk doldu. Bunlar programın kusuru değildir; öngörülmüş ve ele alınmıştır.
Programcı hatası, kodun kendi sözleşmesini ihlal etmesidir: tanımsız bir değerin alanına erişmek, zorunlu bir argümanı geçirmeyi unutmak, bir sayı beklenen yere nesne vermek. Bunlar öngörülmemiştir ve programın o andaki durumu hakkındaki varsayımların yanlış olduğunu gösterir.
Ayrımın kuralı şudur: işletimsel hata ele alınır, programcı hatası ele alınmaz. İkincisini yakalayıp devam etmek, bozulmuş bir durumla çalışmaya devam etmek demektir; ne kadar sürdüğü belli olmayan ve tanısı çok daha zor bir arıza üretir.
Ayrımın kodda görünür olması için işletimsel hatalar kendi tipiyle işaretlenir:
// hata-sinifi.mjs export class IsletimselHata extends Error { constructor(ileti, { kod, durum = 500, cause } = {}) { super(ileti, { cause }); this.name = 'IsletimselHata'; this.kod = kod; this.durum = durum; this.isletimsel = true; } } export function isletimselMi(hata) { return hata instanceof IsletimselHata && hata.isletimsel === true; }
Nedeni Zincirlemek
Alt katmandaki bir hata üst katmana taşınırken çevrilir: üst katman kendi diliyle
konuşan bir hata fırlatır, ama asıl nedeni kaybetmez. cause seçeneği bu bağı kurar.
// hata-dene.mjs import { readFile } from 'node:fs/promises'; import { IsletimselHata, isletimselMi } from './hata-sinifi.mjs'; async function olcumleriOku(yol) { try { return await readFile(yol, 'utf8'); } catch (hata) { if (hata.code === 'ENOENT') { throw new IsletimselHata(`olcum dosyasi yok: ${yol}`, { kod: 'OLCUM_DOSYASI_YOK', durum: 503, cause: hata, }); } throw hata; } } for (const yol of ['olcumler.ndjson', 'yok.ndjson']) { try { const metin = await olcumleriOku(yol); console.log(`${yol}: ${metin.trim().split('\n').length} satir`); } catch (hata) { console.log(`${yol}: isletimsel mi = ${isletimselMi(hata)}, kod = ${hata.kod}, durum = ${hata.durum}`); console.log(` asil neden: ${hata.cause.code}`); } } // Programci hatasi: sozlesme ihlali, yakalanip yutulmaz try { null.deger; } catch (hata) { console.log(`programci hatasi: isletimsel mi = ${isletimselMi(hata)}, tur = ${hata.constructor.name}`); }
node hata-dene.mjs
olcumler.ndjson: 12 satir yok.ndjson: isletimsel mi = true, kod = OLCUM_DOSYASI_YOK, durum = 503 asil neden: ENOENT programci hatasi: isletimsel mi = false, tur = TypeError
Üç ayrıntı önemlidir.
Yalnızca tanınan hata kodu çevriliyor; geri kalanı olduğu gibi yukarı fırlatılıyor. Bütün hataları tek bir tipe çevirmek, bilinmeyen arızaları bilinen bir kategoriye sokarak gizler.
Çevrilen hata kendi kodunu taşıyor. Çağıran, ileti metnini ayrıştırmadan karar verebilir; ileti metni değişse de kod sabit kalır.
Asıl neden erişilebilir durumda. Tanı sırasında zincirin en altındaki sistem çağrısı hatasına ulaşmak, arızayı bulmanın en kısa yoludur.
Hizmette Karşılığı
HTTP Sunucusu dersindeki hata yakalayıcı her hatayı 400 olarak bildiriyordu. Ayrım kurulduktan sonra doğru eşleme yapılabilir:
// hata-sunucu.mjs import { createServer } from 'node:http'; import { IsletimselHata, isletimselMi } from './hata-sinifi.mjs'; import { satirdanKayit } from './ozetleyici.mjs'; function hataYaniti(hata, yanit) { if (isletimselMi(hata)) { yanit.writeHead(hata.durum, { 'content-type': 'application/json' }); yanit.end(JSON.stringify({ hata: hata.message, kod: hata.kod }) + '\n'); return; } // Programci hatasi: ayrinti disariya verilmez, iceride tam kaydedilir process.stderr.write(`programci hatasi: ${hata.stack.split('\n')[0]}\n`); yanit.writeHead(500, { 'content-type': 'application/json' }); yanit.end(JSON.stringify({ hata: 'ic hata', kod: 'IC_HATA' }) + '\n'); } const sunucu = createServer(async (istek, yanit) => { try { if (istek.url === '/olcum') { const parcalar = []; for await (const p of istek) parcalar.push(p); const govde = Buffer.concat(parcalar).toString('utf8'); try { satirdanKayit(govde.trim()); } catch (hata) { throw new IsletimselHata('gecersiz olcum kaydi', { kod: 'GECERSIZ_KAYIT', durum: 400, cause: hata, }); } yanit.writeHead(201, { 'content-type': 'application/json' }); yanit.end('{"alinan":1}\n'); return; } if (istek.url === '/coken') { null.deger; // bilerek programci hatasi } throw new IsletimselHata('bulunamadi', { kod: 'YOL_YOK', durum: 404 }); } catch (hata) { hataYaniti(hata, yanit); } }); sunucu.listen(8791, '127.0.0.1', () => console.log('dinleniyor'));
Sunucu çalışırken dört istek gönderelim:
curl -s -X POST --data-binary 'bozuk' -w 'durum: %{http_code}\n' http://127.0.0.1:8791/olcum
{"hata":"gecersiz olcum kaydi","kod":"GECERSIZ_KAYIT"}
durum: 400
curl -s -w 'durum: %{http_code}\n' http://127.0.0.1:8791/yok
{"hata":"bulunamadi","kod":"YOL_YOK"}
durum: 404
curl -s -w 'durum: %{http_code}\n' http://127.0.0.1:8791/coken
{"hata":"ic hata","kod":"IC_HATA"}
durum: 500
Son çağrıda sunucunun uçbiriminde şu satır yazıldı:
programci hatasi: TypeError: Cannot read properties of null (reading 'deger')
İşletimsel hatanın iletisi istemciye veriliyor, programcı hatasınınki verilmiyor. Bu bir gizleme alışkanlığı değil, iki gerekçeye dayanan bir karardır: yığıt izi ve iç ileti, dosya yollarını ve kod yapısını dışarıya sızdırır; ayrıca istemcinin yapabileceği bir şey yoktur — 500 yanıtının tek anlamlı yanıtı yeniden denemek veya bildirmektir.
Eşzamansız Hatanın Yakalanamadığı Yerler
try/catch yalnızca kendi eşzamanlı gövdesini ve beklenen sözleri kapsar. Bir geri
çağrının içinde fırlatılan hata bu kapsamın dışındadır.
// yakalanmayan.mjs import { readFile } from 'node:fs/promises'; // try/catch, geri cagriya ertelenen hatayi yakalamaz try { setTimeout(() => { throw new Error('geri cagri icinde firlatildi'); }, 0); } catch (hata) { console.log('bu satir calismaz'); } // Beklenmeyen reddetme readFile('yok.ndjson', 'utf8'); console.log('senkron akis bitti');
node yakalanmayan.mjs
Çıktının ilk satırı senkron akis bitti olur; ardından yakalanmayan hata yazılır ve
süreç sıfırdan farklı bir kodla sonlanır. bu satir calismaz satırı hiç yazılmaz:
setTimeout çağrısı hemen döndü, hata ise çok sonra, döngünün zamanlayıcı
aşamasında fırlatıldı — o anda try bloğu çoktan bitmişti.
Aynı dosyadaki ikinci sorun daha sinsidir. readFile çağrısı beklenmediği için
döndürdüğü sözün reddi hiçbir yerde ele alınmaz. Bu, işlenmemiş reddetmedir ve
varsayılan davranışı süreci sonlandırmaktır:
// reddetme.mjs import { readFile } from 'node:fs/promises'; readFile('yok.ndjson', 'utf8'); // donen soz beklenmiyor, hatasi da islenmiyor console.log('senkron akis bitti');
node reddetme.mjs > /dev/null; echo "cikis kodu: $?"
cikis kodu: 1
Sürecin sonlanması sert görünür ama doğrudur: sessizce yutulan bir reddetme, verinin yazıldığı sanılırken yazılmamış olmasına yol açar. Kural şudur: söz döndüren her çağrı ya beklenir ya da reddi açıkça ele alınır.
Son Çare Kancaları
İki kanca, yakalanmamış hataları programın eline verir:
// son-care.mjs process.on('uncaughtException', (hata, koken) => { process.stderr.write(`olumcul: ${hata.message} (koken: ${koken})\n`); process.exit(1); // durum bozulmus sayilir: yeniden baslat }); process.on('unhandledRejection', (neden) => { process.stderr.write(`islenmemis reddetme: ${neden.message ?? neden}\n`); process.exit(1); }); setTimeout(() => { throw new Error('gec gelen hata'); }, 0); console.log('kanca kuruldu');
node son-care.mjs; echo "cikis kodu: $?"
kanca kuruldu olumcul: gec gelen hata (koken: uncaughtException) cikis kodu: 1
Bu kancaların tek meşru kullanımı, sürecin ölmeden önce arızayı kaydetmesidir. Kancayı kurup çalışmaya devam etmek, çalışma zamanının varsayılan davranışını bozar ve bilinmeyen bir durumda ilerlemeye izin verir.
Kancanın içinde uzun iş yapılmaz. Süreç zaten şüpheli bir durumdadır; kancada eşzamansız bir kütük yazımı başlatmak, yazının tamamlanacağının güvencesi yoktur. Kısa ve eşzamanlı bir satır yazıp çıkmak en güvenlisidir.
Süreç öldükten sonra hizmetin ayakta kalması, sürecin kendi sorumluluğu değildir. Bu işi, süreci yeniden başlatan dış bir denetçi üstlenir; Süreç Yönetimi ve Sağlamlık dersinde ele alınacak.
Özet
- İşletimsel hata öngörülmüş bir çalışma koşuludur ve ele alınır; programcı hatası sözleşme ihlalidir ve ele alınmaz.
- İşletimsel hatalar kendi tipleriyle işaretlenir ve kod alanı taşır; çağıran karar verirken ileti metnini ayrıştırmaz.
- Hata çevrilirken
causeile asıl neden korunur; yalnızca tanınan durumlar çevrilir, geri kalanı olduğu gibi yukarı fırlatılır. - Geri çağrı içinde fırlatılan hata ve beklenmeyen sözün reddi
try/catchkapsamına girmez; ikincisinin varsayılan sonucu sürecin sonlanmasıdır. - Son çare kancaları yalnızca kaydetmek ve çıkmak için kullanılır; kancayı kurup devam etmek bilinmeyen bir durumda ilerlemektir.
Sonraki Adım
Hataların kaydedilmesinden birkaç kez söz edildi ama nereye ve hangi biçimde yazılacağı belirlenmedi. Serbest metin bir ileti, arızayı arayan kişinin işine yaramaz: arama, süzme ve sayma gerektiren bir kayıt biçimi gerekir. Sonraki ders yapılandırılmış günlüklemeyi kurar ve düzey yönetimini ekler.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.