Programlamanın Temelleri

Kodunuz Neden Kokuyor? En Yaygın 7 Code Smell ve Refactoring Çözümleri

Okuma Süresi: 17 dakika

Kodun dünyasında bazen her şey yolunda görünebilir; testler geçer, uygulama çalışır ve kullanıcılar mutludur. Ancak projenin derinliklerine indiğinizde, burnunuza gelen o rahatsız edici kokuyu alırsınız: Code Smell (Kod Kokusu).

Bu “kokular” teknik bir hata ya da bug değildir; aksine, sistemin derinlerinde bir yerlerde tasarımın çürümeye başladığını, kodun karmaşıklaştığını ve gelecekte başınıza büyük dertler açacağını fısıldayan semptomlardır. Tıpkı bir yemeğin bozulmadan önce koku vermesi gibi, kodunuz da bakımının zorlaştığını bu işaretlerle belli eder. Profesyonel bir geliştiricinin en büyük yeteneği ise bu kokuları erkenden teşhis edip, doğru Code Refactoring teknikleriyle sistemi yeniden ferahlatmaktır.

Bu yazımızda, geliştiricilerin en sık karşılaştığı “kod kokularını” masaya yatırıyor ve her birini hangi cerrahi müdahalelerle (refactoring teknikleri) ortadan kaldırabileceğimizi inceliyoruz.

Code Refactoring Teknikleri

1. Long Method (Uzun Fonksiyon)

Long Method (Uzun Metot), bir fonksiyonun veya metodun artık tek bir işi yapmaktan çıkıp, adeta bir “İsviçre çakısı” gibi onlarca farklı sorumluluğu üstlendiği durumdur. Kodun okunabilirliğini ve bakımını zorlaştıran bu koku, genellikle metodun satır sayısı arttıkça kendini belli eder. Bir metodun çok uzun olması, içinde birden fazla mantıksal katmanı barındırdığının, dolayısıyla test edilmesinin ve hata ayıklanmasının (debugging) güçleştiğinin en somut işaretidir. Geliştiriciler genellikle “bir özellik daha ekleyeyim” diyerek mevcut metodu şişirirler; ancak bu durum, kodun yeniden kullanılabilirliğini (reusability) öldürür.

Temel problemler:

  • Uzun satırlar
  • Birden fazla iş yapma
  • Code review esnasında bile sürekli kafa karışıklığı yaratması

Bu kokuyu gidermek için en sık başvurulan tedavi yöntemi Extract Method (Metot Çıkar) tekniğidir. Bu yöntemde, uzun metodun içindeki anlamlı ve bağımsız iş parçacıkları belirlenir, bu parçalar yeni ve isimlendirilmiş küçük metotlara taşınır. Böylece ana metot, sadece bu küçük parçaları yöneten bir orkestra şefi gibi çalışarak hem daha anlaşılır hale gelir hem de “Tek Sorumluluk Prensibi”ne (Single Responsibility Principle) sadık kalınmış olur.

2. Duplicate Code (Kopya Kod)

yazılım dünyasında “kendini tekrar etme” (Don’t Repeat Yourself – DRY) prensibinin en büyük düşmanıdır. Aynı veya çok benzer kod bloklarının projenin farklı noktalarında boy göstermesiyle oluşan bu koku, genellikle “kopyala-yapıştır” alışkanlığının ya da eksik soyutlamanın bir sonucudur. İlk bakışta zararsız gibi görünse de, sistemde bir değişiklik yapılması gerektiğinde her kopyanın tek tek bulunup güncellenmesi zorunluluğu, hem hata riskini artırır hem de geliştirme sürecini bir kâbusa çevirir. Bir kopyayı güncelleyip diğerini unutmak, yazılımda tutarsızlıklara ve gizli bug’lara davetiye çıkarır.

Temel problemler:

  • Aynı amaç için yazılan kodların farklı yerlerde tekrar etmesi
  • Küçük müdahalelerde bile birçok yerde değişiklik yapma ihtiyacı

Bu kokuyu ortadan kaldırmanın en etkili yolu, ortak yapıyı tek bir merkezde toplamaktır. Eğer aynı kod aynı sınıf içindeki farklı metotlarda bulunuyorsa Extract Method (Metot Çıkar) ile ortak bir yapı oluşturulur. Eğer bu benzerlik, birbirine komşu sınıflar arasındaysa, ortak kod Pull Up Field/Method (Yukarı Çekme) tekniğiyle bir üst sınıfa (parent class) taşınır. Böylece kodun yönetimi tek bir noktaya iner, sistem daha esnek ve bakımı kolay bir hale gelir.

3. Large Class (God Object)

Large Class, ya da daha dramatik ama yerinde bir tabirle God Object (Tanrı Nesnesi), bir sınıfın sınırlarını aşarak her şeyi yapmaya, her veriyi tutmaya ve sistemdeki her süreçten haberdar olmaya çalıştığı bir koddur. Bu tip sınıflar genellikle binlerce satıra ulaşır, çok fazla değişken barındırır ve Single Responsibility (Tek Sorumluluk) prensibini tamamen yerle bir eder. Bir geliştirici olarak bu sınıfa dokunmak, devasa bir Jenga kulesinden bir parça çekmeye benzer; yapacağınız en ufak bir değişiklik, sınıfın hiç alakasız görünen bir başka köşesinde beklenmedik hatalara yol açabilir.

Temel problemler:

  • Tek bir class’ın her şeyi yapma çabası
  • Uzun satırlı class’lar
  • Hem DB işlemi, hem UI, hem business logic hepsi bir arada olması

Bu karmaşayı çözmek ve “Tanrı Nesnesi”ni emekli etmek için en temel tedavi yöntemleri şunlardır:

  • Extract Class (Sınıf Çıkar): Eğer bir sınıf, kendi içinde mantıksal olarak ayrıştırılabilecek farklı görevleri (örneğin hem kullanıcı bilgilerini tutuyor hem de fatura hesaplıyorsa) barındırıyorsa, bu görevler yeni sınıflara taşınır. Fatura hesaplama mantığı BillingService gibi yeni bir sınıfa aktarılır.
  • Extract Subclass (Alt Sınıf Çıkar): Sınıf içindeki bazı özellikler ve metotlar sadece belirli durumlarda kullanılıyorsa, bu özellikler bir alt sınıfa taşınarak ana sınıfın yükü hafifletilir.
  • Extract Interface (Arayüz Çıkar): Eğer farklı istemciler bu devasa sınıfın sadece belirli bölümlerini kullanıyorsa, arayüzler yardımıyla sınıfın sorumlulukları rollerine göre parçalanır.

4. Long Parameter List

Long Parameter List (Uzun Parametre Listesi), bir metodun veya fonksiyonun çalışabilmesi için dışarıdan çok fazla veriye (genellikle 3-4 taneden fazla) ihtiyaç duyduğu durumdur. Bu koku, genellikle metodun kendi başına yeterli olmadığını veya çok fazla farklı işi tek bir potada eritmeye çalıştığını gösterir. Parametre listesi uzadıkça, o metodu çağırmak kafa karıştırıcı bir hal alır, hata yapma payı artar ve kodun test edilmesi zorlaşır. Ayrıca, parametrelerin sırasını karıştırmak gibi “sessiz ama ölümcül” bug’lara davetiye çıkarır.

Temel problemler:

  • Fonksiyon çağırırken uzun parametre tanımlama ihtiyacı
  • Anlaşılması zor ve hata yapmaya açık yapılar

Bu hantal yapıdan kurtulmak ve daha şık bir kod mimarisine geçmek için şu refactoring teknikleri hayat kurtarır:

  • Replace Parameter with Method Call (Parametreyi Metot Çağrısıyla Değiştir): Eğer bir parametre, zaten sınıftaki başka bir metot aracılığıyla elde edilebiliyorsa, onu dışarıdan göndermek yerine metodun içinde hesaplayın. Bu, gereksiz veri trafiğini azaltır.
  • Introduce Parameter Object (Parametre Nesnesi Tanımla): Birbiriyle ilişkili parametreleri (örneğin: start_date, end_date, timezone) tek bir nesne (örneğin: DateRange sınıfı) altında toplayın. Bu sayede 3-4 parametre yerine tek bir nesne göndererek metodu sadeleştirirsiniz.
  • Preserve Whole Object (Nesneyi Bütün Olarak Koru): Bir nesnenin içinden 5 farklı alanı tek tek parametre olarak göndermek yerine, nesnenin kendisini metoda gönderin. Metot, ihtiyacı olan veriyi nesne üzerinden çeksin.

5. Conditional Complexity (if-else cehennemi)

Conditional Complexity, yazılım dünyasında daha popüler adıyla “if-else cehennemi”, bir metodun içinde dallanıp budaklanan, iç içe geçmiş kontrol yapılarının kodun okunabilirliğini tamamen yok etmesi durumudur. Kodun içinde bir mantığı takip etmeye çalışırken kendinizi sürekli “Eğer bu böyleyse ama şu da şöyle değilse…” gibi karmaşık bir labirentin içinde buluyorsanız, bu kokuyla karşı karşıyasınız demektir. Bu durum sadece kodu anlamayı zorlaştırmakla kalmaz, aynı zamanda yeni bir koşul eklemeyi imkansız hale getirerek en ufak bir değişiklikte tüm mantığın çökmesine neden olur.

Temel problemler:

  • Çözümün çok sayıda if/elif/else üzerine inşa edilmesi
  • Yeni bir durum eklemedeki zorluk

Bu labirentten çıkmak ve kodu daha akıcı bir hale getirmek için şu refactoring tekniklerini kullanabiliriz:

  • Replace Conditional with Polymorphism (Koşulu Polimorfizm ile Değiştir): Eğer sürekli bir nesnenin tipine göre farklı davranışlar sergileyen if-else veya switch bloklarınız varsa, bu davranışları alt sınıflara (subclasses) dağıtın. Böylece sistem, çalışma anında hangi metodun çağrılacağına nesnenin tipi üzerinden kendisi karar verir.
  • Decompose Conditional (Koşulu Parçalara Ayır): Karmaşık ve uzun if şartlarını, neyi kontrol ettiklerini açıklayan isimlendirilmiş metotlara bölün. if (user.age > 18 && user.hasLicense && !user.isBanned) yerine if (user.canDrive()) yazmak kodun niyetini anında netleştirir.
  • Replace Nested Conditional with Guard Clauses (İç İçe Koşulları Koruma İfadeleriyle Değiştir): Kodun ana akışını en sağa iten iç içe if blokları yerine, “olumsuz” durumları en başta kontrol edip metottan erken dönmeyi (return) tercih edin. Bu, “merdiven” görüntüsünü yok eder ve ana mantığı en sol hizada tutar.

6. Primitive Obsession

Primitive Obsession (İlkel Veri Tipi Takıntısı), bir yazılımcının aslında kendi başına bir anlam ifade etmesi gereken küçük nesneler yerine, sürekli olarak dilin sunduğu temel (primitive) veri tiplerine (string, integer, float, boolean) sığınması durumudur.

Örneğin; bir telefon numarasını, bir para birimini veya bir koordinatı sadece string veya double olarak tanımladığınızda bu koku başlar. İlk bakışta kolay gelse de, bu “ilkel” veriler zamanla etraflarında özel doğrulama (validation) mantıkları biriktirir. Her telefon numarası kullanılan yerde “alan kodu doğru mu?” kontrolü yapıyorsanız, mantık projenin her yerine dağılır ve kodunuzun nesne yönelimli ruhu zayıflar.

İlkel veri tiplerinden kaçınıp Value Objects (Değer Nesneleri) kullanmaya başladığınızda, kodunuz sadece daha okunaklı olmaz; aynı zamanda hataları derleme aşamasında yakalayan, kendini belgeleyen (self-documenting) profesyonel bir yapıya bürünür.

Temel problemler:

  • Veri tipinde netlik yerine genelleme (Her şey string, int…)
  • Bağlamla ilişki kurulmamış, alanla ilişkilendirilmemiş yapılar.

Bu takıntıdan kurtulmak ve daha sağlam bir mimari kurmak için şu tedavi yöntemleri uygulanır:

  • Replace Data Value with Object (Veri Değerini Nesneyle Değiştir): Basit bir veri alanı artık kendi mantığına ve davranışına ihtiyaç duyuyorsa, onu hemen bir sınıfa dönüştürün. Örneğin, sadece string email kullanmak yerine, içinde isValid() metodu olan bir Email sınıfı oluşturun.
  • Introduce Parameter Object (Parametre Nesnesi Tanımla): Eğer bir grup ilkel veri (enlem, boylam, yükseklik gibi) her zaman birlikte hareket ediyorsa, onları bir Location nesnesi içinde paketleyin.
  • Replace Type Code with Class (Tip Kodunu Sınıf ile Değiştir): Eğer bir int değişkeni sadece belirli kategorileri (Örn: 0=Admin, 1=Editor) temsil ediyorsa, bunu bir Enum veya özel bir sınıf yapısına taşıyarak tip güvenliğini (type safety) sağlayın.

7. Feature Envy

Feature Envy (Özellik Kıskançlığı), bir metodun kendi bulunduğu sınıftan ziyade, başka bir sınıfın verilerine veya metotlarına daha fazla ilgi göstermesi durumudur. Adeta “keşke o sınıfta doğsaydım” diyen bu koku, nesne yönelimli programlamanın temel taşı olan Encapsulation (Kapsülleme) ilkesini zedeler.

Bir metot, sürekli olarak başka bir nesnenin getter metotlarını çağırıp o veriler üzerinde hesaplama yapıyorsa, aslında ait olduğu yere yabancılaşmış demektir. Bu durum, sınıflar arasındaki bağımlılığı (coupling) artırır ve bir sınıfta yapılan değişikliğin, onu “kıskanan” diğer sınıflarda zincirleme hatalara yol açmasına neden olur.

Veri ve o veriyi işleyen mantık her zaman bir arada olmalıdır. Feature Envy’yi ortadan kaldırmak, sınıflarınızı daha bağımsız (decoupled) ve sisteminizi daha esnek hale getirir. Unutmayın; iyi bir kodda her metot, kendi sınıfının “mutlu bir üyesi” olmalıdır.

Temel problemler:

  • İç içe geçmiş class’lar (Örneğin: Bir class, başka class’ın verisini aşırı kullanması)

Bu “kıskançlık” krizini çözmek ve metodu ait olduğu yuvaya kavuşturmak için şu teknikler kullanılır:

  • Move Method (Metodu Taşı): Eğer bir metot vaktinin çoğunu başka bir sınıfın verileriyle uğraşarak geçiriyorsa, en köklü çözüm o metodu tamamen o sınıfa taşımaktır. Metodu, en çok “kıskandığı” verilerin yanına taşıyarak bağımlılığı ortadan kaldırırsınız.
  • Extract Method (Metot Çıkar): Eğer metodun sadece belirli bir kısmı başka bir nesneye aşırı ilgi duyuyorsa, o kısmı ayrı bir metot olarak dışarı çıkarın ve ardından Move Method ile ilgili sınıfa gönderin.
  • Strategy veya Visitor Pattern: Eğer bir hesaplama mantığı birçok farklı sınıfın verisine ihtiyaç duyuyorsa, bu mantığı ayrı bir strateji nesnesine taşıyarak karmaşıklığı yönetebilirsiniz.

8. Data Clumps

Data Clumps (Veri Kümeleri), kodun farklı yerlerinde her zaman bir arada gezen küçük veri gruplarıdır. Bu veri grupları, tıpkı ayrılmaz üç kafadar gibi, metot parametrelerinde veya sınıf değişkenleri arasında sürekli setler halinde karşımıza çıkar.

Örneğin; start_date ve end_date veya red, green, blue gibi değerlerin projenin beş farklı metodunda hep yan yana parametre olarak gönderildiğini görüyorsanız, orada bir “Data Clump” vardır. Bu durum, bu verilerin aslında gizli bir nesne oluşturması gerektiğini ancak sizin onları zorla ayrı tuttuğunuzu gösterir. Eğer bu verilerden birini değiştirirseniz, diğerlerini de manuel olarak takip etmek zorunda kalırsınız; bu da hata payını artırır.

Temel problemler:

  • Kodun içerisinde gezen küçük veri grupları

Bu ayrılmaz grupları resmi bir birlikteliğe dönüştürmek için şu refactoring teknikleri uygulanır:

  • Extract Class (Sınıf Çıkar): Sınıf seviyesinde (field olarak) sürekli yan yana duran veri kümelerini yeni bir sınıfa taşıyın. Örneğin, bir sınıftaki home_phone, work_phone ve mobile_phone alanlarını birleştirip bir ContactDetails veya PhoneSet sınıfı oluşturun.
  • Introduce Parameter Object (Parametre Nesnesi Tanımla): Eğer bu veri kümesi sadece metot imzalarında (parametre listesinde) bir arada görülüyorsa, bu parametreleri tek bir nesne altında toplayın. Metoda beş ayrı değişken yerine, bu değişkenleri sarmalayan tek bir “bilgi paketi” (Data Object) gönderin.
  • Preserve Whole Object (Nesneyi Bütün Olarak Koru): Bir nesnenin içinden üç farklı veriyi çekip başka bir metoda parametre olarak göndermek yerine, nesnenin tamamını gönderin. Bu, hem parametre listesini kısaltır hem de veri bütünlüğünü sağlar.

9. Shotgun Surgery

Shotgun Surgery (Saçma Yarası Etkisi), bir “Code Smell” olarak Feature Envy‘nin tam tersi bir durumdur. Bir sistemde küçük bir değişiklik yapmak istediğinizde, kendinizi projenin onlarca farklı dosyasında veya sınıfında düzenleme yaparken buluyorsanız, “Shotgun Surgery” kokusuyla karşı karşıyasınız demektir.

Tıpkı bir tüfekten çıkan saçmaların geniş bir alana yayılması gibi, tek bir mantıksal değişiklik sistemin geneline yayılır. Bu durum, sorumlulukların (responsibilities) sınıflar arasında çok fazla dağıldığını ve sistemin yüksek bağımlılığa (high coupling) sahip olduğunu gösterir. En büyük tehlikesi ise, değiştirilmesi gereken on yerden birini gözden kaçırdığınızda sistemin tutarsız hale gelmesi ve tespit edilmesi zor bug’ların oluşmasıdır.

Temel problemler:

  • Küçük bir değişiklik için bile bir çok yapıyı veya dosyayı değiştirme ihtiyacı

Bu dağılmış yapıyı tek bir noktada toplamak ve sistemi daha bakımı kolay hale getirmek için şu teknikler kullanılır:

  • Move Method (Metodu Taşı) & Move Field (Alanı Taşı): Birbiriyle ilişkili davranışları ve verileri, sürekli değişimin yaşandığı o çok sayıdaki sınıftan alıp, hepsini tek bir “ev sahibi” sınıfa taşıyın. Eğer mevcut sınıflardan hiçbiri uygun değilse, yeni bir sınıf oluşturun.
  • Inline Class (Sınıfı İçe Aktar): Eğer bir sınıf artık çok az sorumluluk taşıyorsa ve işlevselliği başka bir sınıfa çok fazla bağımlı hale gelmişse, bu sınıfı tamamen ortadan kaldırıp özelliklerini ilgili olduğu sınıfa dahil edin.
  • Combine Functions into Class (Fonksiyonları Sınıfta Birleştir): Eğer aynı veri üzerinde işlem yapan birçok farklı fonksiyon projenin her yerine dağılmışsa, bunları tek bir sınıf altında toplayarak merkezi bir yönetim sağlayın.

10. Lazy Class

Lazy Class (Tembel Sınıf), yazılım dünyasında varlığıyla yük olmaktan başka bir işe yaramayan, “atıl” kalmış sınıfları ifade eder. Bir sınıfın var olabilmesi için kendine has bir sorumluluğu, işlediği bir verisi veya sunduğu bir hizmeti olmalıdır. Ancak bazen bir sınıf, yapılan refactoring işlemleri sonucunda küçülerek işlevini yitirmiş olabilir ya da en baştan “belki lazım olur” diye eklenip içi hiçbir zaman dolmamış olabilir.

Eğer bir sınıfın kod tabanındaki varlığı, o kodu okuyan geliştirici için ekstra bir bilişsel yük oluşturuyor ama karşılığında anlamlı bir iş yapmıyorsa, o artık bir “Lazy Class”tır. Bu durum, projenin gereksiz yere şişmesine ve bakım maliyetlerinin artmasına neden olur.

Temel problemler:

  • Çok küçük yapıda, atıl class’lar

Bu verimsiz sınıfı ortadan kaldırmak ve kod tabanını sadeleştirmek için şu yöntemler uygulanır:

  • Inline Class (Sınıfı İçe Aktar): Eğer bir sınıf neredeyse hiçbir şey yapmıyorsa, onun içindeki az sayıdaki özelliği ve metodu, onu en çok kullanan diğer sınıfa taşıyın. Ardından işlevsiz kalan bu boş sınıfı tamamen silin.
  • Collapse Hierarchy (Hiyerarşiyi Daralt): Eğer bir alt sınıf (subclass), üst sınıfından (parent class) neredeyse hiç farkı olmayacak kadar az özellik barındırıyorsa, bu iki sınıfı tek bir sınıfta birleştirin. Gereksiz kalıtım katmanlarından kurtulmak kodun takibini kolaylaştırır.
image
Kemal ŞAHİN | Akademik Hayat

Akademisyen, kullanıcı deneyimi ve arayüz tasarımı, veri görselleştirme, web/mobil uygulama geliştirme.

Kemal ŞAHİN'i yakından tanıyın.