Yapı ve Sınıflarda Sözleşmeli Programlama
Sözleşmeli programlama, kod hatalarını azaltmaya yarayan çok etkili bir olanaktır. D'nin sözleşmeli programlama olanaklarından ikisini Sözleşmeli Programlama dersinde görmüştük. in ve out blokları, işlevlerin giriş ve çıkış koşullarını denetlemek için kullanılıyordu.
Hatırlamak amacıyla, üçgen alanını Heron formülünü kullanarak kenar uzunluklarından hesaplayan bir işlev yazalım. Üçgenin alanının doğru olarak hesaplanabilmesi için üç kenar uzunluğunun da sıfır veya daha büyük olması gerekir. Ek olarak, bir üçgenin hiçbir kenarının diğer ikisinin toplamından uzun olmaması da gerekir.
O giriş koşulları sağlandığında, üçgenin alanı da sıfır veya daha büyük olacaktır. Bu koşulları ve bu garantiyi sağlayan bir işlev şöyle yazılabilir:
private import std.math; double üçgenAlanı(in double a, in double b, in double c) in { // Kenarlar sıfırdan küçük olamaz assert(a >= 0); assert(b >= 0); assert(c >= 0); // Hiçbir kenar diğer ikisinin toplamından uzun olamaz assert(a <= (b + c)); assert(b <= (a + c)); assert(c <= (a + b)); } out (sonuç) { assert(sonuç >= 0); } body { const double yarıÇevre = (a + b + c) / 2; return sqrt(yarıÇevre * (yarıÇevre - a) * (yarıÇevre - b) * (yarıÇevre - c)); }
Not: dmd 2.038 ile başlayan ve en azından 2.046'da da süren bir hata yüzünden out bloğundaki denetim hep başarısız oluyor. O kod ancak bu hatanın giderildiği yeni bir sürüm kullanıyorsanız çalışır. Aksi taktirde, out bloğundaki denetim satırını kaldırmanız gerekir.
Üye işlevlerle in ve out blokları
in ve out blokları
üye işlevlerle de kullanılabilir ve aynı şekilde, işlevin giriş koşullarını ve çıkış garantisini denetler.
Yukarıdaki alan hesabı işlevini bir üye işlev olarak yazalım:
import std.cstream; import std.math; struct Üçgen { private: double a_; double b_; double c_; public: @property double alan() const out (sonuç) { assert(sonuç >= 0); } body { const double yarıÇevre = (a_ + b_ + c_) / 2; return sqrt(yarıÇevre * (yarıÇevre - a_) * (yarıÇevre - b_) * (yarıÇevre - c_)); } } void main() { auto üçDörtBeşÜçgeni = Üçgen(3, 4, 5); dout.writefln(üçDörtBeşÜçgeni.alan); }
Not: dmd'nin yukarıda değindiğim hatası yüzünden ne yazık ki bu out bloğu da başarısız olacaktır. Yine, eğer hatanın giderildiği bir sürüm kullanmıyorsanız, o satırı çıkartmanız gerekir.
Üçgenin kenarları zaten yapının üye değişkenleri oldukları için, bu işlevin parametreleri yok. O yüzden bu işlevin in bloğunu yazmadım. Üye değişkenlerin tutarlılıkları için aşağıdaki bilgileri kullanmanız gerekir.
Nesnelerin geçerliliği için in ve out blokları
Yukarıdaki üye işlev parametre almadığı için in bloğunu yazmadık. İşlevdeki hesabı da nesnenin üyelerini kullanarak yaptık. Yani bir anlamda üyelerin geçerli değerlere sahip olduklarını varsaydık. Bu varsayımın doğru olmasını sağlamanın bir yolu, sınıfın kurucu işlevine in bloğu eklemektir. Böylece kurucunun aldığı parametrelerin geçerli olduklarını en başından, daha nesne kurulmadan denetleyebiliriz.
in bloğunu da yazabilmek için, kurucu işlevi açıkça bizim tanımlamamız gerekir; derleyicinin otomatik olarak sunduğu kurucuyu kullanamayız:
class Üçgen { // ... this(in double a, in double b, in double c) in { // Kenarlar sıfırdan küçük olamaz assert(a >= 0); assert(b >= 0); assert(c >= 0); // Hiçbir kenar diğer ikisinin toplamından uzun olamaz assert(a <= (b + c)); assert(b <= (a + c)); assert(c <= (a + b)); } body { a_ = a; b_ = b; c_ = c; } // ... }
Bu sayede, üçgen nesnelerinin geçersiz değerlerle oluşturulmaları en başından engellenmiş olur. Artık programın geçersiz değerlerle kurulmuş olan bir üçgen nesnesi kullanması olanaksızdır:
auto eksiKenarUzunluklu = Üçgen(-1, 1, 1); auto birKenarıFazlaUzun = Üçgen(1, 1, 10);
Kurucu işlevin in bloğu, yukarıdaki geçersiz nesnelerin oluşturulmalarına izin vermez:
core.exception.AssertError@deneme.d: Assertion failure
Bu sefer de out bloğunu yazmadığıma dikkat edin. Eğer gerekirse, daha karmaşık türlerde kurucu işlevin out bloğu da yazılabilir. O da nesnenin üyeleri kurulduktan sonra gerekebilecek denetimler için kullanılabilir.
Nesnelerin tutarlılığı için invariant bloğu
Kurucuya eklenen in ve out blokları, nesnenin yaşamının geçerli değerlerle başlayacağını; üyelere eklenen in ve out blokları da işlevlerin doğru şekilde çalışacaklarını garanti eder.
Ancak bu denetimler, nesnenin üyelerinin her zaman için geçerli veya tutarlı olacaklarını garanti etmeye elverişli değillerdir. Nesnenin üyeleri, üye işlevler içinde programcı hataları sonucunda tutarsız değerler edinebilirler.
Nesnenin tutarlılığını tarif eden koşullara "mutlak değişmez" denir. Örneğin bir müşteri takip sınıfında her siparişe karşılık bir fatura bulunacağını varsayarsak; fatura adedinin sipariş adedinden fazla olamayacağı, bu sınıfın bir mutlak değişmezidir. Eğer bu koşulun geçerli olmadığı bir müşteri takip nesnesi varsa, o nesnenin tutarlı durumda olduğunu söyleyemeyiz.
Bunun bir örneği olarak Sarma ve Erişim Hakları dersinde kullandığımız Okul sınıfını ele alalım:
class Okul { private: Öğrenci[] öğrenciler; int kızToplamı; int erkekToplamı; // ... }
Bu sınıftan olan nesnelerin tutarlı olarak kabul edilmeleri için, üç üyesi arasındaki bir mutlak değişmezin sağlanması gerekir. Öğrenci dizisinin uzunluğu, her zaman için kız öğrencilerin toplamı ile erkek öğrencilerin toplamına eşit olmalıdır:
assert(öğrenciler.length == kızToplamı + erkekToplamı);
O koşulun bozulmuş olması, bu sınıf kodlarında yapılan bir hatanın göstergesidir.
Yapı ve sınıf nesnelerinin tutarlılıkları, o türün invariant bloğunda denetlenir. Bu blok, yapı veya sınıf tanımı içine yazılır ve sınıf nesnelerinin tutarlılık koşullarını içerir. in ve out bloklarında olduğu gibi, burada da assert ifadeleri kullanılır:
class Okul { private: Öğrenci[] öğrenciler; int kızToplamı; int erkekToplamı; invariant() { assert( öğrenciler.length == kızToplamı + erkekToplamı); } // ... }
Yapı ve sınıf tanımlarında yalnızca bir tane invariant bloğu bulunabilir.
invariant bloklarındaki kodlar aşağıdaki zamanlarda otomatik olarak işletilir, ve bu sayede programın yanlış verilerle devam etmesi önlenmiş olur:
- kurucu işlev sonunda; böylece nesnenin yaşamına tutarlı olarak başladığı garanti edilir
- sonlandırıcı işlev çağrılmadan önce; böylece sonlandırma işlemlerinin tutarlı üyeler üzerinde yapılacakları garanti edilir
- public bir işlevden önce ve sonra; böylece üye işlevlerdeki kodların nesneyi bozmadıkları garanti edilir
Not: Yukarıdaki listede public işlevler için söylenenler export işlevler için de geçerlidir. export işlevleri kısaca "dinamik kütüphalerin sundukları işlevler" olarak tanımlayacağım, fakat ayrıntılarına burada girmeyeceğim.
invariant bloğundaki denetimlerin başarısız olmaları da in ve out bloklarında olduğu gibi AssertError atılmasına neden olur. Bu sayede programın tutarsız nesnelerle devam etmesi önlenmiş olur.
in ve out bloklarında olduğu gibi, invariant blokları da -release seçeneği ile iptal edilebilir:
dmd deneme.d -w -release
Özet
in ve out bloklarını üye işlevlerle de kullanabilirsiniz; kurucu işleve ekleyerek nesnelerin geçersiz parametrelerle kurulmalarını önleyebilirsiniz.
Nesnelerin yaşamları boyunca her zaman için tutarlı olmalarını garantilemek için invariant bloklarını kullanabilirsiniz.
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları