.Net Üzerinde Hafıza yönetimi - Heap ve Stack kavramları

by msusur 16. Ekim 2010 20:19

Stack (Yığın), son giren ilk çıkar (LIFO, last in first out) prensibi ile çalışan bir veri yapısıdır. Aslında bunu üst üste koyulmuş kutular olarak düşünebiliriz. Bir kutuya erişebilmek için yapmamız gereken onun üstündeki tüm kutuları sıradan almak olacaktır. Dolayısıyla bir anda sadece en üstteki kutuya erişebiliyor olacağız. 

.Net platformunda her method işletildiğinde, uygulamamızda nelerin değiştiğinin bilgisini stack denilen bir alanda takip ederiz. Method un işletilmesi tamamlandığında ilgili kutuyu listenin en üstünden atar ve alttaki kutularla ilgilenmeye başlarız. Heap dediğimiz kavram ise .Net platformunda stack ten daha farklı bir göreve sahiptir. Heap stack 'in aksine olan bitenlerle ilgilenmek yerine bilgi tutmak amaçlıdır. Böylece heap üzerindeki bilgilere her zaman erişebiliriz. Stack kendi başının çaresine kendisi bakabilirken, heap üzerindeki bilgiler Garbage Collector dediğimiz yapı ile temizlenmektedir.  Yani bir method çalışmasını tamamladığında stack üzerindeki bilgiler uçarken, heap üzerindeki bilgilerin bir araç tarafından yönetilmeye ihtiyacı vardır. Böylece basit bir bakış açısıyla, Stack daha düzenli bir yapı gibi görünürken, heap daha karmaşık ve dağınıktır. Entropisi yüksektir.

Diyelim ki yazdığınız programda bir method üzerinde herhangi bir noktaya breakpoint yerleştirdiniz. Programınız çalışmaya başlayıp breakpoint e vurduğu zaman, Stack Trace penceresini açtığınızda alt alta sıralanmış methodları görebilirsiniz. İşte burada gördüğünüz şey aslında methodların hangi sıra ile çalışmış olduğu ve stack üzerinde nasıl yığıldıklarıdır.Eğer içinde bulunduğunuz methodu atlatırsanız, yani method un çalışmasını tamamlarsanız listeden bir tane sıranın eksildiğini göreceksiniz. Yani stack üzerinden bir tane kutuyu çıkarmış (Pop) olacaksınız.

Peki ufak bir benzetme ile konuya biraz daha detaylı bir giriş yapalım. Diyelim bir ofiste çalışıyorsunuz ve bazı iş arkadaşlarınız size gelip sürekli bazı şeyleri yapmanızı söylüyorlar. Siz de her söylenileni bir post-it e yazıp masanıza yapıştırıyorsunuz. Ancak bir noktadan sonra, isteklerinizin sayısı arttığında masanızda yer kalmadığını farkediyorsunuz. Bu durumda aklınıza bir şey geliyor ve daha uzun vadede ihtiyacınız olacak bilgileri bir kağıda yazıp panonuza iğneliyorsunuz. Benzer şekilde çok daha uzun vadede ihtiyacınız olacakları da dosyalayıp masanızdaki çekmeceye yerleştiriyorsunuz.  Bu durumda hangi bilgiye ulaşmanız daha hızlı olacaktır? Tabi ki masanızdaki post it lere. Bu yüzden kısa sürede ve sıkça kullanacağınız bilgileri masanızda post-it olarak tutmak çok daha mantıklıdır. Ancak çok uzun sürecek ve büyük verileri masanızda tutmanız sizin için yer kaybına yol açacağı için onları da klasörlerle çekmecede taşımanız daha mantıklı olacaktır.

Yukarıdaki paragrafta örneklendirmeye çalıştığım şey, stack ve heap yapısı idi. Burada masanız sizin stack alanınız, çekmeceniz ve panonuz ise sizin heap alanınız olacaktır. Bu durumda masanızdaki post it lere, "İhale ile ilgili bilgiler üçüncü çekmecede" yazdığınızda ihtiyaç durumunda ihale bilgilerine hızlı erişimizi de sağlamış oldunuz. 

İşte .Net te aslında arka tarafta buna benzer bir prensip ile çalışıyor. Yani method içerisinde yaptığınız herşey, size o method içinde fayda sağlayacağı için en hızlı erişebileceğiniz yerde yani ilgili stack içerisinde bu bilgileri tutmaktadır ve method içinden çıktığınız anda bu bilgiler artık kullanılmayacağı için stack üzerinden uçurulmaktadır. Burada önemli bir konu daha var. Eğer böyle geçici bir alandan bahsediyorsak benzer şekilde değişkenlerin de yaşama sürelerinden bahsediyor olamamız gerekir. Bir değişkeni bir sınıf içerisinde private olarak tanımladığımızda o değişkene ait bilgiler ilgili stack üzerinde tanımlanır. Eğer bu değişken bir Reference Type ise, o zaman heap üzerinde bir alan ayırtılırken, Value Type denilen tipler de stack üzerinde ayırt edilen bir alanda saklanır. Bu durumda stack ilgili değişkenin içinde bulunduğu scope u adreslemektedir. 

Peki reference type değişkenler heap üzerinde tanımlıysa biz onlara nasıl erişebiliriz? Bu durumda herşeyin stack üzerinde tanımlanmış olması gerekmez mi? Hem bu value type ve reference type nedir?

Bir anda çok fazla soru oldu, düzenli bir biçimde ilerlemeye çalışalım öncelikle Value Type ve Reference Type arasındaki farklardan bahsedelim;

 

Value Type ve Reference Type Nedir?

.Net üzerinde hafıza yönetiminden bahsederken üç tip üzerine konuşmamız gerekiyor, Reference Type, Value Type ve Pointer. Reference Type, class ve delegate olarak tanımladığımız her tiptir. Yani System.Text.StringBuilder, System.String sınıfları birer reference typedır. Value Type ise struct olarak tanımladığımız şeylerdir. Örneğin int, float, byte, bool gibi tipler birer ValueType dır ve bunlar System.ValueType dan gelmektedirler. Pointer'lar ise biraz daha farklıdır. Onları anlatmak için şöyle bir örnekle başlayalım;

Aşağıdaki kod parçasını bir inceleyelim;

 

1 private string GetData(int itemNr)
2 {
3 StringBuilder builder = new StringBuilder();
4 int x = itemNr + 1;
5 builder.AppendFormat("Selected Item={0}", x);
6 //….
7 return builder.ToString();
8 }

Burada üçüncü satırda bir değişken tanımladık. Bu değişken de bildiğimiz gibi bir Reference Type değişken. Yani Heap üzerinde bu değişken için bir alan ayırmış olduk. Ancak bir de stack üzerinde bu alana işaret eden bir başka bir alan daha eklememiz gerekiyor ki bu değere stack üzerinden erişebilelim. Bu alana da pointer denmektedir. .Net üzerinde pointer ları biz kullanamayız bunlar bu şekilde .net tarafından yönetilmektedir. Yani üçüncü satırla birlikte hem heap üzerinde hem de stack üzerinde alanlar ayırıp bu alanları da birbirlerine bağlama işlemini CLR kendisi yapmaktadır. Daha sonra dördüncü satırda yeni bir değişken tanımladık ancak bu sefer bu değişkenimiz bir value type olduğu için stack üzerine integer ın kapladığı alan kadar bir alan ayırmış olduk. Sonunda 8 numaralı satırın da işletilmesi ile birlikte bu method için ayrılmış olan stack alanı boşaltılmalı ve hafızadan kaldırılmalıdır. Yedinci satırdan itibaren Heap ve Stack alanlarının durumu aşağıdaki gibi olmaktadır,

Stack

3. x (int)
2. builder (Pointer)
1. GetData()

 

Heap

1. builder (StringBuilder)

Sekizinci satırdan sonra da stack ten tüm bilgiler silinecektir ancak Heap üzerinde tanımlı olan değişken orada durmaya devam edecektir. Her zaman bu kadar mülayim bir senaryo ile karşı karşıya olamayız, aksine her an sayısız nesne yaratılmaktadır. Bu durumda stack üzerindekilerde sıkıntımız yok ancak heap üzerindeki nesneler nasıl temizlenmektedir?  İşte bu konu ile ilgili olarak .Net in bize sunduğu muhteşem bir mekanizma yardımımıza yetişmektedir. Biz ne kadar değişken yaratırsak yaratalım, her zaman bunların durumlarını kontrol eden ve ilişkileri kesildikçe bunları hafızadan kaldırıp yok eden bir Garbage Collector arka tarafta çalışmaktadır. Böylece heap üzerinde öksüz kalan her alan tek tek toplatılıp yok edilmektedir.

Ne zaman struct ya da class kullanmalıyız?

Dikkat ederseniz, struct ile class arasındaki fark hangi alanlarda saklandığı ile ilgiliydi. struct yani Value Type olan değişkenler stack alanında saklanırken, reference type olanlar da heap alanında saklanıyor ve stack üzerinden bir pointer ile ilişkilendiriyordu. Bu elbette performans sorunların yol açmaktadır. Yani tüm değişkenleri reference type olarak kullanırsak heap üzerinde çalışmış olacağız. Ancak bunun yerine doğru kararlar verip, çok sık kullanılan ve sürekli yeni örnekleri yaratılan nesnelerin value type olarak tasarlanması her zaman performans açısından daha avantajlıdır. System.Drawing.Point nesnesini ele aldığımızda özellikle Windows Form larında çok fazla kullanıldığını görebiliriz. Dikkat edilirse Point nesnesi de bir value type tiptir. Bunun sebebi ise sürekli yeni örneklerinin yaratılması ve pek çok yerde kullanılmasıdır. 

Bunun yanında bu tarz tasarım kararları verirken yine value type ların getirdiği kısıtlamalar da göz önüne alınmalıdır ve ona göre kararlar verilmelidir.

Dependency Injection ve Unity Application Block

by msusur 10. Temmuz 2010 11:20

Burada sürekli bir takım yazılımsal kavramlardan ve tasarım kalıplarından bahsetmeye çalışıyorum. Tabi bunların hepsinin tam olarak ne ifade ettiklerini anlayabilmek için öncelikle problemleri anlayabiliyor olmak çok büyük bir önem taşıyor. Bu yüzden elimden geldiğince gerçek hayat örnekleriyle anlatmak istediklerimi örneklemeye çalışıyorum.

Bu yazıda sizlere sıkça duyduğumuz Dependency Injection ya da başka bir değişle Inversion Of Control adı verilen kalıptan bahsediyor olacağım. Ancak bunu anlatabilmek için öncelikle Loose Coupling den bahsetmek istiyorum.

 

Loose Coupling

Önceki yazılarımda nesneye yönelik programlamanın(oop) bir prensibi olan "Single Responsibility" den ve neden gerekli olduğundan bahsetmiştim. Ancak hızlı bir özet geçmemiz gerekirse, yazdığımız sınıfların ya da program parçalarının amaçlarına uygun olarak bölünmesi ve amaçlarının dışında bir görevi üstlenmemeleri bize yazdığımız bu parçaların başka projelerde ya da mevcut program içerisinde daha çok kullanılabilmesinde fayda sağlamaktadır. Bu yüzden yapacağımız tasarımlarda sınıfların daima tek bir sorumluluğu olmalıdır. Böylece bir takım şeyleri tekrar tekrar kullanmaktan kurtulmuş oluruz ve bu da bize elbette zaman kazandırmış olur. Single Responsibility'nin bir sonraki adımı olarak da Loose Coupling düşünülebilir. Yani mümkün oldukça bir sınıf başka sınıflara bağımlı olmamalı, bunun yanında da tek bir amaca hizmet etmelidir. Burada bağımlılıktan kastımı biraz daha açıklamak istiyorum. Diyelim ki bir sınıfınız var. Bu sınıfınızda bir takım işlemler yapıyorsunuz. Örneğin bu sınıf girdi olarak bir nesne alıyorsa, sizin bu sınıfınız bu nesneye bağımlıdır. Çünkü bu nesne olmadan sınıfınızın bir anlamı yoktur. Benzer şekilde, girdi nesnesinin peşindeki diğer nesneler de bağımlılığı arttıracaktır. Bunun bir dezavantajı olarak yazdığınız sınıf da bağımlı olduğu nesnelerin değişmesi durumunda bu değişiklikten etkilenecektir.

Örnek bir entegrasyon senaryosu

Biraz daha konuya ısınmaya başladığımıza göre basit bir ihtiyaç hakkında konuşmaya başlayalım; Müşteriniz ile bir projeye başladınız. Yapmaya çalıştığınız şey ise basit bir stok programı. Müşterinizin ihtiyacına göre yaptığınız analiz doğrultusunda stok bilgilerini, müşterinizin envanter kayıtlarını sakladığı bir yazılımın veritabanından almanız gerekiyor. Ancak veritabanına erişime izin vermiyor da bu yazılım firması size bir web servis ile bu verileri verebileceğini söylüyor. Siz de bunu göz önüne alarak bu altyapı üzerine projenizi yapmaya başlıyorsunuz. Bir zaman sonra, henüz projeniz bitmeden, karşı taraftaki firma web servisi kapatıyor ve daha ileri bir teknoloji olan WCF üzerinden yayın yapmaya başlıyor. Tabi size de bunu bildirdiğinde siz gidip kodunuzu tekrar değiştiriyorsunuz. Bir zaman sonra projenizi teslim ediyorsunuz ve tamamlamış oluyorsunuz, ya da siz öyle sanıyorsunuz. Aradan bir kaç ay geçiyor ve müşteriniz size stok kayıtlarını tutan programı değiştirdiklerini, onun yerine başka bir firma ile çalıştıklarını ve verilerin hepsini yeni yazılımın veritabanına taşıdıklarını söylüyor. Bunun doğal sonucu olarak sizin de oradan almanız gerekiyor. Ancak bu sefer durum biraz daha farklı çünkü oradaki firma ihtiyacınız olan verileri sizin veritabanınıza belli aralıklarla taşıyabileceğini söylüyor ve sizin de buradan almanız gerektiğini anlatıyor. Bu durumda yine kolları sıvayıp koda girişiyorsunuz.

Yukarıdaki senaryo eminim aşağı yukarı benzer de olsa pek çok yazılımcının başına gelmektedir. Bu durumda nasıl bir mimari tasarlanmalı ya da nasıl bir yol izlenmeli?

İşte burada işin içine Dependency Injection(DI) dediğimiz kavram giriyor. Yukarıda da anlattığım gibi DI'in en ciddi gereksinim haline geldiği zaman bağımlılıkların yönetilmesi anıdır. Yukarıdaki senaryoda yazılan programın ana modulü stok verisinin haricinde veriyi nasıl alacağına da odaklanırsa yapılan iş içinden çıkılamayacak bir hal alır. Halbuki düşünüldüğü zaman yazılan programın yapması gereken iş ile veriyi nereden aldığının hiç bir ilişkisi yoktur. Burada asıl önemli olan, getirilen veridir ve programın çekirdeğinin de sadece gelecek olan veriyi bilmesi gerekmektedir. Bu durumda programın çekirdeğindeki verilere ihtiyaç duyan sınıfın tek bilmesi gereken bir adaptör nesnesinden veriyi alacak kendi bildiği şekilde alacak olmasıdır. Bundan sonra da veri web servisten mi gelmiş, WCF servisten mi gelmiş yoksa bir veritabanından mı alınmış gibi konularla ilgilenmesine ihtiyacı yoktur.

Şimdi yukarıdaki maddeleri göz önüne alarak bir tasarım yapmaya çalışalım. Bu tasarıma da programın çekirdeğinden başlayalım. Ben burada boş bir solution yaratıp, içerisine bir adet console application ekliyorum. Programımın çekirdeğinin burası olduğunu varsayıyorum. Aşağıdaki gibi bir şekilde projemize başlayalım;

 

public class StoreWinApplication
{
    private IStoreProvider m_StoreProvider;
    public IStoreProvider StoreProvider
    {
        get { return m_StoreProvider; }
        set { m_StoreProvider = value; }
    }

    internal IStoreInformation GetStoreInformation()
    {
        return StoreProvider.LoadInformation();
    }
}

Yukarıdaki sınıfa baktığınızda ihtiyacımız olan veriyi çekebilecek olan bir StoreProvider özelliğine sahip. Bu özellik de IStoreProvider tipinde. Yani bir arayüz olarak dışarıya açılmış durumda. Aynı zamanda GetInformation() methoduna bakacak olursak bizim IStoreProvider arayüzünü tanımlayan özelliğimizi, StoreProvider 'ı çalıştırmakta ve karşılığında yine bir arayüz olan IStoreInformation tipinde bir nesne döndürmektedir. Bunu yaparak şu anda business nesnemizi verinin alınacağı yerden ayırmış olduk. Böylece istediğimiz veritabanı, wcf ya da web servis bağımlılığını da kırmış olduk. Bunu kullanırken de aşağıdaki gibi kullanacağız,

 

static void Main(string[] args)
{
    StoreWinApplication application = new StoreWinApplication();
    application.StoreProvider = new DatabaseStoreProvider();
    IStoreInformation information = application.GetStoreInformation();
    Console.WriteLine(information.Name);
}

 

yukarıda da gördüğünüz gibi çalışan sınıfımızın StoreProvider özelliğinin değerini dışarıdan DatabaseStoreProvider tipindeki ve IStoreProvider arayüzünü tanımlayan sınıf olarak belirledik. Böylece çalıştırdığımızda da veriyi veritabanından alacaktır. Böylelikle ilerideki bir zamanda eklenecek olan yeni bir verikaynağının da bu işleme dahil olması çok kolay olmuş oldu.

Ancak hala başka bir sorunumuz var. Her yeni eklenen provider için tekrar tekrar kodumuzu derlememiz gerekiyor. O zaman bunu gönderdiğimizde müşterinin istediği şekilde değişiklik yapabilmesi için eklediği her bir sınıfla birlikte bizim kodumuzu derlemesi gerekmekte. Ve bunları da yine buradan gördüğünüz üzere çalışma anında düzenleyebilmesi için bir şey yapamamış olduk.

Bu gibi sorunları çözebilmenin elbette bir çok yolu olabilir. Ancak ben bu yazıda sizlere Unity Application Block 'tan bahsetmek istiyorum.

 

Unity Application Block

Unity http://unity.codeplex.com/ adresinden erişebileceğiniz bir dependency injection container dır ve Microsoft Patterns&Practices tarafından geliştirilmektedir. Hakkında kısaca bahsetmek gerekirse, bizim application config üzerinde (web ise web.config, windows ise app.config) tanımladığımız arayüzlerin ve o arayüzlere karşılık gelen sınıfların çalışma anında bir kutuya yüklenmesini ve daha sonra ihtiyaç halinde ilgili arayüz için yüklenen sınıfın istenen kişiye verilmesini sağlar. Yani yazılımcı ya da müşteri, uygulamanın konfigurasyon dosyasına sizin belirlediğiniz arayüzünüze (bu uygulamada IStoreProvider) karşılık gelecek olan sınıfın tanımını yapıyor ve buna göre o arayüz çağrıldığında size o arayüz için kullanıcının tanımladığı sınıfın örneğini yaratıp verebiliyor. Bu da dikkat ederseniz, bizim az önce hakkında konuştuğumuz problemin ta kendisi!

Bu ihtiyacımıza yönelik güzel bir çözüm de bulduğumuza göre, unity 'yi biraz daha incelemeye koyulalım. Öncelikle konfigurasyon dosyasında nasıl bir değişiklik yapmam gerektiğine bakalım;

 

<?xmlversion="1.0" encoding="utf-8" ?> tsusur.com/Blog/editors/tiny_mce3/themes/advanced/langs/en.js" type="text/javascript">
<configuration>
    <configSections>
        <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,
                                                    Microsoft.Practices.Unity.Configuration"/>
 </configSections>  
<unity>
    <containers>
        <container>
            <type type="StoreClientLibrary.IStoreProvider,StoreClientLibrary"
                        mapTo="ServiceStoreProviderLibrary.ServiceStoreProvider, ServiceStoreProviderLibrary"/>
        </container>
    </containers>
</unity>

 

Konfigurasyon dosyasına baktığımızda en üstte yeni bir config section eklememiz gerektiğini görüyoruz. Burada Unity'nin gerekli bilgileri unity xml düğümünde okuyacağını bildiriyor. Bundan sonraki kısım ise az önce de bahsettiğim hangi tip için hangi nesnenin oluşturulacağı bilgisini tutuyor. Burada birden fazla container yaratıp bu container ların herbirini istediğiniz gibi hem uygulama ayağa kalkarken hem de çalışma anında değiştirebilmeniz de mümkün. Peki bu container'ı nasıl kullanacağımıza bir göz atalım;

 

IUnityContainer container = new UnityContainer();
UnityConfigurationSection unityConfig =
        (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
unityConfig.Configure(container);

 

Öncelikle bir UnityContainer yaratıyoruz. Ardından app.config üzerinden ilgili container a ait olan konfigurasyon bilgisini yüklüyoruz ve container ımızı uygun şekilde ayarlaması için configuration section a veriyoruz. Böylece konfigurasyon dosyasında tanımlamış olduğumuz tüm tipler container içerisine yüklenmiş oluyor. Bundan sonra aşağıdaki gibi istediğimiz tipi çağırabiliriz,

 

IStoreProvider provider = container.Resolve<IStoreProvider>();

 

Artık bu container içerisinden istediğimiz tipi kaldırıp yerine istediğimiz tipi tekrar koyabiliriz. Tabi böylece container ın bilgilerini konfigurasyon dosyasından çekmek zorunda değiliz. Aksine veritabanında da bunları saklayabiliriz. Şimdi de refactor edilmiş kod StoreWinApplication sınıfına bir göz atalım;

 

public class StoreWinApplication
{
    private IUnityContainer container = new UnityContainer();
    private IStoreProvider m_StoreProvider;
    public IStoreProvider StoreProvider
    {
        get
        {
             if (m_StoreProvider == null)
            {
                m_StoreProvider = container.Resolve<IStoreProvider>();
            }
            return m_StoreProvider;
        }
        set { m_StoreProvider = value; }
    }
    public StoreWinApplication()
    {
        UnityConfigurationSection unityConfig =
                (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
        unityConfig.Configure(container);
    }
    internal IStoreInformation GetStoreInformation()
    {
        return string.Format("Store Information: {0}", StoreProvider.LoadInformation());
    }
}

 

Burada artık container ımızı sınıfımız ilk ayağa kalktığında yaratıyoruz ve ondan sonra StoreProvider özelliğini yükleyebilir hale getirip yüklemiyoruz. İlk çağırım ile birlikte StoreProvider özelliği konfigurasyon dosyasından okunarak yükleniyor.

Böylelikle Dependency Injection nedir ve nasıl yapılabilir öğrenmiş olduk. Bunun yanında Unity Application Block ile ilgili olarak basit bir ön bilgiye de sah ip olmuş olduk. Tabi ki Unity ile yapılabilecek pek çok şey var. Ancak onu da başka bir yazıya bırakıyorum. Bu yazıda yapılan örneği de incelemek için buraya tıklayıp bilgisayarınıza indirebilirsiniz. Örnek projede Unity'nin son versiyonunun ihtiyaç duyulan bazı dll leri mevcut. Ancak unity hakkında daha fazla bilgi almak isterseniz http://unity.codeplex.com/ adresini ziyaret edebilirsiniz.

Test Driven Development ve Unit Testing

by msusur 9. Temmuz 2010 07:49

Bundan önceki yazılarımda bazı yerlerde, yazılan programların testlerinden çok üstün körü de olsa bahsetmiştim. Şimdi ise bu ihtiyacın neden ortaya çıktığından ve nasıl bir yöntemle yapılabileceğinden bahsetmeye çalışacağım.

Her yazılımcının eninde sonunda yazdığı kodları bir şekilde test ediyor olması gerekir. Ancak böyle yaptığı işin gerçekten çalışıp çalışmadığından haberdar olabilir. Daha sonra yazdığı kodlar bir ürüne dönüştüğünde de bunlara hızlı cevap verebiliyor olması o yazılımcı için ciddi bir önem taşımaktadır. Hatta kimi zamanlarda, yazılımcının önceden yaptığı bazı şeylerin yöntemlerini değiştirip bunların testlerini baştan yapması gerekir. İşte bu gibi durumlarda, yazılımcının herşeyi eliyle test ediyor olması hem bir zaman kaybıdır hem de hataya açık bir yöntemdir. Tabi bunun yanında hem eski kodların çalışabilirliğini kontrol etmek için hem de ihtiyaç kadar geliştirme yapabilmek için bazı yöntemler sunulmuştur. Bu yazıda onlara değineceğim.

 

Test Driven Development Nedir?

Test Driven Development (TDD), isminden de anlaşılacağı gibi test bazlı bir yazılım geliştirme yöntemidir. Bu yöntem, basit ve kısa adımlarla, önceden tasarlanmış senaryoları hızlı bir şekilde koda dökmeyi hedeflemektedir. Bunun yanında bu yöntemin pek çok avantajı da geliştirme sırasında fark edilebilir, ancak bunlara yazının ilerleyen kısımlarında değinmek istiyorum.

TDD, yazılımcıları basit ve küçük parçalardan oluşan tasarımlar yapmaya zorlamaktadır. Çünkü TDD'nin temel prensibi kodlama sürecini küçük parçalara bölmek üzerine kuruludur. Kent Beck bu yöntemi anlattığı "Test-Driven Development: By Example" isimli kitabında iki basit konsept üzerinde durmaktadır, 

 

  •  Elinizde başarısız bir test senaryosu olmadan asla tek satır kod bile yazmayın,
  •  Kod tekrarı yapmayın.

Bu iki basit madde ileride William Wake'in "Extreme Proramming Explored" adlı kitabında aşağıdaki algoritmaya dönüşmüştür, 

 

  1. Test kodunu yaz,
  2. Test kodunu derle, (bu aşamada kodun derlenemiyor olması gerekmektedir. Çünkü test kodunda yazdığımız hiç bir şeyi aslında henüz tanımlamadık)
  3. Test kodunun derlenmesine yetecek kadar kod yazıp kodu derle,
  4. Testleri çalıştır ve testlerin başarısız olduğunu gör,
  5. Sadece testlerin başarılı olmasını sağlayacak kadar tanım yap,
  6. Testleri tekrar çalıştır ve başarılı olduğundan emin ol,
  7. Kodun açıklayıcı olması ve tekrarlanmaması için gerekli düzenlemeleri yap,
  8. Bir sonraki adım için başa dön.

Buradaki adımlardan da görüldüğü üzere tüm tasarım, küçük küçük parçalara bölündüğü gibi her bir özellik de aslında bir o kadar küçük parçalardan oluşmaktadır. Buradaki asıl amaç, yazılımcının sistemli ve düzenli bir şekilde sadece yapmayı hedeflediği küçük parçaya hakim olması ve gerekli testleri bir kere geçtikten sonra da buraya bir daha dönmemesidir. Fakat bu adımlarda bahsedilen bir kaç küçük ayrıntıya da değinmek istiyorum, örneğin "kodun derlenmesine yetecek kadar kod yazmak" tam olarak ne anlama gelmektedir? Ya da testin başarılı olduğunu nasıl anlarım? Aslında bu yazıda buna da değinmek istiyordum ancak Burak Selim Şenyurt'un bu konuda hazırladığı görüntülü anlatımı izlediğimde burada ne dersem cılız kalacağından daha çok vereceğim örnek üzerine gitmemin daha iyi olacağına karar verdim. Bu yazıda bu teknik ile, analizi tamamlanmış bir liste nesnesini yapmaya çalışacağım.

 

Örnek Proje; Generic List

İlk etapta yapmamız gereken şey, hedefimizi belirlemektedir. Bu yazıda, hepimizin pek çok defa kullandığı .NET kütüphanesindeki generic listenin bir benzerini tasarlamaya çalışacağım. Generic listemiz aşağıdaki özelliklere sahip olmalıdır,

 

  • Liste, tanımlanırken verilen bir tipin koleksiyonu olmalıdır,
  • Liste, yaratıldıktan sonra içi boş olmalıdır,
  • Listeye yeni bir eleman ekledikten sonra listenin eleman sayısı da bir artmalıdır,
  • Listeden bir elemanı sildikten sonra listenin eleman sayısı da bir azalmalıdır,
  • Listeden eleman silme işlemi, indeks numarasını vererek ya da elemanın kendisini vererek mümkün olmalıdır.
  • Listenin içerisinden bir elemanın indeksini almak istediğimizde sıfır bazlı bir indeks olarak değer dönmeli,
    eğer bu eleman listenin bir elemanı değilse o zaman -1 dönmelidir.

Buraya kadar basit bir listeyi tanımladık ve aslında işin önemli bir kısmını bitirmiş olduk. Artık yapmamız gereken şey elimizdeki senaryoyu test koduna dökmek. Buna başlamak için Visual Studio ile yeni bir solution yaratıp, ilgili solutiona yeni bir test projesi eklemekle işe başlamamız gerekiyor. Bununla ilgili olarak yukarıda bahsettiğim görüntülü dersi inceleyebilirsiniz. Bundan sonraki ilk işimiz birinci adım için test kodunu yazmak olacaktır,

[TestMethod] 
public void InitializationTest() 
{ 
    GenericList<string> list = new GenericList<string>(); 
    Assert.AreEqual(list.Count, 0,
 "Liste ilk yaratıldığında listenin eleman sayısı sıfır olmalıdır."); 
}

Aynı şekilde sanki yukarıdaki listedeki maddeleri yazar gibi olmasını istediğimiz kod parçasını yazdık. Şu aşamada elimizde ne GenericList sınıfı ne de o sınıfa ait Count diye bir özellik mevcut. Şu anda sadece GenericList adında bir sınıf olması gerektiğini, bu sınıfın parametresiz yapıcı bir method ile örneklenmesi gerektiğini, bu nesnenin Count diye bir özelliğinin olmasını gerektiğini ve bu nesne yaratılırken bu özelliğin de değerinin sıfır olması gerektiğini yazdık. Bunu yaptıktan sonra projemizi derlemek istediğimizde C# derleyicisi bize derleme anında GenericList<string> gibi bir sınıfın olmadığından bahsedecektir ve tabi ki derlenmeyecektir. Bu problemi çözmek için hemen ilgili sınıfı yaratmaya koyulalım. Bu sınıfı yaratırken de Count diye bir özellik ekleyelim.

public class GenericList<T> 
{ 
    private int m_Count = 0; 
    public int Count 
    { 
        get{return m_Count;} 
    }
} 

Evet artık kodumuzu derleyip testi çalıştırdığımızda testi geçtiğimizi görebiliriz. Programımız ilk test senaryomuzu doğruladığına göre bir sonraki adım için başa dönüp, sonraki test senaryosunu yazmamız gerekiyor,

[TestMethod]
public void AddItemTest()
{
    GenericList<string> list = new GenericList<string>();
    list.AddItem("Yeni bir liste elemanı");
    Assert.AreEqual<int>(list.Count, 1, 
"Listenin eleman sayısı yeni bir eleman eklendiğinde bir artmalıdır.");
}

Yukarıda yine AddItem(string) imzasına sahip bir method varmış gibi düşünüp bu methodu kullandık. Burada da dikkat ederseniz yaptığımız şey, listeye bir eleman ekleyip listenin Count özelliğinin artıp artmadığını kontrol etmek oldu. Yine kodumuzu derlediğimizde hata alacağız. Hatayı gidermek için AddItem(string) imzalı bir methodu da aşağıdaki gibi ekleyebiliriz.

public class GenericList<T>
{
    private int m_Count = 0;
    public int Count
    {
        get{return m_Count;}
    }
    public void AddItem(T item)
    {

    }
}

Yukarıda görüldüğü gibi kodu derlenebilir hale getirdik ve içeriğini henüz doldurmadık. Burada yapmamız gereken şey testi yeniden çalıştırıp, testi geçemediğini görüp, testi geçmesini sağlayacak kodu yazmak olacaktır. Burada birazdan yapacağım şey aslında basit bir şekilde nesnenin içerisinde bir dizi tutmak ve dizinin elemanlarını eklenen her bir yeni eleman ile birlikte doldurmak olacaktır. Bunun çok fazla detayına girmeden aşağıdaki gibi bir ekleme yapıyorum,

public class GenericList<T>
{
    private T[] m_InnerItems = new T[0];

    private int m_Count = 0;    
    public int Count
    {
        get{return m_Count;}
    }

    public void AddItem(T item)
    {
        T[] newItemsArray = new T[m_Count + 1]; 
        Array.Copy(m_innerItems, newItemsArray, m_innerItems.Length);
        newItemsArray[m_Count] = item;
        this.m_InnerItems = newInnerList;
        m_Count++;
    }    
}

Yukarıda sınıfın içerisinde T tipinden oluşan bir dizi yarattım ve eklenen her bir elemanla birlikte bu diziyi genişletmek için yeni bir dizi yaratıp eski diziyi bunun üzerine kopyaladım. Böylece yeni eklenen elemanı da dizinin en sonuna ekleyip işlemi tamamladım. Bu işlemi de gerçekleştirdikten sonra testimi çalıştırdığımda testin başarıyla tamamlandığını gördüm. Bundan sonra yapmam gereken şey, özellikler listemizdeki bir sonraki özelliğe gidip onu da bu aşamalardan geçirmek olacaktır. Ancak ben bunun tamamını burada anlatarak yazıyı sıkıcı bir hale getirmek istemiyorum. Ancak bu örneğin tamamını  buraya tıklayarak indirebilirsiniz.

Eğer dikkat ederseniz yukarıda yazdığımız tüm methodlar ya da parçaları sadece ihtiyacımızı karşılamaya yönelik gelişti. Yani ihtiyacımız olmayan hiç bir şey ile vakit harcamadık, böylece yapmamız gereken işe tam bir konsantrasyon sağlayabildik. Ayrıca ileride Count özelliğini etkileyen bir değişiklik yaptığımızda tüm testleri çalıştırarak önceki yerlerde bir sorun yaşayıp yaşamadığımızı da o anda öğrenebiliyor olacağız. Bunlarda kullandığımız Assert sınfı üzerinde tanımlı olan neredeyse tüm methodlara aynı zamanda bir mesaj da ekliyebiliyor olmamız bize ileride hangi testin ne amaçla yazıldığı konusunda da fikir vermektedir.

Unit Testing bize, yukarıda da bahsettiğim gibi, daha stabil bir çalışma ortamı sağlamaktadır. Bu yüzden mümkün oldukça projelerimizde bunu kullanıyor olmamız projelerimizde hata çıkmasını önlemektedir. Ancak elbette her uygulama yukarıdaki örnek kadar basit olmayabilir. Örneğin kullanıcı arayüzü olan bir projede unit testin nasıl sağlanabilir? Bunun için düşünülmüş ve çözüm olarak sunulmuş tasarım kalıplarından biri olan MVP yi önceki yazımda anlatmıştım. Yani yapmak istediklerimizi ne kadar çok parçalarsak, buna bağlı olarak o kadar test yapmaya uygun bir hale gelmektedir.

Bir sonraki yazımda bu konuya biraz daha detaylı yaklaşacağım. Aynı zamanda biraz daha ileri test methodlarını inceleyip, bu metholarla birlikte bir MVP tasarım kalıbını TDD ile nasıl oluşturabiliriz ondan bahsediyor olacağım. Bir sonraki yazıda görüşmek üzere.

 

MVP ve Asp.net

by msusur 6. Temmuz 2010 08:57

Ülkemizde son zamanlarda gittiğim, konuştuğum pek çok yer ve kişide bazı nesneye yönelik programlama tekniklerinin önem kazandığını duyuyorum. Ancak yine pek çok yerde, bazı kendi projelerim de dahil olmak üzere, hala daha bazı başarısızlıklar hüküm sürüyor. Örneğin kullanıcı arayüzünün kolayca yapılan projelere adapte olamaması bunlardan biri.

Kullanıcı arayüzü katmanı gün geçtikçe projelerde daha fazla önem kazanmaya başlıyor. Hatta yeni teknolojilerle birlikte arayüz katmanında yapılabilecek şeyler çok daha artmaya ve güçlenmeye de başladı. Örneğin artık neredeyse web formlarımızı da sanki birer windows formmuş gibi kullanabiliyoruz hatta wpf teknolojisi ile birlikte çok daha zengin ve kolay tasarlanabilir windows formları yapabiliyoruz.

Ancak bu teknolojiler ne kadar ilerlerse ilerlesin, bunların bize sunduğu gücü uygun şekilde yönlendirip ihtiyacımız doğrultusunda kullanamazsak bize kolaylık sağlamasının aksine işimizi çok daha zora sokabilir ve yaptıklarımızla kodlar içerisinde kaybolabiliriz. Güçlü trendlerden biri olan Test Driven Development (TDD)’ ı uygulamamız da bir o kadar zor olacaktır.

Aslında önermeye çalıştığım modeli biraz daha anlatabilmek için basit bir diyagram üzerinden gitmeyi faydalı görüyorum;

MVP Diagram

 

Yukarıda çizmeye çalıştığım diyagram aslın da 90 lı yılların başında sunulmuş bir tasarım kalıbı olan MVP’ ye (Model-View-Presenter) aittir. Peki ya MVP nedir? MVP; bir kullanıcı arayüzü tasarım desenidir ve asıl amacı unit testing (bir sonraki yazımda bundan daha detaylı olarak bahsedeceğim) ile test edilebilir kullanıcı arayüzleri tasarlamak ve önceki yazımda da bahsettiğim “Seperation of Concern” kavramını arttırmaktır. Peki ya Model, View ve Presenter ne anlama gelmektedir? Model, ekranda gösterilen veriyi tanımlayan bir arayüzdür. View, Kullanıcı arayüzünde modeli gösteren ve kullanıcı aksiyonlarını presenter (sunum) katmanına gönderen ve bu modelin değişikliğini yöneten katmandır. Presenter ise modelden aldığı verileri formatlayıp, modifiye edip view üzerinde gösterilmesini sağlar.

Peki ya böyle bir yapıyı kendi projelerimde nasıl uygulayabilirim? Ya da daha önemlisi elimdeki mevcut bir projeyi bu yapıya uygun hale nasıl getirebilirim? Bunun için basit bir senaryoyu ele alalım. Bir tasarım yaptınız ve projenizi tamamladınız. Ancak yeni versiyonunda MVP gibi bir tasarım desenini kullanarak yaptığınız uygulamayı daha iyi test edilebilir ve böylece daha stabil bir hale getirebilirsiniz. Hatta zaten elinizde yeni versiyonda yapacağınız her değişiklik ya da yenilik bir doküman halinde varsa ve bir takım kuralları zaten önceden kestirebiliyorsanız, TDD yaparak çok daha hızlı ve güvenli bir şekilde uygulamanızı geliştirebilirsiniz. Şimdi de basitçe bir takım konseptlerden bahsedip ardından sözünü ettiğim MVP tasarım desenini uygulamaya çalışcağım.

Şimdi öncelikli olarak basit bir senaryo olan kişi detay formunu ele alalım. Bir tane sayfamız olsun ve ilgili sayfamızda gelen kişiye ait olan bir takım verileri yükleyelim. Bunu yapabilmek için öncelikle asp.net sayfamızı, yani view imizi hazırlamaya koyulalım. Öncelikle IPersonView isminde bir arayüz hazırlıyorum ve burada kişi formunda olmasını beklediğim bir takım özellikleri de tanımlıyorum.

mvp.IView

Böylece view için gerekli arayüzü hazırlamış olduk. Bundan sonra bir presenter ile işlemleri yönetmemiz gerekiyor. Ancak burada dikkat etmemiz gereken bazı noktalar var. Öncelikle view üzerindeki verileri doldurabilmek için parametrik olarak bir yerlerden bazı değerler yüklemem gerekiyor. Dolayısıyla bir veritabanı üzerinden ya da bir dosya üzerinden bir şekilde bu verileri yüklemem gerekiyor ardından yine bir yöntem ile bu yüklediğim verileri bir entity nesnesine doldurup ardından IPersonView arayüzü ile eşleştirip web sayfam üzerinde değişiklikleri göndermem gerekiyor.

Dikkat ederseniz, burada birden fazla iş var. Ancak bunların hepsini bir presenter yapısının hazırlaması ve yönetmesi gerekiyor. Ancak tabi ki yine buradaki işlemleri parçalara bölmeye çalışacağız. Mümkün olduğunda çok parçaya bölmemiz bize ileride çok ciddi kolaylık sağlayacaktır. Tabi burada neden bir entity nesnesine doldurduğumu sorabilirsiniz. Bunun nedeni arka tarafta yapılan veritabanı işlemlerini soyutlaştırıp, araya bir entity katmanı eklemek. Böylece veritabanı işlemlerini yöneten ayrı bir katman daha sağlamış oluyorum. Bunun bana faydası da elbette merkezi noktalar dışında veritabanı işlemi yapmamış olmak oluyor.

Peki öyleyse işe koyulup kodu yazmaya devam edelim. Ne demiştik;

a. İstediğimiz kişiye ait olan kayıt için entity leri veritabanından yükleyecek bir sınıfa (IPersonOperator)

b. Bize kişi entity sini veritabanından yükleyecek olan bir sınıfa (IPersonDataMapper)

c. Yüklenen entity ile view arasındaki ilişkiyi tutan ayrı bir sınıfa (PersonViewPresenter)

ihtiyacımız olacak.

Biraz daha önceden deneyimleme şansım olduğu için ve daha anlamlı olması açısından en içten başlayıp öncelikle IPersonDataMapper arayüzünü ve bunu implemente eden sınıfı tasarlarayak işe başlamak istiyorum.

mvp.IPersonDataMapper

Yukarıdaki kod örneğinden de görülebileceği gibi IPersonDataMapper arayüzü veritabanından IPerson tipinde bir sınıf yükleyeyip bunu bir üst kısıma vermek görevini üstlenmiş durumda. Bunu yaparken de kullanıcıdan bu kaydı yükleyebilmek için bir primary key değeri bekliyor. Peki bunu yapan sınıf nasıl bir davranış sergiliyor,

mvp.PersonDataMapper

 

Bu sınıfın amacı az önce de söylediğim gibi veritabanına s orgu çekmek ve veritabanından çektiği sorgu sonucunda ulaştığı bir IPerson nesnesi yüklemek. Bu işlem DatabaseHelper sınıfında yapılmaktadır. DatabaseHelper sınıfı önce loadFromDb methodu ile birlikte veritabanından bir DataTable çeker, ardından bu DataTable’ı Person nesnesinin içini doldurmak için kullanır. Buradaki DatabaseHelper sınıfı sadece veri üretmek için eklenmiştir ve bu örnekte hiç bir veritabanı işlemi yapmamaktadır. Basit bir şekilde hard coded olarak bir datatable üretir ve bunu nesneye yükler. Ardından reflection ile Person nesnesinin tüm property lerini gezer ve bunların hepsini datatable ın bir satırından doldurur. (Detaylı incelemek için kod örneğine bakabilirsiniz.) Bu sınıf bizim katmanlarımızın en alttakini oluşturmaktadır. Bu katman veri erişim katmanı ile direk iletişime geçer ya da arada bir entity katmanı kullanır. Yukarıdaki örnekte veri erişim katmanına direk erişim sağladığı düşünülmüştür. Ancak tabi ki burada NHibernate vs. gibi ORM araçlarını da kullanabilirsiniz. Son olarak bu sınıfı ve arayüzünü kendi projelerinizde tasarlarken dikkat etmeniz gereken şey sadece ihtiyaç duyduğumuz methodları buraya eklemek olacaktır. Burasının sadece veri çekip bu verileri bir üst katmana taşıma görevini üstleneceğini ve elbette yalın kalması gerektiğini hatırlatmayı gerekli görüyorum.

Artık verileri çekebilece k bir sınıfımız mevcut. Bundan sonra yapmamız gereken şey veritabanından yüklenen nesneyi view entity mize dönüştürmek olacaktır. Bunun için hatırlarsanız yukarıdaki sıralamada IPersonOperator arayüzünü implemente eden bir sınıfa ihtiyaç duyacağımızı anlatmıştım. Peki bu arayüzde nelere ihtiyacımız var şimdi ona bir göz gezdirelim,

mvp.IPersonOperator

Bu arayüzde yapmamız gereken daha doğrusu üstlenmemiz gereken görev, elimizdeki view i anlamlandırmaya yetecek olan minimum sayıdaki veriyi kullanarak PersonView PersonViewPresenter nesnemizin anlayabileceği şekilde bir çıktı üretmek. Burada anlamlandırabileceğimiz çıktı bir PersonEntity nesnesi. Birazdan bu nesnenin içeriğini de inceleyeceğiz ancak öncelikle söylemeden edemeyeceğim, Operator sınıfın, DataMapper sınıfından gelen veriyi anlayıp buna dönüştürmesi gerekmektedir. Bu yüzden de birisinin operator sınıfını yaratırken hangi DataMapper sınıfını kullanarak işlem yapacağını bildiriyor olması lazım. Böylece ilerleyen zamanlarda DataMapper yöntemi değişirse, yani veriyi alacağımız kaynak farklılaşırsa bu sınıfımız bu değişiklikten etkilenmemiş olacaktır.

mvp.PersonDataMapper

Yukarıda az önce bahsettiğim şeyi görebiliyoruz. Yani ben burada hangi DataMapper nesnesi kullanacağını constructor’ında tanımladım. Ancak siz bunu bir konfig urasyon dosyasından almak da isteyebilirsiniz. Bu yüzden bu kısmın ucu tamamen açık bırakıldı. Ayrıca dikkat ederseniz LoadPerson methodunda yapılan şey, tamamen amacına uygun. Yani Operator sınıfı DataMapper sınıfına hangi veriyi alacağını bildiriyor ve karşılığında bir IPerson nesnesi alıyor ve daha sonra da bunu PersonEntity nesnesine dönüştürüyor. Böylece bir üst katman olan yani view öncesinde view den gelen operasyona göre veriyi view e yükleyecek olan PersonViewPresenter nesnemiz için de herşey hazır hale gelmiş oluyor. Bundan sonra Presenter katmanımız PersonEntity nesnesini alacak ve kendi bildiği IpersonView nesnesine ilgili alanlarına dolduracak. Bu işlemin nasıl yapıldığını yine aşağıdaki kod parçasında görebilirsiniz.

mvp.PersonViewPresenter

Burada dikkat etmemiz gereken bir kaç nokta var. PersonViewPresenter nesnesi yapıcı methodu içerisinde bir tane IPersonView instance ına ihtiyaç duymakta. Yani Presenter’ın görevini yerine getirebilmesi en az bir view e ihtiyacı var. Bunun yanında bir de operator sınıfına ihtiyacı var. Ancak elbette az önce söylediğim gibi bu operator sınıfı bir konfigurasyon üzerinden yüklenebilir. Daha sonra LoadPersonDetail() methodu ile birlikte presenter verileri operator sınıfı üzerinden yüklemekte. Burada bir de view üzerindeki SelectedPersonId property si kontrol ediliyor. Buradan gelen değere göre ilgili operator sınıfına primary key veriliyor ve o da gidip kendi bildiği şekilde veriyi okuyor. Dikkat ederseniz burada presenter verinin nereden okunduğu konusunda bilgi sahibi değil. Yani okunacak olan veriden tamamen soyutlanmış. Veritabanı işlemlerine de karışmıyor. Onun ilgilendiği tek şey bir adet PersonEntity nesnesi. Bu durumda bu sınıfımız sadece gelen entity nesnesini kendi bildiği view üzerine eklemekle yükümlü. Eğer sayfamız üzerinde bind edilmesi gereken başka nesneler olsaydı (GridView, TreeView, DropDownList vs. ) onların da yüklenmesi işlemi yine bu presenter tarafından operator sınıf üzerinden yapılacaktı. Elbette bu durum bizim hazırladığımız DataMapper sınıfını da etkileyecek bir değişiklik olacaktı. Bununla ilgili örneği bir sonraki yazımda yapmaya çalışacağım.

Artık elimizde herşey hazır, tek yapmamız gereken yeni bir view tasarlamak. Bunun için yaptığım şey basit bir asp.net web application hazırlamak oldu. Daha sonra ilgili view arayüzümüzü bu sayfa üzerinde implemente ettim. Daha sonra bu pattern’ın bize sağladığı en güzel şey olarak bir benzerini de windows forms üzerinde yaptım. Ve kodun diğer hiç bir tarafına dokunmadan yine aynı view i bir windows form üzerinde implemente ettim ve yine çalıştığını gördüm. Öncelikle asp.net formumuzu inceleyelim,

mvp.View

 

Burada yaptığımız şey hepsinden çok daha basit. Sayfamız initialize olurken, bir tane presenter yaratıyoruz. Bu yarattığımız presenter’a da sayfamızın kendisini veriyoruz. Bu durumda artık presenter katmanımız hangi view üzerinden işlem yapacağını biliyor. Daha sonra SelectedPersonId property’sinin getter’ında seçilen kullanıcı numarasını alabilmesi için query string üzerinden yükleyebilmesi için gerekli şekilde bir hazırlık yapıp sayfanın Page_Load olayında bu işlemi tetikliyoruz. Dikkat ederseniz burada yazdığımız kod neredeyse yok. Sayfamız ne nasıl şekilde verileri yükleyeceğini biliyor, ne de yüklenen verileri nasıl ve nereye eşitleyeceğini biliyor. Tüm bilgi presenter üzerinde saklanıyor. Ayrıca burada yine dikkat ederseniz presenter katmanımız da hangi view i yüklediğinden habersiz çalışıyor. Peki bu tam olarak ne anlama geliyor? Şu anda bir asp.net web form’unu yükledik. Ancak ilerleyen zamanlarda tek bir satır kod değişikliği yapmadan bunu aynı şekilde bir windows formunu yüklemek için de kullanabiliriz. Ayrıca burada yaptığım örneğe ait bir solution dosyasını buradaki link’e tıklayarak edinebilirsiniz. Bu örneğin içerisinde bu makalenin tüm kodları asp.net ve windows forms üzerinde uygulanmış örnekleri yeralmaktır.

Yönelimlerin Tarihçesi

by msusur 2. Temmuz 2010 08:09

Bilgisayarlar varolmaya başladıktan sonra bir takım yazılımların da onlarla birlikte varolması kaçınılmaz bir hal almaya başlamıştı. Ancak ilk etapta bu makineleri programlamak o kadar da kolay değildi. Kimi zaman bazı entegreleri değiştirerek kimi zamansa üç dört harften meydana gelen komutları peşi sıra dizmekle bu işlem gerçekleşiyordu. Elbette her adımda bilim insanları bu konularda çalışmalar yapmış ve belli çözümler üretmeye başlamışlardı. Nispeten bu çözümlerin birazcık da olsa sonuna yetişmiş biri olarak pek çok yazılım problemini deneyimleme şansına erişebildiğim için kendimi çok şanslı hissediyorum.

Yazılım geliştirmeye BASIC (Beginner’s All-purpose Symbolic Instruction Code; evet adı kendisi kadar kolay değil!) ile başlamış biri olarak git gide büyüyen programların nasıl yönetilememeye başladığını görmemiş olsaydım belki de şu anda hala daha neden nesneye yönelik bazı metodları kullanmam gerektiğini tam olarak anlayamıyor olabilirdim. Ancak ne güzel ki bunları yaşamış ve sıkıntılarını görmüş ve artık nerelerde nasıl yöntemler kullanmam gerektiğini algılayabiliyorum hatta buna bağlı olarak nerelerde nasıl soyutlamalar yapmam gerektiğini çok daha kolay algılayabiliyorum.

Nesneye Yönelim,

                Evet, herşeyin aslında nesne olduğunu düşünün. Yarattığınız her sınıfın, kullandığınız her şeyin aslında yaşayan birer nesne olduklarını ve onların birbirleri arasında da haberleşebildiklerini hatta bir nesnenin aslında başka nesneler olarak da varolabildiğini, bir takım özellikleri de atalarından miras aldıklarını düşünün. Herkesin tek bir işten mesul olduğunu ve bu uyumları sayesinde eskilerinin yerine yenilerinin çok kolay bir şekilde adapte olabildiklerini düşünün. Yine düşünün, bir nesnenin sadece bir tane olduğunu ve o spesifik işi yapabilmeleri için birbirlerini özelleştirebildiklerini düşünün.

                Peki yazılım üretmeye bu pencereden bakarsak neleri çözebiliriz? Böyle düşünmemizin bize projelerimizde ne katkısı olur? Aslında bu sorulara cevap verebilmek için ben size bir kaç soru sormakla başlamak istiyorum; Kod yazarken hiç kopyala yapıştır yaptığınız oluyor mu? Yeni bir projeye başlarken herşeyi yeniden baştan düşünüp tekrar tekrar mı yazıyorsunuz? Kodlarınıza baktığınızda bir sınıf içerisinde binlerce satır kod görüyor musunuz? Eğer bu sorduğum sorulara “evet” cevabını verdiyseniz muhtemelen bir şeyler yanlış gidiyordur. Lütfen bundan sonrasını daha dikkatli okumaya gayret edin.

                Nesneye yönelik programlama bu gibi sorunları çözüp, tekrar kullanabilirliği(reuseability) arttırmak adına bir çözüm olarak sunulmuştur. Ancak kimi durumlarda yetmediği bazı noktalar da vardır. Birazdan bunlardan da bahsediyor olacağım. Ama öncelikle aşağıdaki örneği incelemenizi rica ediyor olacağım;

                Diyelim ki bir proje geliştiriyorsunuz. Projenizi bitirdiniz teslim ettiniz. Ancak müşteriniz ya da sponsorunuz size yaptığınız programın bazı noktalarında performansın çok düşük olduğunu ve bunların da bir şekilde takip ediliyor olması gerektiğini söyledi. Buna bağlı olarak da sizden bununla ilgili bir geliştirme talebinde bulundu. Sizde kolları sıvadınız ve sadece oop tekniklerini en doğru şekilde kullanıp bir çözüme doğru ilerlemeye koyuldunuz. Bunun için hemen bir PerformanceTracker nesnesi yaptınız ve sorun olduğunu düşündüğünüz her metodun içerisine bu performans takip kodlarını yazmaya koyuldunuz. Ancak bunun uygulamanız gereken neredeyse elli metod mevcut ve ne yazık ki bir sebepten ötürü bunları ortaklaştırmanız da mümkün değil. O zaman başınızı eğip klavyenin başına geçtiniz ve tamamen “oop tekniklerini kullanarak kopyala yapıştır“ yapmaya başladınız ve aşağıdaki gibi bir sonucu elde ettiniz,

public void ExecuteScript(string scriptCode)
{
                PerformanceTracker.Begin(“ExecuteScript”, scriptCode);
                .....
                PerformanceTracker.End();
}

Bu yaptığınızı elli method üzerinde tekrar ettiniz. Evet çözüm buldunuz. Ancak sizden sonra gelen programcıların da bu yöntemi kullanması gerektiğini onlara anlatmanız ve aynı zamanda bundan sonraki her method için bunu tekrarlamanız gerekmekte. Tamam. Müşteriniz bu durumdan çok memnun kaldı, problemler çözüldü. Bir süre sonra müşteriniz yine size geldi ve “Methodları kimlerin çağırdığını ve çağıran kişilerin bu methodu çağırmaya izni var mı yok mu onu da ayırt etmek istiyorum.” gibi masum bir istekte daha bulundu. Bunun üzerine siz yine kolları sıvadınız ve methodunuzu aşağıdaki şekle sokmaya başladınız,

public void ExecuteScript(string scriptCode)
{
                AuthenticationManager.Authenticate();
                PerformanceTracker.Begin(“ExecuteScript”, scriptCode);
                .....
                PerformanceTracker.End();      
}

Benzer şekilde bunu da elli method içerisinde tekrar ettiniz. Tebrik ederim! Şu anda müşterinizin her isteğini karşılamış durumdasınız. Peki sizce bir şeyler yanlış gitmiyor mu? Yani her adımda bir sürü şeyi tekrar tekrar yazmak zorunda kaldınız. Muhtemelen kopyala yapıştır da yaptınız. Peki ya bu durum sizce nesneye yönelik programlama’ya karşı bir durum değil mi?

Kesinlikle öyle. Bu durumu çözebilecek başka bir takım yöntemler elbette mevcut, hatta oop içerisinde bunları çözebilecek bazı tasarım desenleri (design pattern) bile var. Ancak içlerinden benim en çok beğendiğim yaklaşım, Aspect Oriented Programming (AOP).

Bakış Açısı,

                Aspect kelime anlamı gereği, bakış açısı anlamına gelmektedir. Aspect oriented da “Seperation of Concern” (İlişkilerin ayrılması) yaklaşımıyla ortaya çıkmış bir metodolojidir. İlgilendiği şeylerle nesneye yönelik programlamanın üstüne eklenmiş bir tuğla daha gibidir ve en temel amacı tekrar kullanılabilirliği daha da üst düzeylere çıkartmaktır. Peki yukarıdaki problemi aspect oriented bir şekilde nasıl çözebilirdik? Şimdi şu durumda konuyu biraz daha kolaylaştırmaya çalışalım. Diyelim ki elinizde bir attribute olsun ve bu attribute ile işaretlediğiniz tüm sınıflar bir anda ilgili bir özelliği tanımlamaya koyulsunlar. Ve bu özelliği de ancak eklenen attribute varoldukça tanımlasınlar. Diğer hiç bir durumda bu özellik çalışmasın. Nasıl mı? Buyrun bir örneği,

 

[PerformanceTracker]
public class ScriptInvoker : ContextBoundObject
{
                public void ExecuteScript(string scriptCode)
                {                             
                               .....
                }
}

Yukarıdaki durumda dikkat ederseniz method’umuz tamamen işine yönelmiş oldu. Yani kendi yapması gerekenden başka bir işi üstlenmemiş durumda. Dolayısıyla bakımını yaparken ya da yeni bir takım methodlar eklerken ilgili sınıf dahilinde hiç bir ekstra işlem yapmamış oluyoruz. Bu durumda bu sınıfın geliştirilmesinden sorumlu olan yazılımcı kesinlikle performansı nasıl takip ettiğinden de haberdar değil. Sizce de daha güzel bir çözüm olmadı mı? Peki bu çözümü gerçekten nasıl uygulayabiliriz isterseniz şimdi de o konuda .Net ‘in bize sunduğu bir kaç güzelliğe göz atalım.

Dikkat ederseniz bu durumda nesnemiz ContextBoundObject adında bir nesneden türemiş durumda. Peki nedir bu ContextBoundObject? Bir sınıf eğer ContextBoundObject nesnesinden türüyorsa, üzerindeki metodları stack-based çalıştırma modelinin aksine mesajlar göndererek çalıştırmaya çalışır. Böylece ilgili sınıfı bu nesneden türeterek yeni bir instance ı yaratıldığında ve herhangi bir metodu çağrıldığında aradaki mesajları yakalamış olabiliriz . Bu özellik daha çok remoting ve COM interoplarında kullanılır. Zaten dikkat ederseniz ContextBoundObject nesnesi MarshalByRefObject nesnesinden türemiştir. Bu nesneden türetmemiz durumunda .net çalışma zamanında ilgili nesnemiz için ayrı bir tane context oluşturur ve bu context nesnemiz dispose olana kadar nesnemizle birlikte hayatta kalır.

Nesnemizi ContextBoundObject nesnesinden türettikten sonra yapmamız gereken artık aspect dediğimiz nesneleri hazırlamaktır. Bu durumda aspect dediğimiz şeyler bizim nesne üzerine ekleyeceğimiz bazı özel attribute sınıfları olacaktır. Böylece bunları bir sınıfa “uygulayabilir” hale gelmiş olacağız. Bunu yapabilmek için yine .net üzerindeki ContextAttribute sınıfından türeyen bir sınıf yapacağız. Böylece context oluşturulduğunda bir takım özellikleri implemente edeceğimiz IContextProperty leri context üzerinde çalışır hale getirmiş olacağız. Bunun için IContextProperty  ve IContributeObjectSink arayüzleriyle işlem yapıyor olacağız.

Aslında burada çok fazla kod yazıp kafa karıştırmak istemiyorum. Bu yüzden kod örneğini buradan indirebilirsiniz. Ben kısaca çalışma mantığından bahsedip bu yazıyı tamamlayacağım;

Şu anda elimizde bir sınıfımız var ve bu sınıfımız ilk kez örneklenirken bir context oluşturuyor. Bunu gerçekleştirirken de biz bu context in içine bir takım context property ler ekliyoruz. Bu property lerin ekleneceğini de sınıfımızın üzerine yazdığımız bazı attribute lar ile gerçekleştiriyoruz. Bu durumda bunu gerçekleştirebilmek için ;

1.       ContextBoundObject nesnesinden türemiş bir sınıfa,

2.       ContextAttribute ten türemiş bir attribute sınıfına –ki böylece context üzerine yeni özellikler kazandırabilelim,

3.       Mesajları ilgili aspectlere gönderip çalıştırabilecek  bir ContextProperty sınıfına,

4.       Ve sonunda mesajların gönderilmesinden önce gelen mesajları işleyip istediğimiz aksiyonu almamızı sağlayacak olan ve bizim aspect sınıfımız olan bir MessageSink sınıfına ihtiyacımız olacak.

Bu ihtiyaçları karşıladıktan sonra adım adım nasıl çalıştığını izlersek aşağıdaki sıralamaya ulaşabiliriz,

1.       Nesnenin instance ı alınırken üzerindeki attribute lar okunur.

2.       Attribute lar nesnenin ilgili context üzerine kendi bildikleri context özelliklerini eklerler,

3.       Bir metodun çağrımı esnasında oluşturulan mesaj öncelikle bu property üzerinden geçer ve o sırada GetObjectSink metodu ile birlikte ilgili Aspect nesnemiz olan ve IMessageSink ten implemente olan nesne yaratılır.

4.       Mesajın gönderilip, bir sonraki mesajın aktarılabilmesi için mesaj ilgili MessageSink nesnesine gönderilir ve orada işlenir.

 

Böylece yaptığımız bu kısa işlem sonrasında hangi metodun ne zaman çağrıldığını, izin kontrollerini vs. gibi bir takım özellikleri sınıfın içine tek satır kod yazmadan gerçekleştirmiş oluruz. Böylece sınıfımız kendi işine odaklanır ve sadece kendi işinden sorumlu hale gelmiş olur böylece Single Responsibility prensibini de tam manasıyla karşılamış oluruz.

 

Bir sonraki yazıda görüşmek üzere.

Başlangıç

by msusur 30. Haziran 2010 06:42

Merhaba, çok uzun zamandır planladığım ancak bir türlü açma fırsatını yakalayamadığım şey olan günlüğümü sonunda açmayı başardım. Bu konuda bana destek olan ve yüreklendiren herkese çok teşekkür ederim. 

Bu durumda ilk yazımda bencilce  kendi hakkımda birşeyler yazmayı uygun gördüm.

Bilgisayara olan ilgim çok uzun zamanlar önce farkında olmadan başladı. Yavaş yavaş etrafımı tanımaya başladığım dönemlerden itibaren bilgisayarlar hep hayatımın içerisinde yeraldı. İlkokuldayken babamın asla dokunmama izin vermediği ve inanılmaz önemsediği bilgisayarına dokunmak için can atardım. Belki bu yüzden babamın yanına çağırıp bilgisayar kullanmayı öğrettiği o günü asla unutamıyorum. Gecelerce rüyamda enter ve escape tuşlarına basıp space tuşu ile boşluk bıraktığımı görmüştüm. Bunun benim hayatımdaki bir dönüm noktası olduğunu anlamam neredeyse iki yılımı aldı. Bunu yaşadıktan tam iki yıl sonra Compaq LTE laptop ve bir gw-basic kitabını yaz tatili için bana verdiğinde yaşadığımı heyecanı eminim tahmin edebiliyorsunuzdur. Gw-basic editorunde kitaptaki herşeyi bire bir kopyalayıp çalıştırmaya çabaladığım o günlerde, elbette elimdeki o bilgisayarla neler yapabileceğimi hayal bile edemiyordum. 

Daha sonra ortaokula başladığım zamanlarda artık teknoloji ilerlemiş, çevremde bilgisayarlar git gide yaygınlaşmıştı. Ms-Dos ile başladığım serüvenim, windows 3.1, windows 95 ile devam ediyordu. Tabi bunun getirdiği teknolojik yeniliklerden de geri kalmamak için yavaş yavaş Borland Delphi'ye ve Visual Basic'e yönelmiştim ve her nasılsa bir yerlerden Delphi 2 edinmiş, ancak nasıl kullanacağımı henüz öğrenmeye başlayamamıştım. Yalova gibi küçük şehirlerde bu tarz şeyleri yapanlarla çok sık karşılaşılmıyordu, bu yüzden etrafımda yardım isteyebileceğim pek kimseyi bulmam da mümkün olmuyordu. Tabi çevrede kimse bu konularla ilgilenmediği için etrafta bunlarla ilgili dokümanlar ya da kitaplar da bulamıyordum. Ben de kendi kendime kurcalamaya ve çat-pat İngilizcem ile Delphi'nin yardım dokümanlarını okumaya başladım. Ancak çok fazla ilerlemeyemiyordum. Yine bir gün babam İstanbul'dan, elinde bir Delphi 3 kitabıyla çıkageldi. Kitabı okumaya başladım ancak Delphi 2 ile Delphi 3 arasında ciddi farklar vardı ve ben de bu durumu bir şekilde çözmeye çalışıyordum. Bu dönemlerde tanıştığım bilgisayar öğretmenim sayesinde pascal öğrenmeye başladım. Pascal çok daha kolaydı ve basic'e nazaran çok daha hızlı gelişme gösterdim. Ardından C öğrenmeye başladım ve sonunda  C++ öğrendim. İpin ucunu yakalayabildikten sonra bir anda ilerlemeye ve hızlanmaya başladım. 

Lise yıllarımın sonunda, Üniversite'ye yeni başladığım yıllarda C# ile tanıştım ve bir anda inandığım bildiğim herşey değişmeye başladı. Çok uzun bir zaman sonunda internetten indirdiğim kod örneklerini bir kerede çalıştırabiliyordum. Bu benim için o kadar kıymetli birşeydi ki kelimelerle anlatmamın çok mümkün olacağını düşünemiyorum. Herşeye yeni bir pencereden bakmaya başladım ve ivmem bir anda yeniden arttı.

Yaklaşık dokuz on yaşlarımdan beri bilgisayarlarla iç içe bir hayat yaşama şansına sahiptim. Bu yüzden yazdığım bir kodun bir kerede çalışabiliyor olmasından inanılmaz bir haz alabiliyorum. Yıllardır amatör anlamda sayısız satır yazdım, kimileri bomboş saçma sapan şeylerdi, kimileri ise hiç birşey ifade etmiyordu belki. Bilgisayarım silmeye kıyamadığım başlanmış ama bitirilememiş onlarca projeden dolup taşıyor. 

Bu günlüğü yapmamdaki asıl amaç, bu heyecanımı en az benim kadar heyecanlı kişilerle paylaşabilmek ve eskiden yaşadığım kaynak sıkıntısını mümkün oldukça başkalarının yaşamamasını sağlamak. Bu yüzden dilediğiniz konuda bana mail gönderebilirsiniz, elimden geldikçe bunlara cevap vermeye çalışacağım. 

Başlangıç

by msusur 16. Şubat 2010 17:46

Merhaba, çok uzun zamandır planladığım ancak bir türlü açma fırsatını yakalayamadığım şey olan günlüğümü sonunda açmayı başardım. Bu konuda bana destek olan ve yüreklendiren herkese çok teşekkür ederim. 

Bu durumda ilk yazımda bencilce  kendi hakkımda birşeyler yazmayı uygun gördüm.

Bilgisayara olan ilgim çok uzun zamanlar önce farkında olmadan başladı. Yavaş yavaş etrafımı tanımaya başladığım dönemlerden itibaren bilgisayarlar hep hayatımın içerisinde yeraldı. İlkokuldayken babamın asla dokunmama izin vermediği ve inanılmaz önemsediği bilgisayarına dokunmak için can atardım. Belki bu yüzden babamın yanına çağırıp bilgisayar kullanmayı öğrettiği o günü asla unutamıyorum. Gecelerce rüyamda enter ve escape tuşlarına basıp space tuşu ile boşluk bıraktığımı görmüştüm. Bunun benim hayatımdaki bir dönüm noktası olduğunu anlamam neredeyse iki yılımı aldı. Bunu yaşadıktan tam iki yıl sonra Compaq LTE laptop ve bir gw-basic kitabını yaz tatili için bana verdiğinde yaşadığımı heyecanı eminim tahmin edebiliyorsunuzdur. Gw-basic editorunde kitaptaki herşeyi bire bir kopyalayıp çalıştırmaya çabaladığım o günlerde, elbette elimdeki o bilgisayarla neler yapabileceğimi hayal bile edemiyordum. 

Daha sonra ortaokula başladığım zamanlarda artık teknoloji ilerlemiş, çevremde bilgisayarlar git gide yaygınlaşmıştı. Ms-Dos ile başladığım serüvenim, windows 3.1, windows 95 ile devam ediyordu. Tabi bunun getirdiği teknolojik yeniliklerden de geri kalmamak için yavaş yavaş Borland Delphi'ye ve Visual Basic'e yönelmiştim ve her nasılsa bir yerlerden Delphi 2 edinmiş, ancak nasıl kullanacağımı henüz öğrenmeye başlayamamıştım. Yalova gibi küçük şehirlerde bu tarz şeyleri yapanlarla çok sık karşılaşılmıyordu, bu yüzden etrafımda yardım isteyebileceğim pek kimseyi bulmam da mümkün olmuyordu. Tabi çevrede kimse bu konularla ilgilenmediği için etrafta bunlarla ilgili dokümanlar ya da kitaplar da bulamıyordum. Ben de kendi kendime kurcalamaya ve çat-pat İngilizcem ile Delphi'nin yardım dokümanlarını okumaya başladım. Ancak çok fazla ilerlemeyemiyordum. Yine bir gün babam İstanbul'dan, elinde bir Delphi 3 kitabıyla çıkageldi. Kitabı okumaya başladım ancak Delphi 2 ile Delphi 3 arasında ciddi farklar vardı ve ben de bu durumu bir şekilde çözmeye çalışıyordum. Bu dönemlerde tanıştığım bilgisayar öğretmenim sayesinde pascal öğrenmeye başladım. Pascal çok daha kolaydı ve basic'e nazaran çok daha hızlı gelişme gösterdim. Ardından C öğrenmeye başladım ve sonunda  C++ öğrendim. İpin ucunu yakalayabildikten sonra bir anda ilerlemeye ve hızlanmaya başladım. 

Lise yıllarımın sonunda, Üniversite'ye yeni başladığım yıllarda C# ile tanıştım ve bir anda inandığım bildiğim herşey değişmeye başladı. Çok uzun bir zaman sonunda internetten indirdiğim kod örneklerini bir kerede çalıştırabiliyordum. Bu benim için o kadar kıymetli birşeydi ki kelimelerle anlatmamın çok mümkün olacağını düşünemiyorum. Herşeye yeni bir pencereden bakmaya başladım ve ivmem bir anda yeniden arttı.

Yaklaşık dokuz on yaşlarımdan beri bilgisayarlarla iç içe bir hayat yaşama şansına sahiptim. Bu yüzden yazdığım bir kodun bir kerede çalışabiliyor olmasından inanılmaz bir haz alabiliyorum. Yıllardır amatör anlamda sayısız satır yazdım, kimileri bomboş saçma sapan şeylerdi, kimileri ise hiç birşey ifade etmiyordu belki. Bilgisayarım silmeye kıyamadığım başlanmış ama bitirilememiş onlarca projeden dolup taşıyor. 

Bu günlüğü yapmamdaki asıl amaç, bu heyecanımı en az benim kadar heyecanlı kişilerle paylaşabilmek ve eskiden yaşadığım kaynak sıkıntısını mümkün oldukça başkalarının yaşamamasını sağlamak. Bu yüzden dilediğiniz konuda bana mail gönderebilirsiniz, elimden geldikçe bunlara cevap vermeye çalışacağım.