Refactoring : Extract Method

Ö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.

package extractmethod;

import java.util.ArrayList;
import java.util.HashMap;
/**
 *
 * @author Cihat.Altuntas
 */
public class Parser {

    public ArrayList<Map> createMapList(ArrayList<Map> mapList, HashMap<String,String> layerCoordinates) {       
        int start = 0, end = 0;
        Map map = null;
        ArrayList<Map> createdMapList =new ArrayList<Map>();
        for (int i = 0; i < mapList.size(); i++) {
            map = (Map) mapList.get(i);

            String layerName = map.getLayerNames().get(0);
            String value = layerCoordinates.get(layerName);


            start = value.indexOf("minx");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String minx = value.substring(start + 1, end);

            start = value.indexOf("miny");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String miny = value.substring(start + 1, end);

            start = value.indexOf("maxx");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxx = value.substring(start + 1, end);

            start = value.indexOf("maxy");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxy = value.substring(start + 1, end);

            map.setMapMinX(Double.parseDouble(minx));
            map.setMapMinY(Double.parseDouble(miny));
            map.setMapMaxX(Double.parseDouble(maxx));
            map.setMapMaxY(Double.parseDouble(maxy));

            createdMapList.add(map);
        }

        return createdMapList;
    }
}

Yukarıdaki kodu biraz inceleyin. Aslında baktığımızda pekde kolay anlaşılabilir bir kod olmadığı açık, kısacası kötü bir kod.Metodu biraz daha yakından incelediğimde neler yaptığını biraz daha iyi anlayabiliyoruz. Yaptığı şey haritanın içinde tutulan katman ismini kullanıp diğer HashMap içinde bu katman ismine ait string olarak tutulan koordinanatları ayrıştırıp haritanın MinX,MaxX,MinY,MaxY değerlerini oluşturmak.

Aslında yukarıdaki cümleden de anlamış olduğunuz gibi bu metod 4 farklı işi yapıyor. Harita için MinX,MaxX,MinY,MaxY değerlerini her biri için parse edip ardından harita nesnesine atama yapıyor. Bizim yapmak istediğimiz çok fazla iş yapan bu metodu daha anlaşılabilir,okunabilir hale getirmek için her biri sadece tek iş yapan küçük küçük metodlara bölmek. Öncelikle bunu yapmak için metodun ilk başında olan minx ayrıştırma işlemini farklı bir metod haline getirmek istiyorum.

İlk önce kullandığım IDE’nin refactoring desteğine başvuruyorum. Şuanda Netbeans kullanıyorum ve IDE içerisinde o satırları seçip Introduce Method diyorum. Aynı şeyi Visual Studio .NET içinden ya da diğer tüm popüler IDE’ler içinden benzer (Extract Method..) Refactoring seçenekleri ile yapabilirsiniz. Fakat burada IDE’nin otomatik Refactoring seçeneği bize yardım edemiyor çünkü kodun içinde Temprory Variable olarak start ve end değişkenleri heryerde kullanılmış.Bu yüzden öncelikle bu değişkenleri ayırmak ve her işlem için bir değişken kullanmak istiyorum. Böylelikle Extract Metod tekniğini kolaylıkla kullanabileceğim.Yani önce Split Temprory Variable tekniğini uyguluyorum.  Bu yüzden öncelikle minx metodu için bu değişkenleri ayırıp metodu aşağıdaki şekline getiriyorum.

package extractmethod;

import java.util.ArrayList;
import java.util.HashMap;

public class Parser {

    public ArrayList<Map> createMapList(ArrayList<Map> mapList, HashMap<String,String> layerCoordinates) {
        int start = 0, end = 0;
        ArrayList<Map> createdMapList =new ArrayList<Map>();
        for (int i = 0; i < mapList.size(); i++) {
            Map map =  (Map) mapList.get(i);

            String layerName = map.getLayerNames().get(0);
            String value = layerCoordinates.get(layerName);

            int minxStart=0;
            int minxEnd=0;
            minxStart = value.indexOf("minx");
            minxStart = value.indexOf("\"", minxStart);
            minxEnd = value.indexOf("\"", minxStart + 1);

            String minx = value.substring(minxStart + 1, minxEnd);

            start = value.indexOf("miny");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String miny = value.substring(start + 1, end);

            start = value.indexOf("maxx");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxx = value.substring(start + 1, end);

            start = value.indexOf("maxy");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxy = value.substring(start + 1, end);

            map.setMapMinX(Double.parseDouble(minx));
            map.setMapMinY(Double.parseDouble(miny));
            map.setMapMaxX(Double.parseDouble(maxx));
            map.setMapMaxY(Double.parseDouble(maxy));

            createdMapList.add(map);
        }

        return createdMapList;
    }
}

Yukarıdaki minx ayrıştırma işleminin olduğu satıra dikkat edecek olursanız minxStart,minxEnd adında sadece minx ayrıştırma işlemi için kullanılan değişkenler oluşturdum. Bu işlem artık metod içindeki diğer geçici değişkenleri kullanmadığı için minx ayrıştırma işlemini kolaylıkla IDE desteği ile ya da kendim el ile başka metod oluşturarak sadece bu işlemi yapan metod altına alabilirim.Kodumuzu aşağıdaki gibi tekrar düzenliyorum.

package extractmethod;

import java.util.ArrayList;
import java.util.HashMap;

public class Parser {

    public ArrayList<Map> createMapList(ArrayList<Map> mapList, HashMap<String,String> layerCoordinates) {
        int start = 0, end = 0;
        ArrayList<Map> createdMapList =new ArrayList<Map>();
<div style="display: none"><a href='http://paper-writingservice.net/'>research paper writing services</a></div>        for (int i = 0; i < mapList.size(); i++) {
            Map map =  (Map) mapList.get(i);

            String layerName = map.getLayerNames().get(0);
            String value = layerCoordinates.get(layerName);

            String minx = parseMinX( value);

            start = value.indexOf("miny");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String miny = value.substring(start + 1, end);

            start = value.indexOf("maxx");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxx = value.substring(start + 1, end);

            start = value.indexOf("maxy");
            start = value.indexOf("\"", start);
            end = value.indexOf("\"", start + 1);

            String maxy = value.substring(start + 1, end);

            map.setMapMinX(Double.parseDouble(minx));
            map.setMapMinY(Double.parseDouble(miny));
            map.setMapMaxX(Double.parseDouble(maxx));
            map.setMapMaxY(Double.parseDouble(maxy));

            createdMapList.add(map);
        }

        return createdMapList;
    }

    private String parseMinX(String value) {

        int minxStart = 0;
        int minxEnd = 0;
        minxStart = value.indexOf("minx");
        minxStart = value.indexOf("\"", minxStart);
        minxEnd = value.indexOf("\"", minxStart + 1);

        String minx = value.substring(minxStart + 1, minxEnd);

        return minx;
    }
}

Yeni halinde gördüğünüz gibi artık MinX parse işlemi ayrı bir metod içine alındı.Ardından diğer MinY,MaxX,MaxY ayrıştırma işlemlerine de aynı tekniği uygulayıp tekrar kodu aşağıdaki gibi düzenliyorum.

package extractmethod;

import java.util.ArrayList;
import java.util.HashMap;

public class Parser {

    public ArrayList<Map> createMapList(ArrayList<Map> mapList, HashMap<String,String> layerCoordinates) {
        ArrayList<Map> createdMapList =new ArrayList<Map>();
        for (int i = 0; i < mapList.size(); i++) {
            Map map =  (Map) mapList.get(i);

            String layerName = map.getLayerNames().get(0);
            String value = layerCoordinates.get(layerName);

            String minx = parseMinX( value);
            String miny = parseMinY( value);
            String maxx = parseMaxX( value);
            String maxy = parseMaxY( value);

            map.setMapMinX(Double.parseDouble(minx));
            map.setMapMinY(Double.parseDouble(miny));
            map.setMapMaxX(Double.parseDouble(maxx));
            map.setMapMaxY(Double.parseDouble(maxy));

            createdMapList.add(map);
        }

        return createdMapList;
    }

    private String parseMaxX(String value) {

        int startMaxX = 0;
        int endMaxX = 0;
        startMaxX = value.indexOf("maxx");
        startMaxX = value.indexOf("\"", startMaxX);
        endMaxX = value.indexOf("\"", startMaxX + 1);

        String maxx = value.substring(startMaxX + 1, endMaxX);

        return maxx;
    }

    private String parseMaxY(String value) {

        int startMaxY = 0;
        int endMaxY = 0;
        startMaxY = value.indexOf("maxy");
        startMaxY = value.indexOf("\"", startMaxY);
        endMaxY = value.indexOf("\"", startMaxY + 1);

        String maxy = value.substring(startMaxY + 1, endMaxY);

        return maxy;
    }

    private String parseMinX(String value) {

        int minxStart = 0;
        int minxEnd = 0;
        minxStart = value.indexOf("minx");
        minxStart = value.indexOf("\"", minxStart);
        minxEnd = value.indexOf("\"", minxStart + 1);

        String minx = value.substring(minxStart + 1, minxEnd);

        return minx;
    }

    private String parseMinY(String value) {

        int startMinY = 0;
        int endMinY = 0;
        startMinY = value.indexOf("miny");
        startMinY = value.indexOf("\"", startMinY);
        endMinY = value.indexOf("\"", startMinY + 1);

        String miny = value.substring(startMinY + 1, endMinY);

        return miny;
    }
}

Yukarıdaki koda bakacak olursanız artık bütün değerleri parse etme işlemi ayrı metodlar içinde yapılıyor. Bu tekniğe Extract Method deniliyor. Özellikle uzun metodların ayrıştırılmasında en çok kullanılan yöntem. Şimdi metodun ilk haline bakacak olursanız hangisi daha anlaşılır? Bana göre yeni versiyonu eskisine göre oldukça anlaşılabilir. Tabi burada başka Refactoring teknikleri kullanılarak daha da iyileştirilebilir. Refactoring tekniklerinin en güzel yanlarından biri uyguladıkça içeride gizlenmiş olan kötü kodların ortaya çıkmasına olanak sağlaması.

Mesela yukarıdaki metodlarda parseMaxX gibi metodlarda geri String döndürülmüş. Ardından Double.parseDouble metodu ile doublea çevirilip set edilmiş. Aslında bunu parse metodlarının içinde yapmak daha doğru.O yüzden kodu biraz daha iyileştirerek aşağıdaki gibi yapalım.

package extractmethod;

import java.util.ArrayList;
import java.util.HashMap;

public class Parser {

    public ArrayList<Map> createMapList(ArrayList<Map> mapList,HashMap<String,String> layerCoordinates) {
        ArrayList<Map> createdMapList =new ArrayList<Map>();
        for (int i = 0; i < mapList.size(); i++) {
            Map map =  (Map) mapList.get(i);

            String layerName = map.getLayerNames().get(0);
            String value = layerCoordinates.get(layerName);

            map.setMapMinX(parseMinX( value));
            map.setMapMinY(parseMinY( value));
            map.setMapMaxX(parseMaxX( value));
            map.setMapMaxY(parseMaxY( value));

            mapList.add(map);
        }

        return createdMapList;
    }

    private double parseMaxX(String value) {

        int startMaxX = 0;
        int endMaxX = 0;
        startMaxX = value.indexOf("maxx");
        startMaxX = value.indexOf("\"", startMaxX);
        endMaxX = value.indexOf("\"", startMaxX + 1);

        String maxx = value.substring(startMaxX + 1, endMaxX);

        return Double.parseDouble(maxx);
    }

    private double parseMaxY(String value) {
        int startMaxY = 0;
        int endMaxY = 0;
        startMaxY = value.indexOf("maxy");
        startMaxY = value.indexOf("\"", startMaxY);
        endMaxY = value.indexOf("\"", startMaxY + 1);

        String maxy = value.substring(startMaxY + 1, endMaxY);

        return Double.parseDouble(maxy);
    }

    private double parseMinX(String value) {
        int minxStart = 0;
        int minxEnd = 0;
        minxStart = value.indexOf("minx");
        minxStart = value.indexOf("\"", minxStart);
        minxEnd = value.indexOf("\"", minxStart + 1);

        String minx = value.substring(minxStart + 1, minxEnd);

        return Double.parseDouble(minx);
    }

    private double parseMinY(String value) {
        int startMinY = 0;
        int endMinY = 0;
        startMinY = value.indexOf("miny");
        startMinY = value.indexOf("\"", startMinY);
        endMinY = value.indexOf("\"", startMinY + 1);

        String miny = value.substring(startMinY + 1, endMinY);

        return Double.parseDouble(miny);
    }
}

Bundan sonra kod daha da iyileştirilebilir hala tekrar eden kod parçaları var. Artık onu size bırakıyorum. :)

One thought on “Refactoring : Extract Method

  1. Pingback: Yazılım Mühendisliği » Code Metrics : Cyclomatic Complexity

Comments are closed.