Refactoring — 1 ~ Nedir, Neden Uygulanır ?

Kaan Sönmezöz
Trendyol Tech
5 min readApr 22, 2020

--

Refactoring yapmaktaki bütün amaç, daha hızlı bir şekilde programlayarak daha az efor ile daha fazla değer üretmek — Martin Fowler

Refactoring ile alakalı yazı dizisinin ilki olan bu makalede öncelikle refactoring ne olduğundan, neden ve ne zaman yapıldığından bahsedeceğiz. Ardından da küçük bir kodu refactor edeceğiz. Bu yazının temel amacı aslında refactoring ne olduğu ve uygularken nelere dikkat ettiğimiz hakkında ufak bir giriş yapmak. Refactoring ile ilgili detaylı bilgilere ilerleyen yazılarda değiniyor olacağız.

Refactoring Nedir, Neden Uygulanır ?

Refactoring dediğimiz kavramı, mevcut kodun herhangi bir özellik, davranış kaybetmeden tekrardan düzenleyerek eskisinden daha iyi bir hale getirmek olarak açıklayabiliriz.

Burada bir parça açık uçlu bir ifade olan “daha iyi” hemen göze çarpıyor. “Daha iyi” ifadesi aslında bizim halihazırda çalışan kodu kodu neden tekrardan düzenlediğimizi açıklamaktadır.

Software was invented to be “soft”. It was intended to be a way to easily change the behavior of machines. If we’d wanted the behavior of machines to be hard to change we would have called it hardware.

— Clean Architecture, Robert C. Martin

Yazılım; kolayca değişebilmelidir, değişimi zorlaştırmamalıdır. Ne de olsa yazılım (software) dediğimiz “soft” olmalıdır. Yeni özelliklere, davranışlara kolaylıkla adapte olabilmesi gerekmektedir. Eğer değişimi zor olan ya da değiştirilmemesi gereken bir kavram olsaydı yazılım onu “hardware” derdik. Dolayısıyla bizim yazılımı “soft” tutmamız gerekir ve bunu refactoring ile sağlarız. Bunun dışında aynı zamanda kodu daha anlaşılabilir bir hale getirmek için de refactoring uygulanır. Ne de olsa anlayamadığımız bir kodu değiştiremeyiz de. O yüzden bizim refactoring yaparken ilk önce kodu daha rahat anlayabileceğimiz bir hale ardından da kolayca değiştirilebilir bir hale getirmemiz gerekir.

Refactoring Ne Zaman Yapılır ?

The best time to refactor is just before I need to add a new feature to the codebase. As I do this, I look at the existing code and often, see that if it were structured a little differently my work would be much easier.

— Refactoring 2nd Edition, Martin Fowler

Genellikle refactoring yapma ihtiyacını mevcut kodda herhangi bir yeri değiştirmeye çalıştığımızda hissederiz. Bunun sebeplerini :

  • Kodun yeterince anlaşılır olmaması
  • Takımın belirlemiş olduğu standartlara uymaması
  • Kullanılan dilin standartlarına uymaması
  • Mevcut kodun yeni bir özellik geliştirmeyi zorlaştırması, değişime esnek olmaması.

şeklinde daha da uzatabiliriz. Ama temelde bunları iki şekilde sınıflandırmanın daha doğru olduğunu düşünüyorum. Özellik geliştirilmesine engel olanlar ve mevcut kodun kalitesini düşürenler (mevcut kodu çirkinleştirenler).

for each desired change, make the change easy (warning: this may be hard), then make the easy change — Kent Beck

To add new features, we should be mostly adding new code. But good developers know that, often, the fastest way to add a new feature is to change the code to make it easy to add. — Martin Fowler

Yeni bir özellik geliştirirken ilk olarak özellik geliştirilmesine engel olan temeldeki iki tane sebebi - kodun anlaşılır olmaması, kodun yeni bir özellik geliştirmeyi zorlaştırması - refactor ile çözerek mevcut kodun daha kolay değiştirilmesini sağlayabiliriz. Ardından da özellik geliştirilmesine başlayabiliriz.

You have to refactor when you run into ugly code — but excellent code needs plenty of refactoring too.

— Refactoring 2nd Edition, Martin Fowler

Refactoring’in ne zaman uygulanması noktasında değinmemiz gereken bir diğer nokta da bir kodun kalitesini düşüren, kodu çirkin bir hale getiren nedenlere verdiğimiz “code smell” kavramı. Bu konuyu ilerleyen yazılarda daha detaylı bir şekilde inceleyeceğiz ancak birkaçından bahsetmek gerekirsek:

  • Kod tekrarı
  • Uzun fonksiyonlar
  • Uzun sınıflar
  • Anlaşılmaz isimlendirmeler

gibi sıkça karşılaşılan bu maddeleri sayabiliriz (listenin tam hali için). Bunları kodda tespit edip refactor etmemiz kodumuzun kalitesini arttıracaktır.

Küçük Bir Örnek

Bu yazıda basit bir methodu inceleyerek onu refactor edeceğiz. Örnekte ele alınan methodun yaptığı işi basitçe açıklayacak olursak; verilen kullanıcı için o kullanıcıya ait seyahatleri dönmek.

Bu kod, ilk bakışta hemen anlaşılmayabilir. Bunun sebepleri kod içerisindeki düzensizlikler, girintiler, kodun akışını bozan ifadeler olduğunu söyleyebiliriz.

Kodu ilk bakışta kolay bir şekilde anlayamamak refactoring yapılması gerektiği noktasında en önemli sinyallerden biridir. Methodun bu sinyali vermesine neden olan durumları irdeleyelim.

  • Kodun ne iş yaptığına bakmadan göze çarpan bir bozukluk var. Kod, girintiler yüzünden kötü formatlanmış. Ve bu durum kodu okumayı zorlaştırıyor.
  • Kodu biraz okumaya başlayınca ilk dikkatimizi çeken if(loggedUser != null) kontrolü oluyor. Merakımıza yenik düşüp if’in içinde göz gezdirmeye başlıyoruz ama bir yandan da aklımız else kolunda. if bloğu ile işimiz bittiğinde bir bakıyoruz ki else kolunda yapılan tek işlem bir exceptionın fırlatılması. Kodda 17. satıra gelene kadar boşu boşuna beklemişiz. Kafamızı yormuşuz. Oradaki exception fırlatılmasıyla methodtan çıkıyormuşuz zaten ve akışa devam etmiyormuşuz. Ama biz 17. satıra gelene kadar else bloğunun akışa olan etkisini düşünmeden edemedik. Ve bu if kontrolü aslında bu methodun çalışması için gerekli olan bir ön koşulu kontrolü ediyormuş ama biz bunu ancak methodun sonuna geldiğimiz zaman anladık, o satıra kadar orada yapılacak işlemlerin neler olabileceğini, kodu nasıl etkileyebileceğini, business açısından değerini düşündük. Bu durum aynı zamanda kodun akışını bozuyor. Çünkü methodun sonlarına geldiğimiz zaman bizim odaklanmak istediğimiz nokta kodun ne iş yaptığı, ne döndüğü. Kodun çalışması için gereken ön koşullarının sağlanıp sağlanmaması değil. Bu tarz ön koşullar methodun en başında yapılmalıdır. Bu sayede kodu okuyan kişi en başta hangi durumda methodun çalışmayacağını bilir ve kodun devamını okurken bir daha o kısımla ilgili bir şey düşünmez, akışa odaklanır. Çünkü bu akışı bozabilecek kısım kodun en başına alınmıştır. Validasyon işlemlerinin en başta yapılmasının bir diğer mantıklı sebebi ise, kod patlayana kadar yapılan işlem sayısını minimum tutmaktır. Çünkü kod patladığında o satıra kadar yapılan işlemler çoğu zaman bir anlam ifade etmemektedir.
  • if(friend.equals(loggedUser)) doğru geldiği zaman, isFriend = true oluyor ve break ile döngüden çıkılıyor. Kodun devamında ise gene bir kontrol yapılarak demin isFriend true ise başka bir method çağırılıp atama yapılıyor. En sonunda da tripList dönülüyor. Madem tripList’e atama sadece isFriend true iken yapılıyor ve isFriend ‘in değerinin değişebildiği tek yer var. O zaman neden orada tripList ‘e atama yapılmıyor ya da direkt orada return edilmiyor atanacak değer -ki bu sayede bir sonraki if kontrolünden de kurtulmuş oluyoruz? Bu durum da koddaki bir diğer problem.

Koddaki problemlerin üzerinden geçtiğimize göre artık refactoring işlemine başlayabiliriz.

Refactor işlemine başlamadan önce, ilgili kod parçasının unit testi olup olmadığını kontrol edin. Eğer yoksa başlamadan önce gerekli testleri yazın. Bu sayede kodu değiştirdikten sonra hala aynı davranışa sahip olup olmadığını kontrol edebiliriz. Bu da bize bir şeyleri bozup bozmadığımız hakkında fikir verecektir.

İlk yapacağımız refactoring, kodun girintilerinden kurtulmak. Bu girintiye neden olan validasyon kontrolüdür. Ayrıca validasyon kontrollerinin şartları sağlanmadığında alınacak aksiyonların da olabildiğince başlarda olması gerektiğini, aksi taktirde kod akışının bozulduğundan bahsetmiştik. throw new UserNotLoggedInException() statementini yukarıya taşıyabilmek için gerekli işlemler yapıldığında aşağıdaki kodu elde ederiz. Exception fırlatılması akışı kesip metottan çıkılmasını sağladığı için if bloğundan sonra else bloğuna da gerek kalmamış oluyor.

Validasyon İşlemlerinin Kodda Yukarılara Taşınması

Kodun halihazırda anlaşılır olduğunu düşünebilirsiniz. Haksız da sayılmazsınız ancak bu methodu daha da temiz ve küçük bir hale getirebiliriz. Bunun için for-each’i kendi fonksiyonuna çıkartabiliriz.

Döngünün Fonksiyona Taşınması

for-each döngüsünün yaptığı iş Java’dakicontains(Object o) methoduna denk düşmektedir. İlgili kod parçasını da contains(Object o) ile değiştirdiğimizde aşağıdaki kodu elde ederiz.

Built-in Method ile Değiştirilmesi

isFriend ve tripList değişkenlerini kaldırabiliriz. Bu değişkenler methodu olması gerekenden daha uzun hale getiriyorlar. Bu değişkenlerin tek özellikleri değer tutmak. Bu tuttukları değerler herhangi bir şekilde değiştirilmiyor. Bu tarz değişkenler, kodu daha anlaşılır bir hale getirdiği zaman kod içerisinde önem kazanmaktadır.

if(isFriendOf(user, loggedUser)) { 
return TripDAO.findTripsByUser(user);
}
return new ArrayList<Trip>();

Bu ifadeyi conditional (ternary) operatörüyle kısaltabiliriz.

return isFriendOf(user,loggedUser) ? 
TripDAO.findTripsByUser(user): new ArrayList<Trip>();

Hangisinin daha okunaklı olduğu size kalmış 😛 Kodun son halini aşağıda bulabilirsiniz. Happy Coding ! 😊

Refactor İşlemleri Tamamlanmış Kod

--

--

Kaan Sönmezöz
Trendyol Tech

www.github.com/kaansonmezoz Computer Engineering student @YTU, software developer @Trendyol.com. Writes stories to take notes