_Onk@_, mesajından alıntı:
Hata: Uygulamanın çalışmasına engel olacak herhangi bir olgudur. İkiye ayrılırlar:
1. Ölümcül olan hatalar: Uygulamanın çökmesine ve sistem kaynak yönetiminin bozulmasına neden olan hatalardır. Erişilmemesi gereken bir bellek alanına erişim, platform için tanımlanmamış bir işlem (örneğin 0'a bölme) vs.
Onları programcı hatası olarak değerlendirmeliyiz. Saydıklarının hepsi de yapılmaması gereken işlemler sonucunda oluşur. Yani programda bir hata var...
2. Ölümcül olmayan hatalar: Bir mantık ya da algoritma hatası oluşmasına rağmen uygulamanın çökmesine neden olmayan hatalardır. Büyük oranda "verinin" bozulması ve/veya yanlış çıktı verilmesi gibi.
Örneğin bir nükleer santralın kontrol programının ürettiği bir ara değer...
Tam tersine, programın çalışmaya devam etmesi tam anlamıyla bir ölümcül hatadır.
Programın yanlış sonuçlarla devam etmemesi gerekir.
Potansiyel hata kaynakları:
1. Dinamik bellek yerleşimi: Uygulama işletim sisteminden boş bellek alanı ister, eğer boş alan tahsis edilemezse malloc işlevi NULL değer döndürür. Mantık olarak uygulamanın istediği bellek alanı daha sonra kullanılacağı için dönen NULL değerinin yakalanması gereklidir.
Her işlev, kendisini çağıranla bir sözleşme imzalamıştır: bana geçerli parametreler verirsen, ben de sana geçerli bir sonuç vereceğim.
Örneğin karekök alan bir işlev, "bana sıfır veya daha büyük bir değer verirsen ben de sana sıfır veya daha büyük bir sonuç vereceğim" der. Bu, sözleşmedir.
Bu sözleşme bozuk olduğunda hata atılmasından başka bir çare yoktur. Çünkü işlev, ya işini yapamaz; ya da sözleşmenin sonucunu garanti edememiştir.
malloc gibi C işlevlerinde sözleşmenin tanımı bile doğru dürüst yapılamaz: "benden bir miktar bellek iste, ben de sana o kadar belleği gösteren bir gösterge vereceğim... ama... bazen de vermeyeceğim." Bana pek bir sözleşme gibi gelmiyor... 
C++'da ise hata atma düzeneği olduğu için, new hata atar ve sözleşmenin tanımı kolaydır.
Yani malloc'un başka işlevlerden özel bir farkını göremiyorum. NULL döndürdüğü zaman aslında "benden isteneni yapamadım" diyor.
C++'ta ise, "benden dönüldüğü zaman, benden isteneni yapmışımdır" denir. "Dönüldüğü zaman" çok önemli bir ifade... Ya dönülür ve sözleşme tamamlanmıştır, ya da sözleşme başarılamamıştır ve hata atılmıştır.
O yüzden C++'da C'deki gibi dönüş değeri denetimleri olmaz. Kod tertemizdir.
Daha önce bahsettiğim sunudan bir C, bir de eşdeğeri C++ işlevi:
Kod:
// C islevi
int bar(Kaynak ** in_out)
{
int hata = 0;
Kaynak * r0 = NULL;
Kaynak * r1 = NULL;
hata = allocate_kaynak(&r0);
if (hata) goto cikis;
hata = allocate_kaynak(&r1);
if (hata) goto cikis;
/* ... r0 ve r1 burada kullanilsin ... */
if (hata) goto cikis;
/* sahipligi devret */
*in_out = r0;
r0 = NULL;
cikis:
deallocate_kaynak(&r1);
deallocate_kaynak(&r0);
return hata;
}
Kod:
// C++ esdegeri
Kaynak bar()
{
Kaynak r0(/* ... */);
Kaynak r1(/* ... */);
/* ... r0 ve r1 burada kullanilsin ... */
/* sahipligi devret */
return r0;
}
Bu farkı, C++'nın üstünlüğünü için göstermiyorum. Amacım, işlevin nasıl hiçbir hataya dikkat etmeden "kendi işini" yapıyor olması. Eğer işinin bir adımını becerememişse bundan kendi haberi bile olmuyor; hata atılıyor ve işlevden kısa yoldan çıkılıyor.
Yani, malloc'un NULL döndürmesi gibi davranışların yerini C++'da hata düzeneği alıyor.
4. Bir işleve ya da sınıfa "üzerinde işlem yapamayacağı" veriler göndermek.
Evet, sözleşmenin giriş tarafının bozulması o...
5. Bu madde ve sonrası hakkında bir fikrim ne yazık ki yok
Bu madde, yukarıda da değindiğim gibi, işlevin kendi işini yapamamasıdır.
zarf_hazirla() diye bir işlevdeyiz. Bu işlevin veritabanından adres satırını okuması gerekiyor. Adres satırının boş olması olanaksız; zarfı hazırlayamayız... Diyelim adres satırı boş geldi... Ne yapabiliriz? Nasıl zarf hazırlayabiliriz?
Olanaksız olduğu için tek yapabileceğimiz şey, hata atmaktır.
O zaman:
1. Aldığımız belleği "alabilmiş" olup olmadığımızı test etmeliyiz.
Eğer C gibi bir kütüphane kullanıyorsak, evet... Ama new kullanıyorsak denetleyemeyiz bile. 
Kod:
int * p = new int(42);
if (!p) {
cerr << "hata"; // bu satir hicbir zaman isletilmez
}
Çünkü new ya işini yapar, ya da hata atmıştır. (Not: new'ün hata atmaması da istenebilir; bkz. std::nothrow)
2. Aldığımız bellekle işimiz bitince geri vermeliyiz.
Bütün kaynaklar için geçerli.
3. Erişeceğimiz dosyanın gerçekte orda olup olmadığına emin olmalıyız.
Bir çok başka şey için geçerli. Örneğin dosyanin_icerigi() diye bir işlev var diyelim:
Kod:
string dosyanin_icerigi(const string & dosya_ismi)
{
ifstream dosya(dosya_ismi.c_str());
if (!dosya) {
throw DosyaHatasi;
}
// ... asil islemler ...
}
Çünkü, dosyanın içeriğini üreten bir işlevin işi, dosyanın içeriğini üretmektir. Açılamazsa hata atmaktan başka bir şey yapamaz.
4. Verinin değişmesine gerek olmayacak alanlarda veriyi sabit (const) olarak nitelendirmeliyiz.
Olabilen her yerde const...
5. İşleve ve sınıfa değer ya da referans geçirirken verinin "işlenebilecek olduğundan" emin olmalıyız.
Tabii ki. Eiffel dilinin icat etmiş olduğu "design by contract" dil olanağı, D'de "contract programming" ismiyle geçer. Bu dillerde işlevlerin giriş ve çıkış koşulları dil tarafından denetlenebilir.
Bu ayrıca işlevin kullanıcıları ile işlev arasındaki sözleşmenin dil düzeyinde ifade edilmesidir.
Bu forumla ilgisi yok ama D'nin bu olanağını şu sayfada anlatmıştım:
http://ddili.org/ders/d/sozlesmeli.html
C++'da ve C'de bu işi elle yapmak zorundayız. Şu anda çalıştığım firmada oldukça çok C kodu var. Her bir işlev, her birisi, ilk iş olarak giriş parametrelerinin denetler.
Yukarıdaki C kodunda eksik bırakmışım... Örneğin şöyle olması gerekir:
Kod:
int bar(Kaynak ** in_out)
{
int hata = 0;
if (!in_out) goto cikis;
// ...
Yani evet, her işlevin giriş koşulları denetlenmelidir.
6. Bu madde ve sonrası hakkında bir fikrim yine yok
C++'da bir sürü öğüt (guideline) var. Bunları içeren çok sayıda kitap da var. Bir yerden sonra düşünmeden yapılıyor...
*Yaklaşık bir yıldır ceviz forumdaki tüm C/C++ konularını okumama rağmen istisna işlenmesi konusunda bu kadar az soru olmasına şaşırdığımı belirtmeliyim.
Benim bir taktiğim, soruyla ne kadar ilgisiz olsa da gördüğüm eksikleri düzeltmek. Örneğin daha yeni "hiçbir değişkeni ilklemeden kullanmayın"ı ve "evrensel değişken kullanmayın"ı hatırlatan bir şeyler yazdım. Ukalalık riski ile... :/
Ama şöyle bir işlev gördüğümde onun yanlış olduğunu söylemiyorum, çünkü bu çok derin bir konu ve öğrenen arkadaş daha çok başlarda:
Kod:
void foo()
{
BirTur * p = new BirTur;
// ...
delete p;
}
"Hiçbir kaynağı kod içinde açıkça geri vermeyin"... (Çünkü arada hata atılırsa delete satırı işletilmez.)
Bu arada, ben aynı kodu geçtiğimiz sene içinde mülakata çağırdığımız en az 30 kişiye sormuşumdur. Ancak bir iki tanesi öyle kod yazılmaması gerektiğini söylemiştir. Üstelik bu kişiler ya bu işten para kazanan, ya da Amerika'nın en iyi üniversitelerinden yeni çıkmış insanlar... :/
Ama kimseyi suçlamaya gerek yok. Bu konular yaygın olarak bilinmiyor. Bilgisayar bilimlerine genel olarak bakan bir üniversitenin de dört sene içinde uzman C++'cı yetiştirmesi zaten beklenmemeli.
Ali
Bookmarks