Apr 13 2010

Javascript Refactoring : Use Constants

Tag: Javascript,RefactoringM. Cihat Altuntaş @ 2:45 pm

Javascript kodlarına bakarsanız etrafta bolca string göreceğinizden eminim. En azından benim eski Javascript kodlarım böyleydi.Daha öncede Java, C# gibi static typed dillerde bunların önüne nasıl geçebileceğimizden bahsetmiştik. Javascript dilinde Constant kavramı olmasa da Object Literal notasyonu kullanarak sabit değişkenler tanımlayabiliriz. Aşağıdaki masum Javascript fonksiyonlarını görüyorsunuz.

function createStatusImage(movie) {
    var img = document.createElement("img");
    if (movie.avaliable)
        img.src = '/Content/Images/watched.gif';
    else
        img.src = '/Content/Images/unwatched.gif';

    return img;
};

function makePlanned(img) {
    img.src = img.src = '/Content/Images/planned.gif';
}

Yukarıdaki kodlar ne kadar masum görünse de, her tarafta string tanımı olduğu için kodda eğer resim yani “Content/Images” dizinini değiştirmek istediğinizde kodun 3 yerinde bu değişikliği yapmak zorundasınız. Bunun yerine Object Litaral ile bir konfigürasyon nesnesi oluşturup kodumuzu aşağıdaki gibi refactor edersek daha okunaklı ve değiştirmesi daha kolay olacaktır.

var ImageConfig = {
    IMAGE_PATH: '/Content/Images/',
    WATCHED_IMAGE: 'watched.gif',
    UNWATCHED_IMAGE: 'unwatched.gif',
    PLANNED_IMAGE: 'planned.gif'
}

function createStatusImage(movie) {
    var img = document.createElement("img");
    if (movie.avaliable)
        img.src = ImageConfig.IMAGE_PATH + ImageConfig.WATCHED_IMAGE;
    else
        img.src = ImageConfig.IMAGE_PATH + ImageConfig.UNWATCHED_IMAGE;

    return img;
};

function makePlanned(img) {
    img.src = img.src = ImageConfig.IMAGE_PATH + ImageConfig.PLANNED_IMAGE;
}

Yukarıdaki tanımlama gerçek anlamda oluşturulduktan sonra değiştirilemiyen “constant” ya da “final” değişkenler sunmasa da fantazi olsun diye birisi ImageConfig nesnesi içeriğini değiştirmez ise bu amaçla kullanılabilir.Bu yüzden eğer takım olarak ortak Javascript dosyaları üzerinde değişiklik yapıyorsanız Code Conversions belirlemeniz faydanıza olacaktır. Sizi bilmiyorum ama ben son halini daha çok sevdim :)


Mar 16 2009

I love Regular Expressions

Tag: RefactoringM. Cihat Altuntaş @ 3:53 pm

ilove2

Evet gerçekten Regular Expressions’ı seviyorum. Aslında sevgimde tamamen duygusal diyebiliriz. Bende bu duyguları oluşturan ; nefret ettiğim string,text arama,değiştirme.. işlemlerini benim için çok basitleştirmesi. Dedim ya tamemen duygusal :)

Çok süper Regular Expressions bilmiyorum fakat gün geçtikçe ne kadar kullanışlı olabildiğini gördüğüm için sürekli sevgim sürekli artıyor diyebilirim. Regular Expressions’ı neden çok seviyorum günlük yaşadığım bir problemle sizlere de anlatmaya çalışayım.

Geliştirilen bir Java projesinde Hibernate üzerinden gerçekleştirilen işlemlerin SQL kodunu görmek istiyorduk. Hibernate sağolsun bu konuda bize çok yardımcı oldu. Öncelikle Hibernate’den SQL loglarını alabilmek için baya uğraştık diyebilirim. Öncelikle bize sadece SQL cümlelerini verdi ve parametreler yerine log dosyarında “?” işareti koyarak oldukça kalbimizi kırdı. Ardından uzun süren log konfigürasyon araştırmamız sonucunda “?” işaretleri gelen yerlerde olması gereken parametreleride yazdırabildik. Hibernate Log çıktısı aşağıdaki gibiydi.

11:35:55,656 DEBUG [SQL]  update TABLE set Date=?, ID=?, USERNAME=?, =?, Name=?
11:35:55,656 DEBUG [TimestampType]  binding '2009-03-11 11:35:54' to parameter: 1
11:35:55,656 DEBUG [StringType]  binding 34456 to parameter: 2
11:35:55,656 DEBUG [StringType]  binding 'GM' to parameter: 3
11:36:07,890 DEBUG [SQL]  delete from TABLE where ID=?
11:36:07,890 DEBUG [StringType]  binding 332244 to parameter: 1

Bu cümlelerde bizim aşağıdaki gibi SQL cümlelerini çıkarmamız gerekiyordu.

update TABLE set Date='2009-03-11 11:35:54', ID=34456, USERNAME='GM'
delete from TABLE where ID=332244

Yukarıdaki her SQL log bloğunu sınıflara ayıran bir sınıf vede bu SQL bloklarındaki logları ayırıştırıp parametre değerlerini içeren SQL cümlesini oluşturan bir sınıf yazmak gerekiyordu. Bu sınıf basit olarak “?” işaretleri yerine parametreleri koyacaktı. Bunuda 1. soru işareti yerine “binding” ve “to parameter: 1” kelimeleri arasında bulunan değeri koyacak 2. “?” işareti yerine “binding” ve “to parameter: 2” kelimeleri arasındaki değerleri koyarak devam edip bize SQL cümlesini hazırlayacaktı.Bunun için Verilen indeksteki(1.,2…) “?” işareti yerine gelmesi gereken değeri bulan bir metodu aşağıdaki gibi yazdım.

    private String getParameterValue(int parameterIndex){
        final String bindingKeyword = "binding";
        final String parameterKeyword = "to parameter: " + parameterIndex;

        String[] lines =log.split("\n");

        for(String line:lines){
            if(line.contains(parameterKeyword)){
                int bindingIndex =line.indexOf(bindingKeyword);

                int parameterToIndex=line.indexOf(parameterKeyword);
                String parameter=line.substring(bindingIndex+bindingKeyword.length(), parameterToIndex);
                return parameter.trim();
            }
        }
        return "";
    }

Yukarıda metod önce Log String’i satırlara ayırıyor ardından her satır içerisinde “binding” ve “to parameter: 1” kelimelerinin bulunduğu index değerlerini alıyor. Ardından bu index değerleri kullanarak iki index değeri arasında bulunan "?" işareti yerine gelmesi gereken değeri çıkarıyor. Yani hepimizin oldukça sık kullandığı klasik String işlemleri.Bu metodu Regular Expressions kullanarak birde aşağıdaki gibi yazalım.

    private String getParameterValue(int parameterIndex){
        String regex = "(?<=binding).*(?=to parameter: " + parameterIndex + ")";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(log);

        if (matcher.find()) {
            return matcher.group().trim();
        }
        return "";
    }

Evet Regular Expression Pattern’ımız sayesinde ne Index,Substring.. işlemleri ile uğraşmak zorunda kalıyoruz nede log dosyasını satırlara ayırma işlemleriyle. Basit bir RegEx ifadesi ile aradığımız değeri bütün text içerisinden kolayca çıkarabiliyoruz. Yapmanız gereken tek şey uygun RegEx pattern’ı hazırlamak ve ardından matcher.find() ile kendinizi RegEx’in ellerine bırakıyorsunuz :)

Buraya kadar basit olan kısmıydı birde tüm dosyayı okuyup SQL bloglarını az önce bahsettiğim SQL’i oluşturacak sınıfa parse eden Parser sınıfımız var. Burada RegEx’in gücünü daha iyi görebileceksiniz.Tüm dosyayı okuyup SQL log bloklarını ayıran metodumuz Regular Expression kullanmadan aşağıdaki gibiydi.

    public ArrayList<sqllog> parse() {
        try {

            String line;
            boolean statementFound = false;
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                if (statementFound == true && isSqlStatement(line)) {
                    sqlStatements.add(stringBuilder.toString());
                    stringBuilder.delete(0, stringBuilder.length());
                    statementFound = false;
                }

                if (statementFound == false && isSqlStatement(line)) {
                    statementFound = true;
                }

                if (statementFound) {
                    stringBuilder.append(line + "\n");
                }

            }

            if (statementFound == true) {
                sqlStatements.add(stringBuilder.toString());
                stringBuilder.delete(0, stringBuilder.length());
                statementFound = false;
            }

            reader.close();
        } catch (IOException e) {
            System.out.println("Exception " + e.getMessage());
        }
        return getSqlLogs();
    }

Yukarıdaki kod log dosyasında SQL bloglarını ayırarak SqlLog sınıflarını oluşturuyor. Tabi bunu yaparken satırların alakasız birçok log arasında satırların SQL içerip içermediğini kontrol ediyor. Eğer bulunduysa diğer SQL satırı içeren satıra kadar alıp SqlLog sınıfını oluşturuyor. Bu kodu Regular Expressions kullanarak aşağıdaki gibi tekrar yazalım.

    public ArrayList<sqllog> parse() {
        try {
            Pattern pattern = Pattern.compile("(?s)((insert)|(update)|(delete)).+?(?=SQL)|((insert)|(update)|(delete)).*\\b");
            Matcher matcher = pattern.matcher(fromFile(file));

            while (matcher.find()) {
                String log = matcher.group();
                sqlLogs.add(new SqlLog(log));
            }
        } catch (IOException e) {
        }

        return sqlLogs;
    }

Yukarıdaki RegEx pattern ile insert,update,delete içeren tüm SQL bloklarını çıkarabiliyoruz. Geçici değişkenler yok birçok if-else kontrolü yok, daha az kod, hayat daha güzel…. Beni anladınız umarım.İşte bu yüzden Regular Expressions’ı gerçekten seviyorum. :) .Regular Expressions öğrenmeden önce bende uzun süre gerek varmı diye düşünüp kararsız kalmıştım. Fakat gerçekten öğrenmeye değer…


Jan 29 2009

Refactoring : C# 3.0

Tag: RefactoringM. Cihat Altuntaş @ 6:30 pm

Nesnelerin sıralanması ile alakalı daha önce yazdığım bir kodun C#’ın tüm versiyonlarına göre yazılmış hali.Çok fazla açıklama yapmadan kodun C# 3.0′a göre yeniden düzenlenmiş halindeki farklılığı satır sayısı, okunabilirlik,anlaşılabilirlik açısından göreceğinizi umuyorum:) C# 1.0 olarak yazıdığım kodda Generic sınıflar var aslında bu C# 2.0′ın özelliği olsada kodun çoğu 1.0 özelliklerini kullanılarak yazılmış o yüzden 1.0 diye adlandırdım.

C# 1.0

    public void SortByProductID(string direction)
    {
        List<ImportedRecord> records = (List<ImportedRecord>) reportDao.GetReportRecords(view.ProductImportID);
        records.Sort(new ImportedRecordProductIDComparer(direction));
        view.ImportRecords = records;
    }

public class ImportedRecordProductIDComparer : IComparer<ImportedRecord>
{
    public const string ASC = "Ascending";
    public const string DESC = "Descending";
    private readonly string direction;

    public ImportedRecordProductIDComparer(string direction)
    {
        this.direction = direction;
    }

    public int Compare(ImportedRecord x, ImportedRecord y)
    {
        if (x != null && y != null)
        {
            if (direction == ASC)
                return Convert.ToInt32(x.ProductID).CompareTo(Convert.ToInt32(y.ProductID));
            if (direction == DESC)
                return Convert.ToInt32(y.ProductID).CompareTo(Convert.ToInt32(x.ProductID));
            throw new ArgumentException("Invalid sort direction");
        }
        return 0;
    }
}

C# 2.0

    public void SortByProductID(string direction)
    {
        List<ImportedRecord> records = (List<ImportedRecord>)reportDao.GetReportRecords(view.ProductImportID);
        records.Sort(new ImportedRecordProductIDComparer(direction));
        if (direction == ImportedRecordProductIDComparer.ASC)
            records.Sort(
                delegate(ImportedRecord x, ImportedRecord y)
                {
                    return Convert.ToInt32(x.ProductID).CompareTo(Convert.ToInt32(y.ProductID));
                }
                );
        else if (direction == ImportedRecordProductIDComparer.DESC)
            records.Sort(
                delegate(ImportedRecord x, ImportedRecord y)
                {
                    return Convert.ToInt32(y.ProductID).CompareTo(Convert.ToInt32(x.ProductID));
                }
                );

        view.ImportRecords = records;
    }

C# 3.0

    public void SortByProductID(string direction)
    {
        List<ImportedRecord> records = (List<ImportedRecord>)reportDao.GetReportRecords(view.ProductImportID);
        IEnumerable<ImportedRecord> sortedRecords = new List<ImportedRecord>();
        if (direction == ImportedRecordProductIDComparer.ASC)
            sortedRecords = records.OrderBy(p => Convert.ToInt32(p.ProductID));
        else if (direction == ImportedRecordProductIDComparer.DESC)
            sortedRecords = records.OrderByDescending(p => Convert.ToInt32(p.ProductID));

        view.ImportRecords = sortedRecords.ToList();
    }

Oct 01 2008

Refactoring : Extract Method

Tag: RefactoringM. Cihat Altuntaş @ 8:06 am

Örnekleri buradan indirebilirsiniz.Örnekleri Java kullanarak yaptım. Mantık herhangi bir dilde aynı olduğu için problem olacağını düşünmüyorum. IDE olarak Netbeans 6.1 kullandım.

En çok kullandığım refactoring yöntemi kesinlikle Extract Method. En çok kullandığım tekniklerden bahsetmek neden sonlara kalıyor hala bende çözebilmiş değilim :) Hemen örneğe geçelim ve aşağıdaki gibi bir metoda bu tekniği uygulayalım.Öncelikle şunu belirteyim normal şartlarda Refactoring yapmadan önce mutlaka testlerin olmasına dikkat ederim.Eğer yoksa değiştireceğim metod için test yazıp ardından Refactoring işlemini yaparım. Fakat burada sadece Extract Method nedir onu anlamanız için yazıyı fazla uzatmak istemiyorum. Siz aşağıdaki metodların testlerinin olduğunu düşünün ve gerçek projelerde testler ile refactoring yapmaya çalışın. Nasıl refactoring yapmalıyız başka bir yazıda bu konuya değineceğim.

Devamını Burdan Okuyabilirsiniz


Sep 03 2008

Refactoring : Table Driven Methods

Tag: RefactoringM. Cihat Altuntaş @ 2:36 pm

Değerli arkadaşım Yahya ile ASP.MVC frameworkü pratik olarak öğrenmek için MyMovieCollection adında open-source bir proje geliştiriyoruz. Film koleksiyonu yapmayı sevdiğim için ancak böyle bir proje aklıma geldi. :) Proje basit olarak kullanıcıların filmlerini saklamasını,düzenlemesini .. gibi işlemleri sağlıyor. Geçen gün projede yaptığım basit bir  refactoring if-else, switch-case yapılarından kurtulmak için oldukça güzel bir yöntem olduğu için buraya da yazmak istedim.

Projede her filme kullanıcılar tarafından belirli bir puan verilebiliyor.Listeden seçilen elemana göre Filmlerde verilen puanın düştüğü belirli bir puan aralığına göre “İyi,Kötü,Çok iyi ” tarzı sınıflanıyorlar. Mesela 0-2 arası çok kötü 2-4 arası kötü gibi.Şimdi sınıfı aşağıya yazıp inceleyelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            switch (scale)
            {
                case 1:
                    return new PointScale(0, 1.99, VERYBAD, 1);
                case 2:
                    return new PointScale(2, 3.99, BAD, 2);
                case 3:
                    return new PointScale(4, 5.99, NOTBAD, 3);
                case 4:
                    return new PointScale(6, 7.99, GOOD, 4);
                case 5:
                    return new PointScale(8, 10, EXCELLENT, 5);
                default:
                    return new PointScale(0, 10, ALL, 0);
            }
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }
    }

Şimdi yukarıdaki sınıfın ParseScale metoduna bakmanızı istiyorum.Kullanıcı bu metodu çağırarak bir numara veriyor ve bu numaraya karşılık gelen PointScale nesnesi geri dönüyor. Gördüğünüz gibi metod içinde birçok switch-case yapısı mevcut.Şimdi bu switch-case yapılarından nasıl kurtulabiliriz biraz düşünün. Hemen akla gelmesede çok güzel ve basit bir yöntemle bu tarz koşullu yapılardan kolaylıkla kurtulabiliriz. Yeni halini yazalım ve incelemeye devam edelim.

public class PointScale
    {
        public const string ALL = "Lütfen Seçiniz";
        public const string VERYBAD = "Çok Kötü";
        public const string BAD = "Kötü";
        public const string NOTBAD = "Fena Sayılmaz";
        public const string GOOD = "İyi";
        public const string EXCELLENT = "Çok iyi";

       private static readonly IDictionary<int, PointScale> intToPointScale
            = new Dictionary<int, PointScale>()
                                                {
                                                    {0,new PointScale(0,10,ALL,0)},
                                                    {1,new PointScale(0, 1.99, VERYBAD, 1)},
                                                    {2,new PointScale(2, 3.99, BAD, 2)},
                                                    {3,new PointScale(4,5.99,NOTBAD,3)},
                                                    {4,new PointScale(6,7.99,GOOD,4)},
                                                    {5,new PointScale(8,10,EXCELLENT,5)}
                                                 };
        private readonly double minPoint;
        private readonly double maxPoint;
        private readonly string scaleName;
        private readonly int scaleID;

        public PointScale(double minPoint, double maxPoint, string scaleName, int scaleID)
        {
            this.minPoint = minPoint;
            this.maxPoint = maxPoint;
            this.scaleName = scaleName;
            this.scaleID = scaleID;
        }

        public static PointScale ParseScale(int scale)
        {
            if (intToPointScale.ContainsKey(scale))
                return intToPointScale[scale];

            return new PointScale(0, 10, ALL, 0);
        }

        public virtual double MinPoint
        {
            get { return minPoint; }
        }

        public virtual double MaxPoint
        {
            get { return maxPoint; }
        }

        public virtual string ScaleName
        {
            get { return scaleName; }
        }

        public virtual int ScaleID
        {
            get { return scaleID; }
        }
    }

Yukarıdaki ParseScale metoduna baktığımızda switch-case yapılarının ortadan kalktığını görüyoruz. Bunu basit olarak verilen numaraya karşılık gelecek olan PointScale nesnelerini tablo mantığı ile bir IDictionary intToPointScale  nesnesinde eşleştirerek yapıyoruz. Ardından metod içinde verilen numaraya karşılık gelen nesne switch-case,if-else kontrolü yapmadan return intToPointScale[scale]; sayesinde geri dönülüyor. Bu şekilde uygulama oldukça esnek hale geldi. Dictionary içinde sakladığımız nesneleri istersek dışarıdan bir XML, yada Veritabanından alabiliriz ve herhangi bir if-else eklemek zorunda kalmayız. Bu tarz Tablo mantığı ile çalışan metodlara Table Driven Methods deniliyor. Çoğu durumda oldukça faydalı olabiliyor.


Aug 06 2008

Refactoring : Replace Magic Number with Symbolic Constant

Tag: Code Smells,RefactoringM. Cihat Altuntaş @ 5:56 pm
public class Siparis
{
    private int siparisDurumu=0;

    public void SiparisEt()
    {
        if (siparisDurumu == 1)
        {
            //sipariş işlemlerini başlat
        }
    }
}

Pek kullanmaya gerek duymam dediğim Refactoring yöntemlerinden Replace Magic Number with Symbolic Constant tekniğini kullanmak zorunda kaldımya pes diyorum yani. Şimdi biri bana gelsin yukarıdaki kodun ne yaptığını anlatsın. Bunu anlamak için veritabanına girip sipariş durumu tablosunu bulup ardından 1 sayısının karşılığı olan anlamı bulmak zorunda kalıyorum. Tabi birsürü zaman kaybı israf. Ayrıca her defasında kodu okuduğumda acaba 1 neydi 2 neydi diye hafızamı zorlamak zorundayım hafızamda pek kuvvetli olmadığı için sürekli hatada yapabiliyorum. Tabi size gösterdiğim kod örneği çok basitleştirilmiş hali birde bunun onlarca sayıdan oluştuğunu düşünün tam anlamıyla işler arap saçına dönmeye başlıyor.

Bu tarz sihirli numaraları(Magic Numbers) lütfen kodun içine gömmeyelim.Hem okuması zor, hemde sabit değişken değiştiğinde başımıza birsürü dert açıyor.Yukarıdaki durumu düşünün sipariş kabulu 1 değilde artık 111 ile temsil etmek istiyoruz ne yapacağız?Gidip tek tek 1 geçen yerleri 111 e çevirmek zorunda kalıyoruz işin yoksa uğraş. Çok zor değil o sihirli numara yerine sabit bir değişken kullanacağız. 21. yüzyılda yaşıyoruz programlama dillerimiz oldukça gelişmiş,bize bu tarz olanakları kolaylıkla sunuyorlar.Kodumuzu aşağıdaki gibi değiştirelim.

public class Siparis
{
    private int siparisDurumu = 0;

    private const int KABUL_EDILMEDI = 0;
    private const int KABUL_EDILDI = 1;

    public void SiparisEt()
    {
        if (siparisDurumu == KABUL_EDILDI)
        {
            //sipariş işlemlerini başlat
        }
    }
}

Gördüğünüz gibi alt tarafı bir sembolit sabit yazarak kodun okunulabilirliğini,anlaşılabilirliğini ne kadar arttırdık. Ayrıca kod üzerinde daha sonradan çalışacak olan benim gibi developer arkadaşlarımızın arkamızdan bizim kulaklarımızı çınlatmasını da önlemiş olduk.


Jul 16 2008

Refactoring-Consolidate Duplicate Conditional Fragments

Tag: Code Smells,RefactoringM. Cihat Altuntaş @ 2:16 pm

Evet bu aralar parserlar ile boğuşuyorum kendi çapımda ufak bir dil yapmaya çalışıyorum. Programlamlama dillerini kullanması kolay ama yapması gerçekten zormuş ilk olarak onu söyleyebilirim. Örnek bir parser bulup nasıl yapıldığı hakkında fikir edinmek için onu inceliyordum.Kodu çok karmaşık ve anlaması çok zor olduğu için önce testlerin sağlamlığından emin olana kadar Unit Test hazırladım. Ardından kodu daha okunabilir,anlaşılabilir olması,ve code smell’lerden kurtulmak için refactoring yaparım.Bunu yaparken aşağıdaki gibi bir kod ile karşılaştım.Aşağıdaki kodu öncelikle biraz inceleyin bir problem görebilecekmisiniz?

        private double atom() throws InterpreterException {
            double result = 0.0;

            if (tokenizer.getTokenType() == TokenTypes.NUMBER){
                try{
                    result = Double.parseDouble(tokenizer.getToken());
                }
                catch (NumberFormatException exc){
                    throw new InterpreterException("Syntax Error :" + exc.getMessage());
                }
                tokenizer.obtainNextToken();
            }
            else if (tokenizer.getTokenType() == TokenTypes.VARIABLE){
                result = getValueOfVariable(tokenizer.getToken());
                tokenizer.obtainNextToken();
            }
            else
                throw new InterpreterException("Syntax Error");

            return result;
        }

Şimdi yukarıdaki koda baktığınızda ilk başta büyük bir problem gözünüze çarpmıyo olabilir benimde çarpmıyordu çünkü. Fakat biraz daha incelediğimde beni rahatsız eden bir kötü kod barındırdığını farkettim. Şimdi if-else yapılarını beraberce inceleyelim. İlk if yapısına baktığımızda Token tipinin numara olup olmadığını kontrol ediyor. Eğer numaraysa Tokeni double çevirip sonucu üretiyor ve bir sonraki tokeni alıyor. İkinci else if yapısına baktığımızda tokenin değişken olup olmadığını kontrol ediyor. Ardından değişken ise değerini alıp sonucu üretiyor ve burada da bir sonraki tokeni alıyor.Son olaral eğer hiçbiri değilse hata fırlatıyor.Yani değişken ya da numara değilse hata fırlatıyor.

Şimdi buradaki problem biraz zor farkedilse de if-else yapıları içerisinde tekrarlayan kod içeriyor.Yukarıda açıklamada farkettiyseniz Eğer token değişken ya da numara ise bir sonraki tokeni alma işlemi ikisinde de yapılıyor.Son else yapısında bu yapılmamış aslında son else ilk başta yapılması gereken bir hata kontrolü.Hani hata kontrolünü başa alırsak bu tekrar daha bariz bir şekilde ortaya çıkacaktır. Kodu aşağıdaki gibi düzenleyip hata kontrolünü başa alalım. Ve tekrar içeren yerleri işaretleyelim.

        private double atom(){
            double result = 0.0;

            if (tokenizer.getTokenType() != TokenTypes.NUMBER &amp;&amp; tokenizer.getTokenType() != TokenTypes.VARIABLE)
                throw new InterpreterException("Syntax Error");

            if (tokenizer.getTokenType() == TokenTypes.NUMBER){
                try{
                    result = Double.parseDouble(tokenizer.getToken());
                }
                catch (NumberFormatException exc){
                    throw new InterpreterException("Syntax Error : " + exc.getMessage());
                }
                tokenizer.obtainNextToken();
            }
            else if (tokenizer.getTokenType() == TokenTypes.VARIABLE)
            {
                result = getValueOfVariable(tokenizer.getToken());
                tokenizer.obtainNextToken();
            }

            return result;
        }

Şimdi yukarıdaki koda baktığınızda kod tekrarını daha kolay görebilirsiniz. Tekrar olan yerleri kırmızı içinde görüyorsunuz.Şimdi tekrarlardan kurtulup kodu daha iyi hale getirmek için aşağıdaki gibi tekrar refactoring yapıyoruz.

private double atom(){
    double result = 0.0;

    if (tokenizer.getTokenType() != TokenTypes.NUMBER &amp;&amp; tokenizer.getTokenType() != TokenTypes.VARIABLE)
        throw new InterpreterException("Syntax Error");

    if (tokenizer.getTokenType() == TokenTypes.NUMBER){
        try{
            result = Double.parseDouble(tokenizer.getToken());
        }
        catch (NumberFormatException exc){
            throw new InterpreterException("Syntax Error : " + exc.getMessage());
        }
    }
    else if (tokenizer.getTokenType() == TokenTypes.VARIABLE){
        result = getValueOfVariable(tokenizer.getToken());
    }
    tokenizer.obtainNextToken();
    return result;
}

Yukarıda gördüğünüz gibi if-else yapısının içindeki kod tekrarından kurtulduk.Bu şekilde şartlı yapıların içindeki tekrar eden kodu tek bir yere alıp tekrardan kurtulma işlemine Consolidate Duplicate Conditional Fragments diyoruz. Burada ismi fazla önemli değil aslında yaptığı işlemi anlamanız yeterli.Bu kodu aslında biraz daha düzenleyebiliriz. İlk olarak gözüme çarpan hata kontrolündeki uzun if cümlesi oluyor. Bu uzun ifade daha önce yazdığım Decompose Conditional kullanılarak daha basit ve anlaşılabilir hale getirilir.Onuda size bırakıyorum. Kolay gelsin…


Nov 23 2007

Refactoring-Decompose Conditional

Tag: RefactoringM. Cihat Altuntaş @ 5:15 am

Kodun okunulabilirdiğini arttıran en önemli Refactoring yöntemlerinden biride Decompose Conditional yani türkçeye çevirmeye çalışırsak Şartlı ifadeleri ayırma diyebiliriz.Küçük bir örnek üzerinde nasıl yapıldığını görürsek daha iyi anlaşılacağını umuyorum.Aşağıda daha önce uğraştığım verilen veritabanındaki tablolar için sınıf oluşturan programdan ufak bir sınıfın constructor metodunu görüyorsunuz.

private const int USER_TABLE_ATTRIBUTE = 0;
private TableDefs tableDefs;
public MDBResolver(string mdbPath)
{
	this._mdbPath = mdbPath;
	DAO.DBEngineClass dbEng = new DAO.DBEngineClass();
	DAO.Database db = dbEng.OpenDatabase(this._mdbPath,false, false, &quot;&quot;);
	tableDefs = db.TableDefs;
	ArrayList tables = new ArrayList();
	for(int tableIndex = 0; tableIndex &lt; tableDefs.Count; tableIndex ++)
	{

                if (tableDefs[tableIndex].Attributes == USER_TABLE_ATTRIBUTE)
		{
		Table table ;
                string tableName=tableDefs[tableIndex].Name;
                table= new Table(tableName,this.GetFields(tableDefs[tableIndex].Name));
		tables.Add(table);
		}
	}
	db.Close();

	this._tables = (Table[])tables.ToArray(typeof(Table));
}
//geriye kalan metodlar........
//....................

İlk gözüme çarpan yukarıdaki kodun okunulabilirliğinin kötü olduğu. Çünkü kodu okuduğumda bana konuşma dilindeki gibi ne yapmaya çalıştığını ifade edemiyor.Kodu iyice incelediğimde ne yapmaya çalıştığını anlıyorum. Verilen veritabanındaki tabloları alıp bir tableDefs nesnesine atıyor ardından bu nesne içindeki tabloları alıp eğer USER_TABLE ise bunu sınıfını oluşturacağı tablolar listesine atıyor.Çünkü işimize yaramayacak olan Sistem tablolarının sınıflarını üretmesini istemiyoruz.

Kırmızı satıra baktığımızda bir if kontrolü görüyoruz.Tablonun USER_TABLE olup olmadığını kontrol ediyor. O satırın içindeki parantezleri iyice okumadığımız zaman USER_TABLE kontrolü yaptığını anlayamıyoruz.Şimdi Refactoring yaparak kodumuzu aşağıdaki gibi değiştirelim.

private const int USER_TABLE_ATTRIBUTE = 0;
private TableDefs tableDefs;
public MDBResolver(string mdbPath)
{
	this._mdbPath = mdbPath;
	DAO.DBEngineClass dbEng = new DAO.DBEngineClass();
	DAO.Database db = dbEng.OpenDatabase(this._mdbPath,false, false, &quot;&quot;);
        tableDefs = db.TableDefs;
        ArrayList tables = new ArrayList();
	for(int tableIndex = 0; tableIndex &lt; this.tableDefs.Count; tableIndex ++)
	{

		if (isUserTable(tableIndex))
		{
		Table table ;
                string tableName=tableDefs[tableIndex].Name;
                table= new Table(tableName,this.GetFields(tableDefs[tableIndex].Name));
		tables.Add(table);
		}
		}
	db.Close();

	this._tables = (Table[])tables.ToArray(typeof(Table));
}

private bool isUserTable(int tableIndex)
{
        return this.tableDefs[tableIndex].Attributes == USER_TABLE_ATTRIBUTE;
}

//geriye kalan metodlar........
//....................


Yukarıdaki yeşil satırda eski şartlı ifademizi nasıl değiştirdiğimizi gördünüz. Önceden if içinde bulunan ifadeyi ayrı bir metod olarak ayırdık.Yani şartlı ifadeyi ayrıştırdık.Kod artık okunduğunda gayet kolaylıkla anlaşılabilir hale geldi.Bu şekilde if-else şartlı kontrol yapıları içindeki anlaşılması zor olan kontrolleri ayrı bir metod içine alarak ayırmaya Decompose Conditional deniyor. Sonuçta tek satır içeren ufak bir metod ortaya çıktı gereksiz olarak görebilirsiniz fakat kodun okunulabilirdiği temizliği ilerisi için her zaman daha önemli. Kodun diğer kısımlarıda aslında Refactoring gerektiriyor bunun farkındayım amacım sadece Decompose Conditional olduğu için diğerlerini sizlere bırakıyorum…


Nov 17 2007

Neden Kod İçin Açıklama Satırları Yazmamalıyız?

Tag: Code Smells,RefactoringM. Cihat Altuntaş @ 8:44 am

Kod açıklama satırları denince hemen okul yılları aklıma geliyor . Hocalarımız üstüne basa basa kodumuza açıklama satırı yazmamız gerektiğini vurgulardı. Hatta ödevlerde,projelerde açıklama satırı olmayan kodlardan puan kırarlardı yanlış hatırlamıyorsam. Ayrıca yeni bir programlama dili öğrendiğimizde ilk olarak nasıl açıklama satırı koyarız onu öğrenirdik.

İlk zamanlar bunu neden yaptığımızı hiç sorgulamamıştım ama o açıklama satırları kodun okunuşunu,şeklini bozduğu için pek sevmezdim diyebilirim. Fakat yinede açıklama satırlarını bol bol kullanıp, kullanmayanları da uyarırdım.Peki neden kodumuza açıklama satırı koyma ihtiyacı duyuyoruz hiç düşündük mü?İlk başlarda gerekli olduğunu düşündüğüm için bunu hiç sorgulamamıştım fakat şimdi düşündüğümde o zamanlar koyduğum çoğu açıklama satırının şimdi gereksiz olduğunu anlıyorum. Peki neden gereksizdi? Çünkü o açıklama satırları okunulabilirliği,anlaşılabilirliği düşük olan kötü kodun kendim ya da başkaları tarafından anlaşılabilmesi için yazılmıştı. Kod okunduğu zaman ne yaptığını ifade edemiyordu bende bu yüzden açıklama satırları ile kodun ne yaptığını ifade etmeye çalışıyordum. Kodumuzun okunduğu zaman kolaylıkla anlaşılabildiğini düşünün o zaman bu kod için açıklama satırları yazmamıza gerek olurmu sizce? Bence olmaz çünkü kodun kendisi açıklama satırları gibi kendini ifade ediyor.Bu yüzden tekrar açıklama satırı koymaya gerek kalmayacaktır.

İş yerindeki çalışma arkadaşım bana Commentless Programming savunucusu diye bir ünvan takmıştı.Tabi bunun benim ortaya attığım bir fikir olmadığını söylemem gerekir. İlk olarak kod açıklama satırlarının bir kötü kod belirtisi olduğunu Martin Fowler’ın Refactoring Improving the Design of Existing Code kitabında okumuştum. Fowler kitabında benimde çok hoşuma giden “Kod açıklama satırları kötü kokan kodun fazla kokmasın diye üzerine sıkılmış deodorantlardır.” ifadesini kullanıyordu. Daha sonrada kendi deneyimlerimle çoğu yerde açıklama satırlarının gereksiz, çoğu zaman kötü yazılmış bir kodun belirtisi olduğunu gördüm.

Ufak tefek birkaç örnekle neden ve hangi durumlarda açıklama satırlarının gereksiz olduğunu anlatmaya çalışacağım.Bu örnekleri mümkün oldukça gerçek hayatta yazılmış kodlardan kesitler alıp göstereceğim. Öncelikle en fazla karşılaştığım açıklama satırlarından başlamak istedim.

Gereksiz açıklama satırları

public class MapObject{
    Map mapData = null;
    MapList mapList = null;
    Vector mapJList = new Vector(4, 1);

    /**
     * Consructor, Creates a new instance of MapObject
     */
    public MapObject() {
        MapListLoader mapListLoader = new MapListLoader();
        mapList = mapListLoader.loadMapList();
        Logger.printDebugMessage("Maps loaded...");
        mapJList = createMapJObjects(mapList);
    }
}

class User {
    private String address;
    //..

    /*
    gets address
     */
    public String getAddress() {
        return address;
    }
}

Şimdi yukarıdaki kodda gördüğünüz açıklama satırına bir bakalım.MapObject sınıfındaki Consructor’ının üzerine “Consructor, Creates a new instance of MapObject” yani anlamazsınız diye söylüyorum demiş yazan “bu bir yapıcı metod ve bu sınıfın yeni bir örneğini oluşturur.” :) Evet bunun zaten bir yapıcı metod olduğunu görebiliyoruz. Her Java, C# kullanıcısıda bunun böyle olduğunu açıklama satırını okumadan da anlayabilir.İkinci User sınıfımızda da javada zaten bir getter olan ayrıca gayet anlaşılır getAddress metodunun üzerine “Adresi getirir” açıklama satırı eklenmiş. Bunlara benzer açıklama satırları oldukça gereksizdir. Ekranda boşu boşuna yer kaplayıp diğer kodun okunmasını zorlaştırır.Bu tarz açıklama satırlarını hiç düşünmeden elimizden geldiğince hızlı sililiyoruz.

Değişkenler için kullanılan açıklama satırları

public class GetMapInfo{
    private static String strMapDir = ""; // map physical path
    private static String strMapServerURL = ""; // Map Connection URL
    //.................
}

Yukarıda gördüğümüz açıklama satırlarıda düzgün isim verilmemiş değişkenleri ifade etmek için kullanılmış. Mesela ilk değişkenin ismi strMapDir açıkçası ben ilk gördüğümde harita klasörü gibi bişey anlıyorum fakat yanındaki yorum satırını okuyunca haritanın fiziksel yolunu ifade ettiğini anlıyorum.Diğer strMapServerURL değişkenine baktığımızda da yanına açıklama satırı yazma gereği duyulmuş çünkü değişkenin ismi tam olarak kendini ifade edemiyor bu yüzden açıklama satırlarından yardım alınmış. Şimdi bu değişkenleri düzgün isimlendirip açıklama satırlarını kaldırdığımızda aşağıdaki gibi olan kodumuza bakalım.

public class GetMapInfo{
    private static String mapPhysicalPath = "";
    private static String mapConnectionURL= "";
    //.................
}

Şimdi yukarıdaki koda baktığımızda değişkenlerin isimleri ne olduklarını gayet iyi ifade ediyor.Gördüğünüz gibi değişkenleri düzgün isimlendirdikten sonra bu değişkenler için tekrar açıklama satırı yazmaya gerek kalmadı.

Metodlar ve parametreleri için kullanılan açıklama satırları

    /**
     * Builds LayerList object with Layer list
     * @param list Layer list
     * @return LayerList
     */
    private LayerList loadLayerList(List list){
        Layer layer = null;
        LayerList layerList = new LayerList();
        for(int i = 0; i<list.size(); i++){
            layer = (Layer) list.get(i);
            layerList.add(layer);
        }
        return layerList;
    }

Yukarıda gördüğünüz metodun açıklama satırına bakacak olursak metodun ne yaptığını ve parametre olarak gelen list değişkeninin ne ifade ettiğini yazmış.Açıklama satırına bakacak olursak verilen Layer(katman) listesi ile bütün katmanların bilgilerini içinde barındıran LayerList nesnesi oluşturduğunu yazıyor buraya kadar güzel metodun ne yapmak istediğini anlıyoruz.Fakat metod ismine baktığımızda loadLayerList ben açıkçası “harita listesini yükle” anlıyorum.Açıklama satırlarını okumasam sanki bir veritabanından ya da başka biryerden LayerList nesnesini yükleyeceğini sanırdım ama öyle olmadığını açıklama satırı bana söylüyor.İkinci açıklama satırıda parametre olarak alınan list parametresinin bize Layer listesi olduğunu ifade ediyor fakat metoda baktığımızda (List list) gibi parametre aldığını görüyoruz açıklama satırlarını okumadan da bu list parametresinin Layer listesi olduğunu anlamamız çok zor.Buradaki problem düzgün verilmemiş metod ve paramatre isimlerinden kaynaklanıyor. Metod ismi metodun ne yaptığını ifade edemediği için,parametre ismide kendisini ifade edemediği için açıklama satırlarının yardımına başvurulmuş sorun geçici olarak çözülmüş. Fakat biz bu metodu ve aldığı parametreyi aşağıdaki gibi düzgün olarak isimlendirirsek açıklama satırlarına gerek kalmayacak.

<span style="color: blue">
    private LayerList buildLayerList(List layerList){
        Layer layer = null;
        LayerList layerList = new LayerList();
        for(int i = 0; i<list.size(); i++){
            layer = (Layer) list.get(i);
            layerList.add(layer);
        }
        return layerList;
    }

Açıklama satırlarını silip metodumuzun ismini private LayerList buildLayerList(List layerList) olarak değiştirdik. Şuanda metodun adını okuduğumda bana direk olarak ne yaptığını ifade edebilir hale geldi adından da anlaşıldığı gibi buildLayerList yani LayerList nesnesi oluşturuyor.Parametre olarak alınan listeninde layerList olduğunu parametre adından kolaylıkla anlayabiliyorum. Gördüğünüz gibi düzgün isimlendirilmiş metod ve parametreler açıklama satırlarına gerek kalmayacak şekilde kodun okunulabilirdiğini arttırıyor.

Metodların içinde kullanılan açıklama satırları

private void Aktivasyon_Load(object sender, System.EventArgs e)
{
    //Disk modelinin bilgilerini al
    ManagementClass clsDiskSurucu = new ManagementClass("Win32_DiskDrive");
    ManagementObjectCollection DiskSurucu = clsDiskSurucu.GetInstances();
    ManagementObjectCollection.ManagementObjectEnumerator DiskSurucuEnumerator;
    DiskSurucuEnumerator=DiskSurucu.GetEnumerator();
    DiskSurucuEnumerator.MoveNext();
    ManagementObject DSS = (ManagementObject)DiskSurucuEnumerator.Current;
    string diskModel = DSS["Model"].ToString();

    //BIOS seri numarasının bilgilerini al
    ManagementClass clsBios = new ManagementClass("Win32_BIOS");
    ManagementObjectCollection Bios = clsBios.GetInstances();
    ManagementObjectCollection.ManagementObjectEnumerator BiosEnumerator;
    BiosEnumerator=Bios.GetEnumerator();
    BiosEnumerator.MoveNext();
    ManagementObject BBios = (ManagementObject)BiosEnumerator.Current;
    string biosSeriNumarasi = BBios["Caption"].ToString();

    //Disk modeli ile BIOS seri no string olarak alınıyor ve şifre oluşturuluyor
    sifre= biosSeriNumarasi + diskModel;
    //Oluşturulan makina bağımlı string md5 ile hashleniyor
    sifreHash = FormsAuthentication.HashPasswordForStoringInConfigFile(sifre, "md5");

    //Oluşan hashli stringi alanlarına yerleştiriliyor
    SKod1.Text = sifreHash.Substring(0, 3);
    SKod2.Text = sifreHash.Substring(3, 3);
    SKod3.Text = sifreHash.Substring(6, 3);
    SKod4.Text = sifreHash.Substring(9, 3);
    SKod5.Text = sifreHash.Substring(12, 3);
}

Yukarıda gördüğümüz bu küçük C# metodu içinde birçok açıklama satırı kullanılmış.Açıklama satırlarını okumadan anlayamıyorsunuz ama Metod kısaca aktivasyon işlemleri için bazı işlemler yapıyor.Yorum satırlarına baktığınızda önce diskin modelini alıyor ardından bios seri numarasını alıyor ardından bunları birleştirerek bazı alanlara yerleştiriyor. Bu tarz; içinde açıklama satırı bulunduran metodların problemi çok fazla iş yapmaları, aslında içlerinde birden fazla potansiyel metod barındırmalarıdır bunu da yorum satırlarından kolaylıkla anlayabilirsiniz. Burada yorum satırları mantıksal olarak ayrı olan üç ayrı işlemi ifade etmek için kullanılmış.Bunlar diskin modelini alma,bios seri numarasını alma ve bunları bazı alanlara yerleştirme.Bu tarz yorum satırları bize kodumuzun Refactoringe ihtiyacı olduğunun sinyalini verir.Kodumuzu tekrar düzenleyerek yorum satırlarını silip aşağıdaki gibi değiştiriyoruz.

private void Aktivasyon_Load(object sender, System.EventArgs e)
{
sifre=GetBiosSeriNumarasi()+GetDiskModel();
sifreHash=FormsAuthentication.HashPasswordForStoringInConfigFile(sifre,"md5");
HashVeriyiAlanlarinaYerlestir();
}

private void HashVeriyiAlanlarinaYerlestir()
{
        SKod1.Text=sifreHash.Substring(0,3);
        SKod2.Text=sifreHash.Substring(3,3);
        SKod3.Text=sifreHash.Substring(6,3);
        SKod4.Text=sifreHash.Substring(9,3);
        SKod5.Text=sifreHash.Substring(12,3);
}

private static string GetBiosSeriNumarasi()
{
        ManagementClass clsBios= new ManagementClass("Win32_BIOS");
        ManagementObjectCollection Bios = clsBios.GetInstances();
        ManagementObjectCollection.ManagementObjectEnumerator BiosEnumerator;
        BiosEnumerator=Bios.GetEnumerator();
	BiosEnumerator.MoveNext();
        ManagementObject BBios =(ManagementObject)BiosEnumerator.Current;
	return BBios["Caption"].ToString();
}

private static string GetDiskModel()
{
        ManagementClass clsDiskSurucu= new ManagementClass("Win32_DiskDrive");
        ManagementObjectCollection DiskSurucu = clsDiskSurucu.GetInstances();
        ManagementObjectCollection.ManagementObjectEnumerator DiskSurucuEnumerator;
        DiskSurucuEnumerator=DiskSurucu.GetEnumerator();
	DiskSurucuEnumerator.MoveNext();
	ManagementObject DSS = (ManagementObject)DiskSurucuEnumerator.Current;
	return DSS["Model"].ToString();
}

Yeniden düzenlenmiş metodlarımıza bakacak olursanız açıklama satırlarına ihtiyaç duymadan kodu okuyarak ne yapıldığını rahatlıkla anlayabilirsiniz.En önemli avantajlarından biride uzun metodumuzu yorum satırlarına göre 3 ayrı metoda bölmemiz oldu.Ve bu yorum satırlarımız aslında yeni gelen bu 3 metodun adları oldu.Metodların adlarını okuduğumuzda gayet açık olduğu görülebilir.

Gördüğümüz gibi kod açıklama satırları çoğu zaman kötü yazılmış kodun belirtileridir.Bu açıklama satırlarını kodumuza düzgün isim vererek, küçük küçük mantıksal parçalara ayırarak vb. tekniklerle ortadan kaldırabiliriz.Bu yüzden açıklama satırlarına çoğu zaman kodun düzeltilmesi gerektiğini belirten fırsatlar olarak bakıyorum. Tabi hiç açıklama satırı yazmayacakmıyız tarzı bir soru aklınıza geliyor olabilir.Yazacağız ama sadece gerektiği durumlarda ve çoğu durumda gerekmeyecek buna inanabilirsiniz.Belki yazdığınız bir metodda kullandığınız algoritmanın neden kullanıldığını yada daha sonra okuyan için dikkate alması gereken açıklamalar yapabilirsiniz. Ama genellikle kodun ne yaptığını açıklama satırları ile değilde daha düzgün okunabilir temiz kod yazarak sağlamaya çalışın.


Sep 02 2007

Refactoring-Feature Envy

Tag: Code Smells,RefactoringM. Cihat Altuntaş @ 5:30 am

Feature Envy Martin Fowler’ın Refactoring: Improving the Design of Existing Code kitabında geçen code smell’lerden ve özellikle benim en sevmediklerimden biri. Bu arada yazılım dünyasında yerleşmiş orjinal pattern, refactoring vb.. isimleri orjinal adıyla kullanacağım çünkü bu isimlerin genel amacı iletişimi arttırmak olduğu için bütün yazılım dünyasınca kabul edilmiş bu isimlerin türkçeleştirilip ilginç ve anlaşılmayan bir isim alması taraftarı değilim. Neyse fazla uzatmadan konumuza dönelim..

Feature Envy genelde bir sınıfın başka sınıfın verisini kullanarak bir takım işlemleri yaptığında ortaya çıkar. Geçenlerde bahsettiğim (P)Object Oriented dönüşümü aşamalarında en çok karşımıza çıkan kötü kod örneklerinden biridir. Klasik prosedürel mantıkta herşey fonksiyon ve veriler üzerine kurulduğu için Record, Struct, Primitive vb. gibi yapılar ve bunları kullanarak çeşitli işlemleri yapan fonksiyonlar vardı. Bir türlü prosedürel programlama mantığı sevdamızdan(ilk aşk unutulmaz derler :) ) vazgeçemediğimiz için bunu Object Oriented dillerede taşıdık malesef.

Geçenlerde projede iş arkadaşlarımdan biri bir konuda yardım istemişti konu kullanıcının tanımladığı çeşitli hız aralıklarına göre harita üzerinde o hız aralıklarına uygun renklerin çizilmesiydi. Beraber arkadaşın kodunu inceliyorduk ortada Lejand diye bir sınıf vardı. Bu arada Lejand harita üzerinde neyin ne anlama geldiğini belirten bir çizgiymiş. Çizgiyi sürekli görüyordum fakat o zamana kadar Lejand olarak anlandırıldığını bende bilmiyordum :) Lejand sınıfına baktığımızda tam hatırlamasamda aşağıdaki yapıya benzer bir kod vardı.

class Lejand{
    private int r,g,b;
    private int maxSpeed,minSpeed;

    public void setRGB(int r,int g,int b){
        this.r=r;
        this.g=g;
        this.b=b;
    }

    public int getR() {
        return r;
    }

    public int getG() {
        return g;
    }

    public int getB() {
        return b;
    }

    public int getMinSpeed() {
        return minSpeed;
    }

    public void setMinSpeed(int minSpeed) {
        this.minSpeed = minSpeed;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
}

class Controller{
    private Lejand lejand; //kullanıcının hız aralıklarına g&amp;amp;amp;#246;re red,green,blue değerleri
    private int currentSpeed ;//cizilecek olan şuandaki hızın değeri
    //.....
    //....
    void drawLine(){
        //....
        if(currentSpeed<lejand.getMaxSpeed () && currentSpeed >lejand.getMinSpeed()){
            Color color =new Color(lejand.getR(),lejand.getG(),lejand.getB());
            //..yukarıdaki color nesnesini kullanarak yapılan cizim işlemler.
            //g.setColor(color);
            //..
        }

    }
}

Burada yapılan işlemin o andaki hız değerine göre kullanıcının lejand değerlerinde tanımlı olan maximum ve minimum değerleri karşılaştırıp eğer o aralığa denk düşüyorsa lejandta o aralık için tanımlanmış RGB değerlerinden bir renk üretip çeşitli çizim işlemleri yapmak.Buraya kadar herşey güzele benziyor fakat bizden bu renk değerlerinin artık RGB olarak değilde direk metin şeklinde renkler olarak tutulması istendi o zaman ne yapacağız ?Hemen kodumuzu aşağıdaki gibi değiştireceğiz..

class Lejand{
    private String color;
    private int maxSpeed,minSpeed;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getMinSpeed() {
        return minSpeed;
    }

    public void setMinSpeed(int minSpeed) {
        this.minSpeed = minSpeed;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
}

class Controller{
    private Lejand lejand; //kullanıcının hız aralıklarına gore red,green,blue değerleri
    private int currentSpeed ;//cizilecek olan şuandaki hızın değeri
    //.....
    //....
    void drawLine(){
        //....
        if(currentSpeed<lejand.getMaxSpeed () && currentSpeed< lejand.getMinSpeed()){
            Color color ;
            if(lejand.getColor().equals("Yesil")) {
                color = Color.GREEN;
            }else if(lejand.getColor().equals("Mavi")){
                color = Color.BLUE;
            }
            //..yukarıdaki color nesnesini kullanarak yapılan cizim işlemler.
            //g.setColor(color);
            //..
        }
    }
}

Gördüğünüz gibi hem Lejand hemde bu sınıfı kullanan Controller sınıfı kodumuzu değiştirmek zorunda kaldık. Tabi sadece bu değil aynı lejand sınıfını kullanan başka sınıflarda aynı işlemi yapıyorlardı onlarda da bu kodu değiştirmek zorunda kaldık. Gördüğümüz gibi yukarıdaki yapıya benzer şekilde yazılan kod hem aynı mantığı başka sınıflarda da tekrarlattırdığı için kod tekrarı içeriyor hemde herhangi bir değişikliğe karşı çok kırılgan yapıda olduğu için bir sınıfta yapılan değişiklik birden fazla sınıfı etkiliyor.

Genel olarak gerçek Object Oriented yazılmış kodun en önemli avantajlarından birisi de değişimin etkisinin en aza indirgenmesidir. Böylece geliştirdiğimiz yazılım için değişikliklerin maliyetinin en aza indirgenmesi sağlanır.Peki ilk bakışta düzgün görünen bu kodlardaki problem neydi ona bakalım biraz..

İlk olarak dikkat çekmesi gereken Lejand sınıfı hiçbir iş yapmıyor daha doğrusu içerisinde iş yapan bir metod yok.Bu arada sadece private alanın önünde duran Getter/Setter ya da C#’ki Property yapıları iş yapan metod kategorisine girmiyor. Kısaca bu tarz sınıflara

sorumluluktan yoksun Veri sınıfları (Data Class) diyoruz. Projenizde bu tarz yani sadece Property ya da Getter/Setter içeren ve bunları kullanılan sınıflara fazlaca rastlanıyorsa kodunuzu tekrar gözden geçirmeniz ve bunları düzetleniz projenin saadeti için iyi olur.

Object Oriented’ın temel felsefesi ve Prosedürel mantıktan ayrılan en önemli özelliklerinden biriside veri ve bu veriyle işlem yapan fonksiyonların bir arada bulunmasıdır. Bu tarz sınıflar bu özelliği ihlal edip kırılgan bir koda sebep olurlar. Yukarıdaki sınıflarda ise veri Lejand sınıfının içinde olmasına rağmen onla alakalı renk üretme işlemi Controller sınıfında bulunuyordu. Şimdi bunu düzeltelim ve herkesi olması gereken yere yerleştirelim. Bunun için uygulanacak refactoring Move Method olacak ileride ayrı bir yazıda basit olmasına rağmen değiniriz. Bunu uyguladıktan sonra kodumuz aşağıdaki şekilde olur.

class Lejand{
    private String color;
    private int maxSpeed,minSpeed;

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getMinSpeed() {
        return minSpeed;
    }

    public void setMinSpeed(int minSpeed) {
        this.minSpeed = minSpeed;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    public Color getColor(int currentSpeed) {
        Color color =null;
        if(currentSpeed< getMaxSpeed() && currentSpeed< getMinSpeed()){
            if(getColor().equals("Yesil")) {
                 color = Color.GREEN;
            }else if(getColor().equals("Mavi")){
                color = Color.BLUE;
            }
        }
        return color;
    }
}

class Controller{
    private Lejand lejand; //kullanıcının hız aralıklarına gore red,green,blue değerleri
    private int currentSpeed ;//cizilecek olan şuandaki hızın değeri
    //.....
    //....
    void drawLine(){
        //....

        Color color= lejand.getColor(currentSpeed);
        //..yukarıdaki color nesnesini kullanarak yapılan cizim işlemler.
        //g.setColor(color);
        //..
    }
}

Lejand sınıfının yeni haline baktığımızda artık bize kendi verisini kullanarak renk üretme işlemini yapan bir fonksiyona sahip olduğunu görüyoruz. Ve controller artık Lejand sınıfının iç yapısını bilmiyor ve sadece getColor() metodunu çağırıyor. Renk üretme işleminin Lejand sınıfında RGB mi yoksa Renk isimleriylemi yapıldığından habersiz. Lejand sınıfında tekrar RGB ye dönsek bile Controller sınıfını değiştirmek zorunda değiliz çünkü sadece getColor metodunu kullanıyor ve içeride neler olduğundan habersiz. Yani artık kodumuz değişimlere karşı birden fazla yerde değişiklik gerektirmiyor.

Evet bir veri sınıfını adam etme öykümüz burda sona ermiştir. Sloganımız sorumluluktan yoksun veri sınıflarına ölüm :)


Next Page »