Refactoring

Refactoring Nedir?

Refactoring (Yeniden Tasarım), "bir yazılım sisteminin dışarıya olan davranışını değiştirmeden iç yapısını değiştirme süreci" olarak tanımlanabilir. Bu süreç, hatalı kod içerme olasılığını azaltan sistematik bir disiplindir. Kısaca, kod yazıldıktan sonra tasarımın geliştirilmesi anlamını taşımaktadır.

"Kodu yazdıktan sonra tasarımı geliştirmek..." acayip bir söz. Günümüzde, önce tasarım yapılıp sonra kod yazılır. Zaman geçtikçe kod değişir; sistemin bütünlüğü, yani ilk yapılan tasarıma göre iç yapısı yavaş yavaş bozulur. Yazılan kod üzerinde çalışmak mühendislikten çok "hacking"e benzer.

Refactoring bunun tam tersi bir işlemdir. Kötü tasarımlı, hatta kaos olmuş kod refactoring ile iyi tasarlanmış bir yapıya çevrilebilir. Her bir adım çok basittir: Mesela, bir alan bir sınıftan diğerine taşınır, metodun içinden bir parça çıkartılıp ayrı bir metod oluşturulur ve sınıf hiyerarşisinde metodlar aşağı (sub-class) veya yukarı (super/base class) taşınır. Bu değişiklikler küçük olmasına rağmen tasarımda büyük gelişmeler sağlayabilir.

Tasarımın sadece en baştan yapılıp bitirilen birşey olmadığını, aynı zamanda yazılım geliştirme süresince devam ettiğini refactoring ile görmek mümkündür. Refactoring sayesinde, geliştirme devam ettiği sürece tasarım güzel olarak kalır.

Bu noktada şunu ortaya koymak gerekir: Refactoring ve yeni özellik ekleme iki farklı aktivitedir. Yeni özellik eklemek için işe başlanmış olabilir. Ancak yeni özellik kodun yapısına uymuyorsa ya da yapısını bozuyorsa, yani kötü bir çözüm olduysa, önce ilgili değişiklikler yapılır, sonra yeni özellikler eklenir. Değişiklikler yapılırken kodun sağladığı fonksiyonlarda bir değişiklik olmamalı, sadece yeni özelliklerin eklenebilmesini sağlayacak içsel değişiklikler gerçekleştirilmelidir. Bunu, birim testler yazarak kontrol altında tutmak mümkündür. Değişiklikten önceki bütün durumları kapsayan birim testler yazılmalı, değişiklikten sonra da bu testlerin başarılı bir şekilde çalıştığı, dolayısı ile refactoring'in başarılı bir şekilde gerçekleştiği görülmelidir. 


Refactoring'le ilgili Sorunlar

Refactoring, göz ardı edilmemesi gereken bir çalışma olmasına rağmen, bazı durumlarda ya yapılması çok zordur ya da yapılmaması gereklidir.
  • Veritabanı: Bazı uygulamalar veritabanı şemasına son derece bağımlı gerçekleştirilirler. Bu gibi uygulamaları değiştirmek son derece zordur. Veritabanı modeli ile ve nesne modeli arasında bir katman koyarak bu sorun aşılabilir. İki modelden biri değiştiğinde ara katmanı değiştirmek (teorik olarak) yeterli olacaktır. Bu yapı karışık olmasına rağmen oldukça esneklik sağlar.
  • Arayüz: Uygulamanın sunduğu arayüz değişmeden arka taraftaki implementasyon kolaylıkla değiştirilebilir. Ancak değişen ihtiyaçlara göre arayüzde de değişiklik yapılacaksa problem orada başlıyor demektir. Arayüzde yapılan değişiklik, sadece erişilebilir kodlarda değişiklik gerektiriyorsa refactoring problemsiz olarak gerçekleştirilebilir. Ancak arayüz erişilemeyecek/değiştirilemeyecek programlar tarafından kullanılıyorsa bunu kolayca değiştirmek mümkün olmayacaktır. Bu durumda yeni arayüz eklenirken eski arayüz de korunmalı, ancak miadının dolduğu (deprecated olduğu) belirtilmelidir.
  • Ciddi Tasarım Hataları: Uygulamanın baştan yazılmasının daha kolay olduğu durumlarda Refactoring zaman kaybıdır. Uygulama hatalarla dolu ve bir türlü düzgün çalışmıyorsa bu durum düşünülebilir. Ancak, baştan yazmaya karar vermek için üzerinde çok iyi düşünülmelidir.
  • Süre: Süre bitimine yakın zamanlarda Refactoring'den kaçınılmalıdır. Ancak şu akılda tutulmalıdır: Yapılmayan refactoring, daha sonra hata ve bakım masrafı olarak geri gelecektir (Bkz. Technical Debt).

Refactoring Nedir?

Refactoring, zaman çizelgesinde planlanarak kaynak ayrılması gereken bir işlem değildir. Yani planlamaya gerek yoktur. Normal işler gerçekleştirilirken uygulamanın değiştirilmesi düşünülen noktalarında ortaya çıkan bir işlemdir. Değişiklik yapılmasına yardımcı olur.
Tam ne zaman yapılacağı söylenemese de Don Roberts'in tavsiyesi baz alınabilir: "Bir şey ilk kez yapıyorsan olması gerektiği gibi yap! Benzer birşey yapıyorsan tekrar ettiğinin farkına var. Üçüncü kez ise karşına çıktığında ise refactoring yap."
Refactoring genellikle yeni özellik eklerken karşımıza çıkar. Yeni özellik yapıya tam olarak uymuyordur ve değişiklik gerektirmektedir.
Bazen de uygulamadaki hatalar refactoring için bir ipucudur. Hatanın anlaşılabilmesi ve çözülebilmesi için değişiklik gerekebilir.
Kod Gözden Geçirme (Code Review) sırasında, kodun ne yaptığını anlayabilmek adına değişiklikler yapılabilir. Bu değişiklik sonucunda anlamlı bir koda ulaşıldı elimizde refactor edilmiş bir kod olur. Yani, sadece tavsiyede bulunmak yerine bu tavsiyenin implementasyonu da elimizde olur.


Kötü Kokular (Bad Smells)

Kod yazarken ya da başkasının koduna bakarken birşeylerden rahatsız oluyorsanız, kodun bazı kısımları canınızı sıkıyorsa ya da kodun içinden pis kokular yükseliyorsa o zaman refactoring yapma zamanı gelmiş demektir. Kesin olarak ne zaman yapılması gerektiği söylenemese de aşağıdaki sıralanan "kötü kokular" görüldüğünde refactoring düşünülmelidir.


Kötü Koku Açıklama Refactoring Yöntemi
Kod Tekrarı
(Duplicated Code)
Kod tekrarı pis kokuların başında gelir. Kurtul ondan! Extract Method
Pull Up Field
Form Template Method
Substitue Algorithm

Uzun Method
(Long Method)
Uzun method'ların ne yaptığını anlamak zordur ve uzadıkça daha da zorlaşır. Extract Method

Replace Temp with Query
Introduce Parameter Object 
Preserve Whole Object 
Replace Method with Method Object

Büyük Sınıf
(Large Class)
Bir sınıfın gereğinden fazla iş yaptığını anlamak için genellikle "instance variable" sayısına bakmak yeterlidir. Extract Class 
Extract Subclass

Uzun Parametre Listesi
(Long Parameter List)
Herşeyi değil, sadece ihtiyacı olanları method'a geçirin. Replace Parameter with Method

Preserve Whole Object
Introduce Parameter Object

Iraksayan Değişiklik
(Divergent Change)
Bir sınıfın belirli kısımları sartların değişmesine göre (misal: farklı veritabanı seçimi) sürekli değişebilir. İlgili sınıfta sürekli değişiklik gösteren kısımları Extract Class ile bir başka sınıfa toplayın.
Saçma Ameliyatı
(Shotgun Surgery) 
Şartların değişimi birden fazla sınıfta değişikliği gerektiriyor olabilir. Değişiklik gerektiren bütün sınıfların ilgili kısımlarını Extract Class ile başka bir sınıfa toplayın.
Özellik Kıskançlığı
(Feature Envy)
Bir method bulunduğu sınıftan çok başka bir sınıfın işini yapıyormuş gibi görünebilir. Doğrusu, bu method hangi sınıfın özelliklerini daha çok kullanıyorsa ona ait olmalıdır. Gerekirse önce Extract Method kullanılarak ilgili kısın method'a çekilebilir. Sonra Move Method kullanılarak method ait olması gereken sınıfa gönderilmelidir.

Veri Yığını
(Data Clumps)
Küme halinde bulunan veriler genellikle beraber bulunmalıdırlar. Veri yığınlarını Extract Class ile sınıf haline getirin. Gerekirse Introduce Parameter Object ve Preserve Whole Objectile refactoring'e devam edin.
İlkel Saplantı
(Primitive Obsession)
(Birimi ve miktarıyla beraber) para  ve zaman aralığı gibi bilgileri ifade etmek için nesne kullanılmalıdır. Replace Data Value with Object

Replace Type Code with Class
Replace Type Code with Subclasses 
Replace Type Code with State/Strategy  

Eğer birkaç alan beraber olmak zorunda ise Extract Class kullanın.
Eğer primitif veri bir parametre listesi halinde ise Introduce Paramter Object kullanın.
Diziler ile çalışıyorsanızReplace Array with Object kullanın.

Switch İfadeleri
(Switch Statements)
Kodun bir yerinde switch-case ifadesi varsa muhtemelen diğer yerlerde de aynı switch-case ifadesi bulunur. 1.Extract Method ile switch-case ifadesini bir method'a çıkar ve Move Method ile ilgili sınıfa taşıyın.
2. Bu noktada, Replace Type Code with Subclasses veya Replace Type Code with State/Strategy yöntemlerinden birini seçin. 
3. Hiyerarşik yapıyı sağlamak için son olarak Replace Conditional with Polymorphism yöntemi uygulayın.

Paralel Kalıtım Hiyerarşisi
(Parallel Inheritance Hierarchies)
Bir sınıf için alt sınıf oluşturuluyor ve benzerinin başka sınıflar için de yapılması gerekiyorsa, yani bir sınıf hiyerarşisi başka sınıfların hiyeraşisi ile sadece isim farklılığı ile ayrışıyorsa, bu hiyerarşiler birleştirilebilir. Move Method ve Mode Field yöntemlerini kullanarak hiyerarşileri birleştirin.

Tembel Sınıf
(Lazy Class)
Eğer bir class yeterince iş yapmıyorsa ortadan kaldırılmalıdır. Yeterince iş yapmayan sınıf 
- alt sınıf ise Collapse Hierarcy 
- normal sınıf ise Inline Class 
yöntemleri kullanılmalıdır.

Spekülatif Genelleme
(Speculative Generality)
Gelecekte olması öngörülen özellikler bugünden gerçekleştirildi ise bu özellikler nedeniyle kodu anlamak ve geliştirmek zorlaşır. Aşağıdaki durumlarda gereksiz kodu kaldırın:
- Abstract sınıf fazla iş yapmıyorsa Collapse Hierarchy 
- Gereksiz delegasyon varsa Inline Class
- Method'a verilen parametreler gereksizse Remove Parameter 
- Üst sınıflardan kötü method isimleri alındıysa Rename Method

Geçici Alan
(Temporary Field)
Sadece belirli durumlarda kullanılan alanlar kafa karıştırıcı olabilir. Misal, kodun belirli bir bölümünde karışık bir algoritma gerçeklenirken sınıfın içinde bir alan tanımlamak sınıfı daha karışık hale getirebilir.
Bu gibi başıboş alanları ve bunlarla ilgili kodu Extract Class ile bir sınıfa çekin. Bunu yaparken if-else durumlarından kurtulabilmek için Introduce Null Object kullanılabilir.

Mesaj Zincirleri
(Message Chains) 
Kodun içinde x.getA().getB().getC().getD().getObject() gibi, client tarafının xxx sınıfının iç yapısını bilmesini gerektiren durumlarda arada bir yerde yapılan değişiklik client tarafını da etkiler.
Bu durumdan sakınmak için bu zincirin herhangi bir yerinde Hide Delegate yöntemi kullanılabilir. Ama bunu çok sık uygulamak Middle Man oluşmasına neden olabilir.

Ortadaki Adam
(Middle Man)
Encaptulation, bir sınıfın iç yapısını saklayan bir özelliktir ve genellikle delegation (temsil) ile görülür.
Ancak delegation zincirinin çok uzaması (dikkat, mesaj zinciri değil) uygulamanın bakımını zorlaştırır.
- Client'ın kullandığı sınıftan direkt olarak birkaç seviye alttaki bir nesneyi istemek yerine, aradaki sınıflara (delegation) erişerek onlar üzerinden ilgili nesneye erişmek daha doğru olabilir (Remove Middle Man).
- Eğer sadece birkaç çok az iş yapan method varsa Inline Method ile onların yaptığı işi client'a yüklenebilir. - Eğer sınıfın fazladan davranışları varsa delegasyon yerine inheritance kullanılabilir. (Replace Delegation with Inheritance)

Uygunsuz İlişki
(Inappropriate Intimacy)
Bazen sınıflar birbirlerinin private kısımlarını aşırı miktarda kullanabilirler. Birbirleri arasında karışık ilişkiler vardır. - Bu durumda ilişkiyi azaltmak için Move Method ve Move Field ilgili kısımları birbirinden ayırın ve Change Bidirectional Association to Unidirectional kullanıp kullanamayacağınızı görün.
- Sınıfların ortak özellikleri varsa Extract Class ile bunları ortak bir noktaya toplayın.
- Alt sınıflar, ataları hakkında gereğinden çok şey biliyorlarsa bu alt sınıfları delegasyon yapma zamanı gelmiş olabilir. (Replace Delegation with Inheritance)

Değişik Arayüzlere Sahip Alternatif Sınıflar
(Alternative Classes with Different Interfaces)
Arayüzleri farklı ama birbirine benzer sınıflar varsa bunlardan ortak bir arayüz çıkarmak için benzerlikleri kullanılabilir. 1. Method'ların isimleri değiştirilir (Rename Method) ve ilgili sınıflara taşı (Move Method).
2. Gerekirse Extract Superclass ile ortak bir üst sınıf oluşturulabilir.

Eksik Kütüphane Sınıfı
(Incomplete Library Class)

Bazı kod parçaları bizim kodumuza değil de daha çok kullanılan kütüphanenin içinde bulunması gerekiyormuş gibi görünebilir. Ancak kütüphaneyi değiştirmek ya imkansız ya da çok zordur. Bu şekilde sadece birkaç method varsa Introduce Foreign Method ile ayrı bir method olarak bulundurun. Ancak birkaç method'dan fazla ise Introduce Local Extension ile ayrı bir sınıfa çıkartın.

Veri Sınıfı
(Data Class)
Çok fazla getter ve setter method'ları bulunan sınıflardır. - Bu sınıfın public alanları varsa Encapsulate Field veya Encaptulate Collection ile bunlar kapatılmalıdır.
- Değiştirilmemesi gereken alanların setter method'ları silinmelidir. (Remove Setting Method)
- Getter ve setter method'larının kullanıldığı yerdeki davranışlarının bu sınıfa taşınabilip taşınamadığına bakın (Move Method). Eğer taşınamıyorsa önce Extract Method yapmayı deneyin. Bundan sonra dışarıdan çağrılmayan method'lar saklanabilir/silinebilir (Hide Method).

Reddi Miras
(Refused Bequest)
Alt sınıflar üst sınıfların bütün özelliklerini almak zorundadırlar. Ya alt sınıflar, bazı özellikleri istemiyorlarsa? Demek ki hiyerarşide yanlışlık var. - Yeni bir sınıf oluşturulup istenmeyen method'lar ve alanlar Push Down Method ve Push Down Field ile bu yeni sınıfa taşınabilir.
- Ancak alt sınıf üst sınıfın arayüzünü reddediyorsa arayüz uygun değildir. Arayüzü reddeden sınıfıReplace Delegation with Inheritance ile hiyerarşik yapıdan çıkartın.

Yorumlar
(Comments)
Kodun içine yazılan yorumlar kesinlikle yararlıdır. Ancak yorum yazmadan önce ilgili kısmın refactoring gerektirip gerektirmediğine bakmak gerekir. - Koda yorum yazmak zorunda hissediyorsanız o kısmı method'a çıkarmayı deneyin (Extract Method)
- Eğer hala yorum yazma gereği hissediyorsanız method'un adını değiştirin (Rename Method).
- Eğer sistemin state'i hakkında bazı kurallarınız varsa Introduce Assertion kullanın.
- Bütün bunlara rağmen gerektiği kadar yorum yazmak iyidir.