Bu dökümanın, bir makale olarak değilde GitHub'da olmasının sebebi, herkesin katkılarına açık bir şekilde sürekli güncel bir kılavuz hazırlamak.
Bazı kelimeler Türkçeye çevrilmedi. Bunun sebebi, birçok kelime artık, o kalıp içinde daha anlamlı oluyor. Örneğin, Refactoring, Exract Method, Primitive Obsession vs.
- Refactoring Nedir?
- Koddan Kötü Kokular Geliyor
- Long Method
- Large Class
- Primitive Obsession
- Long Parameter List
- Data Clumps
- Switch Statements
- Temporary Field
- Refused Bequest
- Alternative Classes with Different Interfaces
- Divergent Change
- Shotgun Surgery
- Parallel Inheritance Hierarchies
- Comments
- Duplicate Code
- Lazy Class
- Data Class
- Dead Code
- Speculative Generality
- Feature Envy
- Inappropriate Intimacy
- Message Chains
- Middle Man
- Incomplete Library Class
- Refactoring Teknikleri
Refactoring, kodun işlevselliğini değiştirmeden, kodun kalitesini artırma, temiz bir hale ve kolay bir tasarıma dönüştürme sürecidir. Refactoring kavramını anlamak için öncelikle temiz ve basit kod nedir, temiz kodu engelleyen, kötü kod yazmaya iten sebepler nelerdir bir diğer deyişle teknik borç nedir teknik borca iten sebepler nelerdir, onu anlamaya çalışalım.
Değişken isimlendirmeleri, sınıfların ve metotların uzunlukları ve karmaşıklıkları vs. gibi kodun okunmasını, anlaşılmasını ve bakımını zorlaştıran şeylerin olmaması.
Kod tekrarı, her defasında, aynı değişiklikleri farklı yerlerde yapmaya sebep olur. Her defasında, bir değişiklik yapıldığında, başka nerelerin değişeceğini akılda tutmak gerekir. Kodun anlaşılmasındaki yükü artırır ve süreçleri uzatır.
Az kod, daha az akılda tutulması gereken şey, daha az bakım yapılması, daha az hata demektir. Olabildiğince kısa ve basit kod her zaman temiz koddur.
Temiz kod bakımı kolaylaştırır, hız kazandırır ve bakım maliyetini düşürür.
Hiç kimse, projeye zarar vermek için bilerek kötü kod yazmaz. Herkes elinden gelenin en iyisini yapmak ister. Kötü kod yazmaya iten sebepler vardır. Kötü yazılan kod da, ilerde başımıza dert açabilir.
Teknik borcu anlatmak için, bankadan çekilen kredi örnek verilir. Acil ödemeniz gereken bir borç için, günü kurtarmak adına çekilen kredi, daha sonra daha fazla borç olarak tekrar karşımıza çıkar. Çekilen tutar tekrar faizi ile geri ödenir.
Aynı şekilde, daha hızlı geliştirmek adına; mesela test yazmadan, geliştirilen her özellik, gün geçtikçe bakım maliyetini artırarak, geliştirme hızını da düşürür, ta ki teknik borcu ödeyene kadar.
Peki teknik borca, kötü kod yazmaya iten sebepler nelerdir?
Bazen iş koşulları, tamamlanmadan önce özellikleri kullanıma sunmaya zorlayabilir. Bu durumda, projenin bitmemiş bölümlerini gizlemek için kodda fazladan (aslında kodun parçası olmayan) satırlar olabilir.
İşverenler/yöneticiler, geride teknik borç biriktirdikçe, maliyetin katlanarak arttığını anlamayabilirler. Bundan dolayıda, ekibin refactoring için zaman ayırmasını, vakit kaybı olarak görürler ve değer vermezler.
İletişim eksikliğinden dolayı bilgi, ekip üyeleri arasında sağlıklı dağılmaz veya tecrübeli birisi bilgiyi tecrübesiz olana yanlış aktarabilir. Bundan dolayıda ekip elindeki güncel olmayan, eksik veya yanlış anlaşılmış bilgi ile geliştirme yapabilir.
Teknik borcun birikmesine ve birleştirme işleminde daha da artmasına sebep olur. Toplam teknik borç, ayrı ayrı biriken teknik borç toplamından daha büyük olur.
Refactoring gerekli durumlarda, refactoring ertelenirse, düzenlenmesi gereken bu parçaya bağımlı yeni yazılan her kod, eski düzene göre yazılacağından, teknik borç her yeni yazılan kod için de artar. Oysa anında müdahale edilse, arkasından gelen kodlar için de aynı düzenleme gerekmeyecek.
Bazen sadece tecrübesizlikten veya beceriksizlikten kötü kod yazarak, teknik borç biriktiririz.
- İlk defa bir şey yapıyorsan, sadece yap.
- İkinci defa aynı şeyi yapıyorsan, tekrara düşmekten çekin ama yinede bir şekilde yap.
- Üçüncü defa aynı şeyi yapıyorsan, refactor et.
Refactoring, başkalarının kodlarını anlamayı kolaylaştırır. Yeni özellik eklemek için kodun iyi anlaşılması gereklidir. Kod ne kadar temiz olursa o kadar anlaşılır olur. Dolayısıyle yeni özellik eklemeden önce kodlar refactor edilebilir.
Yine hata bulmak için öncelikle kodun iyi anlaşılması lazımdır. Daha iyi anlamak için kodu refactor ederiz. Refactor işlemi sırasında, çoğunlukla hata bulunur.
Kod inceleme, hem kodu yazan hem de inceleyen için en faydalı iştir. Kod incleme yaparken, hata bulmak daha kolay ve hızlı olur. İlerde yapılabilecek daha büyük hatalar için de, önceden bilgi sahibi olmayı sağlar.
Refactoring kodun normal çalışmasında hiçbir değişiklik yapmadan, kodun daha iyi bir hale gelmesi için yapılan değişiklikler şeklinde olmalıdır.
Refactoring işleminden sonra kodlar hala temiz ve anlaşılır değilse, harcadığımız zaman boşa gitmiş demektir. Bu durumda neden böyle olduğunu anlamaya çalışmak lazım. Refactoring yaparken genelde; küçük değişiklikler yaparken, birden tek seferde çok büyük bir değişiklikler silsilesi içine girince ve üstüne bir de zaman kısıtı varsa işlerin karışmasına sebep olabilir. Bundan dolayı da refactoring sonucunda ortaya çıkan kod pek de temiz bir kod olmaz.
Ancak bazı kod altyapısı o kadar kötüdür ki, ne yaparsanız yapın iyileştirilemeyebilir. İşte bu durumda, kodun yeniden yazılması düşünülebilir. Tabi bunu yapacaksak, kesinlikle ilgili yerlerin testlerinin yazılmış olması şart. Ayrıca biraz zaman ayırmak da gerekli.
Yeni özellik eklemek için yazılan kodlar ile refactoring için yazılan kodlar farklı commit'lerde olmalıdır. Refactoring kodun işlevini değiştirmez, sadece daha iyi hale getirir.
Testleri olmayan kodları refactor etmek, refactoring sürecindeki en tehlikeli kısımdır. Refactoring yaptıktan sonra 2 durumda testler başarısız olabilir.
- Refactoring sırasında hata yaptın ve çokta önemli değil: Devam et ve hatayı düzelt.
- Testler çok alt seviye kodları test ediyordur. Örneğin, bir sınıfın private metodunu test ediyordur: Bu durumda, sıkıntı testlerdedir. Dolayısıyla testleri de refactor edebiliriz veya yüksek seviye kodları test eden yeni testler yazabiliriz. Tabi bunun en ideal çözümü, BDD-style test yazmakdır.
Refactoring yapılacak kod aslında kendisi alarm verir. Koddaki kötü kokular, refactoring için ipuçları içerir. Bu kötü kokuların neler olduğunu bilirsek, refactoring yapılacak kodları daha iyi ayırt edebiliriz.
Bir metot çok fazla satırdan oluşuyorsa o metotta sorunlar olabilir. Genel olarak 10-15 satırdan fazla uzun metotlar olunca, metodun uzunluğunu sorgulamaya başlayabiliriz.
Yazılımcı için kod yazmak, kod okumaktan daha kolaydır. Bundan dolayı metotlara sürekli gerekli olan kodlar eklenir ama kullanılmayan satırlar silinmez. Her gelen bir şey ekler. Böylece metodun uzunluğu gittikçe artar, okunamaz ve bakım yapılamaz hale gelir.
Psikolojik olarak, yeni bir metot oluşturmak, var olana ekleme yapmaktan daha kolay gelebilir. İki satır kod için, yeni bir metoda ihtiyaç yok diye düşünebiliriz. Her defasında bu düşüncüyle eklenen her satırdan sonra, metot adım adım karmaşık bir spagetti koda dönüşür.
Eğer metot içinde, yorum yazma gereksinimi olan satırlar varsa, bu satırların ayrı bir metoda alınması gerekebilir. Tek satırı bile açıklamaya ihtiyaç duyuyorsanız, ayırmanız gerekir. Ayrılan yeni metodun adı da, metodun ne iş yaptığını tanımlayıcı olursa, kimse kodun ne iş yaptığını anlamak için kodu okumak zorunda kalmaz. Çözüm için aşağıdaki refactoring kuralları uygulanabilir.
- Metot uzunluğunu azaltmak için: Extract Method
- Değişkenler ve parametreler yeni metoda almayı engelliyorsa: Temp with Query, Introduce Parameter Object, Preserve Whole Object.
- İlk 2 yöntem de yardımcı olmuyorsa: Replace Method with Method Object
- Kod içerisindeki koşullu ifadeler ve döngüler metodun ayrılması için ipucu verirler. Koşullu ifadeler için: Decompose Conditional.
- Tüm OOP dillerde en uzun yaşayan metotlar/sınıflar en kısa olanlarıdır. Metot ne kadar uzun olursa, anlamak ve bakım yapmak da o kadar zorlaşır.
- Uzun kodlar içinde, tekrarlanan kodlar birikmiş olabilir.
Akıllara gelen sorulardan birisi şu; metot sayısı arttıkça performans kötü etkilenir mi? Aslında neredeyse tüm durumlarda, etki o kadar önemsiz ki, sağladığı fayda yanında bu etkinin bir önemi yok.
Ayrıca temiz ve bakımı kolay bir kodu istediğimiz gibi kolayca refactor edebilir, yeniden tasarlayabilir ve gerçekten performanslı bir hale daha kolay getirebiliriz.
Çok fazla satır, alan ve metot içeren sınıflar.
Sınıflar en başta genelde küçük başlar. Ancak zamanla, program büyüdükçe şişerler.
Uzun metotlarda olduğu gibi, programcılar genellikle mevcut bir sınıfa, yeni bir özellik/satır eklemenin, yeni bir sınıf oluşturmaya göre daha kolay olduğunu düşünür.
- Eğer sınıfın davranışının bir parçası ayrılabiliyorsa: Extract Class
- Eğer sınıfın bir parçası, başka bir şekilde yazılabiliyorsa veya nadir durumlarda kullanılabilirse: Extract Subclass
- İstemcinin kullanabileceği, sınıfın yaptığı işlerin bir listesini tutmak için: Extract Interface
- Grafik arayüzünden büyük bir sınıf sorumluysa, verilerinin ve davranışlarının bir kısmını ayrı bir domain object nesnesine taşımayı deneyebilirsiniz. Bunu yaparken, bazı verilerin kopyalarını iki yerde saklamak ve verileri tutarlı tutmak gerekebilir. Bunun için: Duplicate Observed Data
- Bu sınıfların yeniden yapılandırılması, geliştiricilerin bir sınıf için çok sayıda şeyi hatırlama zorunluluğundan kurtarır.
- Çoğu durumda, büyük sınıfları parçalara bölmek, kod ve işlevselliklerin tekrarlanmasını önler.
- Küçük nesneler (range, currency, special strings) yerine primitif tipler kullanma.
- Kodun içinde sabit değerlerde bilgi tutmak. (Örneğin; admin yetkisi için
USER_ADMIN_ROLE = 1
şeklinde bir tanımlama yapmak) - Her bir alanın adının, dizi içinde sabit olarak tutulması. (Örneğin;
dizi["istanbul", "34"]
)
Diğer hatalarda olduğu gibi, Primitive Obsession hatası, zayıf olduğumuz anlarda ortaya çıkar. Sadece 1 tane değişken için, sınıf oluşturmak zor gelir ve bu 1 veriyi tutmak için primitif tipte bir değişken tanımlanır. Yeni bir alan lazım olduğunda her defasında bu şekilde eklenir ve sınıflar/metotlar gereksiz olarak şişer.
Bazen de sabit değerler, kod içinde kullanılacak olan bir entity hakkında bilgi tutmak için kullanılır. Ayrı bir tip tanımlaması yapmak yerine, sınıf içinde sabit değerler bu bilgiyi tutar. Bunun sebebi ise, kod içerisinde bu değerler kullanılırken daha anlaşılır olmasını sağlamak. Örneğin; if(userRole == AppConsts.USER_ADMIN_ROLE)
. Bu kullanım çok yaygındır.
Bir diğer hata ise, sınıfın her bir alanının tutması gereken veri, kolay olsun diye bir diziye atılır.
- Çok çeşitli primitif tipler varsa, bu verileri ilişkilerine göre gruplayıp, kendi sınıflarına taşımak: Replace Data Value with Object
- Gruplanabilecek primitif tipler, eğer metot parametresi olarak kullanılıyorsa: Introduce Parameter Object, Preserve Whole Object
- Kendi başına bir nesne olabilecek, veri tutan bir değişken için: Replace Type Code with Class, Replace Type Code with Subclasses, Replace Type Code with State/Strategy
- Bir nesnenin alanlarını tutan dizi varsa: Replace Array with Object
- Primitif tipler yerine nesnelerin kullanılması, kodu daha esnek yapar.
- Daha anlaşılabilir ve organizasyonu daha iyi bir kod yapısı sağlar. Veri üzerindeki her işlemi temsil eden alanlar aynı yerde ve düzenli olur. Diziler içindeki verilerin sürekli ne anlama geldiğini tahmin etmekten kurtarır.
- Kod tekrarlarını(code duplication) keşfetmek daha kolay olur.
Bir metodun 3,4 veya daha fazla parametre alması.
Birkaç farklı tipte algoritmanın bir metotda birleşmesi sonucunda, parametreler çoğalabilir. Metodun içindeki algoritmanın ne yapıyor olduğuna dair bilgi veren kontrol parametreleri kullanılıyor olabilir. Hangi algoritma hangi adımlarla çalışıyor diye çok ayrım yapınca, parametre sayısı artabilir.
Birbirinden bağımsız sınıflar oluşturmak istemenin yan etkisi olarak da fazla parametre alan metodlar oluşabilir. Örneğin; bir sınıf içinde, bir nesne oluşturan metodlar kullanılırlar. Oluşan bu nesneler de başka bir metoda parametre olarak verilir. Böylece ana sınıf, nesneler arasındaki ilişkiyi bilmez ve bağımlılık azalır. Ama bu şekilde bir çalışma en son parametre alan metodun parametre sayısını artırır.
- Bazı parametreler, metotların sonucunda gelen nesneler ise: Replace Parameter with Method Call
- Başka bir nesneden alınan bir veri grubunu parametre olarak göndermek yerine, nesneyi kendisini metoda parametre olarak geçmek: Preserve Whole Object
- Birden çok ilişkisiz parametre varsa bunları tek bir nesne içinde toplayıp, parametre olarak geçme: Introduce Parameter Object
- Daha okunabilir ve kısa kodlar.
- Varsa daha önceden farkedilmemiş olan, kod tekrarı farkedilebilir.
Sınıflar arasında gereksiz bağımlılık oluşturabilecek durumlarda parametrelerde kurtulmak iyi bir yöntem olmayabilir.
Ortak nesneye sahip olabilecek parametre listelerinin parametre olarak kullanılması. Müşteri bilgileri, adres bilgileri, veritabanı bağlantı bilgileri gibi. Bunların hepsi ayrı nesne olarak tanımlanabilir.
Genelde zayıf kod tasarımı veya "kopyala-yağıştır programcılığı" ile ortaya çıkar.
- Parametreler, bir sınıfın alanları olabilecek şekilde gruplanabiliyorsa: Extract Class
- Aynı veri kümeleri, parametre olarak geçiliyorsa (örneğin; startDate, endDate): Introduce Parameter Object
- Metot içindeki bazı veriler, başka metoda parametre olarak verilecekse, onun yerine komple nesneyi parametre vermek: Preserve Whole Object
Kodun anlaşılabilirliğini ve organizyonunun kalitesini artırır. Parametreler dağınık olarak, etrafta duracağına, bir sınıf içinde toplanmış olur. Kod tekrarı engellenir.
Kodu kısaltır.
Eğer sınıflar arasında gereksiz bir bağımlılık oluşturacaksa göz ardı edilebilir.
Kompleks switch-case
veya uzun if-else
ifadeleri.
OOP dillerde switch-case
ifadesinin az kullanımı, iyi tasarımın işaretlerinden biridir. Kompleks switch-case
ifadeleri, genelde kodda birden çok yerde ihtiyaç olduğunda ve birden çok yerde aynı işi yapan ifadeler olduğunda oluşur. Yeni bir koşul geldiğinde, aynı switch-case
ifadesi olan tüm yerlerde bu koşulu eklemek gerekir.
Bir kural, switch-case
gördüğün anda, polymorphism olabilme ihtimalini düşün.
switch-case
ifadesini ayırmak ve bağımsız yapmak için: Extract Method ve ardından Move Methodswitch-case
ifadesinde, tip karşılaştırması varsa: Replace Type Code with Subclasses veya Replace Type Code with State/Strategy- Kalıtım yapısının nasıl olacağını belirledikten sonra: Replace Conditional with Polymorphism
switch-case
ifadesinde çok fazla koşul varsa ve her koşulda, aynı metot farklı parametrelerle çağrılıyorsa, polymorphism gereksiz olacaktır. Bu durumda, metodu küçük farklı metodlara bölebiliriz: Replace Parameter with Explicit Methods- Koşullardan birisi null karşılaştırma yapıyorsa: Introduce Null Object
Daha iyi kod organizasyonu.
- Basit
switch-case
ifadeleri. - Factory tasarım desenleri içindeki
switch-case
ifadeleri.
Geçici alanlar, sadece belirli koşullar altında değer alırlar. Bu koşullarında dışında, her zaman boş olurlar.
Genellikle geçici alanlar, çok fazla girdisi olan bir algoritma içinde kullanılmak için oluşturulurlar. Dolayısıyla, metot için çok fazla parametre geçmek yerine, sınıfın içinde, veriyi tutması için bir alan oluşturulur. Bu alanlar sadece bu algoritma içinde kullanılır ve sonrasında artık anlamsızdır.
Bu tarz bir kodun anlaşılması zordur. Siz sürekli ilgili alanın bir veri tuttuğunu varsayarsınız ama o tek bir algoritma dışında, her zaman boştur.
- Geçici alanı ve onunla ilişkili olan işlemleri farklı bir sınıfa taşıma: Extract Class veya Replace Method with Method Object
- Geçici alanları kontrol etmek için: Introduce Null Object
Daha iyi kod organizasyonu ve sadelik.
Bir sınıf kalıtım aldığı sınıfın sadece bir kaç metodunu veya özelliğini kullanıyorsa, sınıflar arasındaki hiyerarşi bozulur. İhtiyaç duyulmayan metotlar artık gereksiz hale gelir.
Kodun yeniden kullanılması isteği, bazen gereksiz hiyerarşi kurmaya sebep olabilir. Ama kalıtım alınan sınıf ile alan sınıf arasında çok farklılık vardır. Örneğin; AnimalLegs
sınıfından türeyen, DogLegs
ve ChairLegs
.
- Kalıtım mantıklı değil ve kalıtım alan sınıf ile üst sınıf arasında bir benzerlik yok ise: Replace Inheritance with Delegation
- Eğer kalıtım yapmak uygunsa, kalıtım alan sınıf içindeki gereksiz alanlardan ve metotlardan kurtulun. Üst sınıfta olan ve alt sınıfta kullanılan metot ve alanları ayrı bir alt sınıfa taşıyın ve bu sınıftan kalıtım alın: Extract Superclass
Kodun okunabilirliğini ve organizasyonunu artırır. Artık, neden Chair
sınıfının Animal
sıfından türediğini merak etmeye gerek yok.
İki farklı sınıfın, aynı işlemi farklı metot isimleri ile yapması.
Sınıflardan birini oluşturan yazılımcının, muhtemelen aynı işlevi yapan başka bir sınıfın varlığından haberi yoktur.
- Tüm metotların tamamen aynı olması için: Rename Method
- Metodların imzasının ve uygulanmasının aynı olması için: Move Method, Add Parameter ve Parameterize Method
- Sınıflardan sadece bir bölümünün benzer olduğu durumlarda: Extract Superclass
- Sınıflardan birisinin tüm işlemleri yaptığı ve diğerinin aslında gereksiz olduğunu düşündüğünüzde, gereksiz sınıfın silinmesi.
- Gereksiz kodların silinmesi.
- Kod daha temiz ve okunabilir olması.
Bazen sınıfları birleştirmek mümkün olmayabilir veya çok zor olabilir. Mesela, sınıflar farklı kütüphanelerde olabilir. Her kütüphanenin geliştirilmesi, versiyonlanması farklı yönetilir.
Bir sınıfta değişiklik yaparken, kendinizi bir sürü metodu değiştirirken bulabilirsiniz. Örneğin; yeni bir ürün tipi eklediğinizde, bulma, gösterme, sıralama yapan metotları da değiştirmek zorunda kalabilirsiniz.
Genelde bu problemin sebebi, yazılımın kötü yapısı/tasarımı ve "copy-paste programming" in sonucudur.
- Sınıfın davranışını bölmek: Extract Class.
- Farklı sınıflar aynı davranışa sahipse, sınıfları kalıtım yoluyla birleştirmek: Extract Superclass ve Extract Subclass.
- Kod organizasyonunun iyileştirilmesi.
- Kod tekrarının azaltılması.
- Desteği basitleştirme.
Herhangi bir değişiklik yapmak, birçok farklı sınıfta birçok küçük değişiklik yapmanızı gerektirir.
Çok sayıda sınıf arasında tek bir sorumluluk dağılmıştır. Bu aşırı bir şekilde "Divergent Change" uygulamasından sonra olabilir.
- Mevcut sınıf davranışlarını tek bir sınıfa taşı: Move Method ve Move Field.
- Kodları taşıdıkdan sonra, diğer sınıflar neredeyse boş kalıyorsa, boş kalan sınıflardan kurtul: Inline Class.
- Daha iyi bir kod organizasyonu.
- Daha az kod tekrarı.
- Kolay bakım.
Her ne zaman bir sınıf için alt sınıf oluştursan, kendini başka bir sınıf için alt sınıf oluşturma ihtiyacı duyarken bulabilirsin. İlişkili iki sınıf farklı dallardan hiyerarşi oluşturur.
Hiyerarşi küçük olduğu sürece çok problem değil ama yeni sınıflar eklenmeye başlandıkça, değişiklik yapmak sürekli zorlaşmaya başlar.
- Hiyerarşilerin en tepesindeki sınıfların ikisinide kalıtım alan yeni bir hiyerarşi veya tepedeki paralel sınıfların birleşiminden yeni bir hiyerarşi: Move Method ve Move Field.
- Kod organizasyonu iyileşmesi.
- Kod tekrarını engellemesi.
Bazen paralel sınıf hiyerarşilerine sahip olmak, yazılımın mimarisiyle daha büyük karışıklıktan kaçınmanın bir yoludur. Hiyerarşileri çoğaltma girişimlerinizin daha çirkin kodlar ürettiğini tespit ederseniz, tüm değişikliklerinizi geri alın ve bu koda alışın.
Metodun açıklayıcı yorumlarla dolu olması.
Yorumlar genellikle yazılımcının kendi kodunun sezgisel olarak anlaşılmadığını veya açık olmadığını fark etmesiyle, iyi niyetle yazılır. Bu gibi durumlarda, yorumlar, kötü kokan kodun kokusunu gizleyen deodorant gibidir.
En iyi yorum bir metot veya sınıf için iyi bir addır.
Bir kod parçasının yorum yapılmadan anlaşılmayacağını düşünüyorsanız, kod yapısını yorumları gereksiz kılacak şekilde değiştirmeyi deneyin.
- Bir yorumun karmaşık bir ifadeyi açıklaması amaçlanıyorsa, ifade, anlaşılabilir alt ifadelere bölünmelidir: Extract Variable.
- Bir yorum kodun bir bölümünü açıklıyorsa, bu bölüm ayrı bir metot yazpılabilir. Yeni yöntemin adı, büyük olasılıkla, yorum metninin kendisinden alınabilir: Extract Method.
- Bir metot zaten oluşturulmuşsa, ancak metodun ne yaptığını açıklamak için yorumlar hala gerekliyse, metoda açıklayıcı bir isim verin: Rename Method.
- Sistemin çalışması için gerekli olan bir durum hakkında kurallar koymak için yorum yazmak gerekirse: Introduce Assertion.
Kod daha sezgisel ve açık hale gelir.
- Bir şeyin neden belirli bir şekilde uygulandığını açıklarken.
- Karmaşık algoritmaları açıklarken (algoritmayı basitleştirmek için tüm diğer yöntemler denendikten sonra).
İki kod parçasının neredeyse aynı olması.
Kod tekrarı, birden çok yazılımcının aynı yazılımın farklı bölümlerinde aynı anda çalıştığı durumlarda olur. Farklı işler üzerinde çalıştıklarından, diğer yazılımcının kendi ihtiyaçları için benzer bir kod yazmış olabileceğinin farkında olmayabilir.
Ayrıca, kodun belirli kısımları farklı göründüğü halde aslında aynı işi yaptığı durumlar da vardır. Bu tür bir kod tekrarını bulmak ve düzeltmek zor olabilir.
Bazen kod tekrarı bilerek yapılır. İşin yetişmesi gerek zamanın sonuna geliniyorsa ve mevcut kod gereken iş için "neredeyse doğru" ise acemi yazılımcı, "kopyala-yapıştır" yapmaktan kendini alamayabilir. Bazen de yazılımcı tembellik ederek kod tekrarına göz yumabilir.
- Aynı kod, aynı sınıfta iki veya daha fazla metotta bulunursa: Extract Method.
- Aynı kod, aynı seviyedeki iki alt sınıfta bulunursa;
- İki sınıf içinde, alanı üste taşıma Pull Up Field yöntemini takip ederek: Extract Method.
- Tekrar eden kod bir yapıcı metot içinde ise: Pull Up Constructor Body.
- Eğer yinelenen kod benzer ancak tamamen aynı değilse: Form Template Method.
- İki metot da aynı şeyi yapar, ancak farklı algoritmalar kullanırsa, en iyi algoritmayı seçin: Substitute Algorithm.
- Tekrar eden kod iki farklı sınıfta bulunursa;
- Sınıflar bir hiyerarşinin parçası değilse: Extract Superclass.
- Bir üst sınıf oluşturmak zor veya imkansızsa: Extract Class.
- Çok sayıda koşullu ifade varsa ve aynı kodu çalıştırıyorsa (yalnızca koşullu ifadeler farklı), bu operatörleri tek bir koşulda birleştirin: Consolidate Conditional Expression ve Extract Method.
- Aynı kod, koşullu bir ifadenin tüm dallarında uygulanıyorsa: aynı kodu, koşul ağacının dışına yerleştirin: Consolidate Duplicate Conditional Fragments.
- Tekrar eden kodun birleştirilmesi, kodunuzun yapısını basitleştirir ve daha kısa hale getirir.
- Sadeleştirme + kısayol = basitleştirmesi kolay ve bakımı daha ucuz kod.
Çok nadir durumlarda, iki kod parçasının birleştirilmesi, kodu daha az sezgisel ve haha az açık hale getirebilir.
Bir sınıfın anlaşılması ve bakımı, zaman ve maliyet gerektirir. Dolayısı ile bir sınıf anlaşılmıyorsa ve yeterince istekleri karşılamıyorsa, o sınıf silinmelidir.
Belki bir sınıf tamamen işlevsel olacak şekilde tasarlanmıştır, ancak refactoring yaptıktan sonra saçma derecede küçük hale gelmiştir. Veya gelecekte yapılacak ama daha yapılmamış bir özellik için tasarlanmış olabilir.
- Neredeyse işe yaramaz olan bileşenler için: Inline Class.
- Az işlevli alt sınıflar için: Collapse Hierarchy.
- Kod uzunluğunu azaltır.
- Bakımı kolaylaştırır.
Koddaki basitlik ve açıklık arasınki dengeyi korumak şartı ile, gelecekteki gelişmelere yönelik niyetleri betimlemek için bir Lazy Class oluşturulabilir.
Martin Fowler'ın "Code Smell" dediği "Data Class", çoğu yazılımcı tarafından, "Code Smell" olarak kabul edilmiyor. Data Transfer Objects, Entity Objects vs. gibi birçok kullanımı var ve bunlar kaçınılmaz. Peki kim haklı?
Bir değişken, parametre, alan, metot veya sınıfın artık kullanılmamasıdır(genellikle artık eskimiş olduğundan).
Yazılımın gereksinimleri değiştiğinde veya düzeltmeler yapıldığında, eski kodu temizlemek için hiç kimse zaman harcamak istemez. Bu tür bir kod karmaşık kod bloklarında da bulunabilir.
Ölü kodu bulmanın en hızlı yolu iyi bir IDE kullanmaktır. Çözmek ise basit; sil.
- Kullanılmayan kodu ve gereksiz dosyaları silin.
- Gereksiz bir sınıfın bulunması durumunda: Inline Class ve Collapse Hierarchy.
- Gereksiz parametreleri kaldırmak için: Remove Parameter.
- Kod uzunluğu azalır.
- Kolay bakım.
Kullanılmayan bir sınıf, metot, alan veya parametrenin olmaması.
Bazen kod, asla uygulanamayacak olan gelecekteki özellikleri desteklemek için yazılır. Sonuç olarak, kodun anlaşılması ve bakımı zorlaşır.
- Kullanılmayan soyut sınıfları kaldırmak için: Collapse Hierarchy.
- Gereksiz fonksiyonelliklerin başka bir sınıfa devredilmesi engellemek için: Inline Class.
- Kullanılmayan metotlardan kurtulmak için: Inline Method.
- Gereksiz parametreli metotlar için: Remove Parameter.
- Kullanılmayan alanlar kolayca silinebilir.
- Temiz kod.
- Daha kolay bakım.
- Eğer bir framework geliştiriyorsanız, framework'ün kendisinin kullanmadığı ama kullanıcılarının kullanabileceği bir işlev için göz ardı edilebilir.
- Bazı birim testler, sınıf hakkında bilgileri kullanamk için ekstra alanlara ihtiyaç duyabilir. Bundan dolayı, gereksiz kod bloklarını silerken, birim testlerin bu kod bloklarını kullanıp kullanmadığından emin olun.
Bir metodun, başka bir sınıfın verisine, kendisindeki veriden daha fazla erişmesi.
Alanlar veri sınıfına taşınırken oluşur. Bu durumda, veri ile işlem yapan kodları da bu sınıfa taşımak isteyebilirsiniz.
Genelde veri ve bu veriyi kullanan kod blokları birlikte değişir. Bundan dolayı hepsini aynı yerde tutmak gerekir.
- Eğer metotlar taşınacaksa: Move Method.
- Bir metodun yalnızca bir kısmı başka bir nesnenin verilerine erişiyorsa: Extract Method.
- Bir yöntem diğer birkaç sınıftan fonksiyonlar kullanıyorsa, ilk önce hangi sınıfların kullanılan veriyi içerdiğini belirleyin. Ardından metodu bu sınıfa diğer verilerle birlikte taşıyın. Alternatif olarak, metot küçük parçalara ayrılıp, farklı sınıflar içinde farklı metotlar olarak yer alabilirler: Extract Method.
- Daha az kod tekrarı (veri işleme kodu merkezi bir yere yerleştirilirse).
- Daha iyi kod organizasyonu (veri işleme metotları, ile veri aynı yerde olursa).
Bazen davranış bilerek verileri tutan sınıftan ayrı tutulur. Bunun genel avantajı, davranışı dinamik olarak değiştirme yeteneğidir.
Bir sınıf, başka bir sınıfın "internal" alanlarını ve metotlarını kullanır. Sınıflar ne kadar birbirinden bağımsız olursa, yeniden kullanımı ve bakımı kolaylaşır.
Kodların parça parça taşınması sırasında veya yanlış tasarımdan kaynaklanabilir.
- En hızlı ve basit çözüm, bir sınıfın metotlarını ve alanlarını başka sınıfa taşımak (eğer ilk sınıf bu metotlara tamamen ihtiyaç duymuyorsa): Move Method ve Move Field.
- Sınıflar ilişkili ise, o zaman gerçekten ilişkili sınıflar yapmak: Extract Class ve Hide Delegate.
- Sınıflar karşılıklı olarak birbirine bağımlıysa: Change Bidirectional Association to Unidirectional.
- Bu "samimiyet" bir alt sınıf ile üst sınıf arasındaysa: Replace Delegation with Inheritance.
- Kod organizasyonunu geliştirir.
- Bakımı ve kodun yeniden kullanımını kolaylaştırır.
Kodda $a->b()->c()->d()
gibi bir dizi çağrı görürsünüz. Bu zincirler, sınıfların birbirlerine aşırı bağlı olmasına sebep olur. Bir sınıfta yapılan değişiklikler, diğerlerini de etkiler.
Bir istemci bir nesne talep ettiğinde, talep edilen nesne başka bir tane daha ister ve bir mesaj zinciri oluşur.
- Bir mesaj zincirini silmek için: Hide Delegate.
- Bazen son nesnenin neden kullanıldığını düşünmek daha iyidir. Belkide bunu zincirin en önüne taşımak daha mantıklı hale gelecektir: Extract Method ve Move Method.
- Bir zincirin sınıfları arasındaki bağımlılığı azaltır.
- Şişirilmiş kodun miktarını azaltır.
Aşırı agresif sınıf gizleme, işlevselliğin gerçekte nerede olduğunu görmenin zor olduğu kodlara neden olabilir. Aksi halde başka bir sıkıntı oluşabilir: Middle Man.
Bir sınıfın tek işi, tüm işleri başka sınıflara yaptırmak.
"Message Chains" den kurtulmak için aşırı derecede kod başka sınıflara taşındığında bu durum oluşabilir. Diğer bir sebepte, bir sınıfın kodları parça parça başka sınıflara taşındığında ortaya çıkar. İçi boşalan bir sınıf, içi boş bir kabuk gibi kalır.
Bir sınıf içindeki bir çok metot başka sınıflara alınıyorsa: Remove Middle Man.
Daha az kod.
- Sınıflar arası bağımlılıkları önlemek için Middle Man eklenmiş olabilir.
- Bazı tasarım desenleri bilerek Middle Man yaratır.
Er ya da geç, kütüphaneler kullanıcı ihtiyaçlarını karşılamayı durdurur. Tek çözüm ise kütüphaneyi değiştirmek ama kütüphanenin sadece okunabilir (read-only) olması, kütüphanenin değiştirilmesini imkansız hale getirir.
Kütüphanenin yazarı, ihtiyaç duyduğunuz özellikleri sağlamadığında ya da geliştirmeyi reddettiğinde ortaya çıkar.
- Bir kütüphane sınıfına birkaç metot tanıtmak: Introduce Foreign Method.
- Bir sınıf kütüphanesinde büyük değişiklikler için: Introduce Local Extension.
Kod çoğaltmasını azaltır (sıfırdan kendi kütüphanenizi oluşturmak yerine, hala mevcut olandan birisini kullanabilirsiniz).
Bir kütüphaneyi genişletmek, eğer kütüphanedeki değişiklikler koddaki değişiklikleri içeriyorsa ek iş üretebilir.
NOT: Yararlanılan kaynaklar sürekli eklenecek.
- https://refactoring.guru/
- http://www.yilmazcihan.com/yazilim-gelistirmede-teknik-borc/
- https://softwareengineering.stackexchange.com/questions/365017/when-is-primitive-obsession-not-a-code-smell
- https://martinfowler.com/bliki/DataClump.html
- http://blog.ploeh.dk/2015/09/18/temporary-field-code-smell/
- https://dzone.com/articles/code-smell-series-parallel-inheritance-hierchies
- https://softwareengineering.stackexchange.com/questions/338195/why-are-data-classes-considered-a-code-smell
- https://stackoverflow.com/questions/16719270/is-data-class-really-a-code-smell
- http://wiki3.cosc.canterbury.ac.nz/index.php/Middle_man_smell