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