Kitap – Refactoring Martin Fowler

24 Haz

DSC_1348

Martin Fowler imzalı Refactoring, temel ilke mevcut kodun iyileştirilmesi amacıyla kod işlevini değiştirmeden yeniden yapılandırılmasını hedeflemektedir. Kitabın temel iddiası, çalışan programların çeşitli nedenlerle kötü tasarlanmış olmasıdır.

Çok büyük yapıdaki metod veya fonksiyonların beklenmedik bir şekilde programda karmaşıklığa yol açacağı aşikardır. Martin Fowler’a göre Refactoring işlemi, metodun çalışmasını etkilemeden fonksiyonelliğini koruyarak dönüştürmektir. Dönüşüm ile koda yeni işlevselliklerin kolay bir şekilde kazandırılabilmesi ve kodun okunabilirliğinin sağlanması  amaçlanmaktadır. Küçük adımlarla işlevselliğin korunarak kod paketi sık sık birim testlere tabi tutulmalıdır. Kitapta Java programlama dili ile geliştirilmiş örnek uygulamalar ile anlatım güçlendirilmiştir.

15 bölümden olşumuş kitaptaki bölümler, refactoring işlemlerin adımları küçük adımlarla vevörnek uygulamalar ile  anlatılmıştır. Kodun temiz, okunabilir, test edilebilir bir şekilde düzenlenmesi amaçlanmıştır.

Kitapta ele alınan konuların bir kısmı, farkındalık açısından her programcının bilmesi gereken teorik bilgilerdir. Ancak bana göre günümüzde kullanılan gelişmiş IDE’ler veya Resharper tarzı IDE eklentiler, değişkenlerin yeniden adlandırılması, kod tekrarları, sınamaların kısa ifadeler şeklinde sadeleştirilmesi gibi işlemleri otomatik olarak geliştiriciye önermekte ve isterse otomatik olarak kodu düzenlemektedir. Bekli bu gibi bölümler bir süre sonra geliştiriciler açısından okunmaya değmez bölümler olarak algılanabilse de bence okumakta fayda var diyebilirim.

Chain Constructors Refactoring C#

10 Eyl

Bir sınıfın birden fazla kurucu metodu (constructor) varsa, kod tekrarları da oluşacak demektir. Çünkü kurucu metodların fazla olması, parametrelerin fazla olmasını gerektirir. Dolayısıyla sınıfın üyelerinin tekrarlı bir şeklide atama işlemine tabi tutulması demektir. Bu kod tekrarlarının nasıl oluştuğunu şu örnekle inceleyebiliriz.


public class PersonManager
{
      private string firstName;
      private string lastName;
      private string socialSecurityNumber;
      private string age;

      public PersonManager(string firstName, string lastName)
      {
          this.firstName = firstName;
          this.lastName = lastName;
      }

      public PersonManager(string firstName, string lastName, string socialSecurityNumber)
      {
          this.firstName = firstName;
          this.lastName = lastName;
          this.socialSecurityNumber = socialSecurityNumber;
      }

      public PersonManager(string firstName, string lastName,string age, string socialSecurityNumber)
      {
          this.firstName = firstName;
          this.lastName = lastName;
          this.age = age;
          this.socialSecurityNumber = socialSecurityNumber;
      }

}

Private erişimli field’lar her kurucu metodda tekrarlı bir şekilde işleme sokulmuş. Bunun önüne şu şeklide geçmek mümkündür.


public PersonManager(string firstName, string lastName)
      :this(firstName,lastName,"Unknown", "Unknown")
{
}

public PersonManager(string firstName, string lastName, string socialSecurityNumber)
       : this(firstName, lastName, socialSecurityNumber, "Unknown")
{
}

public PersonManager(string firstName, string lastName, string socialSecurityNumber, string age)
{
       this.firstName = firstName;
       this.lastName = lastName;
       this.age = age;
       this.socialSecurityNumber = socialSecurityNumber;
}

Bu şekilde kodumuzu tekrarlı yapıdan arındırdık.

Tekrar görüşmek üzere.

Koşullu Mantığın Komutla Değiştirilmesi (Replace Conditional Dispatcher with Command)

5 Eyl

Programcıların öğrenme aşamasındaki alışkanlıklarından dolayı olsa gerek, koşullu mantığın kullanılması hat safhadadır. Olay Nesneye Dayalı programlamaya gelince, koşullu mantık bir takım değişikliklere uğrayabiliyor. Bu değişiklikler, aslında olması gereken değişikliklerdir. Bu değişikliklerle çoğu zaman karmaşık kod yapılarından kurtuluruz. Nasıl mı?


public class PersonManager
{
     public double GetHourlyRate(Person person)
     {
         if (person is Employee)
             return 2.0;
         if (person is Student)
             return 1.0;
         return 0.0;
     }
}

Yukarıda tanımlı GetHourlyRate metodu içerisinde person tipinin Employee veya Student olup olmadığı kontrol ediliyor. Bir süre sonra sisteme yeni bir tip eklendiğinde GetHourlyRate metodunda bir koşul daha eklenerek değişiklikler gerekecek. Bu iyi bir yöntem değil. Zaten Open Closed tasarım ilkesine de uymayan bir durum. Bu durumu değiştirelim.


public class PersonManager
{
       public double GetHourlyRate(Person person)
       {
           return person.GetRate();
       }
}

public class Person
{
     public virtual double GetRate()
     {
         return 0.0;
     }
}

public class Student:Person
{
     public override double GetRate()
     {
         return 1.0;
     }
}

public class Employee:Person
{
      public override double GetRate()
      {
         return 2.0;
      }
}

GetHourlyRate metodunda Person nesnesinin bir komutu olan GetRate kullanılıyor. Bu durumda sisteme istediğimiz kadar yeni tip ekleyebiliriz. Çünkü artık gelişime açık bir kod ortaya koyduk. Open Closed ilkesine uyduk.

Tekrar görüşmek dileğiyle.

Test Edilebilir Tasarımın Düzenlenmesi (Refactoring)

28 Nis

Refactoring işlemi kodun işlevini değiştirilmeden daha okunabilir hale getirmek amacıyla yeniden düzenlenmesi işlemidir. Özellikler test güdümlü programlamada (Test Driven Development) gerekli olan gevşek bağlılık (loosely coupled) prensibinin uygulanabilmesi için bazı kurallara uymak durumunda kalabiliyoruz.

Özellikle sıkı bağlılığın(tightly coupled) yok edilmesi için uygulamak gereken bazı özellikler vardır. Bunlar:

  • Uygulama kodunu bir interface ile temsil etmek
  • Test edilen sınıfa soyut nesneleri aktararak soyut nesneler üzerinden işlemleri yapmak.
  • Soyut Interface nesnesini constructor seviyesine almak.
  • Soyut interface nesnesini bir property olarak almak.
  • Soyut interface nesnesini metod çağırma işleminden önce almak.

Constructor seviysinde nesne almak (Constructor Injection)

Bu senaryoda, constructor yardımıyla interface nesnesini kabul edeceğiz. Daha sonra alınan bu nesne kullanılmak üzere private erişim belirleyicili bir metoda aktarılır.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = reporter;
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Problemler

Test edilen sınıf eğer birden fazla soyut nesne alıyorsa constructor sayısı veya bir constructorun aldığı parametre sayısı artacak. Bu da kodun düzenini ve okunabilirliğini bozacaktır.

Bu sorunu çözmek için kullanılacak yöntemlerden biri, gerekli tüm başlangıç değerlerinin bir sınıf içerisinde başlatılmasıdır (parameter object refactoring).

Bir diğer çözüm ise bağımlılıkların ters çevrilmesidir(Inversion of Controls IoC). Bu işlemi yapan hazır paketler bulunmaktadır. Örneğin Spring.NET, Castle Windsor, Ninject gibi. Bu konteynır yapılar sayesinde bir interface nesnesine karşılık gelecek somut nesne belirlenir ve o interface görüldüğünde somut nesnesi bağlanmaktadır.

Ne zaman kullanılmalı

Constructor seviyesinde nesneler alıp başlatmak istediğinizde, IoC konteynırlar nesne oluşturma sırasında hız kazandırmaktadır. Öte yandan constructor parametreler, API’nizi kullananlara, parametrenin opsiyonel olmadığını göstermektedir.

Property seviyesinde nesne almak(Property Injection)

Bu senaryoda, esnek bağı sağlayacak olan, bir property olacaktır. Dependency injection olarakta bilinen bu teknik bir önceki yönteme benzemektedir. Constructor injection gibi bu yöntem de API üzerinde gerekli bağımlılıkları göstermektedir.


public class ReportSender{

       public  IReporter Reporter {get; set;};

       // Diğer üyeler
       //....
       //..
       //.
}

Ne zaman kullanılmalıdır

Bu teknik, bağımlılığın isteğe bağlı olduğu durumlarda kullanılır. Bağlılığın opsiyonel bırakılmak istenen durumlarda tercih edilebilir.

Metod çığırılmadan önce nesne almak

Bu teknikte ise nesneyi almak için property veya constructor yerine factory sınıfları kullanılır. Bu sayede soyut nesne, sınıf içerisine aktarılmış olur.


public class ReportSender{

       private IReporter reporter;

       public ReportSender(IReporter reporter){
              this.reporter = FactoryClass.CreateReportInstence();
       }

       // Diğer üyeler
       //....
       //..
       //.
 }

Ne zaman kullanılmalıdır

Bu teknik, sınıf girdilerini(input) kontrol etmek istendiğinde kullanılabilir. Ancak test ortamında, bağımlılıkları test edmek istediğinizde kötü sonuç verecektir. Çünkü test ortamında sınıf içine mock object aktarıp davranışları test etmek istendiğinde başarısız oluruz.