Bu dokümanın bir makale olarak değil de 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, Extract 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
- Kaynaklar
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 faiziyle 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ış bilgiyle 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 yine de 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ıyla 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 inceleme 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 kodlarla 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 çok da ö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.
Metodun gövdesinin gereksiz, aşırı, okunamayacak ve bakımı yapılamayacak derecede uzun olması.
Yazılımcı her zaman kodu kendisi yazmak ister. Kendi yazmadığı kodlara güvenmez/güvenmek istemez. Bundan dolayıda kod yazmak, kod okumaktan kolay gelir yazılımcıya. Her yeni gelen yazılımcı, bir yandan okumadan kod ekleyip, bir yandan da kullanılmayan kodları silinmeyince, metot gittikçe şişer, okunamaz ve bakımı yapılamaz hale gelir. Okunamayan kod, sürekli yeni kodların eklenmesini tetikleyerek kısır döngü oluşturur.
İki satır kod için yeni bir metoda ihtiyaç yok diye düşünüp, tüm kodu var olan metotlara eklemek, her zaman yazılımcıya daha kolay gelir. Bu da kodu işin içinden çıkılmaz bir hale sokar.
Anlaşılması için illa açıklanması gereken her kod bloğu ayrı bir metoda taşınabilir. Kodların anlaşılması için taşınan metodun ismi, metodun ne yaptığına dair kesin bilgi vermelidir. Böylece metodu kullananlar, metodun ne yaptığını, içindeki kodları okumadan anlayabilir.
Kod içerisindeki koşullu ifadeler, döngüler her zaman, kodları farklı metoda taşımak için birer adaydır. Kullanılabilecek refactoring teknikleri: Extract Method, Temp with Query, Introduce Parameter Object, Preserve Whole Object, Replace Method with Method Object ve Decompose Conditional.
Sürekli büyüyen kodlar için, tekrar eden, aynı işi yapan, belki de yanlış çalışan kodlar olabilir. Bu kodlar içinde hata bulmak, tekrar eden kodları temizlemek ve bakım yapmak çok zor iştir. Bu kötü tasarımdan kurtulursak, tüm bu olumsuzluklardan da kurtuluruz.
Kodun daha anlaşılır, bakım yapılabilir ve daha az hataya açık olması, her zaman maliyeti düşürür ve geliştirme hızını artırır. Ayrıca daha iyi tasarlanmış bir kod, daha hızlı çalışır. Daha iyi tasarlanmış bir kodda gereksiz kodlarda olmaz. Yani aslında performansın dengelenmesinin yanında, fazladan geliştirme hızı ve maliyet düşüşü sağlamış oluruz.
Bir sınıfta gereğinden fazla alan, metot, özellik ve dolayısıyla fazla kod olması.
Uzun metotlardaki gibi, uzun sınıfların oluşmasındaki en büyük sebep, kolay olmasından dolayı, kodu okumak yerine, direk koda ekleme yapmasıdır.
En iyi çözüm en başta sınıfı parçalara ayırmak. Mesela, GUI'nin kullandığı kısımlar ayrı bir sınıfa taşınabilir. Sınıf paralel veya hiyerarşik olarak ayrıştırılabilir. Aynı şekilde, sınıf içinde gruplanabilecek davranışlar, interface olarak da ayrıştırılabilir.
Kullanılabilecek refactoring teknikleri: Extract Class, Extract Subclass, Extract Interface, Duplicate Observed Data.
Sınıfın daha düzenli, daha okunabilir ve daha kolay bakım yapılabilir bir hale gelmesiyle, yazılımcı sınıfın ne yaptığını, içinde neler olduğunu hatırlama zorunluluğundan da kurtulur. Kod daha fazla kontrol altında tutulabildiğinden, kod tekrarlarının da önüne geçilmiş olur.
Kod içerisinde primitif tiplere, işlerinin dışında sorumluluklar vermek. Örneğin; range
diye bir nesne yerine, start
ve end
diye değişken kullanmak, USER_ADMIN_ROLE = 1
şeklinde, kodun içinde sabit veriler tutmak veya dizi["istanbul", "34"]
gibi bir dizi içinde, bir nesnenin her bir alanının tutacağı verileri tutmak.
Biraz tembellikten, belki biraz da tecrübesizlikten, başta bir tane veri için, bir nesne oluşturmak yerine, kolay olan yolu yani değişken tanımlama yoluna gideriz. Benzer bir alan daha lazım olduğunda, kodu refactor edip nesneye çevirmek yerine, bu yeni alanı da, başka bir değişkene atarız. Her defasında bu hatayı yaptıkça, sınıflar/metotlar şişer.
Bir diğer hata da kullanımı kolay ve anlaşılır olan değişkenlerde veritabanına ait olabilecek veriler tutmak. En kötüsü ise, bir sınıfın her alanının, bir dizide veri olarak tutulması. Neyse oluşturmak o kadar zor gelirki, bir dizide bu nesnenin her alanı için bir veri tutulur.
Çözüm basit: nesne oluşturmak. Gruplanabilecek olanlar bir nesne(sınıf) çatısı altında toplamalıyız. Sınıfın alanları olabilir, metodun parametreleri olabilir, kendi başına nesne olabilecek bir değişken olabilir veya zaten grup olan dizi elemanları olabilir; bunların hepsi ayrı bir nesne olabilir.
Kullanılabilecek refactoring teknikleri: Replace Data Value with Object, Introduce Parameter Object, Preserve Whole Object, Replace Type Code with Class, Replace Type Code with Subclasses, Replace Type Code with State/Strategy, Replace Array with Object.
Kod daha düzenli, esnek, anlaşılır olur ve bu da kod tekrarının önüne geçer, bakım maliyetini düşürür. Çünkü artık dizi içindeki verilerin neyi ifade ettiğini anlamakla uğraşmayız, birbiri ile ilişkili verileri, tek bir yerden kontrol edebiliriz.
Methodun çok fazla parametre alması.
Metot değiştikçe yeni parametreler eklemek gerekebilir. Her yeni parametre ekledikten sonra, önlem alınmazsa parametreler gittikçe çoğalır veya metot kendi içinde sınıfın verilerini kullanmak yerine, onları parametre olarak alabilir. Bunun sebebi bağımlılığı azaltmak ama yine metodun parametreleri artmış olur.
Metot aynı sınıf içindeki verileri parametre alıyorsa buna gerek yok. Parametre geçmek yerine, metot içinde bu veriler kullanılabilir. Bir sınıfın alanlarını tek tek parametre geçmek yerine, sınıfın kendisini parametre geçmek daha mantıklıdır. Diğer bir durum ise, ilişkili olabilecek parametreleri, bir nesneye çevirmek. Örneğin, start
ve end
parametrelerini range
nesnesine çevirip, bunu parametre olarak geçebiliriz.
Kullanılabilecek refactoring teknikleri: Replace Parameter with Method Call, Preserve Whole Object, Introduce Parameter Object.
Daha okunabilir, kısa, bakımı kolay kodlar. Daha okunabilir kod içerisinde, gereksiz kodl tekrarları da daha rahat farkedilir, dolayısıyla, kod tekrarlarından da kurtulunabilir.
Sınıflar arasında gereksiz bağımlılıklar oluşturabilecek her durumda göz ardı edilebilir.
Ortak bir sınıfta toplanabilecek değişkenlerin, tek tek parametre olarak kullanılması. Örneğin; Müşteri bilgileri, adres bilgileri, veritabanı bağlantı bilgileri gibi sınıflar oluşturabilir ve ortak parametreleri bu sınıfların özelliği haline getirebiliriz.
Sebep Long Parameter List
başlığındakilerle aynıdır.
Çözüm de yine Long Parameter List
başlığındakiler ile benzerdir. Parametreleri gruplayıp, bir sınıfa taşıyıp, sınıfı parametre olarak geçmek.
Kullanılabilecek refactoring teknikler: Extract Class, Introduce Parameter Object, 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, dolayısıyla kod kısalır, okunabilirliği artar ve bakımı kolaylaşır.
Eğer sınıflar arasında gereksiz bir bağımlılık oluşturacaksa göz ardı edilebilir.
Okunması/anlaşılması ve bakımı zor switch-case
veya gereksiz uzunluktaki if-else
ifadeleri.
switch-case
ifadeleri çok fazla koşul gerektiği durumda birer ihtiyaç olarak kullanılırlar. Gereğinden fazla uzun if-else
ifalerinin oluşması da yine, her yeni gelen koşul için, kodda hiç bir değişiklik yapmadan, uç uca eklenen koşullardan dolayı oluşur.
switch-case
veya uzun if-else
olan yerde büyük ihtimalle bir sıkıntı vardır ve refactor edilmesi gerekebilir ve büyük olasılıkla polymorphism
çözüm olabilir.
switch-case
ifadeleri genellikle sınıfların daha iyi tasarlanması ile çözülebiliyor. Çözülemediği durumlar, koşul ifadelerinde metotların kullanıldığı durumlardır ve bunların çözümü de farklıdır.
Kullanılabilecek refactoring teknikler: Extract Method, Move Method, Replace Type Code with Subclasses, Replace Type Code with State/Strategy, Replace Conditional with Polymorphism, Replace Parameter with Explicit Methods, Introduce Null Object.
Uygulamanın tasarımı daha iyi bir hal alır ve bakımı kolaylaşır.
Aslında switch-case
ifadeleri çok kullanışlıdır ve yerinde kullanıldığında, hız kazandırır. Eğer uygulanın içerisinde, yönetilemeyecek kadar çok dağılırsa ve kodun okunabilirliğini ve bakımını zorlaştıracaksa, switch-case
ifadelerinden kaçınılmalıdır. Ama basit kullanımlarda hiçbir sakıncası yoktur.
Ayrıca, Factory design pattern
içinde de switch-case
ifadeleri kullanmakta bir sakınca yoktur.
Bir sınıf içinde, belirli bir kapsam dahilinde geçerli ve birbirine bağımlı olarak tanımlanmış, geçici alanların olması. Bu alanların, sınıfı geneli için ifade ettiği bir şey yoktur. Sadece bazı metotlar bunların değerlerini değiştir ve kullanır. Bu alanların ne için kullanıldığını bulmak çok zordur.
Karmaşık algoritmalar, her zaman çok fazla değişken kullanırlar. Bazen bu değişkenleri parametre olarak, metoda geçmek gerekir ve yazılımcı bunun kötü bir tasarım olduğunu bildiği için bunu yapmak istemez ama başka bir kötü tasarım yapabilir. Algoritma için gerekli olan değişkenleri, sınıfın alanları olarak tanımlar ve bu alanlar sadece ilgili algoritma tarafından kullanılır Dolayısıyla bu algoritma dışında bu değişkenlerin hiç bir manası yoktur.
Bu kötü tasarımdan kurtulmak için, ilişkili olan işlemleri farklı sınıfa taşıyabilir veya Null object pattern
yöntemini kullanabiliriz.
Kullanılabilecek refactoring teknikleri: Extract Class, Replace Method with Method Object, Introduce Null Object.
Daha rahat okunabilir ve sade kodlar.
Bir sınıfın, kalıtım aldığı sınıfın çok az özelliğini veya metodunu kullanması.
Kodun yeniden kullanılması, kalıtım ve benzer tasarım desenlerini kullanma isteği bazen bunların gereksiz kullanımına bizi iter. Ama hiyerarşi kurmak istediğimiz sınıflar çok farklı olabilir. Örneğin birisi ördek, diğeri oyuncak ördek olabilir.
"The Liskov Substitution Principle" şöyle der: Ördeğe benziyor, ördek gibi ses çıkarıyor ama bataryaya ihtiyacı varsa, büyük ihtimalle yanlış bir soyutlama peşindesin.
Kalıtımla da çözülebilir, kalıtım olmadan da çözülebilir. Kalıtım mantıklı değilse, sınıflar paralel hiyerarşiye çekilir.
Kalıtım yapmak uygunsa, kalıtım alan sınıf içindeki gereksiz metotlardan, alanlardan ve özelliklerden kurtulup, başka bir üst sınıf oluşturup, uygun şekilde yeniden hiyerarşi sağlanabilir.
Kullanılabilecek refactoring teknikleri: Replace Inheritance with Delegation, 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.
Farklı interface
kullanan veya hiç interface
kullanmayan, ama aynı işi yapan kod bloklarının, sınıfların veya metotların olması. Bu sınıfların içindeki aynı işleri yapan metotların isimleri, imzaları falan farklıdır. Hangisinin ne zaman kullanılacağı tam olarak belli değildir. Birbirlerinin alternatifleri bile olurlar.
Yazılımcının kod okumaması, kötü tasarlanmış kodların olması, sürekli yeni yazılımcıların katılmasından kaynaklı olarak, yazılımcıların, muhtemelen böyle bir işlevi yapan kod parçalarından habersiz olarak, aynı işi yapan kod blokları eklemesinden kaynaklanır.
Sınıfların ve metotların isimlerinin, imzalarının ve uygulanmalarının birebir aynı olması sağlanmalıdır. Birebir aynı olduğunda sınıflardan birisinin gereksiz olduğu anlaşılırsa, gereksiz olan silinir. Sınıfların ortak kullandıkları bölümler varsa bunlar, ortak başka bir sınıfa alınabilir.
Kullanılabilecek refactoring teknikleri: Rename Method, Move Method, Add Parameter, Parameterize Method, Extract Superclass.
Daha az ve temiz, kolay okunabilir, kolay bakım yapılabilir, geliştirme maliyeti azalmış kod altyapısı sağlar.
Bazı sınıflar farklı kütüphanelerde olabilir ve bunlar kendi içinde geliştirilir ve versiyonlanırlar. Bu gibi durumlarda, birleştirmek, silmek ve taşımak mantıksızdır.
Bir kod bloğunu değiştirirken, kendinizi silsile halinde başka kodları da değiştirirken bulabilirsiniz. Örneğin; bir nesne değiştiği zaman, onun listelendiği, sıralandığı, eklenip, düzenlendiği gibi ilişkili yerlerinde değişmesi gerekebilir.
Kötü tasarım ve kötü kod altyapısından veya birbirine aşırı bağımlı kod parçalarının olmasından kaynaklanan bir durum olabilir.
Sınıfların davranışlarını bölmek veya duruma göre eğer aynı işi yapıyorlarsa birleştirmek.
Kullanılabilecek refactoring teknikleri: Extract Class, Extract Superclass, Extract Subclass.
Daha iyi kod organizasyonu, kod tekrarının azaltılması, bakımın kolaylaşması.
Kodun çok daha derinlerindeki, büyük bir sıkıntının, bize görünen küçük kısmı. Buzdağının görünen yüzü de diyebiliriz. Küçük gibi görünen bu kötü tasarımı düzeltmeye kalktığında çok farklı yerlerin etkilendiğini görürüz.
Genelde sorumlulukların iyi ayrılamamasından (Seperation of Concerns ve Single Responsibility), tasarım desenlerinin acemice ve kötü uygulanmasından kaynaklanabilir.
Çözüm basit, nesneleri ve davranışları kuralına uygun olarak ayırmak, gereksiz nesneleri ve davranışları silmek.
Uygulanabilecek refactoring teknikleri: Move Method, Move Field, Inline Class.
Daha okunabilir, daha kolay bakım yapılabilir, kod tekrarlarının az olduğu, daha iyi bir kod organizasyonu sağlar.
Hiyerarşik bir sınıf grubunun, bu grupla ilişkili ve paralelinde başka bir sınıf hiyerarşisinin olması. Mesela Vehicle
sınıfından türeyen, Car
ve Truck
nesnelerinin, paralelinde bu hiyerarşiyle alakalı, XmlFormatter
sınıfından türeyen CarXmlFormatter
ve TruckXmlFormatter
sınıflarının olması durumu. İki ağaç da birbirine paralel olarak dallanıyor.
Okunamayacak kadar kötü tasarımlı kod altyapısı ile çalışmaktan dolayı, kod/tasarım tam olarak anlaşılmamış olabilir. Bir diğer sabep ise tecrübesizlikten kaynaklanmış olabilir.
Bu kötü tasarımı çözmenin birçok yöntemi var. Bu yöntemlerin hepsi farklı bir tasarım desenini içeriyor. Örneğin, en tepedeki iki sınıfı da kalıtım alan yeni bir hiyerarşi veya tepedeki sınıfların birleşiminden yeni bir hiyerarşi oluşturulabilir. Çok farklı tasarımlarla çözülebilir ve bu tasarımlar da, tasarım desenleri konusuna girmektedir, ama temelde kullanılacak refactoring teknikleri bellidir.
Kullanılabilecek refactoring teknikleri: Move Method, Move Field.
Kod tekrarının engellenerek, daha iyi bir kod altyapısının oluşmasının sağlanması. Daha iyi tasarıma sahip kodun, bakımının kolaylaşması.
Paralel hiyerarşi, her ne kadar değişiklik maliyeti yüksek kod üretse de, dengeli kullanımda aslında daha okunabilir bir kod yapısı sunar. Bazen bu tasarımdan kurtulmak için, çok daha karışık ve gereksiz tasarımlara doğru kayabiliriz. Paralel hiyerarşi çok fazla dallanmadığı sürece göz ardı edilebilir.
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 olarak yazı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çin de, 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ıyla bir sınıf anlaşılmıyorsa ve istekleri yeterince 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ıyla, 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, veriyle 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ıyla 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şkiliyse, 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ıfla ü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. Belki de 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 sebep de, 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.
- Tersi: Inline Method
- Benzer: Move Method
- Yardımcı olduğu diğer teknikler: Introduce Parameter Object, Form Template Method, Parameterize Method
- Düzeltiği kötü tasarımlar: Duplicate Code, Long Method, Feature Envy, Switch Statements, Message Chains, Comments, Data Class
Gruplanabilecek kod bloklarının olması.
C#
public class ExtractMethodBad
{
public void DoSomeThing()
{
// diğer kod blokları...
// kullanıcı bilgilerini ekrana bas
Console.WriteLine("Kullanıcı adı: ali_veli");
Console.WriteLine("E-posta: [email protected]");
}
}
Go
package main
func DoSomeThing() {
// diğer kod blokları...
// kullanıcı bilgilerini ekrana bas
fmt.Println("Kullanıcı adı: ali_veli")
fmt.Println("E-posta: [email protected]")
}
Bu kodu ayrı bir yeni metoda taşıyın ve eski kodun yerine bu metodu çağırın.
C#
public class ExtractMethodGood
{
public void DoSomeThing()
{
// diğer kod blokları...
WriteUserInformationToConsole();
}
// kullanıcı bilgilerini ekrana bas
private static void WriteUserInformationToConsole()
{
Console.WriteLine("Kullanıcı adı: ali_veli");
Console.WriteLine("E-posta: [email protected]");
}
}
Go
package main
func DoSomeThing() {
// diğer kod blokları...
writeUserInformationToConsole()
}
// kullanıcı bilgilerini ekrana bas
func writeUserInformationToConsole() {
fmt.Println("Kullanıcı adı: ali_veli")
fmt.Println("E-posta: [email protected]")
}
- Bir metodda ne kadar çok satır bulunursa, metodun ne yaptığını bulmak o kadar zor olur.
- Gruplanan kodlar, ihtiyaç halinde başka yerden de çağrılabilir.
- Sonraki başka bir refactoring tekniği için de bir adım olabilir.
- Daha okunabilir kod. Metot ismi içindeki, gruplanmış kod satırlarının ne yaptığına dair fikir verir.
- Daha az kod tekrarı. Kodun yeniden kullanılabilirliği artar. Tüm satırları tekrar yazmaktansa, metot çağrısı yapılır.
- Bağımsız kod bölümlerini birbirinden izole eder, bu da daha az hata demektir. Çünkü kod bloğunun bakımı tamamen kendi sınırları içinde yapılır.
- Tersi: Extract Method
- Düzeltiği kötü tasarımlar: Speculative Generality
Bir metodun gövdesinin, metodun kendisinden daha açık olması.
C#
public class InlineMethodBad
{
public int GetMultiplier(int number)
{
return IfNumberPositive(number) ? 1 : -1;
}
// bu metoda gerek yok
private static bool IfNumberPositive(int number)
{
return number >= 0;
}
}
Go
package main
func GetMultiplier(number int) int {
if ifNumberPositive(number) {
return 1
} else {
return -1
}
}
// bu metoda gerek yok
func ifNumberPositive(number int) bool {
return number >= 0
}
Metot çağrısını, metodun içeriğiyle değiştirin ve metodun kendisini silin.
C#
public class InlineMethodGood
{
public int GetMultiplier(int number)
{
return number >= 0 ? 1 : -1;
}
}
Go
package main
func GetMultiplier(number int) int {
if number >= 0 {
return 1
} else {
return -1
}
}
Bir metot basitçe başka bir metodu çağırır ve bunda aslında bir problem yoktur. Problem, bu şekilde gereksiz metotların artmasıdır. Böyle çok fazla metot olunca, kafa karıştırıcı kodlar ortaya çıkar.
Gereksiz metotların sayısını en aza indirerek, kodu daha basit hale getiririz.
- Tersi: Inline Temp
- Benzer: Extract Method
- Düzeltiği kötü tasarımlar: Comments
Anlaşılması zor koşulların/ifadelerin olması.
C#
public class ExtractVariableBad
{
public double GetTotalPrice()
{
var order = new Order();// get order
return order.Quantity * order.ItemPrice -
Math.Max(0, order.Quantity - 500) * order.ItemPrice * 0.05 +
Math.Min(order.Quantity * order.ItemPrice * 0.1, 100);
}
}
Go
package main
func GetTotalPrice() float64 {
var order = Order{} // get order
return order.Quantity * order.ItemPrice -
math.Max(0, order.Quantity - 500) * order.ItemPrice * 0.05 +
math.Min(order.Quantity * order.ItemPrice * 0.1, 100)
}
İfadenin/koşulların veya bölümlerinin sonucunu kendi kendini açıklayıcı olan ayrı değişkenlere taşıyın.
C#
public class ExtractVariableGood
{
public double GetTotalPrice()
{
var order = new Order();// get order
var basePrice = order.Quantity * order.ItemPrice;
var quantityDiscount = Math.Max(0, order.Quantity - 500) * order.ItemPrice * 0.05;
var shipping = Math.Min(basePrice * 0.1, 100);
return basePrice - quantityDiscount + shipping;
}
}
Go
package main
func GetTotalPrice() float64 {
var order = Order{} // get order
var basePrice = order.Quantity * order.ItemPrice
var quantityDiscount = math.Max(0, order.Quantity - 500) * order.ItemPrice * 0.05
var shipping = math.Min(basePrice * 0.1, 100)
return basePrice - quantityDiscount + shipping
}
Kod içerisindeki uzun ifadeler kodun anlaşılmasını zorlaştırır. Kodu karmaşıklaştırır ve gereksiz uzatır. Kodu daha anlaşılır, daha kısa yapmak ve Extract Metot için bir adım oluşturmak.
Daha okunabilir ve anlaşılabilir kod. İfadenin ne anlama geldiğini ismi ile anlatan değişkenler.
Çok fazla değişken oluşmasına sebep olabilir. Ama kodun daha okunabilir olması bu yan etkiyi dengeler.
NOT: Yararlanılan kaynaklar sürekli eklenecek. Bu döküman anlatım tarzı olarak https://refactoring.guru/ sitesindekine benzer bir yapı kullanıyor. Ana kaynak olarak bu siteden yararlanılıyor. Bu sitenin sahibi Alexander Shvets, içeriğin üzerine bina ettiği başka bir içeriği paralı olarak sattığı için, bedava olan kısmın birebir çevirisinin MIT lisans altında GitHub da olmasını istemiyor. Dolayısıyla bu dökümanın içeriği olabildiğince özgün, araştırılmış, tecrübe ile desteklenmiş, farklı kaynaklardan düzenlenmiş içeriklerden oluşmaktadır.
- 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
- https://refactoring.com/catalog/extractVariable.html
- https://dzone.com/articles/code-smell-shot-surgery
- https://stackoverflow.com/questions/696350/avoiding-parallel-inheritance-hierarchies