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.
Kod bloklarını açıklamak için yazılmış yorumların olması. Bir söz var; kod yazmak espri yapmak gibidir, eğer açıklamak zorunda kalıyorsan, kötü yazılmıştır. Yorum satırları, kod içerisine yazılmış döküman gibidir. Kod her değiştiğinde yorum da değişmelidir. Ama çoğu zaman kimse kod değiştikçe, yorum satırlarını da güncellemez. Kod bir şey yapıyorken, yorum başka şey anlatıyor olabilir.
İyi tasarlanmamış, kötü isimlendirilmiş, kod tekrarının çok olduğu, okunması zor olan kodlar için, yazılımcı yorum yazma yoluna gidebilir. Oysaki en iyi yorum, isimlendirmeler ve iyi tasarımdır. Yazılımcı okunmayan kodu yorumlarla anlatmaya çalışmıştır.
Yorum satırlarından kurtulmak için, kodu parçalayarak, yorum gereken kod bloklarını ayrı yerlere taşıyıp, güzel bir isimlendirme yapmak gereker. Açıklayıcı parçalara ayrılan ve açıklayıcı isimlendirmeler yapılan kod blokları hala yorum satırlarına ihtiyaç duyuyorsa, yorum yerine, "assertion" ifadeleri yazılabilir.
Kullanılabilecek refactoring teknikleri: Extract Variable, Extract Method, Rename Method, Introduce Assertion.
Kodu okumak ve anlamak daha kolaylaşır. Koda bakınca, içini okumadan, daha net bir şekilde ne yaptığını anlayabilir. Dolayısıyla daha kolay bakım yapılabilir hale gelir.
Bazı durumlarda yorum satırları yazılabilir. Mesela; bir metot içinde yapılan hesaplamanın neden bu şekilde yapıldığı, çıktısında ne beklendiği, örnek girdi gibi kısımlar için yorum yazılabilir. Bazende karmaşık bir algoritmada, tüm refactoring işlemlerinden sonra bile hala anlaşılmayan noktalar kalıyorsa, yine yorum satırları eklenebilir.
Gereksiz tekrar eden kodların olması.
Kod tekrarı yapmak çok kolaydır. Yazılımcının kod okumayı sevmediğini belirtmiştik. Kod okunmadığı zaman, gerekli dökümanlarının olmaması durumları da eklenince, belkide kodda var olan fonksiyonellikler, bizim haberimizin olmamasından dolayı tekrardan yazılabilir.
Kod tekrarları, yazılımın farklı yerlerinde çalışan ve çok birbirleri ile kesişen işler olmadığında veya yazılımcıların birbirinden haberi olmamasından dolayı, iki yazılımcı aynı işi yapan kodları aynı anda yazabilir. Bazende, kodun bazı kısımları, isimleri falan farklı olduğu halde aynı işi yaptığı durumlar da olabilir. Bunun sebebi de, yine kötü tasarımdan dolayı var olan koddan bihaber olmak ve yine aynı işi yapan kodları yazmak.
Bazende kod tekrarı bilerek yapılabilir. Kodun çalışan kısımlarını, o an günü kurtarmak için yazılımcı kopyala-yapıştır ile alıp kullanmak isteyebilir. Bu düşünce bazen acemilikten, bazen de tembellikten olabilir.
Temel olarak bu sorunun çözümü ya parçalamaktır ya da tam tersi birleştirmektir. Bazen de tekrar eden kodlardan/algoritmalardan en iyisini seçip, diğerini silmekdir.
Uygulanabilecek refactoring teknikleri: Extract Method, Pull Up Field, Pull Up Constructor Body, Form Template Method, Substitute Algorithm, Extract Superclass, Extract Class, Consolidate Conditional Expression, Consolidate Duplicate Conditional Fragments.
Kodun kısalması, sadeleşmesi ve daha kolay anlaşılması sağlanır. Daha iyi anlaşılan kod, daha rahat bakım yapılabilir.
Kodun, kod tekrarı olan durumlarda, bazen daha iyi anlaşıldığı nadir durumlar olabilir. Bu durumda kodların birleştirilmesi, bazen kodun anlaşılmasını engelleyebilir.
Neredeyse hiçbir şey yapmayan, gerekliliğinin sorgulanmasına sebep olan sınıfların olması.
Sınıf zaman içerisinde değişikliklere uğrar. İçerisindeki işlevlerin taşınması, rafactor edilmesi gibi nedenlerden dolayı, sınıfın içi boşalabilir veya içerisindeki kodlar artık çok da fazla iş yapmaz duruma gelebilir. Ya da daha sonra yapılacak bir özelliklik için, baştan tasarlanmış olabilir.
Sınıfın işlevleri başka sınıfa taşınabilir. Bu sınıflar aynı hiyerarşide veya üst hiyerarşideki sınıflar olabilir.
Uygulanabilecek refactoring teknikleri: Inline Class, Collapse Hierarchy.
Gereksiz kodların silinmesi ile kod daha kısa, sade ve anlaşılabilir olur. Kodun bakımı daha da kolaylaşır.
Gelecekteki gelişmelere yönelik, önceden fikir vermesi, yol haritası çizmesi bakımından, bu tarz gereksiz sınıflarında oluşturulabileceğinden bahsetmiştik. Kodun karmaşıklığını artırmadan, sadeliği ve okunabilirliği bozmadan, dengeli bir kullanım durumunda göz ardı edilebilir.
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ı?
Artık kullanılmayan kod parçası (alan, parametre, değişken, metot, sınıf).
Yeni geliştirmeler, eklemeler, refactoring işlemlerinden sonra eski kodların, işe yarayıp yaramadığını anlamak ve bunlara aksiyon almak için çoğu zaman kimse vakit ayırmak istemez. Üstelik bir de kötü ve okunması zor bir kod altyapısı ile çalışıyorken, bu işe yaramaz kod parçalarının bulunması da ayrıca zordur. Bundan dolayıda, işe yaramaz kod blokları oluşabilir.
İşe yaramazyan kodları bulmanın en iyi yolu, iyi bir IDE veya IDE ile uyumlu, kod eklentilerin kullanılmasıdır. Bu en etkili ve hızlı yöntemdir. Tabiki işe yaramayan kod parçaları bulununca yapılacak ilk şey silmek veya taşımak.
Uygulanabilecek refactoring yöntemleri: Inline Class, Collapse Hierarchy, Remove Parameter.
Gereksiz kodların silinmesi daha sade, kısa ve okunabilir kod sağlar. Böylece bakım maliyeti düşer.
Gelecekte kullanılacak diye eklenen ama hiç kullanılmayan kod parçalarının olması.
Gelecekte yapılacak bir özellik için, belki fikir de versin diye önceden eklemek isteyebiliriz. Ama bu eklenen kodlar, kullanılmadı için sistem hakkında yanlış bilgi veriyor olabilirler. Başka bir yazılımcı bunun varlığını sorgulayabilir ve kodun bakımını zorlaştırır.
Kullanılmayan kodların silinmesi veya taşınması.
Kullanılabilecek refactoring teknikleri: Collapse Hierarchy, Inline Class, Inline Method, Remove Parameter.
Daha kısa, sade ve temiz kod. Dolayısıyla bakımı daha kolay kod.
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 kullanmak 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 sınıf içindeki metotun, başka bir sınıfın verisine, bulunduğu sınıfınkinde fazla erişmesi. Örneğin; Customer
sınıfındaki, getPhoneNumber
adındaki metot, Phone
adındaki sınıfın "(" + phone.getAreaCode() + ") " + phone.getPrefix() + "-" + phone.getNumber();
şeklinde metotlarını çağırıyorsa.
Bazen sınıflar parçalanırken, alanların veri sınıfına taşınamasından sonra, o alanlarla işlem yapan metotların, diğer sınıfta kalmasından dolayı ortaya çıkar.
Çözüm; alanları taşıdığımızda, bu alanları kullanan metotları da birlikte taşımamız gerekir.
Kullanılabilecek refactoring teknikleri: Move Method, Extract Method.
Metotlar ve bu metotların kullandığı alanlar aynı yerde olursa, kod merkezi bir yerde olur ve merkezi bir yerden yönetilir. Böylece kod tekrarının önüne geçilebilir ve daha iyi bir kod organizsyonu ile beraber daha kolay bir bakım maliyeti sağlanır.
Bazen, davranışın veriden ayrı tutulması ile, davranışın bağımsız olması ve daha dinamik bir şekilde yönetilmesi, değiştirilmesi sağlanabilir. Bu gibi durumlarda göz ardı edilebilir.
Sınıfların, birbirlerinin alanlarını ve metotlarını çok kullanması ve böylece birbirlerine çok bağımlı olmaları.
Yanlış tasarım veya refactoring sırasında, parça parça taşınan kodlardan dolayı, bazı parçaların diğer sınıfta kalmasından kaynaklanabilir.
Çözüm kodların uygun şekilde taşınması varsa gereksiz kod blokları, bunların silinmesi.
Uygulanabilecek refactoring teknikleri: Move Method, Move Field, Extract Class, Hide Delegate, Change Bidirectional Association to Unidirectional, Replace Delegation with Inheritance.
Sade, kısa ve kolay anlaşılır kod. Dolayısıyla, daha sağlam kod altyapısı ve daha az bakım maliyeti.
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