Bir Åey geliÅtirirken, genelde kendi hata sınıflarımıza sahip olmak isteriz, böylece bize has yerlerde oluÅabilecek hataları idare edebiliriz. ÃrneÄin network hataları için HttpError, veri tabanı hataları için DbError, arama hataları için NotFoundError gibi.
Hatalarımız basit hata özelliklerini message, name ve stackâi desteklemelidir. Bunun ile birlikte kendine has özellikleri de olabilir. ÃrneÄin HttpError objesi statusCode özelliÄine sahip olabilir. Bu özelliÄin deÄeri de 404, 403, 500 gibi hata kodları olacaktır.
JavaScript throwâun argüman ile atılmasına izin verir. Teknik olarak hata sınıflarımızın hepsinin Errorâdan türemesine gerek yoktur. Fakat türetirsek obj instance of kullanmak mümkün olacaktır. Böylece hata objesi tanımlanabilir. Bundan dolayı türetirseniz daha iyidir.
Uygulamanızı geliÅtirirken oluÅturacaÄınız HttpTimeoutError gibi sınıflar HttpErrorâdan türetilebilir.
Hata sınıflarını geniÅletme
Ãrnek olarak, readUser(json) adında bir fonksiyon olsun ve bu fonksiyon JSON okusun.
Geçerli bir json Åu Åekildedir:
let json = `{ "name": "John", "age": 30 }`;
Dahili olarak gelen JSON.parse kullanılacaktır. EÄer bozuk json gelirse bu durumda SyntaxError atar.
Fakat json yazım olarak doÄru olsa bile geçerli sayılmayabilir, deÄil mi? Belki bizim ihtiyacımız veri bulumamaktadır. ÃrneÄin, name ve age özellikleri bulunmaz ise bu durumda bizim için geçerli bir veri sayılmaz.
readUser(json) fonksiyonu sadece JSON okumayacak, doÄruluk kontrolü de yapacaktır. EÄer gerekli alanlar yok ise, format yanlıÅsa bu durumda bu bizim için hatadır. Ayrıca bu bir SyntaxError yani yazım hatası deÄildir. Ãünkü yazım olarak doÄru olsa da baÅka türde bir hatadır. Bu hatalara ValidationError diyeceÄiz ve bunun için bir sınıf oluÅturacaÄız. Bu tür hatalar ayrıca hatanın nedeni hakkında da bilgi içermelidir.
Bizim yazacaÄımız ValidationError sınıfı dahili olarak bulunan Error sınıfından türemelidir.
Error sınıfı gömülü olarak gelmektedir. GeniÅletmeden önce neyi geniÅleteceÄimizi bilmek iyi olacaktır:
Åu Åekilde:
// Gömülü gelen error sınıfının basitleÅtirilmiÅ tanımı JavaScript kodu olarak gösterilmektedir.
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (farklı gömülü hata sınıfları için farklı isimler)
this.stack = <nested calls>; // standartlarda yok fakat çoÄu ortam desteklemekte
}
}
Åimdi ValidationError kalıtımını yapabiliriz:
class ValidationError extends Error {
constructor(message) {
super(message); // (1)
this.name = "ValidationError"; // (2)
}
}
function test() {
throw new ValidationError("Whoops!");
}
try {
test();
} catch(err) {
alert(err.message); // Whoops!
alert(err.name); // ValidationError
alert(err.stack); // İç içe çaÄrıların hangi satırlarda olduÄunun listesi.
}
Yapıcıya bakarsanız:
(1)satırda üst sınıfın yapıcısı çaÄırılmakta. Javascript bizim kalıtılan sınıftansuperile üst sınıfı çaÄırmamız koÅulunu koymaktadır. Böylece üst sınıftaki yapıcımessageâı doÄru bir Åekilde ayarlar.- Ãst sınıfın yapıcısı da
nameve"Error"özelliÄini ayarlar, bundan dolayı(2)satırda bunu doÄru deÄere ayarlamıŠoluruz.
readUser(json) kullanmayı deneyelim:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
// Usage
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("No field: age"); // age alanı bulunmamakta
}
if (!user.name) {
throw new ValidationError("No field: name");// name alanı bulunmamakta
}
return user;
}
// try..catch ile çalıÅan bir örnek.
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Invalid data: " + err.message); // YanlıŠveri: No field: name
} else if (err instanceof SyntaxError) { // (*)
alert("JSON Syntax Error: " + err.message);
} else {
throw err; // bilinmeyen bir hata, tekrar at(**)
}
}
Yukarıdaki try..catch bloÄu hem bizim yazdıÄımız ValidationError hem de gömülü olarak gelen SyntaxError hatalarını idare etmektedir.
instanceof ile hataların tiplerinin nasıl kontrol edildiÄine dikkat edin. (*)
Ayrıca err.nameâe Åu Åekilde bakabiliriz:
// ...
// (err instanceof SyntaxError) kullanmak yerine
} else if (err.name == "SyntaxError") { // (*)
// ...
instanceof kullanmak daha iyidir. İleride ValidationErrorâu geniÅletir ve PropertyRequiredError gibi alt tipler oluÅturursanız instanceof ile kalıtılan sınıfı da kontrol etmiÅ olursunuz. Bundan dolayı gelecekte olacak deÄiÅikliklere daha iyi tepki verir.
Ayrıca catch bilinmeyen bir hata olduÄunda tekrardan bu hatayı atması (**) oldukça önemlidir. catch sadece veri kontrolü ve yazım hatalarını kontrol etmektedir. DiÄer hatalar ise bilinmeyen hatalar bölümüne düÅmektedir.
İleri seviye kalıtım
ValidationError sınıfı çok genel bir sınıf. ÃoÄu Åey yanlıŠgidebilir. Ãzellik eksik olabilir veya farklı formatta olabilir( örneÄin age özelliÄinin karakter dizisi olması). Bundan dolayı daha özel PropertyRequiredError sınıfını yazmakta fayda var. Bu eklenmeyen özellikle ilgili bilgi verecektir.
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
// Usage
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
// Working example with try..catch
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Invalid data: " + err.message); // Invalid data: No property: name
alert(err.name); // PropertyRequiredError
alert(err.property); // name
} else if (err instanceof SyntaxError) {
alert("JSON Syntax Error: " + err.message);
} else {
throw err; // unknown error, rethrow it
}
}
Yeni yazdıÄımız PropertyRequiredError sınıfının kullanımı kolaydır: sadece new PropertyRequiredError(property) ismini göndermek yeterli. Okunabilir message yapıcı tarafından üretilir.
dikkat ederseniz PropertyRequiredError içerisindeki this.name yapıcısı elle yapılmıÅtır. Bu biraz gereksiz gelebilir ( this.name = <classname>'in her yeni üretilen sınıf için yazılması). Bunun bir çözümü var elbette. Kendimize ait bir âbasic errorâ ile bu zor olayı omzumuzdan atmak mümkündür. Bunun için yapıcıda this.name için this.constructor.name kullanılarak çözüm saÄlanabilir. Sonra bundan kalıtım yapılır.
ÃrneÄin buna MyError diyelim
AÅaÄıda MyError ve diÄer özel hata sınıflarının basitleÅtirilmiÅ hali görülmektedir:
class MyError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends MyError { }
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.property = property;
}
}
// name is correct
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError
Böylece hata sınıfları kısalmıŠoldu, özellikle "this.name=..."'i attıktan sonra ValidationError daha da kısalmıŠoldu.
İstisnaları Kapsama
Hatırlarsanız yukarıdaki readUser âkullanıcıların verilerini okumakâ amacıyla yazılmıÅtı, deÄil mi? Farklı hatalar olabileceÄinden dolayı Åimdilik SyntaxError, ValidationError gibi hata sınıflarına sahibiz. Fakat readUser ileride daha da büyüyebilir: yeni kod yeni hatalara neden olacaktır.
Bundan dolayı readUserâı çaÄıran fonksiyon hataları ile baÅa çıkmalıdır. Åu anda birçok if, catch ile kontrol edilip eÄer bunlar dahilinde deÄil ise tekrar hata atma iÅlemini yapmaktayız. Fakat readUser fonksiyonu daha fazla hataya neden olursa, kendimize: gerçekten de tüm hataları birer birer kontrol etmemiz gerekli mi sorusunu sormalıyız.
Tabiki cevap âHayırâ: DıÅtaki kod her zaman âdiÄerlerinden bir üst seviyedeâ olmak ister. âveri okuma hatasıâ gibi bir hata olmak ister. Neden olduÄu çok da önemli deÄildir. Tabi hataların detayları olsa iyi olur fakat sadece ihtiyaç olursa.
Bunlar ıÅıÄında ReadError sınıfını yeniden yazacak olursak. EÄer readUser içerisinde bir hata olursa bunu yakalayacak ve ReadError üreteceÄiz. Ayrıca orjinal hatanın cause (neden) özelliÄine referans vereceÄiz. Bundan dolayı dıÅtaki kod sadece ReadErrorâu kontrol etmeli.
AÅaÄıdaki kod ReadErrorâu tanımlamakta ve readUser ve try..catchâin nasıl kullanılacaÄını göstermektedir:
class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = 'ReadError';
}
}
class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }
function validateUser(user) {
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
}
function readUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError("Syntax Error", err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
if (err instanceof ValidationError) {
throw new ReadError("Validation Error", err);
} else {
throw err;
}
}
}
try {
readUser('{bad json}');
} catch (e) {
if (e instanceof ReadError) {
alert(e);
// Original error: SyntaxError: Unexpected token b in JSON at position 1
alert("Original error: " + e.cause);
} else {
throw e;
}
}
Yukarıdaki kodda readUser tam da tanımlandıÄı Åekliyle çalıÅmaktadır â yazım hatalarını yakalar, eÄer doÄrular ve bilinmeyen hatalar yerine ReadError hatası atar.
Bundan dolayı dıÅtaki kod instanceof ReadErrorâu kontrol eder, hepsi bu! DiÄer tüm muhtemel hataları listelemeye gerek yok.
Bu yaklaÅıma âİstisnaları kapsamaâ yaklaÅımı denilir, âdüÅük seviye istisnalarâ'ı alıp bunları âkapsayarakâ ReadError haline getirdik. Böylece daha soyut, ve çaÄırması kolay bir kod yazmıŠolduk. Bu kullanım nesne tabanlı dillerde oldukça yaygındır.
Ãzet
- Genelde
Errorâdan veya diÄer gömülü hata sınıflarından yeni hata türetilir, yapmanız gerekennameözelliÄini ayarlamak vesuperâi çaÄırmaktır. - ÃoÄu zaman
instanceofile belirli hataları kontrol edebilirsiniz. Bu kalıtım ile de çalıÅmaktadır. Fakat bazen 3. parti kütüphanelerden gelen hatalar olabilir, bunların sınıflarını almak çok da kolay olmaz. Bu durumdanameözelliÄi ile konrol saÄlanabilir. - Alt-seviye istisnaların idaresi ve üst seviye hataların raporlanması oldukça genel bir tekniktir. Alt seviye istisnalar bazen o objenin özelliÄi olur. ÃrneÄin yukarıdaki
err.causebuna iyi bir örnektir, fakat katı bir biçimde gereklidir diyemeyiz.
Yorumlar
<code>kullanınız, birkaç satır eklemek için ise<pre>kullanın. EÄer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)