Code Contracts‘ın, uzun bir süredir .NET platformunda da olan ancak çok fazla kullanılan bir kavram olmadığını, ama oldukça önemli bir özellik olduğunu düşünüyorum. Alışkanlıkların ve kolayı tercih ediyor olmamız, avantajlarının önüne geçtiğinden öneminin çok farkında olmuyoruz sanırım bu tarz özelliklerin.

Code Contracts, temelinde yazdığımız koda, adından da anlaşılacağı üzere, statik kontratlar belirlememizi sağlıyor. Bu kontratlar, pre-condition ve post-condition olarak kodun belirtilen yerlerinde derlenme aşamasından sonra(statik analiz) ya da belirtilirse run-time sırasında da çalışıyor. “Pre-Condition” ve “Post-Condition”  dışında, koddaki sınıfların durumlarını da kontrol etmek için Code Contracts’dan faydalanabiliyoruz. Ayrıca test otomasyonu ve test edilebilir kod yazmak için, Code Contracts oldukça önemli bir özellik ve hatta kolaylık.

contracts_Yazdığımız metotların aldığı parametreleri doğrulamak ya da parametrelerin durumlarını kontrol etmek, bir yazılım geliştirirken mutlaka yaptığımız bir şey. Bu, iş kurallarının oluşturulması ve kodun sağlıklı çalışması için olarak iki açıdan yorumlanabilir. Örnek olarak, kodun sağlıklı çalışması için null kontrolünün yapılması ya da iş kurallarını oluşturmak için yaşın 18’inden büyük olmasını kontrol etmeyi verebilirim.

Bu tarz kontroller daha doğrusu ihtiyaçlar yazdığımız birim testleri ile zaten sağlanacak ya da sağlanması gerekliliği ortaya çıkacaktır. Bu noktada Code Contracts bu gerekliliğin ortaya çıkmasında oldukça faydalı.

Sizden başka kişilerin de geliştirme yapması için sunacağınız, API, kütüphane falan tarzı şeyler geliştiriyorsanız da ayrıca Code Contracts oldukça faydalı olacaktır. IsAgeValid(int age) şeklinde sunduğunuz bir metoda, statik olarak verilecek parametrelerin kontrolünü, Code Contracts ile gerçekleştirirseniz, bu metodu kullanan yazılımcı, 18’den küçük bir değer girdiğinde, derleme aşamasında hata/uyarı alacağından ilerleyen adımlarda oluşacak hataların önüne geçilebilir.

Neleri, nasıl yapıyoruz kısaca kod üzerinden de bakalım. System.Diagnostics.Contracts namespace’indeki sınıfları Visual Studio ile entegre olacak şekilde kullanabilmek için öncelikle Code Contracts Tools’u kurmak gerekiyor. Bu adresten ilgili kurulumu hemen yapabilirsiniz. Code Contracts, proje seviyesinde ayarlanabilen, açılıp, kapatılan bir özellik. Proje özelliklerini açarsanız aşağıdaki gibi bir ekran ile istediğiniz ayarı yapabilirsiniz.

CodaContracts

Sarı ile belirttiğim ayarları özellikle yapmanızı tavsiye ederim. Bu ayarlar kodunuzun statik olarak analiz edilmesi ve kontratların işletilmesini, eğer bir uygunsuzluk var ise de Error olarak çıkmasını sağlıyor. Eğer Fail build on warnings seçili olmaz ise, uygunsuzluklar Warning olarak gözükecektir.

Code Contracts’ları peki nasıl tanımlıyoruz?

Aşağıdaki gibi AddUser(User user) şeklinde bir metodumuz var diyelim. Ve bunu metodun parametresindeki User sınıfının hangi özelliklerinin tanımlanması ve nasıl tanımlanması gerektiği, metodu çağıran kişi tarafından bilinemez, -eğer farklı bir şekilde belirtilmediyse. Contract.Requires() metodu ile metotların input’larını yani parametrelerini, belli koşulları(pre-conditions) sağlıyor mu diye kontrol edebiliriz.


        public OperationResult<User> AddUser(User user)
        {

            Contract.Requires(user != null, "user parameter can not be null");
            Contract.Requires(!String.IsNullOrEmpty(user.LogonName));
            Contract.Requires(user.Domain == "DOMAIN");

            try
            {

                if (IsUserExists(user.LogonName, user.Domain))
                {
                    return new OperationResult<User>(user,
                        new CommonException("A user with same logon name amd domain is already exists"));
                }

                //Do some data operations

                return new OperationResult<User>(user);
            }
            catch (Exception ex)
            {
                return new OperationResult<User>(user, new CommonException("Unable to save user.", ex.Message, ex));
            }
        }

Yukarıdaki kod örneğinde, sırasıyla çeşitli Code Contract’larını tanımlamış bulunuyoruz. Bu kontratlara göre, user parametresi, null olamaz, user.LogonName boş olamaz ve user.Domain’de sadece DOMAIN olabilir. Bu noktada küçük bir uyarı yapmak isterim. Code Contract’ları metotların ilk satırlarına yazmamız gerekmekte. İlk satırlar kontratlarımız olmalı. Aksi takdirde hata alıyor oluruz.

Neyse… Aşağıdaki gibi bu metodu çağırdığımızda ve kodumuzu derlediğimizde, Code Contracts’lara takılıp Visual Studio’dan aşağıdaki gibi bir hata alıyor olacağız.


            UserManagement p = new UserManagement();

            User u = new User();
            u.Domain = "minepla";
            u.LogonName = "";
            p.AddUser(u);

CodaContracts_1

İlk koşul, yani LogonName ile alakalı olan, sağlanmadığında direk hata onunla ilgili olacaktır. Diğer koşulların sağlanmamış olması daha henüz kontrol edilmiyor. Eğer LogonName koşulu doğru bir şekilde sağlanıyor olsaydı, bu sefer hata Domain ile ilgili koşulun hatası olacaktı.

Bu şekilde ön koşulları oluşturup, onların geçerliliğini daha geliştirme sırasında sağlayabiliyoruz. Bu şekilde birim testlerimiz için ön tanımlarımı da ayrıca yapmış oluyoruz. Code Contract’sın PreCondition tanımları, birim test için oldukça önemli ve faydalı. Bunun altını özellikle çizmek isterim.

Metodların sonlanırken bazı koşulları sağlıyor olduğunu da Code Contracts ile benzer bir şekilde kontrol edebiliyoruz. Bunun için Contract.Ensures() metodundan yararlanacağız.

Üsteki kodu aşağıdaki gibi değiştirdiğimizde, AddUser() metodunun dönüş değerinin null olamayacağını kontrat ile belirtmiş oluyoruz.

        public OperationResult<User> AddUser(User user)
        {

            Contract.Requires(user != null, "user parameter can not be null");
            Contract.Requires(!String.IsNullOrEmpty(user.LogonName));
            Contract.Requires(user.Domain == "DOMAIN");

            Contract.Ensures(Contract.Result<OperationResult<User>>() != null);

            try
            {

                if (IsUserExists(user.LogonName, user.Domain))
                {
                    return new OperationResult<User>(user,
                        new CommonException("A user with same logon name amd domain is already exists"));
                }

                //Do some data operastions

                return null;
            }
            catch (Exception ex)
            {
                return new OperationResult<User>(user, new CommonException("Unable to save user.", ex.Message, ex));
            }
        }

Hata mesajını görebilmek adına kod tarafında dönüş değerini null olarak belirttim. Dolayısıyla kontrat sağlanmayacağı için aşağıdaki gibi hem “pre-condition”, hem de “post-condition” için hata alıyor olacağız.

CodaContracts_2

Umarım biraz olsun faydalı ve açıklayıcı olmuştur. İhtiyaçlarınıza göre bu kontratların genişletmeniz oldukça mümkün. Gerekli yerlerde kullanım alışkanlığı elde edebilirseniz çok faydasını göreceksiniz…Garanti veriyorum 🙂