assert İfadesi
Programları yazarken çok sayıda varsayımda bulunuruz ve bazı beklentilerin doğru çıkmalarını umarız. Programlar ancak bu varsayımlar ve beklentiler doğru çıktıklarında doğru çalışırlar. assert, programın dayandığı bu varsayımları ve beklentileri denetlemek için kullanılır. Programcının en etkili yardımcılarındandır.
Çoğu zaman bu varsayımların farkına bile varmayız. Örneğin iki kişinin yaşlarının ortalamasını alan aşağıdaki işlevde kullanılan hesap, yaş parametrelerinin ikisinin de sıfır veya daha büyük olacakları varsayılarak yazılmıştır:
double ortalamaYaş(double birinciYaş, double ikinciYaş) { return (birinciYaş + ikinciYaş) / 2; }
Yaşlardan en az birisinin eksi bir değer olarak gelmesi hatalı bir durumdur. Buna rağmen, işlev mantıklı bir ortalama üretebilir ve program bu hata hiç farkedilmeden işine yanlış da olsa devam edebilir.
Başka bir örnek olarak, aşağıdaki işlev yalnızca iki komuttan birisi ile çağrılacağını varsaymaktadır: "şarkı söyle" ve "dans et":
void komutİşlet(in char[] komut) { if (komut == "şarkı söyle") { robotaŞarkıSöylet(); } else { robotuDansEttir(); } }
Böyle bir varsayımda bulunduğu için; geçerli olsun olmasın, "şarkı söyle" dışındaki her komuta karşılık robotuDansEttir işlevini çağıracaktır.
Eğer bu varsayımları kendimize saklarsak, sonuçta ortaya çıkan program hatalı davranabilir. assert, bu varsayımlarımızı dile getirmemizi sağlayan ve varsayımlar hatalı çıktığında işlemlerin durdurulmalarına neden olan bir olanaktır.
assert, bir anlamda programa "böyle olduğunu varsayıyorum, eğer yanlışsa işlemi durdur" dememizi sağlar.
Söz dizimi
assert iki şekilde yazılabilir:
assert(mantıksal_ifade); assert(mantıksal_ifade, hata_mesajı);
assert, kendisine verilen mantıksal ifadeyi işletir. Eğer ifadenin değeri true ise varsayım doğru çıkmış kabul edilir; bu durumda assert ifadesinin hiçbir etkisi yoktur. Eğer ifadenin değeri false ise varsayım yanlış çıkmış kabul edilir ve AssertError hatası atılır. İsminden de anlaşılabileceği gibi, bu hata Error'dan türemiştir ve Hatalar bölümünde gördüğümüz gibi, yakalanmaması gerekir. Böyle bir hata atıldığında programın hemen sonlanması önemlidir; böylece programın yanlış varsayımlara dayanarak yanlış olabilecek sonuçlar üretmesi önlenmiş olur.
Yukarıdaki ortalamaYaş işlevindeki varsayımlarımızı iki assert ile şöyle ifade edebiliriz:
import std.cstream; double ortalamaYaş(double birinciYaş, double ikinciYaş) { assert(birinciYaş >= 0); assert(ikinciYaş >= 0); return (birinciYaş + ikinciYaş) / 2; } void main() { auto sonuç = ortalamaYaş(-1, 10); }
O assert'ler Türkçe olarak "birinciYaş'ın 0 veya daha büyük olduğunu varsayıyorum" ve "ikinciYaş'ın 0 veya daha büyük olduğunu varsayıyorum" anlamına gelir. Başka bir bakış açısıyla, "assert" sözcüğünün "emin olarak öne sürmek" karşılığını kullanarak, "birinciYaş'ın 0 veya daha büyük olduğundan eminim" gibi de düşünülebilir.
assert bu varsayımları denetler ve yukarıdaki programda olduğu gibi, varsayımın yanlış çıktığı durumda bir hata atar:
core.exception.AssertError@deneme(5): Assertion failure
Hatanın @ karakterinden sonra gelen bölümü hangi dosyanın hangi satırındaki varsayımın doğru çıkmadığını gösterir. Bu örnekte, deneme(5)'e bakarak hatanın deneme.d dosyasının beşinci satırında olduğunu anlarız.
assert ifadesinin yanlış çıktığı durumda kendi yazdığımız özel bir mesajı görmek istediğimizde assert ifadesinin ikinci şeklini kullanırız:
assert(birinciYaş >= 0, "Yaş sıfırdan küçük olamaz");
core.exception.AssertError@deneme.d(5): Yaş sıfırdan küçük olamaz
Programda kesinlikle gelinmeyeceği düşünülen veya gelinmemesi gereken noktalarda, özellikle başarısız olsun diye mantıksal ifade olarak bilerek false sabit değeri kullanılır. Örneğin yukarıdaki "şarkı söyle" ve "dans et" örneğinde başka komutların geçersiz olduklarını belirtmek ve bu durumlarda hata atılmasını sağlamak için:
void komutİşlet(in char[] komut) { if (komut == "şarkı söyle") { robotaŞarkıSöylet(); } else if (komut == "dans et") { robotuDansEttir(); } else { assert(false); } }
Artık işlev yalnızca o iki komutu kabul eder ve başka komut geldiğinde assert(false) nedeniyle işlem durdurulur.
Kesinlikle doğru olan (!) varsayımlar için bile assert
"Kesinlikle doğru olan"ın özellikle üzerine basıyorum. Hiçbir varsayım bilerek yanlış olmayacağı için, zaten çoğu hata kesinlikle doğru olan varsayımlara dayanır.
Bu yüzden bazen kesinlikle gereksizmiş gibi duran assert ifadeleri de kullanılır. Örneğin belirli bir senenin aylarının kaç gün çektikleri bilgisini bir dizi olarak döndüren bir işlev ele alalim:
int[] ayGünleri(in int yıl) { int[] günler = [ 31, şubatGünleri(yıl), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; assert((diziToplamı(günler) == 365) || (diziToplamı(günler) == 366)); return günler; }
Doğal olarak bu işlevin döndürdüğü dizideki gün toplamları ya 365 olacaktır, ya da 366. Bu yüzden yukarıdaki assert ifadesinin gereksiz oduğunu düşünebilirsiniz. Oysa, her ne kadar gereksiz gibi görünse de, o ifade şubatGünleri işlevinde ilerideki bir zamanda yapılabilecek bir hataya karşı bir güvence sağlar. şubatGünleri işlevinde ileride yapılabilecek bir hata, dizinin Şubat elemanının örneğin 30 olmasına neden olsa, o assert sayesinde bu hata hemen farkedilecektir.
Hatta biraz daha ileri giderek dizinin uzunluğunun her zaman için 12 olacağını da denetleyebiliriz:
assert(günler.length == 12);
Böylece kendimizi diziden yanlışlıkla silinebilecek veya diziye yanlışlıkla eklenebilecek bir elemana karşı da güven altına almış oluruz.
Böyle denetimler her ne kadar gereksizmiş gibi görünseler de son derece yararlıdırlar. Kodun sağlamlığını arttıran ve kodu ilerideki değişiklikler karşısında güvencede tutan çok etkili yapılardır.
Kodun sağlamlığını arttıran ve programın yanlış sonuçlar doğuracak işlemlerle devam etmesini önleyen bir olanak olduğu için, assert bundan sonraki derslerde göreceğimiz birim testleri ve sözleşmeli programlama olanaklarının da temelini oluşturur.
Değer üretmez ve yan etkisi yoktur
İfadelerin değer üretebildiklerini ve yan etkilerinin olabildiğini söylemiştim. assert değer üretmeyen bir ifadedir; yani dönüş türü void'dir.
Ek olarak, assert ifadesinin yan etkisi de yoktur. Üstelik, ona verilen mantıksal ifadenin de yan etkisinin olmaması, D standardı tarafından şart koşulmuştur. assert, programın durumunu değiştirmeyen ve yalnızca varsayımları denetleyen bir yapı olarak kalmak zorundadır.
Ne zaman kullanmalı
assert ifadesinin hata atan bir olanak olması, onun yerine bizim doğrudan hata atabileceğimiz düşüncesini doğurabilir. Hatalı durumlarda bu iki olanaktan hangisini kullanacağımızın kararı bazen güç olabilir.
Başka dillerde assert hata atmaz ve programı hemen sonlandırır. Bu yüzden, geleneksel olarak programcı hatalarını yakalamak için kullanılır. Siz de programlarınızda bunu uygulayabilir, ve hatanın program ile ilgili olduğundan emin olduğunuz durumlarda assert ifadesini kullanabilirsiniz. Örneğin yukarıdaki ayGünleri işlevinde kullanılan assert, tamamen programcılıkla ilgili hatalara karşı bir güvence olarak kullanılmıştır. Programcının şubatGünleri içerisinde ileride yapabileceği hataları önler.
Bundan sonra göreceğimiz birim testlerinde ve sözleşmeli programlamada assert temel olarak kullanıldığı için, orada zaten seçiminiz olmayacak. Buna bakarak assert ifadesini belki de yalnızca birim testlere ve sözleşmeli programlamaya ayırabilir, ve diğer hatalı durumlarda hata atma olanağını kullanabilirsiniz. Daha önce de duyduğunuz gibi: karar sizin... :o)
Problemler
- Bu problemde size önceden yazılmış bir program göstermek istiyorum. Bu programın hata olasılığını azaltmak için bazı noktalarına
assertifadeleri yerleştirilmiş. Amacım, buassertifadelerinin programdaki hataları ortaya çıkartma konusunda ne kadar etkili olduklarını göstermek. - Bu sefer programa
6:9ve15:2zamanlarını girin. BirAssertErroratıldığını göreceksiniz. Hatada belirtilen satıra gidin ve bu hatayı giderin. - Bu sefer programa
6:9ve1:1zamanlarını girin. Yeni bir hata ile karşılaşacaksınız. O satıra da gidin ve o hatayı da giderin. - Bu sefer programa
6:9ve20:0bilgilerini girin. Yine hata... - Bu sefer programa
6:9ve1:41bilgilerini girin. Programın da ekinin doğru çalışmadığını göreceksiniz:
Program kullanıcıdan bir başlangıç zamanı ve bir işlem süresi alıyor ve o işlemin ne zaman sonuçlanacağını hesaplıyor. Program, sayılardan sonra gelen 'da' eklerini de doğru olarak yazdırıyor:
9:6'da başlayan ve 1 saat 2 dakika süren işlem, 10:8'de sonlanır
import std.cstream; import std.conv; /* * Verilen mesajı kullanıcıya gösterir ve girilen zaman * bilgisini saat ve dakika olarak okur */ void zamanOku(in dchar[] mesaj, out int saat, out int dakika) { dout.writef(mesaj, "? (SS:DD) "); /* * Not: Aslında burada .dup'a gerek olmamalı; Phobos'un * bir hatası yüzünden ve o hata giderilene kadar * kullanıyoruz */ din.readf("%d:%d".dup, &saat, &dakika); if ((saat < 0) || (saat > 23) || (dakika < 0) || (dakika > 59)) { throw new Exception("Geçersiz zaman"); } } /* * Zamanı dizgi düzeninde döndürür */ dstring zamanDizgisi(in int saat, in int dakika) { assert((saat >= 0) && (saat <= 23)); assert((dakika >= 0) && (dakika <= 59)); return to!dstring(saat) ~ ":" ~ to!dstring(dakika); } /* * İki zaman bilgisini birbirine ekler ve üçüncü parametre * çifti olarak döndürür */ void zamanEkle( in int başlangıçSaati, in int başlangıçDakikası, in int eklenecekSaat, in int eklenecekDakika, out int sonuçSaati, out int sonuçDakikası) { sonuçSaati = başlangıçSaati + eklenecekSaat; sonuçDakikası = başlangıçDakikası + eklenecekDakika; if (sonuçDakikası > 59) { ++sonuçSaati; } } /* * Sayılardan sonra kesme işaretiyle ayrılarak kullanılacak * olan "de, da" ekini döndürür */ dstring daEki(in int sayı) { dstring ek; const int sonHane = sayı % 10; switch (sonHane) { case 1: case 2: case 7: case 8: ek = "de"; break; case 3: case 4: case 5: ek = "te"; case 6: case 9: ek = "da"; break; default: break; } assert(ek.length != 0); return ek; } void main() { int başlangıçSaati; int başlangıçDakikası; zamanOku("Başlangıç zamanı", başlangıçDakikası, başlangıçSaati); int işlemSaati; int işlemDakikası; zamanOku("İşlem süresi", işlemSaati, işlemDakikası); int bitişSaati; int bitişDakikası; zamanEkle(başlangıçSaati, başlangıçDakikası, işlemSaati, işlemDakikası, bitişSaati, bitişDakikası); sonucuYazdır(başlangıçSaati, başlangıçDakikası, işlemSaati, işlemDakikası, bitişSaati, bitişDakikası); } void sonucuYazdır( in int başlangıçSaati, in int başlangıçDakikası, in int işlemSaati, in int işlemDakikası, in int bitişSaati, in int bitişDakikası) { dout.writef("%s'%s başlayan", zamanDizgisi(başlangıçSaati, başlangıçDakikası), daEki(başlangıçDakikası)); dout.writef(" ve %s saat %s dakika süren işlem,", işlemSaati, işlemDakikası); dout.writef(" %s'%s sonlanır", zamanDizgisi(bitişSaati, bitişDakikası), daEki(bitişDakikası)); dout.writefln(); }
Bu programı çalıştırın ve girişine başlangıç olarak 6:9 ve süre olarak 1:2 verin. Programın normal olarak sonlandığını göreceksiniz.
Not: Aslında çıktıda bir hata farkedebilirsiniz. Bunu şimdilik görmezden gelin; çünkü az sonra assert'lerin yardımıyla bulacaksınız.
Başlangıç zamanı? (SS:DD) 6:9
İşlem süresi? (SS:DD) 1:41
6:9'da başlayan ve 1 saat 41 dakika süren işlem,
7:50'da sonlanır
Bunu düzeltin ve duruma göre doğru ek yazmasını sağlayın: 7:10'da, 7:50'de, 7:40'ta, vs.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları