Ders 17 / 20
Hata Ayıklama Araçları
Bir hata sınırı bozuk alt ağacı söküp yedek görünümü kurar. Ders aynı panelin özellik değişince güncellendiğini, hata verince söküldüğünü ve sökülenin kimliğini yitirdiğini ölçer.
Yayın Güncelleme
Bu derste
- Hata sınırının bozuk alt ağaca ve yedek görünüme verdiği akıbeti ölçer
- Güncellenen düğümün kimliğini korurken sökülenin yitirdiğini gösterir
- Sağlam güncelleme ile hatalı güncellemeyi aynı düğüm üzerinde karşılaştırır
- Hata ayıklama araçlarının ağacı gösterdiğini, ağacı değiştirmediğini açıklar
İçindekiler
Önceki ders ekranın kodunu ertelemenin düğümü ne zaman kurduğunu ölçtü. Kod yüklendi ve ağaç sorunsuz kuruldu. Gerçek uygulamada her yükleme ve her çizim sorunsuz bitmez: bir veri beklenen biçimde gelmez, bir işlev beklenmedik bir değer alır ve ekranın bir parçası çizilirken hata verir. Bu ders o hatanın görünüm ağacında neyi söktüğünü ve neyi kurduğunu ölçer.
Kursun sorusu: bu güncelleme görünüm ağacındaki düğüme ne yapıyor? Yanıtlar korunan, güncellenen, kurulan ve sökülen; korunan dışındakilerin toplamı komuttur. Kural çekirdek dosyadadır.
// fate.mjs — bir guncellemenin gorunum agacindaki her dugume ne yaptigini siniflandirir export const FATES = ["korunan", "guncellenen", "kurulan", "sokulen"]; export function el(type, props = {}, children = [], key = null) { return { type, props, children, key }; } function segment(node) { return node.key === null ? node.type : `${node.type}[${node.key}]`; } function identity(node, index) { return node.key === null ? `#${index}` : `key:${node.key}`; } function sameProps(a, b) { const names = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort(); return names.every((n) => JSON.stringify(a[n]) === JSON.stringify(b[n])); } function whole(node, path, fate, ops) { const here = `${path}/${segment(node)}`; ops.push({ fate, path: here }); node.children.forEach((child) => whole(child, here, fate, ops)); } export function reconcile(prev, next, path = "", ops = []) { if (prev === null) { whole(next, path, "kurulan", ops); return ops; } if (next === null) { whole(prev, path, "sokulen", ops); return ops; } if (prev.type !== next.type || prev.key !== next.key) { whole(prev, path, "sokulen", ops); whole(next, path, "kurulan", ops); return ops; } const here = `${path}/${segment(next)}`; ops.push({ fate: sameProps(prev.props, next.props) ? "korunan" : "guncellenen", path: here }); const before = new Map(prev.children.map((child, i) => [identity(child, i), child])); const matched = new Set(); next.children.forEach((child, i) => { const id = identity(child, i); matched.add(id); reconcile(before.has(id) ? before.get(id) : null, child, here, ops); }); prev.children.forEach((child, i) => { if (!matched.has(identity(child, i))) reconcile(child, null, here, ops); }); return ops; } export function printOps(ops) { ops.forEach((op) => console.log(`${op.fate}: ${op.path}`)); } export function countFates(ops) { for (const name of FATES) { console.log(`${name}: ${ops.filter((op) => op.fate === name).length}`); } console.log(`komut: ${ops.filter((op) => op.fate !== "korunan").length}`); }
Hatayı yakalayan düzenek bir hata sınırıdır. Hata sınırı, bir alt ağacı çizen işlevi sarar: işlev hata verirse sınır hatayı yakalar, alt ağacın yerine bir yedek görünüm üretir ve ekranın geri kalanı çizilmeye devam eder. Sınır olmasaydı hata, ekranın hepsini düşürürdü. Aşağıdaki dosya modeldeki bir paneli, onu saran sınırı ve ekranı kurar. Panel state.broken doğruysa hata verir. Sınır hatayı iş diliyle tek satırda yazar ve yedek görünümü döndürür.
// screen.mjs — bir ekrani gorunum agacina cevirir ve bozuk paneli hata sinirina sarar import { el } from "./fate.mjs"; export function renderPanel(state) { if (state.broken) throw new Error("veri okunamadi"); return el("Panel", { total: state.total }, [el("Value", { total: state.total })]); } export function withBoundary(render) { return (state) => { try { return render(state); } catch (err) { console.log(`hata sinirinda yakalandi: ${err.message}`); return el("Fallback", { text: "Bir sorun olustu" }); } }; } export function renderScreen(state) { const safe = withBoundary(renderPanel); return el("Screen", {}, [el("Header", { title: "Ozet" }), safe(state)]); }
renderPanel durumdan bir panel ağacı üretir; panelin bir de değer düğümü vardır. withBoundary bir çizim işlevini sarar: işlev hata verirse hatayı yakalar, mesajı yazar ve Fallback düğümünü döndürür. renderScreen paneli sınırla sarıp başlığın yanına koyar.
Sağlam Güncelleme
İlk ölçümde panel hata vermez; yalnız toplam değişir. Başlık aynı kalır, panel ve değer düğümü yeni toplamı taşır.
// main.mjs import { reconcile, printOps } from "./fate.mjs"; import { renderScreen } from "./screen.mjs"; const before = renderScreen({ broken: false, total: 10 }); const after = renderScreen({ broken: false, total: 12 }); printOps(reconcile(before, after));
korunan: /Screen korunan: /Screen/Header guncellenen: /Screen/Panel guncellenen: /Screen/Panel/Value
Panel ve değer güncellenen çıktı, iki komut gitti. Başlık ve kök korunan kaldı. Güncellenen bir düğüm aynı düğümdür: türü ve anahtarı değişmemiştir, yalnız özelliği değişmiştir. Yerel tarafta varsa durumu, odağı ve kaydırma konumu sürer.
Hatalı Güncelleme
İkinci ölçümde aynı güncelleme panelin hata vermesiyle yapılır. Panel hata verir, sınır hatayı yakalar ve Fallback döner. Yeni ağaçta panelin yerinde yedek görünüm vardır.
// main.mjs import { reconcile, printOps } from "./fate.mjs"; import { renderScreen } from "./screen.mjs"; const before = renderScreen({ broken: false, total: 10 }); const after = renderScreen({ broken: true, total: 12 }); printOps(reconcile(before, after));
hata sinirinda yakalandi: veri okunamadi korunan: /Screen korunan: /Screen/Header sokulen: /Screen/Panel sokulen: /Screen/Panel/Value kurulan: /Screen/Fallback
İlk satır hata sınırının yazdığı tek satırdır. Başlık ve kök korunan çıktı: hata ekranın geri kalanına geçmedi. Panel ve değeri sökülen oldu, Fallback kurulan oldu. Üç komut gitti: iki söküm ve bir kurulum. Panelin özelliği değişmediği için bu söküm özellik karşılaştırmasından gelmez; içerik alanındaki tür değişti, Panel yerine Fallback geldi ve kural eski alt ağacın hepsini söküp yeninin hepsini kurdu.
M11/K05’te “bu işlem kaynağa ne bırakıyor” sorusu bir işlemden sonra kaynak nesnenin hâlini sorar: aynen, değişti, boş ya da belirsiz. Buradaki akıbet bir nesnenin hâlini değil, bir düğümün kimliğinin güncellemeyi aşıp aşmadığını sınıflar. Güncellenen kimliğini korur, sökülen yitirir. Sağlam güncellemede panel yerinde kaldı ve içindeki durum sürdü. Hatalı güncellemede panel yok oldu; panelin yerel durumu, odağı ve kaydırma konumu onunla birlikte gitti.
Sağlığa Dönüş
Veri düzeldiğinde ağaç yeniden panele döner. Dönüş, sökülen düğümlerin geri gelmesi değildir; kurulan düğümlerin yeniden doğmasıdır.
// main.mjs import { reconcile, countFates } from "./fate.mjs"; import { renderScreen } from "./screen.mjs"; const broken = renderScreen({ broken: true, total: 12 }); const healed = renderScreen({ broken: false, total: 12 }); countFates(reconcile(broken, healed));
hata sinirinda yakalandi: veri okunamadi korunan: 2 guncellenen: 0 kurulan: 2 sokulen: 1 komut: 3
Başlık ve kök korundu. Fallback söküldü ve panel ile değeri kuruldu. Üç komut gitti; yeni panel eskinin aynısı değil, yeni bir düğümdür. Panelin hatadan önceki durumu geri gelmez: sökülünce yok olmuştu. Hata sınırı ekranı ayakta tutar ama bozuk alt ağacın yaşamını sürdürmez.
Hata Sınırı Kaynakta
Gerçek bir hata sınırı, hata durumunu tutan bir bileşen sınıfıdır. Çocuk çizilirken hata çıkarsa sınıf hata durumuna geçer ve çocuk yerine yedek görünümü çizer. Model bunu bir try ve catch ile temsil eder. Aşağıdaki parça sınıfın kaynak biçimini gösterir; model onu çalıştırmaz.
// Platform kodu: Boundary
class Boundary extends React.Component {
state = { failed: false };
static getDerivedStateFromError() {
return { failed: true };
}
render() {
return this.state.failed ? <Fallback /> : this.props.children;
}
}
Sınıf, çocuklarını sarar. getDerivedStateFromError hata gelince durumu değiştirir ve sınıfın çizim işlevi yedek görünümü döndürür. Ekranda kullanımı bir sarmalamadır; yalnız sarılan alt ağaç bu sınırın korumasındadır.
// Platform kodu: Summary
function Summary({ total }) {
return (
<View>
<Text>Ozet</Text>
<Boundary>
<Panel total={total} />
</Boundary>
</View>
);
}
Sınırın yeri, hatanın ne kadarını yutacağını belirler. Sınır yalnız paneli sararsa hata yalnız panelle sınırlı kalır; ekranın hepsini sararsa bir paneldeki hata bütün ekranı yedek görünümle değiştirir. Model bunu renderScreen içinde withBoundary çağrısının yeriyle gösterir: başlık sınırın dışındadır ve bu yüzden korunan kaldı.
Hata Sınırı Parçasını Satır Satır Okumak
Birinci parçada dört satır iş görür. state = { failed: false } sınırın başlangıçta hatasız olduğunu söyler. getDerivedStateFromError() alt ağaçta bir hata çıktığında çağrılır ve yeni durumu döndürür; burada durumu failed: true yapar. this.state.failed ? <Fallback /> : this.props.children ise iki yoldan birini seçer: hata varsa yedek görünüm, yoksa sarılan çocuklar.
Modeldeki withBoundary aynı kararı bir try ve catch ile verir. Fark şudur: modelde karar her çağrıda yeniden alınır, gerçek sınırda ise durum saklanır. Hata bir kez çıktıktan sonra gerçek sınır, sıfırlanana kadar yedek görünümü göstermeye devam eder. Bu yüzden bir yeniden dene düğmesi sınırın durumunu sıfırlayacak bir yol taşır.
İkinci parçada <Boundary> yalnız <Panel /> ögesini sarar. <Text>Ozet</Text> sınırın dışındadır. Modeldeki başlığın korunan kalmasının nedeni budur: hata sınırın içinde çıktı ve sınırın dışına geçmedi.
Ayrım Cümlesini Bir Örnekle Okumak
M11/K05’in sorusu ile bu dersin sorusu benzer görünür ve yanlış okunabilir, bu yüzden ayrımı bir örnekle yeniden kurmak yararlıdır. Panelin iki ayrı sonu olabilir.
Birinci sonda panelin yalnız toplamı değişir. Panel aynı düğümdür, özelliği yeni değer alır ve akıbet güncellenendir. Panelin içindeki durum, örneğin açık bir ayrıntı satırı, yerinde kalır. Kimlik güncellemeyi aşmıştır.
İkinci sonda panelin çizimi hata verir. Panel artık ağaçta yoktur; yerine başka türden bir düğüm gelmiştir. Akıbet sökülendir ve açık ayrıntı satırı gibi içerideki her durum onunla birlikte gider. Aynı kullanıcı ekrana yeniden bakınca panelin eski hâlini bulamaz; sağlığa dönüşte yeni bir panel görür.
Her iki sonda da “panele bir şey oldu” denir, ama biri kimliği korur, öteki yitirir. Güncellenen kimliğini korur, sökülen yitirir; akıbet, bir nesnenin hâlini değil bu kimliğin akıbetini sınıflar.
Yaygın Yanılgılar
İlk yanılgı, hata sınırının hatayı çözdüğüdür. Sınır hatayı yakalar ve ekranı ayakta tutar, ama hatanın nedeni yerinde durur. Veri bozuksa bir sonraki güncellemede panel yine hata verir; sınır bunu yine yakalar. Sınır bir sigortadır, onarım değildir.
İkinci yanılgı, yedek görünümün sökülen düğümün yedeği olduğudur. Yedek görünüm yeni bir düğümdür; panelin durumunu taşımaz. Panelin neleri tuttuğu hatayla birlikte kaybolur. Durumu korumak isteyen bir uygulama onu sınırın dışında, daha üstte tutmalıdır.
Üçüncü yanılgı, sınırın her hatayı yakaladığıdır. Çizim sırasındaki hatalar yakalanır; dokunma işleyicisindeki ya da bir zamanlayıcıdaki hata yakalanmaz. Bu yüzden sınırın yokluğuna bakmak yerine hatanın nerede çıktığına bakılır.
Araçlar Ağacı Gösterir
Hata ayıklamanın günlük araçları üç gruba ayrılır. Geliştirici menüsü uygulamayı çalışırken yeniden yüklemek, günlükleri görmek ve inceleyiciyi açmak için bir kapıdır. Hata ayıklama inceleyicisi çalışan uygulamanın görünüm ağacını gösterir: her düğümün türünü, özelliklerini ve çocuklarını. Hata ayıklayıcı ise kodu satır satır durdurur ve değişkenleri gösterir.
Üçü de ağacı gösterir, ağacı değiştirmez. İnceleyici, bu derste printOps’un yaptığını yapar, ama gerçek ağaç üzerinde: hata verdiğinde panelin yerinde Fallback olduğunu gösterir, hata mesajı ise günlükte durur. Hatayı bulmak için ikisine birlikte bakılır. Ağaçta Fallback görülürse bir hata sınırı devreye girmiştir ve günlükte hatanın kaynağı yazılıdır. Ağaçta Fallback yoksa ve ekran düşmüşse sınır yoktur ya da hata sınırın yakalayamadığı bir yerde çıkmıştır.
Sınırın yakalamadığı yerler de vardır. Çizim sırasındaki hataları sınır yakalar. Bir dokunma işleyicisinde ya da bir zamanlayıcı geri çağrısında çıkan hata, çizim sırasında çıkmadığı için sınıra ulaşmaz; onu ayrıca yakalamak gerekir. Model yalnız çizim sırasındaki hatayı kurar, öteki durumlar düzyazıda kalır.
Sınırlayıcı Ölçüm
Son ölçüm iki güncellemeyi aynı panel üzerinde karşılaştırır. Birinde panelin özelliği değişir, ötekinde panel hata verir. Kural panelin yoluna süzülür. Yedek görünümün ve değer düğümünün akıbeti bu sayfada gösterilmez, çünkü soru panelin ne olduğudur.
// main.mjs import { reconcile, printOps } from "./fate.mjs"; import { renderScreen } from "./screen.mjs"; const before = renderScreen({ broken: false, total: 10 }); for (const broken of [false, true]) { console.log(broken ? "hatali guncelleme" : "saglam guncelleme"); const after = renderScreen({ broken, total: 12 }); const ops = reconcile(before, after); printOps(ops.filter((op) => op.path === "/Screen/Panel")); }
saglam guncelleme guncellenen: /Screen/Panel hatali guncelleme hata sinirinda yakalandi: veri okunamadi sokulen: /Screen/Panel
Odak düğüm /Screen/Panel. Sağlam güncellemede güncellenen çıktı: panel kimliğini korudu. Hatalı güncellemede sökülen çıktı: panel kimliğini yitirdi. Dersin akıbeti ikincisidir.
Bu ölçümün sınırı, sökülenin yerine gelenin yedek görünüm olmasıdır. Kural Fallback düğümünü kurulan sayar, ama o düğümün kullanıcıya ne gösterdiğini bilmez. Yedek görünüm “bir sorun oluştu” yazabilir, bir yeniden dene düğmesi taşıyabilir ya da boş kalabilir; üçü de kural için aynı kayıttır. Hata ayıklamada işe yarayan şey de budur: ağaç, hatanın nerede durduğunu söyler; hatanın ne olduğunu günlük ve hata ayıklayıcı söyler.
BU4. Model, bu akıbetin kuralını kurar; model çerçevenin kendisi değildir. Modelde hata sınırı bir try ve catch ile sarılmış çizim işlevidir; hata, işlevin içinden atılır ve sınır onu iş diliyle tek satır yazar. Gerçek bir hata sınırı, hata durumunu tutan ve çocuklarını çizen bir sınıftır; hatanın yakalandığı yer, hata durumunun sıfırlanma biçimi ve hatanın günlüğe nasıl gittiği model dışındadır. Modelde hata her güncellemede yeniden kurulur; gerçek sınır hata durumunu bir sonraki güncellemeye taşıyabilir ve yeniden dene düğmesiyle sıfırlanabilir.
Özet
- Bu dersin akıbeti: sökülen.
/Screen/Paneldüğümü, alt ağacı hata verince söküldü; yerine/Screen/Fallbackkuruldu. - Aynı panel özellik değişince güncellenen çıktı ve kimliğini korudu; hata verince sökülen çıktı ve kimliğini yitirdi.
- Hata ekranın geri kalanına geçmedi: başlık ve kök korunan kaldı.
- Sağlığa dönüşte panel yeni bir düğüm olarak kuruldu; sökülenin durumu geri gelmedi.
- Hata ayıklama araçları ağacı ve hatayı gösterir, ağacı değiştirmez; kural yedek görünümün ne gösterdiğini görmez.
Sonraki Adım
Bu derste hata bir sınırla yakalandı ve ağaçtaki etkisi ölçüldü. Bir hatayı yakalamak da bir işi doğrulamak da aynı gereksinimden gelir: ağacın beklenen biçimi bilinmelidir. Sıradaki ders, Test, beklenen kayıt listesini reconcile çıktısıyla karşılaştırır ve bir güncellemenin doğrulandığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.