C# 4.0 ile Code Contracts

Merhaba Arkadaşlar,

Microsoft gibi dev yazılım firmalarının araştırma geliştirme ekipleri ve labarotuvar çalışmaları her zaman ilgimi çekmiştir. Herhalde pek çok yazılımcının hayalleri arasında bu tip firmalarda çalışmak ve yeni fikirleri ortaya atarak diğer yazılımcılara sunmak yer almaktadır.

Microsoft' un DevLabs isimli portalında bu tip fikirlerin labarotuvar çalışmalarının yer aldığını görebilirsiniz. Örneğin son zamanların popüler konularından birisi olan observable koleksiyonları kullanarak asenkron(Asynchronous) ve olay güdümlü(Event Based) programlamayı kolaylaştıran Reactive Extensions, yeni başlayanlara yazılım anlatan Small Basic yada White Box testleri için geliştirilen Pex... Tabi daha pek çok labarotuvar çalışması yer almaktadır. Bunlardan biriside Code Contracts' tır.

Uzun süredir ilgimi çeken ama fırsat bulamadığım konulardan birisidir Code Contracts. Özellikle test süreçlerinde önem arz eden ve kodun çalışma zamanında veya kodlama zamanında varsayımsal bazı koşulları sağlayıp sağlamadığını tespit etmemizi sağlayan bir yenilik olarak düşünülebilir. Bu noktada kodun ön koşullu(Pre-Conditions) veya son koşullu(Post-Conditions) olarak test edilmesinin mümkün olduğunu söyleyebiliriz. Bu iki yeteneğe ek olarak bir nesnenin durumunun(State) beklendiği gibi olmasının kontrolüde Object Invariant özelliği sayesinde gerçekleştirilebilmektedir. 

Normal şartlarda Microsoft.Diagnostics.Contracts isim alanı(Namespace) altında yer alan tipler ile kod sözleşmelerinin kullanılması mümkündür. Bu isim alanı .Net Framework 4.0 Base Class Library içerisinde doğrudan gelmektedir. .Net Framework 4.0 öncesi sürümlerde kullanmak istediğimizdeyse Microsoft.Contracts.dll assembly' ının projeye referans edilmesi(örneğin XP işletim sisteminde C:\Program Files\Microsoft\Contracts\PublicAssemblies\v3.5 adresinde yer almaktadır) gerekmektedir. Kolay kullanım için yazıyı hazırladığım tarih itibariyle DevLabs' ten gerekli aracın(Tool) indirilerek kurulmasında yarar vardır. Code Contracts, Microsoft DevLabs içerisine dahil edilmiş önemli projelerden birisidir. Code Contracts' ın bloğun yazıldığı tarih itibariyle iki farklı versiyonu bulunmaktadır. Standart versiyon çalışma zamanı kontrollerini(Runtime Checking) yapabilmekteyken, Team System için üretilen versiyon static kontroller(Static Checking) yapabilmektedir. İlgili sürümler yine Visual Studio 2010 Beta 2 sürümü ile de çalışmaktadır.

Not : Eğer elinizde benim gibi Visual Studio 2010 Ultimate Beta 2 sürümü var ise, Team System Edition sürümünü kurabilirsiniz.

Kurulum işlemi sonrasında Visual Studio Ultimate 2010 Beta 2 üzerinde oluşturulan projelerin özelliklerine(Properties) aşağıdaki ekran görüntüsünde yer alan bir ara birimin dahil edildiğini görürüz.

,

Burada yer alan detaylı özellikleri zaman içerisinde öğreniyor olacağız. İlk etapta Static Checking kısmının sadece Team System destekli Visual Studio' lar üzerinde(Visual Studio 2008 içinde geçerlidir) etkinleştirilebildiğini belirtelim. Aslında bu kabiliyetlerin özellikle çalışma zamanında yapılan if...try...catch gibi kontrollerden ne gibi farkı olduğunu kavramamız oldukça önemlidir. Örneğin dökümantasyon avantajları bunlardan birisidir. Söz gelimi metod parametrelerinin gereklilikleri(Requirements), olası istisnalar(Exceptions) ve gerekli izinler(Permissions) için otomatik dökümantasyon üretimi sağlanabilmektedir.

Diğer yandan Unit Test tarafına getirdiği avantajlardan da bahsedilebilir. Daha anlamılı Unit Test' lerin üretilebilmesi, özellikle her sözleşmenin(Contracts) bir analist gibi davranması ve çalıştırılan Test için pass/fail işaretlemesini sağlayabilmesi vb...Tabiki konuyu daha iyi kavrayabilmek adına bol bol örnek geliştirmekte yarar vardır. İşe çok basit bir Hello World uygulaması ile başlamakta yarar olacağı kanısındayım. Bu nedenle aşağıda yer alan Console uygulaması kodlarını yazdığımızı varsayalım.

using System.Diagnostics.Contracts;

namespace CodeContracts
{
    class Program
    {
        static void Main(string[] args)
        {
            ChinookContext context = new ChinookContext();        
            context.CreateAlbum(null, 1);
            Album result=context.CreateAlbum("The Best", 1);
            Contract.Ensures(result.Name.Length > 10, "Album nesnesinin oluşturulmasında albüm adının 10 karakterden fazla olması beklenir");
        }
    }

    class ChinookContext
    {
        public Album CreateAlbum(string albumName, int albumId)
        {
            Contract.Requires(!string.IsNullOrEmpty(albumName),"Album nesnesinin oluşturulması için Album adının null veya boş olmaması gerekir");
            Album albm=new Album { 
                AlbumId=albumId
                ,Name=albumName };            
            return albm;
        }
    }

    class Album
    {
        public int AlbumId { get; set; }
        public string Name { get; set; }
    }
}

Bu örnek kod parçasında Album isimli sınıfa ait iki nesne örneği üretiminin gerçekleştirildiğini görmekteyiz. CreateAlbum isimli metod içerisinde Contract.Requires isimli bir static fonksiyon çağrısı olduğu hemen dikkatinizi çekmiş olmalıdır. Bu metod ile bir ön koşul(Pre-Condition) belirtilmektedir. Bu koşula göre CreateAlbum metoduna gelen albumName parametresinin değerinin null veya boş olmaması beklenmektedir. Diğer yandan Main metodunun son satırında da Contract.Ensures isimli bir metod çağrısı yer almaktadır. Bu çağrı ilede bir son koşul(Post-Condition) tanımlaması yapılmaktadır. Bu koşula göre CreateAlbum metodu ile üretilen ikinci Album nesne örneğinin Name özelliğinin karakter sayısının 10' un üzerinde olması istenmektedir.

Örneği bu haliyle çalıştırdığımızda hiç bir sorun olmadığını görürüz. Hımmm...Enteresan bir durum.Undecided Oysaki Requires veya Ensures metod çağrılarından en az birisine takılmamız gerekirdi. Aslında sorun henüz kod sözleşmelerinin çalışma zamanı(runtime) veya static olarak izlenmesi gerektiğini belirtmemiş olmamız. Bunun için Tool ile birlikte projeye eklenen Contracts sekmesindeki Runtime Checking kutusunu işaretlememiz yeterlidir.

Şimdi örneğimizi çalıştırdığımızda ilk olarak aşağıdaki mesaj kutusu ile karşılaştığımızı görürüz.

Dikkat edileceği üzere Album adının null geçilmesi nedeniyle bir uyarı mesajı üretilmiştir. Bir başka deyişle ön koşulun sağlanamadığı açık bir şekilde görülmektedir. Bu noktadan sonra hatayı görmezden gelip ilerleme şansımız vardır. Ignore düğmesine basarak devam ettiğimiz takdirde bu kez Post-Condition' a takıldığımızı görebiliriz.

Üretilen uyarı mesajına göre ikinci Album nesnesinin adının karaketer uzunluğunun istenildiği gibi olmadığı gözlemlenmektedir. Şimdi dilerseniz Object Invariant konusuna dair basit bir örnek geliştirmeye çalışarak devam edelim. Invariant özelliğini bir nesnenin istemci tarafından ele alındığı yerdeki durumunun(State) iyi olması ile alakalı bir yetenek şeklinde düşünebiliriz. Bir nesnenin durumunu içerdiği alanlar ifade ettiğinden, bu alanların beklenen şekilde olması iyi bir nesne(good object) olduğu anlamına da gelecektir. İşte örnek kod parçası.

using System.Diagnostics.Contracts;

namespace CodeContracts
{
    class Program
    {
        static void Main(string[] args)
        {
            Product bardak = new Product(1000,"Bardak",3.45);
        }
    }

    class Product
    {
        private int ProductId;
        private string Name;
        private double ListPrice;

        public Product(int pId,string pName,double pListPrice)
        {            
            ProductId = pId;
            Name = pName;
            ListPrice = pListPrice;
        }

        [ContractInvariantMethod]
        protected void InvariantCheck()
        {
            Contract.Invariant(this.ProductId > -1,"Ürün numarası pozitif değer olmalıdır");
            Contract.Invariant(this.Name.StartsWith("PRD-"),"Ürün adları PRD- ile başlamalı");
            Contract.Invariant(this.ListPrice != 0, "Liste fiyatı 0 olamaz");
        }
    }
}

Product isimli sınıfta ContractInvariantMethod niteliği(attribute) ile imzalanmış bir metod bulunmaktadır. Geriye değer döndürmeyen ve protected olarak işaretlenmiş bu metod içerisinde dikkat edileceği üzere Contract sınıfının static Invariant metoduna yapılan bazı çağrılar bulunmaktadır. Bu çağrılar içerisinde, üretilen Product nesnesinin durumunu simgeleyen alanların değerleri kontrol edilmektedir. Örnek çalıştırıldığında aşağıdaki mesaj ile karşılaşılacaktır.

Bu son derece doğaldır nitekim ürün adı Invariant çağrısında olduğu gibi PRD- ön eki ile başlamamaktadır. Dolayısıyla nesne örneği istenilen duruma sahip değildir.

Kişisel Not: Invariant kontrolünde dikkat edilmesi gereken iki durum vardır. Auto Property' ler kullanıldığında çalışma zamanında bir istisna mesajı alınacaktır. Bu istisna Contract.Invariant çağrılarının yapıldığı yerde gerçekleşecektir. Bunun sebebi Invariant çağrılarının null değer içeren özellikleri kontrol etmeye çalışmasıdır. Söz konusu durum varsayılan yapıcı metodlar kullanıldığında da nüksetmektedir. Söz gelimi yukarıdaki kod parçasında Product tipi için varsayılan yapıcı metod yazılıp buna göre bir Product nesnesi örneklendiğinde aşağıdaki çalışma zamanı istisna(Runtime Exception) mesajı ile karşılaşılır.

Name alanı string tipten olduğu için nesnenin ilk örneklenmesi sırasında varsayılan yapıcı metod(Default Constructor) tarafından null değer ile beslenir. Bunun sonucu olarakta Invariant metodu null değer üzerinde kontrol yapmaya çalışmaktadır.

Elbette Code Contracts konusu burada anlatıldığı kadar yalın ve sade değildir. Aksine dökümantasyonuna bakıldığında çok fazla kuralı olduğu görülmektedir. Özellikle Static Checking özelliği derleme işlemi sırasında bazı kod sözleşmelerinin kontrolü sağlamaktadır. Konuyu araştırdıkça ve öğrendikçe sizlere daha fazlasını aktarmaya çalışıyor olacağım. Tekrardan görüşünceye dek hepinize mutlu günler dilerim. 

CodeContracts.rar (25,40 kb)

Yorumlar (1) -

  • Oncellikle bu ve diger bir cok konuda yapmis oldugunuz bilgi paylasimi icin tesekkurlerimi sunmak istiyorum. MS Code Contrats kutuphanesini 3-4 aydir projemizde kullanmaktayiz, yasadigimiz tecrubelere dayanarak ozellikle tamamen interface lerle calistiginizda code contrats larinizi dummy interface ler uzerinde uygulayabiliyorsunuz (cintf) ve boylece asil kodunuzun icerisinde bir karisiklik olusmuyor. (clean code development) ayrica code contracts larinizi kodunuzun icerisinde tanimlayacaginiz zaman Code Contracts icimli region icerisinde tanimlamaniz oneriliyor. (Release ver.) pratik anlamda; debug islemlerimizi cok hizlandirdi, bizleri gereksiz debug islemlerinden kurtardi diyebilirim. ayrica basit unit testler yazma zorunlulugunuda ortadan kaldirdi. ama yinede is akislari icin unit test yazmak gerekiyor.

    Saygilarimla...

    Dipl.-Ing. Ugur Genc
    [email protected]
    mils electronic gesmbh & cokg

Yorum ekle

Loading