Apr 01 2009

Basit Bir Abstraction Örneği

Tag: Code Smells,Yazılım MühendisliğiM. Cihat Altuntaş @ 4:29 pm

Object Oriented tasarım ve programlamada soyutlamanın her zaman önemini vurgulamışımdır. Yazılım geliştirirken soyutlamalar sayesinde yazılımı daha yönetilebilir parçalara, modüllere ayırıp daha esnek yazılımlar geliştirebiliyoruz. Buna geliştirdiğim yazılımdan küçük bir örnek vermek istedim.

Geçenlerde Hibernate Log dosyalarını parse eden bir program yazdığımı burada belirtmiştim.  Program belirli bir dizideki .log uzantılı dosyaları parse edip içlerinden SQL cümlelerini çıkarıyordu. Ekranda bulunan bir butonun altında yazılan kodda seçilen log dizininin altındaki log dosyalarını alıp Parser sınıfına işlemesi için gönderiyordu. Kodun ilk yazılan şekli aşağıdaki gibiydi.

private void openFileActionPerformed(java.awt.event.ActionEvent evt) {
    try {
       //log dosyalarını seçili dizinden alıyoruz

       File directory =fc.getSelectedFile();
       FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".log");
            }
        };
        File[] files =directory.listFiles(filter);

        //*****************

        for (File file : files) {
            Parser parser = new Parser(file);
            sqlLogs = parser.parse();
            for (SqlLog sqlLog : sqlLogs) {
                listModel.addElement(sqlLog);
            }
        }

    } catch (Exception ex) {
        showMessage(ex.getMessage());
    }
}

Yukarıdaki basit bir işlem fakat metodun iki işi bir arada yaptığını fark ettiniz umarım. Log dosyalarını listeleme ve bu dosyaları parse etme. Ayrıca uygulamamı düşündüğümde şöyle bir ihtiyacım olduğunu anlıyorum.Uygulamam “Log dizinindeki dosyaları parse etme” işlemini yapıyor. Bu yüzden basit ama kodu ve yönetimi arttıran yeni bir sınıf ekliyorum. Aslında kısacası soyutlama yapıyorum. Uygulamam da daha önceden geçen “Log Dizini” dediğim kavram için bir sınıf oluşturuyorum. Kodu aşağıdaki gibi değiştirdim.

public class LogDirectory {
    private File directory;

    public LogDirectory(File directory) {
        this.directory = directory;
    }

    public File[] getLogFiles() {
        FilenameFilter filter = new FilenameFilter() {
            public boolean accept(File dir, String name) {
                return name.endsWith(".log");
            }
        };
        return directory.listFiles(filter);
    }
}

Yukarıdaki kodda basit bir soyutlama yaptım. Log dizinindeki dosyaları getiren işi ayrı bir sınıf olarak uygulamaya ekledim.Metod aşağıdaki hale geldi.

private void openFileActionPerformed(java.awt.event.ActionEvent evt) {
    try {
        LogDirectory logDirectory =new LogDirectory(fc.getSelectedFile());
        File[] files = logDirectory.getLogFiles();

        for (File file : files) {
            Parser parser = new Parser(file);
            sqlLogs = parser.parse();
            for (SqlLog sqlLog : sqlLogs) {
                listModel.addElement(sqlLog);
            }
        }

    } catch (Exception ex) {
        showMessage(ex.getMessage());
    }
}

Küçükde olsa bu tarz soyutlamanın uygulama için oldukça önemli olduğunu düşünüyorum.


Mar 13 2009

Affet bizi SOA seni yanlış anladık

Tag: Architecture,Yazılım MühendisliğiM. Cihat Altuntaş @ 4:16 pm

Geçenlerde danışman olarak bulunduğum bir firmada kısa bir Desing Patterns,Principles sunumu yapıyordum. Sunumun asıl sebebi geliştirilen büyük çaplı Java projesinde sınıflarında ortalama  20.000, 30.000 satırlardan oluşan gerçekten büyük sınıflar olmasıydı.Ve sınıflar neredeyse sadece statik metodlardan oluşuyordu. Şuana kadar çalıştığım projelerde en fazla 5000’i görmüştüm orda 30.000 satırı görünce artık 5000,10.000 satır kod içeren sınıflara küçük sınıf gözüyle bakmaya başladım diyebilirim :)

Hal böyle olunca bu konuda firmaya bu sayının çok büyük bir rakam olduğunu başlarının ağrımaması için düşürülmesinin iyi olacağını belirttik. Onlarda bizden neler yapılabilir bunun hakkında bir sunum yapmamızı istediler. Bende büyük sınıfların düşmanı olan Design Patterns, S.O.L.I.D Principles konularını uygulamalı olarak anlatan bir sunum yaptım.Sunumdan sonra birkaç ilginç diyalog yaşadım o yüzden sizlerlede paylaşmak istedim.

Yazılımcı arkadaşlardan birkaç tanesi projelerinin Service Oriented olduğunu bu yüzden benim anlattığım bu tarz problemleri önleyecek Object Oriented desing ve prensiplerin projelerinde uygulanamayacağını belirttiler. Biraz şaşırdım diyebilirim aslında. Çünkü SOA ve OOP hakkında büyük bir yanlış anlaşılma olmuş.

SOA yani Service Oriented Architecture bir uygulama mimarisidir.SOA uygulamayı birbirinden bağımsız, belirli fonksiyonlara bölünmüş, tekrar kullanılabilir olacak şekilde servisler halinde tasarlamaktır. SOA size uygulamanızı şu teknolojilerle,şu şekilde yapacağınızı söylemez altında yatan teknolojiyi istediğiniz gibi seçebilirsiniz. İster RMI, ister Web Servisleri,ister WCF kullanarak geliştirin bu size kalmış.

Object Oriented uygulama geliştirirken kullandığını bir teknik fakat SOA üst seviye bir uygulama mimarisi. Service Oriented bir uygulama geliştirirkende ister Functional Programming kullanın, isterseniz Object Oriented Programming kullanın servislerin ne ile geliştirildiğinin hiç bir önemi yok.Yani herhangi bir programlama teknolojisi gibi Object Oriented teknikleri SOA uygulama geliştirirken rahatlıkla kullanabiliriz. Bu yüzden Object Oriented Programming/Design ile SOA’yı karşılaştırmak elma ile armudu karşılaştırmak gibi birşey olsa gerek. Bu yüzden üzülerek SOA’dan bizi affetmesini istiyorum ve birdaha elma ile armudu karıştırmayacağımıza söz veriyorum :)


Mar 02 2009

Fluent Interface Örneği

Tag: Patterns,Principles,Yazılım MühendisliğiM. Cihat Altuntaş @ 3:45 pm

Örnek Kodlar

Fluent Interface kavramı ile ilk olarak 2007 yılında  Martin Fowler’ın bu yazısını okuyunca tanışmıştım. Hatta örnekte gösterdiği

TimeInterval meetingTime = fiveOClock.until(sixOClock);

kodu beni oldukça etkilemişti. Tabi kısa bir an bir kendi yazdığım koda birde şekilde yazılan koda bakakaldım diyebilirim. Okunabilir kod deyip dururuz ya; bu kodu okuyunca “İşte okunabilir kod budur”  dediğim anlardan biridir.

Duymayanlar,bilmeyenler aslında çok fazla birşey kaçırmıyor.Fluent Interface kısaca kodun okunulabilirliğini artırmak için kullanılan bir API tasarım stili diyebiliriz. Ne tasarımınıza bir esneklik katıyor, ne de daha az kod yazmanızı sağlıyor. Amacı kodun okunulabilirliğini arttırmak ki, bence bunu da çok iyi yapıyor.

Kullandığınız birçok kütüphanede de farketmesenizde bu şekilde tasarlanmış API’ler ile karşılaştığınızı düşünüyorum. Mesela benim aklıma gelenler .NET’de kullandığım Rhino Mocks,Ninject.. Java’da kullandığım Nonblocking IO,EasyMock.. gibi birçok kütüphane bu stil ile tasarlanmış. Dikkat edecek olursanız bu kütüphanelerde yazdığınız kod aynı satırda sırayla birbirini çağıran metodlar şeklindedir.(method chaining).

İlk karşılaştığımda oldukça etkilendiğimi söylemiştim. Tabi ne zaman böyle etkilensem hemen kendi kodumda kullanmak için yer aramaya başlarım. Fakat ne kadar uğraşsamda çok fazla uygulayabileceğim yer bulamamıştım. Fluent Interface’i kodun heryerine uygulamak biraz zahmetli ve gereksiz olabiliyor. Genelde en uygun kullanım şeklinin sınıfların,kütüphanelerin konfigürasyon işlemleri olduğunu düşünüyorum.(Fluent NHibernate buna çok güzel bir örnek.).

Lafı fazla uzatmadan örneğimize dönelim.Geçenlerde geliştirdiğim bir kodda Fluent Interface şeklinde bir sınıf yazmanın oldukça faydasını gördüm sizinlede paylaşayım.

Genelde bazı uygulamalarda ekranda seçtiğiniz bir seçeneğe göre bazı alanların,girilmesi gereken kontrollerin enable,visible… olması, bazılarının ise o seçeneğe göre disable,hidden… olması gerekir. Benimde yaptığım örnek kısaca bu tarz bir uygulamaydı. ASP.NET Webforms ile geliştirilen bu uygulamada ekranda bulunan bir adet DropDownList kontrolü ile yapacağınız işlem türünü seçiyorunuz. Ardından o işlem türüne göre ekranda bazı alanlar enable, bazı alanlar disable oluyor. Vede seçtiğimiz türe göre veritabanından bazı kontrolleri dolduruyoruz,kısacası bazı işlemleride yapıyoruz.

Öncelikle akla ilk gelen yöntem seçtiğiniz kontrolün altında if-else kontrolü yapıp uygun seçeneğe göre uygun kontrolleri enable,disable edebiliriz. Fakat bu çözüm hem bu kodun diğer ekranlarda kullanılması, hemde esnekliği açısından hoş bir çözüm olmaz bu yüzden hemen aklımızdan çıkarıyoruz :)

Daha uygun olarak daha önceden bahsettiğim Table Driven Methods yöntemini kullanabiliriz. Bu şekilde if-else kontrollerinden kurtulup daha genel bir yapı tasarlamış oluruz ve bu yapıyı bu şekilde çalışan diğer ekranlarda da kullanabiliriz.

        private void ConfigureScreen()
        {
            screenState = new ScreenState();

            ControlState onay =new ControlState();
            onay.AddControlToEnable(pnlOnay);
            onay.AddControlToDisable(pnlItiraz);
            onay.AddControlToDisable(pnlYonlendirme);

            screenState.AddControlState(ONAY, onay);

            ControlState yonlendirme = new ControlState();
            yonlendirme.AddControlToEnable(pnlYonlendirme);
            yonlendirme.AddControlToDisable(pnlItiraz);
            yonlendirme.AddControlToDisable(pnlOnay);
            yonlendirme.Do(()=>BirimleriYukle());

            screenState.AddControlState(YONLENDIRME, yonlendirme);

            ControlState itiraz = new ControlState();
            itiraz.AddControlToEnable(pnlItiraz);
            itiraz.AddControlToDisable(pnlYonlendirme);
            itiraz.AddControlToDisable(pnlOnay);
            itiraz.Do(() => UrunleriYukle(false));

            screenState.AddControlState(ITIRAZ, itiraz);

            ControlState secim = new ControlState();
            secim.AddControlToDisable(pnlYonlendirme);
            secim.AddControlToDisable(pnlOnay);
            secim.AddControlToDisable(pnlItiraz);

            screenState.AddControlState(DEFAULT, secim);

        }

Her durum için bir ControlState sınıfı oluşturuyoruz. Ona enable,disable etmesi gereken kontrolleri veriyoruz,ardından yapması gereken işlemleri söylüyoruz. Ardından bu sınıfı ekranı temsil eden ScreenState sınıfına ekliyoruz.

ScreenState adındaki sınıfımız ekranın bütün durumlardaki durumunu simgeliyor. ControlState ise belirli bir seçim sırasındaki durumunu simgeliyor. Sınıfları şuanda çok fazla düşünmeyin. Yukarıdaki gördüğünüz koddaki okunabilirliği, ve kodun yazılım şekline dikkat etmenizi istiyorum.

Bu kodda herhangi bir problem yok gayet güzel çalışıyor. Fakat birde olaya biraz daha akıcılık katıp Fluent bir API ile yukarıdaki kodu tekrar yazalım bakalım hangisi gözümüze daha hoş gözükecek :)

        private void ConfigureScreen()
        {
            screenState = new ScreenState();

            screenState
                .WhenStateIs(ONAY)
                .EnableControls(pnlOnay)
                .DisableControls
                (
                    pnlItiraz,
                    pnlYonlendirme
                );

            screenState
                .WhenStateIs(YONLENDIRME)
                .EnableControls(pnlYonlendirme)
                .DisableControls
                (
                    pnlItiraz,
                    pnlOnay
                )
                .Do(() => BirimleriYukle());

            screenState
                .WhenStateIs(ITIRAZ)
                .EnableControls(pnlItiraz)
                .DisableControls
                (
                    pnlYonlendirme,
                    pnlOnay
                )
                .Do(() => UrunleriYukle(false));

            screenState
                .WhenStateIs(DEFAULT)
                .DisableControls
                (
                    pnlItiraz,
                    pnlOnay,
                    pnlYonlendirme
                );
        }

Yukarıdaki kodu ingilizce olarak okumaya çalışın. Mesela ikincisini sizin için ben okuyayım.Ekran durumu YONLENDIRME olduğunda pnlYonlendirme’yi enable et,pnlItiraz,pnlOnay disable et ve Birimleri yükle. Okunuşu diğerine göre daha akıcı değil mi? Peki okunuşu bizim için neden önemli?

Yukarıdaki kodda da gördüğünüz gibi Fluent Interface tekniği ile kodun okunulabilirliğini oldukça geliştirdik. Aslında küçük bir Internal DSL yaptık diyebiliriz. Uygulamamızın küçük bir bölümüne göre bir dil tasarladık yani.Fluent Interface olarak API geliştirmek için aslında çok değişik bir teknik yok. Metodların sonunda void yerine sınıfın kendisini döndürüyoruz. Bu şekilde noktadan sonra o sınıfın başka metodlarınıda çağırabiliyoruz. Özellikle sınıflara konfigürasyon yapılırken işimizi oldukça kolaylaştırdığını düşünüyorum.Fluent NHibernate bunun çok güzel bir örneği. XML konfigürasyon dosyalarından tamamen kurtulup Fluent Interface şeklinde NHibernate mapping konfigürasyonunu yapabiliyorsunuz.Fluent Interface tekniğinin aklınızın bir köşesinde bulunmasını tavsiye ederim.


Jan 24 2009

Örnek Kod : Visual Studio Class, Table Generator Add-In

Tag: Test Driven Development,Yazılım MühendisliğiM. Cihat Altuntaş @ 3:16 pm

Visual Studio’da Add-In geliştirmeyi öğrenmek için küçük bir proje geliştireyim dedim. Projede basit olarak bir Solution Explorer’dan seçtiğiniz sınıfı Server Explorer üzerindeki veritabanına tablo olarak ekliyor.Ayrıca Server Explorer üzerindeki eklenmiş veritabanı bağlantısı üzerinden seçtiğiniz tabloyu projenize sınıf olarak ekliyor.

Kaynak kodunu ve setup dosyalarını aşağıdan indirebilirsiniz. Proje Visual Studio üzerinde Add-In geliştirmek isteyenler için faydalı olabilir. Ayrıca projeyi küçük bir Test Driven Development ve OOP örneği olarak inceleyebilirsiniz yeni başlayanlar için faydalı olabilir..Gerçekte işinize yararsada kullanıp kodu istediğiniz gibi değiştirmekte serbestsiniz.İçerisinde bug,ve eklenmesi gereken özellik olabilir, kendiniz ekleyebilir veya benden eklememi isteyebilirsiniz :) .

Proje Visual Studio 2008 üzerinde çalışıyor. Eğer kodu kullanmak ve değiştirmek istiyorsanız bilgisayarınızda Visual Studio 2008 SDK kurulu olması gerekli.

Tablodan Sınıf Oluşturma

Adım 1 : Server Explorer’dan Çalıştığınız Veritabanı bağlantısını ekleyin ve ekledikten sonra sınıfını oluşturmak istediğiniz tabloyu seçin

TableToClassAdim1

Adım 2 : Gelen ekran üzerinden sınıfın otomatik olarak oluşturulmuş özellikleri üzerinde değişiklik yapın.Sınıf adını değiştirin, yeni özellik ekleyin,özellik silin,özellik adını güncelleyin,özellik tipini güncelleyin…

TableToClassAdim2

Sınıftan Tablo Oluşturun

Adım 1 :Solution Explorer üzerinde veritabanına tablo olarak eklemek istediğiniz sınıfın üzerinde sağ tıklayın.

ClassToTableAdim1

Adım 2: Gelen ekran üzerinden veritabanına eklenecek tablo özelliklerini otomatik olarak görüntüleyecektir. Oradan ekran üzerinde oluşturulacak tablonun primary key, allow null gibi özelliklerini değiştirin, kolon ekleyin, kolon silin, kolon adını değiştirin…

ClassToTableAdim2


Jan 09 2009

Tell Don’t Ask Principle

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 4:13 pm

oneRing Herşey prosedürel programlamayla başladı. Bazılarımız büyük programlar yazdı, bazılarımız ise küçük birer hesap makinası yaparak kıyısından geçiverdi :) Ama hepimiz prosedürel programlamayı çok sevdik. Programlarımızı yazarken güzel güzel yapılar(struct,record),veri tipleri(int,double,char…) kullanırdık, yapılarımızı,verilerimizi metodlarımıza parametre olarak gönderirdik onlarda gereken işi güzelce yapardı.Tabi sonradan yazdığımız programlar 100.000 satırlar boyutuna ulaşmaya başlayınca o masum masum kod dosyalarımızın üst tarafında tanımlı yapılar bize düşman görünmeye, en az 5 tane parametre alan fonksiyonlarımız da yönetilmeme ye ve pekde hoş gözükmemeye başladı. Ardından kulaklarımıza Mordor diyarından şu nağmeler gelmeye başladı. (Yüzüklerin efendisini çok sevdiğim belli oluyor herhalde:) )

One Object to rule them all, One Object to find them,
One Object to bring them all and in the Class bind them

Nağmelerde, hepsini ; yani veri ve fonksiyonları bir araya getirecek, ondan sonra onları Class(sınıf) içerisinde birleştirecek bir güç olan nesneden bahsediyordu. Hepimiz bu nağmelerin içerisnde olan nesnelerin büyüsüne kapıldık ve o aşamadan sonra kendimizi Object Oriented rüzgarının önünde buluverdik.  Artık hepimiz bu gücü kullanıp daha yönetilebilir, okunabilir, esnek yazılımlar geliştirmek için can atıyorduk. Fakat prosedürel programlamayı o kadar çok sevmiştik ki eski alışkanlıklarımızdan bir türlü vazgeçemiyorduk. İlk aşklar unutulmaz derler herhalde ondan olsa gerek :) Artık benim PObject Oriented olarak adlandırdığım nesneler ile prosedürel programlama yapıyorduk.

Her ikisinin sevdiğimiz özelliklerini kullanıp güzel güzel programlar yazmaya başlamıştık. Verileri bu sefer struct,record gibi yapılar içerisine değilde sınıflar içerisine koymaya başladık.Fonksiyonlarımızıda uygun bir yer bulabiliyorsak oraya değilse sağa,sola yerleştiriyorduk. Fakat yine işler projeler büyüdükçe çığırından çıkmaya başladı. Projeler büyüdükçe, değişiklikler yapılmaya başladıkça işler oldukça zahmetli olmaya başladı. Küçük bir talepteki değişiklik uygulamanın birçok yerinde değişiklik yapmamızı gerektiriyor ve bu değişiklik sonucunda ortaya çıkan hatalarla baş edememeye başlıyorduk. Aslında biraz da kafamız karışmıştı. Nağmelerde duyduğumuz o güçlü nesneye yönelik programlama aslında pekde güçlü olmadığını düşünüyorduk.

Kaptırmışım kendimi yüzüklerin efendisi falan araya girince hikayeyi fazla uzattım kusura bakmayın :) Aslında problem bu noktada geliştirdiğimiz uygulamada iş mantığının nesnelerin içinde değilde uygulamanın geneli içine dağılmış olması. Yani en temel nesneye yönelik programlama prensibi verilerin ve verileri işleyen metodların bir arada bulunması gerçekleşmediğinden dolayı uygulama geneline artan Bağımlılık(Dependency,High Coupling) sorunlarının yol açtığı problemlerdir.

Bu sorunların önüne geçebilmek ve sınıflara sorumluluğu daha düzgün atayıp, iş mantığını gerçekten olması gereken yere koymak için kullanılan prensiplerden biride Tell Don’t Ask Principle yani “Sorma, söyle” prensibidir.

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things.
— Alec Sharp

Alec Sharp’ın dediği gibi prosedürel kod veriyi alır ve o veri üzerinden kararlar verir. Object oriented kod ise nesnelere yapmaları gereken şeyleri söyler. Bunu meşhur gazeteci çocuk, cüzdan problemi üzerinde örneklerle inceleyelim. İlk olarak nerede okumuştum hatırlamıyorum ama bu prensibi anlamama kod ve kavramsal olarak oldukça yardımcı olmuştu. Aynı örnek üzerinden devam edelim.

Örnek

Kodları buradan indirebilirsiniz : TellDontAsk.rar

Gazeteci arkadaşımız her sabah bisikletine atlayıp ev ev dolaşıp gazete dağıtıyor. Haftalıkta müşterilerinden gazete paralarını topluyor.Para toplayacağı müşterilerin listesi elinde alıp kapı kapı dolaşıyor ve ücretleri alıyor. Ardından topladığı hasılatı patronuna teslim ediyor.İlk olarak programımızı şu şekilde yazalım.

public class Cuzdan {
    private double para;

    Cuzdan(double para) {
        this.para =para;
    }

    public double getPara() {
        return para;
    }

    public void setPara(double para) {
        this.para = para;
    }
}
public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        if(musteri.getCuzdan().getPara()<miktar){
            throw new RuntimeException("Cüzdanki para yeterli değil.");
        }else{
            musteri.getCuzdan().setPara(musteri.getCuzdan().getPara()-miktar);
            hasilat +=miktar;
        }
    }

    public double getHasilat() {
        return hasilat;
    }
}
public class Musteri {
    private String adi;
    private Cuzdan cuzdan;

    public Musteri(String adi,double para) {
        this.cuzdan =new Cuzdan(para);
        this.adi =adi;
    }    

    public Cuzdan getCuzdan() {
        return cuzdan;
    }

    public String getAdi() {
        return adi;
    }
}
public class Main {
    public static void main(String[] args) {
        Musteri ahmetAmca =new Musteri("Ahmet",100);
        Musteri bakkalMehmet =new Musteri("Mehmet",50);
        Musteri kasapAli =new Musteri("Ali",80);

        List<Musteri> musteriler =new ArrayList<Musteri>();
        musteriler.add(ahmetAmca);
        musteriler.add(bakkalMehmet);
        musteriler.add(kasapAli);

        Gazeteci cihat =new Gazeteci();

        for(int i=0; i<musteriler.size(); i++){
            Musteri musteri =musteriler.get(i);
            try{
                cihat.odemeAl(musteri, 60);
            }
            catch(Exception ex){
                System.out.println("Tahsilat yapılamadı! Müşteri adı : "+musteri.getAdi());
            }
        }
        System.out.println("Toplanan hasılat miktarı : "+cihat.getHasilat());
    }
}

Prosedürel stili o kadar çok seviyoruz ki muhtemelen yukarıdaki kodda pek bir problem göremeyeceğiz.Yanılmıyorum değilmi?Çünkü ilk başlarda bu tarz kodlar bana süper Object Oriented kodlar gibi geliyordu ve açıkçası biri bana o zaman bu kodda problem var dese gülerdim :)

Probleme teknik olarak değilde gerçek hayat problemi olarak bakalım. Evinize gelen gazeteciye cüzdanınızı verip içerisinden ne kadar lazımsa al kardeş dermisiniz? Eğer öyleyse sizin gazeteciniz olmayı isterdim :) Çoğumuzun cüzdanını vermeyeceğini düşünüyorum açıkcası.Gazeteciye “kardeş ne kadar lazım söyle” diye sorup ona göre parayı cüzdanımızdan çıkarıp öyle veririz. Bu koddaki problemde bu. Gazeteciye aslında cüzdan nesnesi ile cüzdanımızı veriyoruz gazeteci önce bi içerisine bakıyor gerekli para varmı ardından varsa içerisinden gereken parayı alıyor.

Tell Don’t Ask(sorma, söyle) presibinin belirttiği gibi aslında biz burada Musteri ve Cuzdan nesnelerine surekli sorular soruyoruz ve ardından sorduğumuz soruların cevabına göre bazı işlemleri o nesneler yerine biz yapıyoruz. Musteriye sordugumuz soru getCuzdan(), Cuzdan nesnesine sordugumuz soru ise getMiktar() metodları.Bu yüzden Musteri nesnesinin iç yapısını öğrenmiş oluyoruz.Mesela müşterinin parayı cüzdanında taşıdığını biliyoruz. Ya müşteri bize parasını evdeki kasasından vermek isterse?Gazeteciye tutup “Al kasayı kardeş içerisinden ne kadar lazımsa o kadar al ” mı diyeceğiz? :) Dolayısıyla nesneler arasındaki bağımlılık(coupling) artıyor. Uygulamada bu şekilde müşteri nesnesinin içerisindeki alanlara soru sorup ona göre karar verip çeşitli işlem yaptıran sınıflar ne kadar artarsa müşterinin iç yapısını değiştirmemiz o kadar zorlaşıyor. Örnek olarak müşterinin artık parayı Cüzdan’dan değilde kredi kartından vereceğini düşünelim. Hangi sınıfları değiştirmemiz gerekir onlara bakalım.

Şimdi yeni sınıfımız Kredi kartına göre sınıflarımızın düzeltilmiş halini yazalım. Bakalım bir sınıfdaki değişiklik yüzünden kaç sınıfı değiştirmek zorunda kalacağız.Ana sınıf değişmediği için onu yazmıyorum. Diğer değişen sınıfları yeni halleri ile aşağıya yazıyorum.

public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        if(musteri.getKrediKarti().getLimit()<miktar){
            throw new RuntimeException("Cüzdanki para yeterli değil.");
        }else{
            musteri.getKrediKarti().setLimit(musteri.getKrediKarti().getLimit()-miktar);
            hasilat +=miktar;
        }
    }

    public double getHasilat() {
        return hasilat;
    }
}
public class Musteri {
    private String adi;
    private KrediKarti krediKarti;

    public Musteri(String adi,double para) {
        this.krediKarti =new KrediKarti();
        this.adi =adi;
    }    

    public KrediKarti getKrediKarti() {
        return krediKarti;
    }

    public String getAdi() {
        return adi;
    }
}

[code lang='java']
public class KrediKarti {
private double limit =1000;

KrediKarti() {

}

public double getLimit() {
return limit;
}

public void setLimit(double limit) {
this.limit = limit;
}
}
[/code]

Gördüğünüz gibi Main sınıfı hariç bütün sınıfları basit bir değişiklik yüzünden değiştirmek zorunda kaldık. Burada aslında sadece Müşteri yani cüzdana sahip sınıfın etkilenmesi gerekirdi fakat prosedürel stilde sürekli nesnelerin iç yapısını sorgulayarak ona karar veren kodumuz yüzünden Gazeteci sınıfıda bu değişiklikten etkilendi.

Peki bu problemi nasıl düzeltebiliriz ? Tell Don’t Ask yani işimizi nesnelere soru sorararak değilde onlara ne yapması gerektiğini söyleyerek yapabiliriz. Aslında bu yeni birşey değil OOP’nin en temel kuralını kullanıyor alıyor yani Encapsulation. Nesnelerin iç yapısını çok fazla dışarı açtığımızda kendi üzerine düşen işleri başkaları yaptığında bu tarz problemler kaçınılmaz. Bu küçük bir örnek belki çok fazla problem olmaz fakat büyük bir uygulamada bu tarz iç yapısını dışarı açan bir nesnede değişiklik olduğunu düşünün? Belkide yüzlerce sınıfı değiştirmek,hatalarını düzeltmek,test etmek zorunda kalacaksınız. Şimdi bu kodu Tell Don’t ask prensibine uygun olarak aşağıdaki gibi yazalım.

public class Cuzdan {
    private double para;

    Cuzdan(double para) {
        this.para =para;
    }

    double cek(double fiyat) {
        if(fiyat>para)
          throw new RuntimeException("Cüzdanki para yeterli değil!");
        else{
            para =para-fiyat;
            return fiyat;
        }
    }
}

[code lang='java']
public class Musteri {
private String adi;
private Cuzdan cuzdan;

public Musteri(String adi,double para) {
this.cuzdan =new Cuzdan(para);
this.adi =adi;
}

public double odemeYap(double fiyat){
return cuzdan.ver(fiyat);
}

public String getAdi() {
return adi;
}
}
[/code]

public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        hasilat +=musteri.odemeYap(miktar);
    }

    public double getHasilat() {
        return hasilat;
    }
}

Şimdi Tell Don’t Ask prensibine göre ile yazılmış yeni kodumuzu inceleyelim. Gazeteci sınıfına bakın artık müşterinin ne cüzdnından haberi var nede kredi kartından. Ona sadece yapması gereken şeyi söylüyor : “Bana şu kadar ödeme yap”. Müşteri nesneside aynı şeyi yapıyor cüzdana bana şu kadar para ver diyor. Ardından cüzdan da kendi içerisinde olan parayı istenen miktarla karşılaştırıyor ve ona göre veri parayı veriyor. Yani herkes yapması gereken işi yapıyor. Artık cüzdan nesnesi içerisinde sadece veri bulunan bir aptal sınıf yada Anemic Domain Model değil, onu nasıl işleyeceğini bilen bir sınıf.

Şimdi bu yapıda yazılmış bir kod üzerinde Cuzdan yerine paramızı kredi kartı üzerinden ödemek için değişmesi gereken sınıfları tekrar yazalım.

public class Musteri {
    private String adi;
    private KrediKarti krediKarti;

    public Musteri(String adi,double para) {
        this.krediKarti =new KrediKarti();
        this.adi =adi;
    }    

    public double odemeYap(double fiyat){
        return krediKarti.cek(fiyat);
    }

    public String getAdi() {
        return adi;
    }
}

[code lang='java']
public class KrediKarti {
private double limit =1000;

KrediKarti() {

}

double cek(double fiyat) {
if(fiyat>limit)
throw new RuntimeException("Limit yeterli değil!");
else{
limit =limit-fiyat;
return fiyat;
}
}
}
[/code]

public class Gazeteci {
    private double hasilat =0;
    public void odemeAl(Musteri musteri,double miktar){
        hasilat +=musteri.odemeYap(miktar);
    }

    public double getHasilat() {
        return hasilat;
    }
}

Gazeteci sınıfında yukarıda gördüğünüz gibi hiçbir değişiklik yapmadık. Yani sadece iç yapısını değiştirdiğimiz sınıflar etkilendi. O da müşteri sınıfı ama diğer şekilde hem gazeteci hemde müşteri sınıfı etkileniyordu.

Tabi bunun dışında gördüğünüz gibi kodun okunulabilirliği ve yönetimi oldukça kolaylaştı. Çünkü herkes kendi sorumluluğunu yerine getiriyor, nesneler kendi içerisindeki veriler üzerinde işlem yapıyor. Bu şekilde uygulamada değişiklik sadece gereken yerde yapılmış oluyor. Ama diğer şekilde iş mantığı uygulamanın bütün yerine dağılmış olduğu için değişiklik sonucunda birsürü yerde değişiklik yapmamız gerekiyor. Buda hata riskini ve değişim maliyetini oldukça arttırıyor.

İlk başlarda bu prensibi okuduğumda oldukça şaşırmıştım ve açıkçası nesneye yönelik programlamanın en temel ilkesini o zaman anlamıştım. Veri ve veri üzerinde işlem yapan metodların bir arada olması. Prosedürel mantığa o kadar alışmışızki nesneye yönelik bir programlama dilinde onun etkisinden hala kurtulamıyoruz. Bu prensip anlaması ve alışması belkide en zor temel nesneye yönelik programlama prensiplerinden birisi. Ben bile hala bazı yerlerde içgüdüsel olarak nesnelere soru sorup bazı işleri yaptırmaktan kendimi alıkoyamıyorum. Ama sürekli kodu gözden geçirip “Acaba bu işi bu sınıf mı yapmalı?, Bu işin ait olduğu yer burası mı?” diye sorgularsanız sorumluluğun gerçekten ait olduğu yeri belirleyebilirsiniz. Eğer nesnelerinizde sadece getter,setter(java) yada property(C#) bulunuyorsa nesnelerinizden şüphelenmenizin ve tekrar gözden geçirmenizin vakti gelmiştir.


Nov 20 2008

Ortak Düşmanımız : Bağımlılık (Dependency,High Coupling)

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 6:21 pm

domino1

Bu yazımızda yazılım geliştirirken karşımıza çıkan en büyük problemlerden biri olan bağımlılık (Dependency,Coupling) konularına değineceğim. Neden az olanı makbüldür onu anlatmaya çalışacağım.

Yazılım geliştirirken gerçek dünyadaki gibi bağımlılık (Dependency,Coupling) başımıza çoğu zaman dert açıyor.Bağlanılması zararlı bir şeye bağlandınızmı nasıl ondan kurtulmak onsuz birşey yapmak imkansız oluyorsa aynısı yazılım geliştirirkende geçerli oluyor. Yazılımda bağımlılığı tanımyacak olursak iki metodun,sınıfın,modülün bir işi gerçekleştirmek için birbirine ihtiyaç duymaları kısacası birbirlerini kullanmalarıdır diyebiliriz.

Peki bağımlılığın ne gibi zararları var neden düşman ilan ettik birazda ondan bahsedelim. Öncelikle iki sınıfın birbirine bağımlı olmasının en büyük problemlerinden biri herhangi birindeki değişikliğin diğerini etkilemesidir bunu yandaki domino taşlarına benzetebilirsiniz. Herhangi bir yerden taşın birini düşürdüğünüz zaman birbiri ardına olan bütün taşlar devrilmeye başlar. Aynı şekilde geliştirdiğimiz yazılımda sınıflar,modüller… arasındaki bağımlılık ne kadar fazla ise domino taşı gibi herhangi birinde meydana çıkan hataların,değişikliklerin bağımlı olan diğer sınıfları etkileme olasılığı o kadar artıyor.

Üstadlardan Robert Martin amcamızın internet üzerinde dinlediğim The Principles Of Agile Design konferansında nesneye yönelik tasarım(Object Oriented Design) nedir diye izleyicilere bir soru yöneltiyordu. İzleyiciler “Gerçek dünyayı modelleme”,”Kavramların ayrılması” .. gibi değişik cevaplar verdi. Fakat Robert Martin kısa ve öz olarak nesneye yönelik tasarımı şu şekilde özetledi “Bağımlılığı yönetmek(managing dependency)”. Gerçekten çok yerinde bir tespit nesneye yönelik tasarımın başarısı için uygulamada bağımlılığın iyi yönetilmesi ve mümkün olduğunca az olması gerekiyor. 

Aslında bağımlılığı hem kod üzerinde, hemde projeler üzerinde izlemek ve anlamak çok kolay. Bunun için JDepend,NDepend gibi birçok gelişmiş olsada basit olarak bağımlılığı ölçmek için iki yöntem kullanabilirsiniz. Mesela aşağıdaki gibi bir koda bakalım ve nelere bağımlı gözden geçirelim.

		private void tbiMesajiGonder_Click(object sender, System.EventArgs e)
		{
			Database.VeriTabani clsDatabase=new SMSNET.Database.VeriTabani();
			string strSQLTel="SELECT ID,Adi+' '+Soyadi AS AdSoyadi,GSM FROM Kisiler WHERE ID IN ("+strMesajGonderilecekIDs+")";
			DataTable dtTel=clsDatabase.GetDataTable("Telefon",strSQLTel);
			PosterClass clsPosterClass=new PosterClass();
			string strSQLParametre="SELECT * FROM Parametreler WHERE ID IN (1,2,3,4) ORDER BY ID";
			DataTable dtParametreler=clsDatabase.GetDataTable("Parametreler",strSQLParametre);

			string strFirmCode="",strUserName="",strPassword="",strOriginator="";
			string strErrDefinition="";
			int iErrCode=0;
			string strMSGID="";

			if(dtParametreler.Rows.Count==4)
			{
				strFirmCode=dtParametreler.Rows[0]["Degeri"].ToString();
				strUserName=dtParametreler.Rows[1]["Degeri"].ToString();
				strPassword=dtParametreler.Rows[2]["Degeri"].ToString();
				strOriginator=dtParametreler.Rows[3]["Degeri"].ToString();
			}
			else
			{
				MessageBox.Show("Sistem SMS gönderebilmek için gerekli parametrelere ulaşamadı. Lütfen Sistem Tanımlarınızı kontrol ediniz...");
			}

			int k=0;

			for(int i=0;i<dtTel.Rows.Count;i++)
			{
				if(dtTel.Rows[i]["GSM"].ToString()!=""&&dtTel.Rows[i]["GSM"].ToString()!=null)
				{
					clsPosterClass.AddToSmsBasket(tbSMSMesaji.Text,dtTel.Rows[i]["GSM"].ToString());
					k++;
					if(k>100)
					{
						strMSGID=clsPosterClass.sendSms(strFirmCode,strUserName,strPassword,strOriginator,null,ref iErrCode,ref strErrDefinition);
						clsPosterClass.ClearSmsBasket();
					}
				}
			}
			strMSGID=clsPosterClass.sendSms(strFirmCode,strUserName,strPassword,strOriginator,null,ref iErrCode,ref strErrDefinition);
			clsPosterClass.ClearSmsBasket();
		}

Şimdi yukarıdaki kod baktığımızda pek de iyi şeyler görmediğimiz belli oluyor galiba. Kodu yaklaşık 4 sene önce ben yazmama rağmen pek birşey anlamıyorum açıkcası :) Muhakkak yukarıdaki kodda birsürü problem var fakat diğer problemlerinde büyük oranda sebebi olan en büyük problemlerden birisi kodun birçok sınıfa bağımlı olması(High Coupling).

Şimdi kodun nelere bağımlı olduğuna bakalım.Dediğim gibi bunu anlamanın çok basit bir yolu var bu da kodun çalışması için gerekli olan modüllerin,sınıfların, metodların sayılması. Bunu yapabilmek için öncelikle kodun Import kısmına bakmanızı tavsiye ederim. Import kısmında olan modüllere zaten direk olarak bağımlıdır. Projeler arasındaki bağımlılığı ölçmek içinde referans verdiği Dll,Jar gibi kütüphaneleri sayarak anlayabilirsiniz.Sınıflara olan bağımlılığıda o sınıf çıktığında kod çalışmaya devam edecek mi oradan anlayabilirsiniz.

  • Yukarıdaki koda baktığımızda ilk satırlarda Database nesnesi üzerinden SMS atılacak kişilerin SQL ile alındığını görüyorsunuz.Kodumuz Database’e bağımlı.
  • DataTable nesnesi üzerinden Telefonlar alınıyor. Kodumuz DataTable nesnesine yani arkaplanda olan ADO.NET teknolojisine bağımlı
  • 3. parti PosterClass nesnesi üzerinden SMS gönderimi yapılıyor. Yani PorterClass nesnesine ve bunu satın aldığımız firmaya bağımlıyız.

Şimdi bu bağımlılığın yol açtığı problemler neler onlara bakalım.

  • SMS sistemimiz sadece veritabanı üzerinden çalışacak. Mesela oldukça yaygın olan XML üzerinden aldığımız kayıtlara SMS göndermek istediğimizde bunu kodu değiştirmeden yapamayacağız.
  • Kodumuz DataTable yani Microsoft’ın sık sık değiştirmeyi sevdiği veri erişim katmanı olan ADO.NET alt yapısına bağlı. Mesela LINQ to SQL işimizi baya kolaylaştırırdı fakat onuda kodumuzu değiştirmeden kullanamayacağız.
  • Bir firmadan satın aldığımız PosterClass üzerinden SMS gönderiyoruz. Bunda bir problem yok fakat yarın birgün firma bu kütüphaneye destek vermediğinde ya da daha iyi alternatifler çıktığında yine değişik alternatifleri kullanmak hiçde kolay olmayacak.

Yukarıdaki kodda modüller, sınıflar arasındaki bağımlılığı gördük. Fakat bağımlılık bu kadarla bitmiyor açıkcası. Birazda daha sinsi olan metodlar,alanlar üzerindeki bağımlılığa bakalım. Aşağıdaki kodu inceleyelim

    public class Field
    {
        private string name;
        private bool allowNulls;
        private bool isPrimaryKey;
        public string DataType { get; set; }        

        public Field(string name, string dataType)
        {
            this.name = name;
            DataType = dataType;
            IsPrimaryKey = false;
            AllowNulls = true;
        }

        public bool IsPrimaryKey
        {
            get { return isPrimaryKey; }
            set {isPrimaryKey = value;}
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public bool AllowNulls
        {
            get { return allowNulls; }
            set { allowNulls = value; }
        }
    }

class Program
{
    public void SaveFields()
    {
        IList<Field> fields=new List<Field>();
        for (int i = 0; i < fields.Count; i++)
        {
            Field field = fields[i];
            string sqlString = GetFieldAsSQLString(field);
            //....
        }
    }

    public string GetFieldAsSQLString(Field field)
    {
        string sqlString = field.Name + " " + field.DataType;
        if (field.IsPrimaryKey)
            sqlString += " PRIMARY KEY";
        if (field.AllowNulls)
            sqlString += " NULL";
        else
            sqlString += " NOT NULL";
        return sqlString;
    }
}

Yukarıdaki koda baktığımızda ilk başta çok fazla problem görmeyebilirsiniz fakat Field sınıfının içerisindeki alanların kodun çoğu yerinde bu şekilde kullanıldığında kodumuz bu sefer de Field sınıfının alanlarına bağımlı oluyor. Mesela GetFieldAsSQLString metoduna baktığımızda Field sınıfının alanları üzerinde bazı işlemler yapıp bir string oluşturuyor. Şunu düşünün DataType alanı artık string olarak değilde bir sınıf olarak tutacağımızı düşünün. Kodumuzun çoğu yeri GetFieldAsSQLString metodu gibi Field sınıfının alanlarını bu şekilde kullanıyorsa DataType alanını değiştirdiğimizde birçok yerde hata verecek ve kodun çoğu değişmek zorunda kalacak.

Kısaca bağımlılığın zararlarını sıralayacak olursak :

  • Üzerinde çalışan yazılım geliştiricilerin,özellikle benim gibi şuanda şanssız kesimden olan yani kodu kendi yazmayıp yönetmek zorunda olanların sistemin bütününü anlamak zorunda olmalarıdır. Çünkü bütün sınıflar,modüller birbirine bağımlıdır.
  • Yapmak zorunda olduğunuz değişikliklerin birçok sınıfı etkilemesi dolayısıyla daha uzun sürmesi vede ne kadar süreceğinin tahmin edilememesi. Herhalde ne demek istediğimi anladınız. Proje yöneticiniz gelip size bu değişiklik ne kadar sürer dediğinde çoğu zaman “Biryerde bir problem çıkmazsa şu kadar sürer..” dediğinizi hatırlıyorsunuzdur.Çoğu zamanda eğer bağımlılık fazla ise biryerde birşeyler çıkıp işinizi uzatacığına emin olun :)
  • Değişen teknolojiye adaptasyon,farklı alternatifler arasında geçiş hiçte kolay olmayacaktır. Çünkü kodun heryeri o teknolojiye bulaşmış durumdadır.

Nesnelerin birbirleri ile iletişim halinde görevlerini yerine getirmelerinden dolayı sıfır bağımlılık genelde mümkün olmasada bağımlılığın az olması çoğu durumda daha makbüldür.Bu yüzden bağımlılığı iyi bir şekilde yönetmek hem kalite yönünden,hemde zaman bakımından bize oldukça kazanç sağlayacaktır.

Peki bunları niye yazdım? :) Aşırı bağımlılığın çoğu durumda kötü olduğunu size ispatlayabilmek için :) Umarım sizinde kafanıza yatmıştır. Yukarıdaki kodların bağımlılığı azaltılmış şekilde nasıl yazarıza şuanda değinmeyeceğim çünkü  yazdığım ve yazacağım birçok konu bununla alakalı aslında. Dependency Inversion,Dependency Injection… birçoğunun adını duymuştursunuz. Bundan sonraki birkaç yazımda bağımlılığı azaltan, kodun kalitesini arttıran işlerimizi kolaylaştıran birkaç yönteme değineceğim.O yüzden bu kodlar işimize yarıyacak gibi :)


Nov 08 2008

Premature Optimization

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 9:22 am

Erken performans optimizasyonunun yol açtığı belalar yazılım dünyasında oldukça iyi biliniyor. Bu konuda daha önceden kendi yaşadıklarımı da burada biraz aktarmıştım. Bugün internette aşağıdaki resme rastladım oldukça hoşuma gitti o yüzden burayada koymak istedim.

premature-6

Resimdeki şeytanmı yoksa başka birşeymi bizi sürekli bunu yapmamız için dürtüyor bilmiyorum ama ne ise ona aldanmayın derim :)


Oct 28 2008

Composition Over Inheritance (Kalıtım yerine Birleşim)

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 6:35 pm

Uzun zamandır işlerin yoğunluğundan dolayı yazamıyordum ama telafisi olarak düşündüğüm Object Oriented programlama açısından hayati konulara değinerek kendimi affettireceğimi umuyorum :)

Şimdi neden önemlidir ondan biraz bahsedelim. Öncelikle Object Oriented programlama dillerinde kodun tekrar kullanımını(code reuse) iki şekilde sağlayabilirsiniz. Birincisi inheritance(kalıtım) ikincisi ise Composition(güzel bir türkçe isim bulana kadar “birleşim” :) ) yöntemidir. Şimdi bu iki kullanımın neler olduğuna bakalım. Önce UML sınıf diyagramı olarak aralarındaki farka bakalım.
Devamını Burudan Okuyabilirsiniz


Aug 17 2008

Sınıf Tasarımında Kontrol Edilecekler

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 6:40 am

Bu günlerde iki kere okunması gereken kitaplardan Code Complete 2‘yi tekrar gözden geçiriyorum.Kitabın Chapter 6.6′da bulunan kaliteli sınıf tasarımı için kontrol edilmesi gerekenler başlığı altında yazdıklarını buraya yazmadan geçemedim. Sınıflarımızı tasarlarken aşağıdaki maddelere dikkat etmemiz sınıfların ve yazılımın kalitesini oldukça arttıracaktır. Türkçeye çevirerek yazıyorum.Aşağıdaki listede sınıfın arayüzü(class interface) kavramını java ya da C# daki interface kavramları ile karıştırmayın. Sınıfın arayüzü derken dışarıya kullanıma açtığı metodları kast edilir.Mesela bir sınıfın 4 metodu varsa bunlardan 3′ü private 1′i public ise sınıfın arayüzünü bu dışarıya açılan public metod temsil eder.

Soyut Veri Tipleri

    Programınızdaki soyut veri tiplerinin neler olduğunu düşünüp bunların kullanım açısından arayüzlerini değerlendirdiniz mi?

Soyutlama

  • Sınıfın belirli bir amacı var mı?
  • Sınıf iyi isimlendirilmiş mi ve ismi asıl amacını tanımlıyor mu?
  • Sınıfın kullanım arayüzü iyi bir soyutlama sunuyor mu?
  • Sınıfın arayüzü onun nasıl kullanılacağını belli ediyor mu?
  • Sınıfın arayüzüne baktığınızda içinin nasıl implemente edildiğini düşündürmeyecek kadar iyi soyutlanmış mı?Sınıfı bir kara kutu(black box) olarak düşünebilirmisiniz?
  • Sınıflar kendi iç yapılarını diğer sınıfların ulaşmasına izin vermeyecek şekilde iyi koruyor mu?
  • Sınıflardan amaçları ile alakalı olmayan bilgiler çıkartılmış mı?
  • Sınıfı başka sınıflar,bileşenlere ayırmayı düşündünüz mü?Sınıfları sadece tek sorumluluğu yerine getirecek kadar fazla ayırdınız mı?
  • Sınıfın içini değiştirirken sınıf arayüzünün değişmemesine özen gösteriyormusunuz?

Encapsulation

  • Kalıtım(inheritance) sadece IS-A(dır,dir..) ilişkileri için kullanılıyor mu?Kalıtım yapan sınıflar Liskov Substitution Principle’a uyuyormu?
  • Sınıf dökümantasyonu kalıtımın nasıl yapılacağını tanımlıyor mu?
  • Diğer sınıflardan kalıtım yapan sınıflar üzerine yazılamayacak metodların(non overridable) üzerine yazmaktan kaçınıyor mu?
  • Kalıtım yapısı fazla derin değil mi?(Derin olmaması daha iyi)
  • Kalıtım yapılan temel sınıflardaki bütün field lar private mı?

Diğer Konular

  • Sınıf en fazla 7 veya daha az field içeriyor mu?
  • Sınıf diğer sınıfların metodlarını minumum sayıda çağırıyor mu?
  • Sınıf diğer sınıflar ile sadece gerçekten gerektiği anda iletişime geçiyormu?
  • Sınıfın bütün elemanları yapıcı metodlarda oluşturuluyormu?

Aslında her madde ayrı ayrı bir yazı başlığı altında incelenebilir yeri geldikçe bunlar ile alakalı yazmaya çalışacağım. Fakat liste iyi sınıf tasarımı için yapılması gereken çoğu şeyi içeriyor. Sınıflarınızı listeyi kontrol ederek tekrar gözden geçirmenizde fayda var.


Jul 13 2008

Interface mi, Abstract mı?

Tag: Yazılım MühendisliğiM. Cihat Altuntaş @ 1:44 pm

Genellikle Nesneye Yönelik Programlamaya yeni başlayanların kafasındaki büyük soru işaretlerinden bazıları "Interface ile Abstract arasındaki farklar nelerdir?, Ne zaman Interface ne zaman Abstract sınıfları kullanmalıyız ? …." gibi sorulardır.

Acı ve komik bir anımdan bahsetmeden olmaz. Okulda Java hocama neden Interface kullanırız diye sorduğumda bana "Genelde pek kullanılmaz. Fakat benim daha önce çalıştığım bilmem ne işyerinde bi abi interface kullanarak yazılım geliştirirdi. Biz 2 haftada yapıyorsak o bir haftada bitirirdi" demişti. Tabi daha sonra interface,abstract gibi kavramların nesneye yönelik programlamada ne kadar önemli kavramalar olduğunu öğrenince neden o abinin 1 haftada bitirdiğini anladım. :)

Interface ve Abstract kavramları nesneye yönelik programlamanın en temel ve önemli kavramlarından biridir.Önceki yazılarımızda Interface nedir ne zaman kullanılır?, ve Abstract nedir ne zaman kullanılır? ile bu kavramların nerede nasıl kullanıldığına ayrı ayrı konular altında değinmiştik. Eğer okumadıysanız öncelikle eski yazıları okumanızı tavsiye ederim. Aslında iki yazıyıda dikkatlice okuduğunuzda hangi durumda hangisi kullanılabilir diye karar verebilirsiniz.  Bu yazıda da genel bir özet yapıp bu kavramlar hakkında son düşüncelerimi ve kişisel tercihimi yazmak istedim.

Öncelikle daha önceki yazılarda Abstract sınıfların genellikle IS-A(dır,dir) ilişkilerinde,kalıtım(inheritance) özelliğini kullanarak kod tekrarını azaltmak için kullanıldığını söylemiştik. Interface sınıfların ise daha çok CAN-DO(yapabilir..) tarzı ilişkilerde değişen kavramları uygulamadan soyutlamak için kullanıldığını söylemiştik. Birde bu iki kavramın avantaj ve dezavantajlarına bakalım.Sonuçta Abstract bir sınıfın bütün metodlarını abstract yaparak onu da aynı bir interface gibide kullanabiliriz.

Öncelikle bence en büyük fark Abstract sınıfların tekli kalıtım(inheritance ) kullanması Interface sınıfların ise çoklu kalıtıma(multiple inheritance) izin vermesidir.Bildiğiniz gibi bir sınıfı başka bir sınıftan,ya da abstract bir sınıftan türettiğiniz zaman başka bir sınıftan daha türetme imkanınız olmuyor. Fakat interface sınıflarda durum daha farklı. Bir sınıfı istediğiniz kadar interfaceden türetebilirsiniz. Bu bakımdan interface sınıflar abstract sınıflardan oldukça daha esnek diyebiliriz. Fakat interface sınıflara baktığımızda abstract sınıflardan daha yavaş çalıştığı için hız bakımından devavantajı var diyebiliriz.

Şimdi hız mı esneklik mi diye bana soracak olursanız tabiki tercihim esneklikten yana olacaktır. Bu soruyu 2-3 sene önce sorsaydınız önce hız derdim büyük ihtimalle :)

Kendi kişisel tercihim olarak eğer soyutlamak istediğim yapıda ilişki CAN-DO ise ve hiç ortak kod yoksa sadece interface kullanırım. Fakat ortak kod içeriyorsa öncelikle bir interface(dikkat edin yine interface koyarım) koyup ardından ortak kodu kullanan bir abstract sınıf koyup onu o interfaceden türetir ve diğer sınıfları interface sınıfını kullanır hale getiririm.Burada interface i koymasakta olur fakat koymamın sebebi esnekliği arttırması ve özellikle Test Driven Development açısından Mock objelerin oluşturulması için interface kullanan sınıfların test edilmesinin çok daha kolay olması.Mock objelere ayrı bir konu başlığı altında değiniriz fazla uzatmayalım.

Eğer soyutlamak istediğim yapı IS-A ilişkisi ise sadece Abstract sınıf kullanırım. Abstract sınıfların sevmediğim yanı kalıtım(inheritance) diyebilirim.Kalıtım ilişkisi herzaman bağımlılığı(coupling) fazla olan bir ilişkidir. Yani türettiğiniz sınıfa tamamen bağımlı oluyorsunuz. Türettiğiniz sınıftaki herhangi bir değişiklik alt sınıfları etkiliyor. Fakat interface sınıflarda bu tarz bir problem daha az interface içindeki bir metodu değiştirmedikçe hiç bir sınıf bundan etkilenmiyor.Geliştirdiğiniz sınıflar tamamen plug-in mantığı ile interface sınıflar sayesinde hiç problemsiz değiştirilebiliyor. Bu yüzden kalıtıma her zaman şüpheyle yaklaşıyorum. Zaten Object OOP nin insanlar tarafından uzun yıllar kullanıldıktan sonra da aynı fikre varılmış. Kalıtımın bu problemli yapısı görüldüğü için daha sonra kalıtım yerine birleşim(Composition over Inheritance) kullanmak çoğu durumda tercih edilen bir yöntem.OOP için oldukça önemli olan bu konuyuda başka bir yazı konusu olarak ileriye bırakıyorum.

Konu ile ilgili yazıyı yazdığım gün konu ile alakalı çok güzel bir webcast e denk geldim. Buradan sizde izleyebilirsiniz. Konu Interface vs Abstract sınıflar. Burada konuşmacı benden daha katı olarak tamamen interface kullanılması gerektiğini savunuyor.Kısmen katılsamda bu şekile katı bir interface kullanımı her zaman doğru olmayabilir. Ayrıca konu ile ilgili daha derin bilgi edinmek için okumaya fırsat bulamadığım Interface Oriented Design kitabını okuyabilirsiniz. Ayrıca Wikide de  kısa bir bilgi verilmiş(Interface Based Programming).

Bu konuda şuanda aklıma gelenler bu kadar.Dediğim gibi konu bir blog yazısına sığmayacak kadar detaylı. En azından biraz ışık tutabilmiştirim. Ne kadar örneği bol tutmaya çalışsamda kendiniz yazmadıkça projelerinizde kullanmadıkta biraz havada kalabilir. O yüzden eller klavyeye diyorum…


Next Page »