İçeriğe geç

Defansif Programlama

“The best defense is a good offense.” –Anonymous

Bu söz yazılım geliştirme açısından doğru mudur değil midir tartışılır fakat farklı alanlar için doğru olabilir. Örneğin bana göre futbol 🙂

Defansif programlama aslında öğrenmesi ve anlaşılması çok basit kavramdır fakat ne zaman uygulayacağımız konusu muallaktır. Çünkü bu noktada matematiksel bir formülasyon bulunmuyor. Projeye göre, projedeki iş mantığına göre veya takım içinde belirleyeceğiniz stratejiye göre kullanılıp kullanılmayacağı farklılık gösterebilir. Ama bu kavram temel olarak kodun kalitesinin artmasını, daha az bug çıkmasını ve sürdürülebilirliği olan uygulamalar sunabilmeyi hedefler.

Defansif Programlama Nedir
“Defend against the impossible, because the impossible will happen.”

Defansif programlama yukarıdaki ilkeye dayanan bir yazılım modelidir. Yani bir yazılımcı kesinlikle beklemediği ve imkansız gibi düşündüğü durumlara karşı bile önlem almalıdır. Çünkü o durumun gerçekleşme olasılığı vardır. Bu yaklaşım kodu daha güvenli hale getirir ve ayrıca hataların kolayca tespit edilmesini sağlar. Peki bu yaklaşımı benimsemek için neler yapabiliriz bunları inceleyelim.

Kullanıcı Girdileri
Kullanıcı input değerlerinin doğruluğuna kesinlikle güvenemeyiz. Veriler her ne kadar gelişmiş client-side frameworkler ile ön tarafta validasyon süzgecinden geçiriliyor olsa bile backend taraf için yine de bir kontrol mekanizması olmalıdır! Örneğin verilen dizi içindeki en küçük sayıyı bulan metodu inceleyelim.

        public static int GetMinValue(int[] list)
        {
            //Defensive code (Fast fail)
            if (list == null)
            {
                throw new NullReferenceException("Can not be null!");
            }

            int result;

            result = list[0];
            for (int i = 0; i < list.Length; i++)
            {
                if (list[i] < result)
                {
                    result = list[i];
                }
            }
            return result;
        }

External Servis Çağrımları
External servis çağrımlarının sonucunu sonsuza kadar beklememiz gerekmiyor. Timeout ve tekrar deneme sayıları belirleyebiliriz. Bunun için nuget üzerinden indirebileceğiniz Circuit Breaker isimli kütüphaneyi kullanmak yararlı olabilir.

Pre-Conditon & Post-Condition Kontrolleri
Ön yüze gönderdiğiniz veri ile post işlemi sonrası backend’e gelen data uyuşuyor mu? Örneğin bir müşterinize kredi vereceksiniz ve kredi seçenekleri arasında 10.000 TL ve 20.000 TL var. Fakat bir şekilde post işlemi sonrası ön yüzden gelen kredi değeri 100.000 TL şeklinde oldu. Bu noktada fast fail denilen durum gerçekleştirmeli yani uygulama ArgumentException fırlatmalı. Çünkü bu durumda sizin sunduğunuz 10.000 TL veya 20.000 TL değerlerinden birinin seçimi gerçekleşmemiş ve veri üzerinde değişiklik yapılmış.

Not: Belli başlı teknikler ile post işlemi esnasında veriler üzerinde değişiklik yapılması durumu engellenebilmekte. Fakat yinede backend tarafta bu tip veri doğrulama işlemlerini yapmamız gerekiyor ki bu defansif programlamanın ilkesidir!

Ofansif Programlama Nedir
Hatalara odaklanıp çözmek yerine hataları gizleme esasına dayanır. Böylelikle sistem hata alsa bile yine de çalışabilir durumda olur. Bu teknik defansif programlama ile ters düşüyor olabilir ama işin özünde her ikisi de aynı amaca hizmet eder. Sadece bu teknik ile hatalar yüksek sesle dile getirilmek yerine bunları gizleyip sistemin crash olmasının önüne geçilir.

Bir sayı listesi içinden en küçük sayıyı bulan kod örneğini şimdi de bu tekniğe göre yeniden uyarlayalım:

        public static int GetMinValue(int[] list)
        {
            //Offensive code with default value
            if (list == null)
            {
                return 0;
            }

            int result;

            result = list[0];
            for (int i = 0; i < list.Length; i++)
            {
                if (list[i] < result)
                {
                    result = list[i];
                }
            }
            return result;
        }

Metot için isteyeceğimiz list parametresi null ise bu durumda kendimiz varsayılan değer atayarak işleyişe devam ediyoruz ki bu ofansif programlamaya güzel bir örnektir. Peki diyebilirsiniz ki evet sistem bu haliyle çalışacak fakat hatalı veriler ile çalışacak. Yani söz gelimi yazılımcı bu input için 3, 7, 1, 9 değerlerini barındıran bir array gönderdiği halde bir şekilde metot input değeri null geldi. Bu durumda “0” değerinin en küçük olduğu ekranda görüldüğünde zaten sistemin hatalı olduğu durumu ortaya çıkacaktır. Yani sistem yara alsa da çalışmaya devam edecektir 🙂 Mantıksız gibi gözükebilir ama zaten bu programlama modelinin temel amacı sistemin kesintisini engellemektir.

Ofansif programlama tekniği yerini zaman içinde paranoyak programlama modelini benimseme riskini taşır. Bunu bir kod örneği ile açıklayayım.

Senaryo gereği personel listesini input olarak alacağınız bir metot düşünün ve bu listedeki her personelin e-posta adreslerini güncelleyeceğiz. Kod basit olarak aşağıdaki gibi olacaktır.

        public void UpdateEmail(List<Person> personList)
        {
            foreach (Person person in personList)
            {
                person.job.contact.email = this.GetEmail(person.Id);
            }
        }

Peki bu kod üzerinde ne gibi riskler var? Örneğin “personList” isimli input için null değer gelir mi veya “person” nesnesinin tüm özellikleri(job, contact, email) için null kontrolü gerekir mi? Peki ya “GetEmail” metodu çalışırken bir sıkıntı olur ve uygulama crash olur ise ne olacak? Evet yeterince paranoyaklaştığımıza göre kodumuzu yazabiliriz. 🙂

        public void UpdateEmail(List<Person> personList)
        {
            if (personList != null)
            {
                foreach (Person person in personList)
                {
                    var job = person.job;
                    if (job != null)
                    {
                        var contact = job.contact;
                        if (contact != null)
                        {
                            string emailAddress = null;
                            try
                            {
                                emailAddress = GetEmail(person.Id);
                            }
                            catch (Exception exp)
                            {
                                //Todo: Log error
                            }
                            finally
                            {
                                contact.email = emailAddress;
                            }
                        }
                    }
                }
            }
        }

Buradaki kodlara göre uygulama crash olmasın diye büyük efor sarf ettik ve başardık. 🙂 Artık uygulamamız crash olmayacak. Fakat bu durumda biraz performanstan ödün vermek durumunda kaldık ve çirkin bir kod örneği sunduk. Burada bir karar verip bütün alanları null check işlemine tabi tutmaktansa değer ataması yapacağımız kod satırını try catch bloğu ile sağlamlaştırıp uygulamamızın hatasız çalışmasını sağlayarak ofansif programlama mantığını oturtmuş olabiliriz. Diğer bir yandan input aldığımız değerin null gelmesi durumunda anlamlı bir hata mesajı çıkararak defansif programlama modeli benimseyebiliriz. Kodu revize edece olursak aşağıdaki gibi olacaktır.

        public void UpdateEmail(List<Person> personList)
        {
            /* Defensive code
            if (personList == null)
            {
                throw new ArgumentNullException();
            }
            */

            /* Offensive code
            if (personList == null)
            {
                personList = new List<Person>();
            }
            */

            foreach (Person person in personList)
            {
                //Offensive code
                try
                {
                    person.job.contact.email = GetEmail(person.Id);
                }
                catch (Exception exp)
                {
                    //Log error
                }
            }
        }

Hazırladığımız 3 tip metot için çalışma sürelerini karşılaştıracak olur isek sonuçlar aşağıdaki gibi oluyor. Buradan paranoyak programlama modeli için performanstan ne kadar ödün verdiğimiz sonucu ortaya çıkıyor.

Özet
Kişisel görüşüm defansif programlama modelinin ofansif programlama modeline göre daha etkili olduğu yönündedir. Çünkü sorunları gizleyip sistemin devamlılığını sağlamak sorunların köküne inmeye engel bir durum olabiliyor. Hatta bizi paronayak programlama modeline sürükleyebiliyor.

Lafın özü defansif ve ofansif programlama teknikleri sürdürülebilirliği olan uygulamalar ortaya çıkarmak için etkili yöntemlerdir diyebiliriz. Fakat ne zaman kullanmalıyız sorusunun cevabı tamamen görecelidir ve yazılımcının hissiyatına göre değişkenlik gösterebilir.

Yararlanılan Kaynaklar:
DefensiveProgramming Wiki
OffensiveProgramming Wiki
Johannes Brodwall Blog Post
Codingsoul Blog Post

Kitap Tavsiyesi:
Code Complete (Developer Best Practices)

Herkese mutlu kodlamalar dilerim.

Tarih:Software ArchitectureSoftware Principles

Tek Yorum

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.