Detayları Görebilmek Ya Da Görememek

Robert Martin Transformation Priority Premise – Transformasyon Öncelik Teorisi başlıklı yazısında yeniden yapılandırmaya (refactoring) karşı yöntem olan kod transformasyonuna değiniyor. Refactoring uygulamanın dışa gösterdiği davranış biçimini değiştirmeden kodun yeniden yapılandırılması anlamına gelirken, kod transformasyonu uygulamanın davranış biçimini yeniden şekillendirmek için kullanılan bir yöntemdir.

Benim kod transformasyonlarını bilinçli bir şekilde algılamam kod kataları yapmaya başladığım zamana denk gelir. Daha önce de test güdümlü yazılım yaparken kod transformasyonlarına şahit olmuştum, lakin kod kataları ve Robert Martin’in yazısı aracılığı ile transformasyon sürecinin nasıl işlediğini daha iyi algılama fırsatı buldum.

Bu konuda lafı fazla uzatmadan kod transformasyonunun ne olduğunu bir örnek üzerinden sizlerle paylaşmak istiyorum. Aşağıda yer alan kod prime factor katasından alıntıdır. Bu kata bünyesinde herhangi bir rakamın asal sayı (prime number) olan faktörleri hesaplanmaktadır. Bir asal sayı sadece bire ve kendisine bölünebilir. 2,3,5,7,11,13 asal sayılardır. Örneğin 10 rakamı 2 ve 5 asal sayı faktörlerinden oluşur. 6 rakamının asal sayı faktörleri 2 ve 3’dür. Kata bünyesinde oluşturulan generate() metoduna kod transformasyonları aracılığı ile herhangi bir rakamı oluşturan asal sayı faktör listesini oluşturabilecek kabiliyet kazandırılır. Kata için oluşturduğum birim testi aşağıda yer almaktadır:

package com.kodkata.kata.primefactor.kata;

import static com.kodkata.kata.primefactor.kata.PrimeFactorz.generate;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class PrimeFactorTest {

	private List<Integer> list;
	private int expected;

	public PrimeFactorTest(List<Integer> list, int expected) {
		this.list = list;
		this.expected = expected;
	}

	private static List<Integer> list(Integer... integers) {
		return Arrays.asList(integers);
	}

	@Test
	public void generate_as_expected() throws Exception {
		assertEquals(list, generate(expected));
	}

	@Parameters
	public static Collection<Object&#91;&#93;> testData() throws Exception {

		return asList(new Object[][] { { list(), 1 }, { list(2), 2 },
				{ list(3), 3 }, { list(2, 2), 4 }, { list(5), 5 },
				{ list(2, 3), 6 }, { list(7), 7 }, { list(2, 2, 2), 8 },
				{ list(3, 3), 9 }, { list(2, 5), 10 }, });
	}
}

Bu parametrik JUnit test sınıfı testData() bünyesinde yer alan veri listesini kullanarak, generate_as_expected() test metodunu koşturmaktadır.

İlk birim testi { list(), 1 } veri kümesi ile 1 rakamı için yapılmaktadır. 1 rakamının asal sayı faktörü yoktur, yani generate() metodu boş bir listesi geri döndürmelidir. Bu testi oluşturduktan sonra aşağıdaki şekilde generate metodunu oluşturuyorum.

public static List<Integer> generate(int i) {
	return null;
}

Burada ilk kod transformasyonunu görmek mümkün. Ortada hiçbir kod yokken, bir metot oluşturdum ve metodu null değerini geri döndürecek şekilde yapılandırdım. Robert Martin atılan bu ilk adımı ( {}–>nil ) şeklinde tanımlıyor.

Testin geçebilmesi için kodu aşağıdaki şekilde dönüştürmem (transform) gerekiyor:

public static List<Integer> generate(int i) {
	return Collections.EMPTY_LIST;
}

Bu dönüşüm ( nil->constant ) şeklinde ifade edilebilir. Birinci testin geçebilmesi için yapmamız gereken sadece boş bir listeyi (constant) geri döndürmektir.

İkinci test için { list(2), 2 } veri kümesini kullanıyorum. generate() metodu 2 rakamını parametre olarak aldığında, bünyesinde 2 rakamı olan bir listeyi geri döndürmeli. Bu testin geçebilmesi için kodu aşagıdaki şekilde dönüştürmem gerekiyor.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	if (i > 1)
		primes.add(2);
	return primes;
}

Bu dönüşüm ( unconditional->if ) şeklinde ifade edilebilir. Testin geçebilmesi için en basit olan çözümü seçtim. Bu durumda en basit çözüm if kullanarak 2 rakamını listeye eklemek. Tetiklediğim iki kod transformasyonu iki testin geçebilecek seviyeye gelmesini sağladı.

Üçüncü test için { list(3), 3 } veri kümesini kullanıyorum. generate() metodu 3 rakamını parametre olarak aldığında, bünyesinde 3 rakamı olan bir listeyi geri döndürmeli. Bu testin geçebilmesi için kodu aşağıdaki şekilde dönüştürmem gerekiyor.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	if (i > 1)
		primes.add(i);
	return primes;
}

Bu dönüşüm ( constant->scalar ) şeklinde ifade edilebilir. Testin geçebilmesi için bir constant olan 2 rakamını i (scalar) parametresi ile değiştirdim.

Dördüncü test için { list(2,2), 4 } veri kümesini kullaniyorum. generate() metodu 4 rakamını parametre olarak aldığında, bünyesinde 2,2 rakamları olan bir listeyi geri döndürmeli. Bu testin geçebilmesi için kodu aşağıdaki şekilde dönüştürmem gerekiyor.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	if (i > 1) {
		if (i % 2 == 0) {
			primes.add(2);
			i /= 2;
		}
		if (i > 1)
			primes.add(i);
	}
	return primes;
}

Bu dönüşüm ( unconditional->if ) şeklinde ifade edilebilir. Testin geçebilmesi için yeni bir if bloğu içinde ikinin katlarını bulan modülo işlemini gerçekleştirdim. i/=2 ile i’nin değerini ikiye bölmüş oldum. Bunun yanısıra ikinci bir if bloğu içinde arta kalan değeri birden büyük ise listeye ekledim.

5,6 ve 7 rakamları için generate() metodunda değişiklik yapmaya gerek yok. generate() metodu bu rakamlar için geçerli listeleri geri döndürüyor.

8 rakamının asal sayı faktörlerini bulmak için { list(2,2,2), 8} veri kümesini kullanıyorum. Bu testin geçebilmesi için kodu aşağıdaki şekilde dönüştürmem gerekiyor.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	if (i > 1) {
		while (i % 2 == 0) {
			primes.add(2);
			i /= 2;
		}
		if (i > 1)
			primes.add(i);
	}
	return primes;
}

Bu dönüşüm ( if->while ) şeklinde ifade edilebilir. Testin geçebilmesi için ikinci if bloğunu while döngüsüne dönüştürmem yeterli oldu.

9 rakamının asal sayı faktörlerini bulmak için { list(3,3), 9} veri kümesini kullanıyorum. Bu testin geçebilmesi için kodu aşağıdaki şekilde dönüştürmem gerekiyor.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	int divisor = 2;
	while (i > 1) {
		while (i % divisor == 0) {
			primes.add(divisor);
			i /= divisor;
		}
		divisor++;
	}
	return primes;
}

Bu dönüşüm ( if->while ) ve ( constant->scalar ) şeklinde ifade edilebilir. Divisör (bölen) olarak kullandığım 2 rakamını bir değişkene ( constant->scalar ), ilk if bloğunu bir while döngüsüne dönüştürdüm.

generate() metodu bu son transformasyon ile herhangi bir rakamın asal sayı faktörlerini hesaplayacak duruma gelmiş oldu. Yapabileceğim iki transformasyon kaldı. Bu transformasyonlar ile satır sayısını azaltabilirim. Bu iki tranformasyonu ( while->for ) olarak tanımlayabiliriz.

public static List<Integer> generate(int i) {
	List<Integer> primes = new ArrayList<Integer>();
	for (int divisor = 2; i > 1; divisor++) {
		for (; i % divisor == 0; i /= divisor)
			primes.add(divisor);
	}
	return primes;
}

Görüldüğü gibi testler geçtikten sonra metodun davranış biçimini kod transformasyonları uygulayarak yeniden yapılandırmamız mümkün. Uyguladığımız her transformasyon ile oluşturduğumuz çözüm spesifik olmaktan çıkıp umumi olmaya doğru gidiyor.

Test güdümlü çalışmayan bir programcı doğrudan generate() metodunu yukarda gördüğünüz şekilde kodlayacak ve mevcut kod transformasyonlarından bihaber olacaktır. Bu transformasyonları görmek bana ne fayda sağlar diye bir soru aklınıza gelebilir. Bilmiyorum! Belki bir faydası yok. Ama ben artık işin daha derinlerinde başka şeylerin varlığını hissetmeye başladım. Bu bana programcılıkta başka bir boyuta geçmek ya da olup bitenleri daha iyi algılamak için kullanabileceğim bir kapı olabilir gibi geliyor. Bazı şeyler detaylarda gizli. Onları görebilmek lazım.

EOF (End Of Fun)
Özcan Acar

Share Button
0.00 avg. rating (0% score) - 0 votes

Bir cevap yazın