← Back to list

Refactoring — Mevcut Kodun Tasarımını İyileştirme Sanatı — Bölüm 7: Code Smells (III) — Bağlılık…

Kısım II — Teşhis: Kötü Kodu Tanımak

Sahin Yelkenci · 2026-07-26 19:15 · 0 claps · 17.7 min read
#refactoring #code-smells #software-design #coupling #cohesion
Open on Medium ↗
Wiki topics: 💻 · Programming

Refactoring — Mevcut Kodun Tasarımını İyileştirme Sanatı — Bölüm 7: Code Smells (III) — Bağlılık, Mesaj Zincirleri ve Yapısal Kokular

Kısım II — Teşhis: Kötü Kodu Tanımak

İçindekiler

Teşhis turumuzun son ve en geniş ölçekli durağına geldik. ↗ Bölüm 5'te tek tek metotlara, ↗ Bölüm 6'da tek tek sınıflara baktık. Şimdi mercimizi en zora çeviriyoruz: sınıfların arasındaki ilişkilere, yani bağlılığa (coupling). Bu kokular en sinsileridir, çünkü hiçbir tek sınıfa bakarak görülmezler; ancak sınıflar arasındaki bağ örüntüsüne, sistemin sinir ağına baktığınızda ortaya çıkarlar.

» Bağlılık ve Kohezyon — İki Temel Tasarım Kuvveti » Mesaj Zincirleri (Message Chains) ve Demeter Yasası » Middle Man (Aracı) — Sadece İleten Sınıf » Inappropriate Intimacy (Uygunsuz Yakınlık) » Divergent Change (Farklı Değişim) — Tek Sınıf, Çok Sebep » Shotgun Surgery (Saçma Cerrahisi) — Tek Değişiklik, Çok Sınıf » İkiz Kokular — Divergent Change ve Shotgun Surgery’yi Ayırmak » Yapısal Kokuları Refactoring’e Bağlamak » Üç Gözlem Sorusu » Felsefi Kapanış » Sonraki Bölüm

Bağlılık ve Kohezyon — İki Temel Tasarım Kuvveti

Bu bölümdeki tüm kokuları anlamak için, önce yazılım tasarımının altında yatan iki temel kuvveti net biçimde kavramamız gerekir: bağlılık (coupling) ve kohezyon (cohesion). Bu iki kavram, iyi yapı hakkındaki neredeyse her tartışmanın dilini oluşturur, ve onları bir kez doğru anladığınızda, bu bölümdeki her koku tek bir cümleyle açıklanabilir hâle gelir.

Kohezyon, bir modülün içindeki şeylerin ne kadar birbirine ait olduğunun ölçüsüdür. Yüksek kohezyon iyidir: Bir sınıfın tüm metotları ve alanları tek bir tutarlı amaca hizmet ediyorsa, o sınıf kohezyondur. ↗ Bölüm 6'daki kokuların çoğu aslında kohezyon problemleriydi — Tanrı Sınıf, kohezyonu düşük olduğu için kötüdür; içinde birbiriyle alakasız bir sürü iş bir aradadır. Bağlılık ise farklı bir eksendir: İki modülün birbirine ne kadar bağımlı olduğunun ölçüsüdür. Düşük bağlılık iyidir: Bir modülü değiştirdiğinizde diğerlerinin etkilenmemesi, onları birbirinden bağımsız geliştirebilmeniz ve test edebilmeniz demektir. Bu bölümün kokuları, ağırlıkla bu ikinci eksenle — yanlış türde ya da yanlış miktarda bağlılıkla — ilgilidir.

İyi tasarımın klasik özdeyişi bu iki kuvveti birleştirir: “yüksek kohezyon, düşük bağlılık.” Bunun neden istendiğini somutlaştıralım. Yüksek bağlılık, değişikliğin sistem boyunca dalga gibi yayılması demektir; bir şeyi değiştirmek istediğinizde, ona bağlı yedi yeri de değiştirmek zorunda kalırsınız (↗ Bölüm 3'teki değişim maliyetinin yapısal sebebi tam budur). Yüksek bağlılık ayrıca izole test etmeyi imkânsızlaştırır; bir sınıfı test etmek için ona bağlı tüm dünyayı da kurmanız gerekir. Düşük bağlılık ise tam tersini sağlar: Parçaları birbirinden bağımsız değiştirebilir, test edebilir ve anlayabilirsiniz.

Burada kritik bir nüans var: Amaç sıfır bağlılık değildir. Hiç bağlı olmayan parçalar, aslında bir sistem oluşturmaz — birbiriyle hiç konuşmayan nesneler işe yaramaz. Amaç, doğru bağlılıktır: minimum, kasıtlı, ve mümkün olduğunca kararlı arayüzler üzerinden kurulmuş bağlılık. İki nesne konuşmak zorundadır; mesele, bu konuşmanın birbirinin içine elini sokarak mı, yoksa temiz bir genel arayüz üzerinden mi yapıldığıdır. Bu bölümdeki kokular, işte bu “yanlış konuşma biçimlerinin” kataloğudur. Bağlılığı ve kohezyonu ↗ Bölüm 8'de sayısal metriklerle — afferent/efferent coupling ve LCOM gibi — ölçmeyi öğreneceğiz.

Bağlılık ve kohezyonu bilmenin dersi şudur: Kohezyon bir sınıfın içindeki şeylerin ne kadar birbirine ait olduğu, bağlılık ise sınıfların birbirine ne kadar bağımlı olduğudur; iyi tasarım “yüksek kohezyon, düşük bağlılık” ister, çünkü yüksek bağlılık değişikliği dalga gibi yayar ve izole testi öldürür — ama amaç sıfır bağlılık değil, temiz arayüzler üzerinden kurulmuş doğru, kasıtlı, minimum bağlılıktır.

Mesaj Zincirleri (Message Chains) ve Demeter Yasası

İlk yapısal kokumuz, kodda görsel olarak en kolay fark edilenidir: Mesaj Zinciri (Message Chain). Bir nesneden bir veri istersiniz, o size bir başka nesne verir, ondan bir veri istersiniz, o da size bir başkasını verir… ve ortaya kupon.getSahip().getAdres().getSehir().getPostaKodu() gibi uzun bir get treni çıkar. Bu tren masum görünür — sonunda istediğiniz veriye ulaşırsınız — ama her halkası gizli bir bağlılık taşır.

Bu zincirin asıl zararı, çağıran kodu tüm gezinme yapısına bağlamasıdır. kupon.getSahip().getAdres().getSehir().getPostaKodu() yazdığınızda, çağıran kod artık şunların hepsini "biliyor": Kupon'un bir sahibi olduğunu, sahibin bir adresi olduğunu, adresin bir şehri olduğunu, ve şehrin bir posta kodu olduğunu. Bu zincirin herhangi bir halkası değişirse — diyelim Adres yeniden yapılandırılıp şehir bilgisi başka yere taşınırsa — bu zinciri kullanan her yer kırılır. Yani çağıran kod, nesne grafiğinin şekline gereğinden fazla bağımlı hâle gelmiştir; uzaktaki bir nesnenin iç yapısını bilmek zorunda bırakılmıştır.

Bu kokunun arkasındaki ilke, Demeter Yasası’dır (Law of Demeter): “Yabancılarla konuşma, yalnızca en yakın arkadaşlarınla konuş.” Daha kesin söylemek gerekirse, bir metot yalnızca şunların metotlarını çağırmalıdır: kendisinin (this), parametrelerinin, kendi yarattığı nesnelerin, ve kendi doğrudan alanlarının. Başka bir çağrının döndürdüğü nesnenin metotlarını çağırmak — yani zincirlemek — yasaya aykırıdır. Tedavi, ↗ Bölüm 6'da tanıştığımız "sorma, söyle" ilkesinin bir uygulamasıdır: Gezinmeyi çağırana yaptırmak yerine, en yakın nesneye sorarsınız ve gezinmeyi o yapar (Hide Delegate). Aşağıda zincir ve onun gizlenmiş hâli:

// KOKU — Message Chain: çağıran tüm gezinme yapısına bağımlı
String kod = kupon.getSahip().getAdres().getSehir().getPostaKodu();
// Adres yeniden yapılanırsa bu satırı kullanan HER yer kırılır
// TEDAVİ - Hide Delegate: çağıran tek bir nesneye sorar, gezinmeyi o yapar
public class Kupon {
    private final Musteri sahip;
    public String sahipPostaKodu() {        // gezinmeyi Kupon gizler
        return sahip.postaKodu();
    }
}
public class Musteri {
    private final Adres adres;
    public String postaKodu() {             // her nesne sadece komşusunu tanır
        return adres.postaKodu();
    }
}
// çağıran artık grafiğin şeklini bilmiyor:
String kod = kupon.sahipPostaKodu();

Tedavi edilmiş versiyonda çağıran kod yalnızca Kupon'u tanır; arkadaki sahip-adres-şehir grafiği değişse bile, çağıran kod kırılmaz, çünkü o yapıya hiç bağımlı değildir. İki önemli nüans var. Birincisi, akıcı arayüzler (fluent interfaces) ve builder'lar — builder.ad("X").miktar(100).olustur() gibi — bir Demeter ihlali değildir; çünkü onlar bir nesne grafiğinde gezinmez, aynı nesneyi (ya da builder'ı) döndürerek zincirlenir. İkincisi ve daha önemlisi, Demeter Yasası'nı aşırıya kaçırmak bir başka kokuyu doğurur: Her gezinmeyi gizlemek için bir sürü "iletici" metot eklerseniz, sınıflarınız sadece çağrı ileten kabuklara dönüşür — ki bu, bir sonraki kokumuz olan Middle Man'dir. İşte tasarımın inceliği burada: İki koku birbirinin panzehiri ve aynı zamanda birbirinin tuzağıdır. Hide Delegate refactoring'ini ↗ Bölüm 11'de detaylandıracağız.

Mesaj Zincirleri’ni bilmenin dersi şudur: Uzun get zincirleri çağıran kodu tüm nesne grafiğinin şekline bağlar, böylece herhangi bir halka değişince çağıran kırılır; Demeter Yasası “yalnızca komşunla konuş” der ve tedavi gezinmeyi nesneye gizletmektir (Hide Delegate) — ama akıcı arayüzler bu kokunun istisnasıdır ve gizlemeyi aşırıya kaçırmak bir sonraki kokuyu, Middle Man’i doğurur.

Middle Man (Aracı) — Sadece İleten Sınıf

Mesaj Zincirleri’nin tedavisini aşırıya kaçırdığınızda ortaya çıkan koku, Middle Man’dir (Aracı). Bu, metotlarının çoğu kendi başına hiçbir iş yapmayıp sadece başka bir sınıfa çağrı ileten sınıftır. Sınıfa bakarsınız ve metotların hepsi return baskaNesne.ayniMetot(...) der. Böyle bir sınıf, sisteme hiçbir değer katmadan bir katman ekler; her çağrı onun içinden geçer ama o hiçbir şey yapmaz, sadece postacılık eder.

Bu kokunun neden zarar verdiği ilk bakışta belli olmayabilir — “ne var ki, sadece iletiyor” denebilir. Ama maliyeti gerçektir: Her gereksiz aracı katman, kodu okuyan kişinin izlemesi gereken bir adım daha, bakım yapılacak bir yer daha, ve gerçek işin nerede yapıldığını bulmak için atlanması gereken bir engel daha demektir. Asıl mantığa ulaşmak için önce aracıyı geçmeniz gerekir, ve aracı hiçbir değer katmadığı için bu geçiş tamamen israftır. ↗ Bölüm 6'daki Data Class kabuk gibi içi boştu; Middle Man da öyle, ama davranış yerine çağrıları iletir.

Tedavi sezgiseldir: Remove Middle Man (Aracıyı Kaldır) — çağıranların gerçek nesneyle doğrudan konuşmasına izin verin, gereksiz katmanı çıkarın. İletici metotları gerektiğinde Inline Function ile yerlerine açın. Ama burada en kritik nüans devreye girer ve onu net söylemek gerekir: Her aracılık bir koku değildir. Aslında pek çok değerli tasarım deseni — Proxy, Decorator, Adapter, Facade — bilinçli olarak başka bir nesneye iletim yapar, ama bir değer katarak: Proxy erişim kontrolü ya da tembel yükleme ekler, Decorator yeni davranış sarar, Adapter bir arayüzü diğerine çevirir, Facade karmaşık bir alt sistemi basit bir yüze indirger. Koku, iletimin kendisinde değil, hiçbir değer katmayan iletimde gizlidir.

// KOKU — Middle Man: hiçbir değer katmadan her çağrıyı iletiyor
public class KuponServisi {
    private final KuponDeposu depo;
    public Kupon bul(Long id)        { return depo.bul(id); }        // sadece iletir
    public void kaydet(Kupon k)      { depo.kaydet(k); }             // sadece iletir
    public void sil(Long id)         { depo.sil(id); }               // sadece iletir
    // ...hepsi böyle, hiçbir iş yapmıyor
}
// TEDAVİ — Remove Middle Man: çağıranlar depoyla doğrudan konuşur
// (KuponServisi yalnızca gerçek bir iş — örn. doğrulama, orkestrasyon —
//  ekliyorsa kalır; sadece iletiyorsa kaldırılır)

Burada Mesaj Zincirleri ile Middle Man arasındaki gerilime dikkat edin, çünkü tasarımın gerçek ustalığı bu iki uç arasında denge kurmaktır. Çok az iletim, çağıranları uzaktaki nesnelere zincirler (Message Chains); çok fazla iletim, değersiz aracı katmanlar yaratır (Middle Man). Doğru nokta, ikisinin ortasındadır: gerçek değer katan, gerektiği kadar gizleyen, ama gereksiz katman eklemeyen bir yapı. Bu denge bir formülle bulunmaz, yargıyla bulunur. Remove Middle Man ve Inline Function’ı ↗ Bölüm 11'de ele alacağız.

Middle Man’i bilmenin dersi şudur: Hiçbir değer katmadan çağrıları ileten sınıf, sisteme yalnızca gereksiz bir katman, bakım yükü ve engel ekler; tedavisi onu kaldırıp çağıranı gerçek nesneyle konuşturmaktır — ama Proxy, Decorator, Adapter ve Facade gibi değer katan iletimler bu kokunun istisnasıdır, ve Middle Man ile Message Chains arasındaki denge ancak yargıyla bulunur, formülle değil.

Inappropriate Intimacy (Uygunsuz Yakınlık)

Bazı sınıf çiftleri birbirine fazla yakındır — birbirinin özel hayatına burnunu sokar, birbirinin iç yapısına bağımlıdır, ve hep birlikte değişirler. Inappropriate Intimacy (Uygunsuz Yakınlık) kokusu budur: İki sınıf, temiz bir genel arayüz üzerinden konuşmak yerine, birbirinin mahremine — özel alanlarına, iç detaylarına — elini sokar. Bu, en saf hâliyle yüksek bağlılıktır ve genellikle çift yönlüdür: A, B’nin içini bilir; B de A’nın içini bilir. İkisi öyle iç içe geçmiştir ki, birini diğeri olmadan ne anlamak ne de değiştirmek mümkündür.

Bu kokunun zararı, doğrudan ↗ Bağlılık ve Kohezyon başlığındaki tüm sorunlardır, ama yoğunlaşmış hâliyle. İki sınıf birbirinin içini bildiği için, birinde yapılan en küçük iç değişiklik diğerini kırar. İkisini ayrı ayrı düşünmek imkânsızdır; biri hakkında akıl yürütürken sürekli diğerini de kafada tutmak gerekir. Ve test etmek bir kâbusa döner: Birini izole etmek için diğerinin tüm iç davranışını taklit etmeniz gerekir. Uygunsuz yakınlık, iki sınıfı pratikte tek bir karmaşık yumağa dönüştürür, ama onları iki ayrı sınıf olarak adlandırmanın tüm dezavantajlarını da taşır.

Tedaviler, yakınlığın türüne göre değişir. En yaygın olanı, iç içe geçmiş parçaları doğru yerlerine taşımaktır (Move Method, Move Field) — eğer A’nın bir metodu hep B’nin verisini kullanıyorsa (↗ Bölüm 6'daki Feature Envy!), o metot B’ye taşınır. İkincisi, her iki sınıfın ortaklaşa ilgilendiği bir şeyi ayrı bir üçüncü sınıfa çekmektir (Extract Class). Üçüncüsü, çift yönlü ilişkiyi tek yönlüye indirmektir (Change Bidirectional to Unidirectional). Eğer yakınlık bir alt sınıf ile üst sınıfı arasındaysa — alt sınıf üst sınıfın iç detaylarına aşırı bağımlıysa — kalıtımı delegasyonla değiştirmek gerekebilir, ki bunu ↗ Bölüm 15'te ele alacağız.

Bir nüans, bu kokuyu da bir dogmaya dönüştürmekten korur: Bir miktar yakınlık doğaldır ve hatta gereklidir. Sıkı ilişkili bir küme — örneğin bir Kupon ve onun Bahis'leri, ki bunlar bir bütünün (aggregate) parçalarıdır — kendi içinde belli bir yakınlığa sahip olabilir, çünkü onlar zaten birlikte düşünülmek üzere tasarlanmıştır. Koku, bir bütünün içindeki doğal yakınlıkta değil, ayrı olması gereken sınırlar arasındaki uygunsuz yakınlıkta gizlidir. Yargınızı, "bu iki sınıf gerçekten ayrı kavramlar mı, yoksa zaten bir bütünün parçaları mı?" sorusuna göre verin.

Inappropriate Intimacy’yi bilmenin dersi şudur: Birbirinin iç yapısına elini sokan, hep birlikte değişen iki sınıf en saf yüksek bağlılıktır; tedavisi iç içe parçaları doğru yere taşımak, ortak ilgiyi üçüncü bir sınıfa çekmek ya da ilişkiyi tek yönlüye indirmektir — ama bir bütünün doğal olarak yakın parçaları (Kupon ve Bahis’leri) bu kokunun istisnasıdır, çünkü asıl sorun ayrı olması gereken sınırlar arasındaki yakınlıktır.

Divergent Change (Farklı Değişim) — Tek Sınıf, Çok Sebep

Bir önceki kokular, kodu okuyarak ya da yapısına bakarak fark edilirdi. Sıradaki iki koku ise farklı bir yolla kendini ele verir: onları çoğu zaman ancak değişiklik yaparken hissedersiniz. İlki, Divergent Change’dir (Farklı Değişim): Tek bir sınıfı, birbiriyle alakasız birçok sebeple, tekrar tekrar değiştirmek zorunda kalmanız. Bu kokuyu en net şu cümleyle anlarsınız: “Yeni bir ödeme yöntemi eklemek için KuponServisi'ni açıyorum; bir bildirim metnini değiştirmek için yine KuponServisi'ni açıyorum; bir doğrulama kuralını güncellemek için yine KuponServisi'ni açıyorum." Aynı sınıf, tamamen farklı sebeplerle sürekli karşınıza çıkıyorsa, orada Divergent Change vardır.

Bu koku, ↗ Bölüm 6'daki Tanrı Sınıf’ın davranışsal yüzüdür; ikisi aynı yapısal hastalığın iki belirtisidir. Tanrı Sınıf’ı sınıfa bakarak (“bu sınıf çok şey yapıyor”) fark ederken, Divergent Change’i değişiklik yaparken (“bu sınıfı yine farklı bir sebeple açtım”) fark edersiniz. Her ikisinin de kökü aynıdır: tek bir sınıfta birden çok sorumluluğun karışmış olması, yani düşük kohezyon. İhlal edilen ilke, ↗ Bölüm 6'da andığımız Tek Sorumluluk İlkesi’dir: Bir sınıfın değişmek için tek bir sebebi olmalıdır. Divergent Change, bu ilkenin pratikteki alarm zilidir — sınıfı her açtığınızda farklı bir sebeple açıyorsanız, o sınıfın birden fazla sorumluluğu vardır.

Bu kokunun zararı yalnızca dağınıklık değildir; somut bir risktir. Bir sınıf farklı sebeplerle sürekli değiştiğinde, bir değişiklik için yapılan dokunuş, alakasız bir başka sorumluluğu yanlışlıkla bozabilir. Ödeme mantığını değiştirirken, aynı sınıftaki bildirim kodunu kazara etkileyebilirsiniz. Ayrıca farklı ekipler aynı sınıfa farklı sebeplerle dokunduğu için sürekli çakışırlar (↗ Bölüm 3'teki paralel çalışma engeli). Tedavi nettir: sınıfı, değişim sebeplerine göre ayrı sınıflara bölmek — Extract Class. Her yeni sınıf tek bir sebeple değişir. Aşağıda, üç farklı sebeple değişen bir sınıf:

// KOKU — Divergent Change: üç ayrı sebep aynı sınıfta birikmiş
public class KuponServisi {
    // SEBEP 1: ödeme kuralı değişince burası değişir
    public Para odemeHesapla(Kupon k) { /* komisyon, tavan, bonus... */ return null; }
    // SEBEP 2: bildirim kanalı değişince burası değişir
    public void bildirimGonder(Kupon k) { /* SMS, e-posta şablonları... */ }
    // SEBEP 3: doğrulama kuralı değişince burası değişir
    public void dogrula(Kupon k) { /* oran, kapasite, zaman kuralları... */ }
}
// TEDAVİ — Extract Class: her değişim ekseni kendi sınıfında, tek sebeple değişir
public class OdemeHesaplayici { public Para hesapla(Kupon k) { /* ... */ return null; } }
public class BildirimServisi  { public void gonder(Kupon k) { /* ... */ } }
public class KuponDogrulayici { public void dogrula(Kupon k) { /* ... */ } }

Bir nüans her zaman geçerlidir: Bölmenin de bir maliyeti vardır. Her sorumluluğu ayrı bir sınıfa çekmek, sınıf sayısını artırır ve aşırıya kaçarsa bir sonraki kokuyu — Shotgun Surgery’yi — doğurabilir. Doğru bölme, “değişim eksenine” göre bölmedir: Birlikte değişen şeyler birlikte kalmalı, ayrı sebeplerle değişenler ayrılmalıdır. Bu refactoring’i ↗ Bölüm 11'de detaylandıracağız.

Divergent Change’i bilmenin dersi şudur: Tek bir sınıfı birbiriyle alakasız birçok sebeple sürekli değiştiriyorsanız, o sınıf birden fazla sorumluluk taşıyor demektir — bu, Tanrı Sınıf’ın değişiklik anında hissedilen davranışsal yüzüdür; tedavisi sınıfı değişim eksenlerine göre ayrı sınıflara bölmektir (Extract Class), ki böylece her sınıf tek bir sebeple değişir.

Shotgun Surgery (Saçma Cerrahisi) — Tek Değişiklik, Çok Sınıf

Divergent Change’in tam tersi bir koku vardır ve bu, en sinir bozucu olanlardan biridir: Shotgun Surgery (Saçma Cerrahisi). Adı, av tüfeği saçmasının tek bir atışta her yere dağılmasından gelir. Bu kokuda, tek bir mantıksal değişiklik yapmak için kodun her yerine dağılmış onlarca yeri elle düzeltmeniz gerekir. “Yeni bir para birimi ekleyeceğim” dersiniz ve bunun için on iki farklı dosyada on iki ayrı yere dokunmanız gerektiğini fark edersiniz. Tek bir kavram, kod tabanının her yerine saçılmıştır.

Bu kokunun zararı çok somuttur ve ↗ Bölüm 3'teki değişim maliyetini doğrudan patlatır. Tek bir değişiklik için onlarca yere dokunmak hem yavaştır hem de tehlikelidir: O onlarca yerden birini kaçırırsanız — ki insan kaçırır — sistem tutarsız bir duruma düşer ve sessiz bir hata doğar. Yeni para birimini on bir yerde eklediniz ama on ikinciyi unuttunuz; artık o kod yolunda sistem yanlış davranır. Shotgun Surgery, bir kavramın yerelleştirilmemiş, kod tabanına yayılmış olmasının cezasıdır.

Eğer Divergent Change bir kohezyon problemiyse, Shotgun Surgery bir bağlılık ve dağınıklık problemidir: Birlikte değişmesi gereken şeyler, bir arada değil, her yere dağılmıştır. Tedavi, dağılmış parçaları tek bir yere toplamaktır — Move Method ve Move Field ile ilgili davranış ve veriyi bir araya getirmek, hatta gereğinden fazla bölünmüş sınıfları Inline Class ile birleştirmek. Amaç, o kavramı tek bir eve yerleştirmek, böylece onu değiştirmek tek bir yere dokunmak hâline gelir. Aşağıda, bir bahis durumunun her yere dağılmış işlenmesi:

// KOKU — Shotgun Surgery: yeni bir BahisDurumu eklemek için
//          kod tabanının HER yerindeki switch'leri düzeltmek gerekir
// OdemeServisi.java
switch (bahis.getDurum()) { case KAZANDI: ...; case KAYBETTI: ...; }
// RaporServisi.java
switch (bahis.getDurum()) { case KAZANDI: ...; case KAYBETTI: ...; }
// BildirimServisi.java
switch (bahis.getDurum()) { case KAZANDI: ...; case KAYBETTI: ...; }
// ...8 dosyada daha aynı switch — birini unutursan sessiz hata
// TEDAVİ — davranışı tek bir eve topla (enum'a / polimorfizme)
public enum BahisDurumu {
    KAZANDI  { public Para odeme(Bahis b) { /* ... */ return null; } },
    KAYBETTI { public Para odeme(Bahis b) { return Para.sifir(b.getParaBirimi()); } },
    ACIK     { public Para odeme(Bahis b) { throw new HenuzSonuclanmadiException(); } };
    public abstract Para odeme(Bahis b);
}
// yeni durum eklemek artık TEK yere dokunmak: derleyici eksik case'i de yakalar

Tedavi edilmiş versiyonda yeni bir BahisDurumu eklemek, dağılmış on iki yere değil, tek bir yere — enum'un kendisine — dokunmaktır; üstelik derleyici, eksik bir davranışı bile yakalar. Bu, "birlikte değişen şeyler birlikte yaşamalı" ilkesinin ta kendisidir. Buradaki switch-to-polymorphism dönüşümünü ↗ Bölüm 13'te koşullu mantığı sadeleştirirken derinlemesine işleyeceğiz.

Shotgun Surgery’yi bilmenin dersi şudur: Tek bir mantıksal değişiklik için kodun her yerine dağılmış onlarca yere dokunmak zorunda kalmak, bir kavramın yerelleştirilmemiş olmasının cezasıdır ve birini kaçırmak sessiz hatalara yol açar; tedavisi dağılmış parçaları tek bir eve toplamaktır (Move/Inline), böylece o değişiklik tek bir yere dokunmaya iner.

İkiz Kokular — Divergent Change ve Shotgun Surgery’yi Ayırmak

Bu iki kokuyu art arda işledik çünkü onlar bir ikizdir — birbirinin aynadaki tersi — ve tam da bu yüzden sürekli birbirine karıştırılır. İkisini net biçimde ayırmak, yapısal teşhis ustalığının bir sınavıdır; çünkü yanlış teşhis, yanlış tedaviye ve çoğu zaman bir kokuyu diğeriyle değiştirmeye yol açar. Bu başlık, o ayrımı kalıcı olarak kafanıza yerleştirmek için var.

Ayrım, hangi tarafın “çok”, hangi tarafın “az” olduğunda yatar. Divergent Change’de tek bir SINIF çok sayıda SEBEPLE değişir — yani bir yere çok fazla sorumluluk tıkılmıştır. Shotgun Surgery’de ise tek bir DEĞİŞİKLİK çok sayıda SINIFA dokunmayı gerektirir — yani tek bir sorumluluk çok fazla yere dağılmıştır. Biri yoğunlaşma problemidir (her şey tek yerde), diğeri dağılma problemidir (bir şey her yerde). Ve tedavileri de zıttır: Divergent Change’i BÖLEREK (Extract Class) tedavi edersiniz; Shotgun Surgery’yi TOPLAYARAK (Move, Inline) tedavi edersiniz.

Boyut Divergent Change Shotgun Surgery Belirti Tek sınıf, çok sebeple değişir Tek değişiklik, çok sınıfa dokunur Problem Yoğunlaşma — çok şey tek yerde Dağılma — bir şey her yerde Eksen Kohezyon (düşük) Bağlılık / yerelleşme (zayıf) Tedavi BÖL — Extract Class TOPLA — Move, Inline Class Akrabası Tanrı Sınıf ↗ Bölüm 6 Dağınık switch ↗ Bölüm 13

İkisini birleştiren tek bir ilke vardır ve onu kavramak, her iki kokuyu da bir çırpıda anlamanızı sağlar: “Birlikte değişen şeyler birlikte yaşamalı; ayrı sebeplerle değişenler ayrı yaşamalı.” Divergent Change, bu ilkenin ikinci yarısını ihlal eder — ayrı sebeplerle değişen şeyler tek bir yere tıkılmıştır. Shotgun Surgery, ilk yarısını ihlal eder — birlikte değişmesi gereken şeyler her yere dağılmıştır. İyi tasarım, kodu “değişim eksenine” göre gruplar: aynı sebeple değişenleri bir araya, farklı sebeplerle değişenleri ayrı koyar.

İşte bu yüzden bu ikizler aynı zamanda birbirinin tuzağıdır ve bunu bilmek kritiktir. Divergent Change’i tedavi etmek için aşırı bölerseniz, tek bir mantıksal değişiklik artık birçok küçük sınıfa dokunmayı gerektirir — yani Shotgun Surgery’yi üretirsiniz. Tersine, Shotgun Surgery’yi tedavi etmek için her şeyi tek bir yere toplarsanız, o sınıf çok sayıda sorumluluğu barındıran bir canavara dönüşür — yani Divergent Change’i üretirsiniz. Doğru nokta, ikisinin ortasındadır ve onu bulmanın anahtarı, “değişim ekseni” sezgisidir: Neyin neyle birlikte değiştiğini gözlemleyin ve grupları ona göre kurun. Bu denge bir formülle değil, sistemin değişim geçmişini okuyan bir yargıyla bulunur — ki ↗ Bölüm 3'teki sıcak nokta analizi tam da bu geçmişi görünür kılar.

İkiz kokuları ayırmayı bilmenin dersi şudur: Divergent Change tek sınıfa çok sorumluluk tıkar (böl), Shotgun Surgery tek sorumluluğu çok yere dağıtır (topla); ikisi de tek bir ilkenin — “birlikte değişen birlikte yaşamalı” — zıt ihlalleridir ve birbirinin tuzağıdır, çünkü birini aşırı tedavi etmek diğerini doğurur — doğru denge, neyin neyle değiştiğini okuyan “değişim ekseni” yargısıyla bulunur.

Yapısal Kokuları Refactoring’e Bağlamak

Üç bölümlük teşhis turumuzun sonuna geldik ve şimdi bu son bölümün yapısal kokularını da tedavilerine bağlama zamanı. Ama burada turun tamamına bir bakış atalım, çünkü üç bölümün birlikte oluşturduğu resim, tek tek kokuların toplamından daha fazlasını söyler. ↗ Bölüm 5'te yereldeki kokulara (metot), ↗ Bölüm 6'da yapıdaki kokulara (sınıf), bu bölümde ise ilişkilerdeki kokulara (bağlılık) baktık. Bu üç ölçek, kötü kodun görülebileceği her seviyeyi kapsar: bir satırdan, bir sınıfa, oradan tüm sistemin sinir ağına.

Bu bölümün yapısal kokuları, özel bir zorluk taşır ve onu açıkça söylemek gerekir. Metot kokuları tek bir metoda bakarak görülürdü; sınıf kokuları tek bir sınıfa bakarak. Yapısal kokular ise hiçbir tek yere bakarak görülmez — yalnızca parçalar arasındaki örüntüye, çoğu zaman da ancak değişiklik yaparken hissedilen örüntüye bakarak ortaya çıkar. Bu yüzden onlar en sinsileridir. Ve tedavileri de en ağırıdır, çünkü birden çok sınıfa dokunmayı gerektirir. İşte bu yüzden yapısal kokuları tedavi ederken, serinin baştan beri savunduğu iki disiplin — ↗ Bölüm 1'deki küçük adımlar ve ↗ Bölüm 4'teki test ağı — hayati önem kazanır; çünkü çok yere dokunan bir değişiklik, ancak minik ve test edilen adımlarla güvenli kalabilir.

Aşağıdaki tablo, bu bölümdeki yapısal kokuları birincil tedavileriyle toplar; ↗ Ek B’deki tam matrisin bağlılık parçasıdır:

Koku Birincil tedavi Derinlemesine Mesaj Zincirleri Hide Delegate ↗ Bölüm 11 Middle Man Remove Middle Man / Inline ↗ Bölüm 11 Inappropriate Intimacy Move Method/Field, Extract Class ↗ Bölüm 11 Divergent Change Extract Class (böl) ↗ Bölüm 11 Shotgun Surgery Move/Inline (topla), switch→polimorfizm ↗ Bölüm 13

Üç teşhis bölümünün hepsine birden baktığımızda, tüm kokuların aslında üç temel tasarım sorusunun ihlalleri olduğunu görürüz. Birincisi: Bu kod ne yapıyor, açık mı? (Metot kokuları — okunabilirlik.) İkincisi: Her şey doğru yerde mi? (Sınıf kokuları — kohezyon, sorumluluk yerleşimi.) Üçüncüsü: Parçalar birbiriyle doğru biçimde mi konuşuyor? (Yapısal kokular — bağlılık.) Bu üç soru, iyi tasarımın tamamını kapsar, ve teşhis sanatı bu üç soruyu bir kod tabanına bakarak refleksle sorabilmektir.

Ama bu noktada dürüst bir itirafta bulunmalıyız: Şimdiye kadar öğrendiğimiz her şey sezgiseldir. “Bu metot uzun”, “bu sınıf çok şey yapıyor”, “bu zincir çok uzun” — hepsi eğitilmiş bir burnun yargılarıdır. Bu yargılar değerlidir ama tartışmaya açıktır; bir takımda “bu sana uzun gelebilir, bana normal” tartışması çıkabilir. İşte bir sonraki bölüm, bu sezgisel teşhisi ölçülebilir bir temele oturtarak teşhis turumuzu tamamlayacak: “uzun” yerine bir karmaşıklık sayısı, “çok bağlı” yerine bir coupling metriği koyacağız. Sezgi ile sayı birleştiğinde, teşhis hem hızlı hem de tartışılabilir hâle gelir.

Yapısal kokuları refactoring’e bağlamayı bilmenin dersi şudur: Bağlılık kokuları hiçbir tek yere bakarak görülmez ve tedavileri çok sınıfa dokunduğu için en çok küçük adım ve test disiplini ister; üç teşhis bölümü birlikte iyi tasarımın üç sorusunu kapsar — ne yapıyor (okunabilirlik), her şey doğru yerde mi (kohezyon), parçalar doğru mu konuşuyor (bağlılık) — ama bu yargıların hepsi sezgiseldir ve bir sonraki bölüm onları ölçülebilir bir temele oturtacaktır.

Üç Gözlem Sorusu

Bu yapısal teşhis bölümünü, kod tabanınızın sinir ağını incelemeye davet eden üç soruyla kapatalım.

Birinci Gözlem Sorusu. Kodunuzda bir getX().getY().getZ() zinciri arayın. Bu zincirin herhangi bir halkası — örneğin ortadaki nesnenin yapısı — değişse, bu satırı kullanan kaç yer kırılırdı? Onu ↗ Mesaj Zincirleri başlığındaki Hide Delegate ile gizleseydiniz, çağıran kod hangi bağımlılıklardan kurtulurdu?

İkinci Gözlem Sorusu. Son bir ayda en çok değiştirdiğiniz sınıfı düşünün. Onu hep aynı sebeple mi, yoksa farklı sebeplerle mi açtınız? Eğer farklı sebeplerle açtıysanız, bu bir Divergent Change’tir; o sınıfı değişim eksenlerine göre bölseydiniz, ona dokunan ekipler arasındaki çakışma azalır mıydı?

Üçüncü Gözlem Sorusu. Sisteminize yeni bir tür eklemeyi — yeni bir bahis durumu, yeni bir para birimi, yeni bir bildirim kanalı — hayal edin. Bunu yapmak için kaç ayrı dosyaya dokunmanız gerekir? Eğer cevap “çok” ise, orada bir Shotgun Surgery vardır; o kavramı tek bir eve toplasaydınız, bu sayı kaça inerdi — ve birini unutma riski ne kadar azalırdı?

Felsefi Kapanış

Bu bölümde teşhis gözümüzü en geniş ölçeğe, sistemin kendisine açtık. Ve burada, tek tek parçalara bakmanın yetmediği bir gerçekle yüzleştik: Bir sistemin sağlığı, parçalarının tek tek kalitesinde değil, aralarındaki ilişkilerin kalitesinde yatar. Her sınıfı tek tek mükemmel yapabilir, ama onları yanlış biçimde birbirine bağlayarak yine de çürümüş bir sistem üretebilirsiniz. Bağlılık, sistemin görünmez mimarisidir; ve görünmez olduğu için de en çok ihmal edilenidir.

Bu yüzden bölümü, sistemlerin doğasına dair bir düşünceyle kapatalım:

Bir sistem, parçalarının toplamı değil, parçaları arasındaki ilişkilerin örüntüsüdür. En temiz sınıfları yazıp onları yanlış bağlarsanız, ortaya yine bir bataklık çıkar; çünkü hastalık parçalarda değil, bağlardadır. İyi mimar, tek tek tuğlaların kalitesine değil, onların nasıl bir araya geldiğine bakar — neyin neyle konuştuğuna, neyin neye bağlı olduğuna, ve bir şeyi değiştirmenin neyi sarsacağına. Çünkü bir sistemi gerçekten anlamak, onun nesnelerini değil, nesneleri arasındaki sessiz anlaşmaları okuyabilmektir.

Sonraki Bölüm

↗ Bölüm 8: Ölçmek — Cyclomatic Complexity, Coupling, Cohesion ve Kod Metrikleri ile Kısım II’yi, “Teşhis”i tamamlıyoruz. Bu üç bölümde kötü kodu sezgiyle tanımayı öğrendik — eğitilmiş bir burunla. Ama bir önceki başlıkta itiraf ettiğimiz gibi, sezgi tartışmaya açıktır; “bu metot bana uzun geliyor” cümlesi öznel bir tartışma başlatır. Son teşhis bölümü, bu sezgiyi sayılarla buluşturacak. Bir metodun ne kadar karmaşık olduğunu ölçen cyclomatic complexity’yi, bir sınıfın ne kadar bağlı olduğunu ölçen afferent ve efferent coupling’i, ve bir sınıfın ne kadar kohezyon olduğunu ölçen LCOM’u tek tek ele alacağız — nasıl hesaplandıklarını, ne anlama geldiklerini ve daha da önemlisi nasıl yanlış kullanılabileceklerini. Çünkü “bu kod kötü kokuyor” demek bir sanat, “bu metodun karmaşıklığı 34” demek bir bilimdir; ve gerçek ustalık, ikisini birleştirmektir — sayıyı pusula olarak kullanıp, kararı yargıyla vermektir.

**İçindekiler… « Önceki [Bölüm 6: Code Smells (II) — Sınıf ve Veri Düzeyindeki Kokular] » Sonraki **[Bölüm 8: Ölçmek — Cyclomatic Complexity, Coupling, Cohesion ve Kod Metrikleri] » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis


메타데이터
post_id
da65708db26a
slug
refactoring-mevcut-kodun-tasarımını-i̇yileştirme-sanatı-bölüm-7-code-smells-iii-bağlılık-da65708db26a
url
https://medium.com/@sahinyelkenci/refactoring-mevcut-kodun-tasar%C4%B1m%C4%B1n%C4%B1-i%CC%87yile%C5%9Ftirme-sanat%C4%B1-b%C3%B6l%C3%BCm-7-code-smells-iii-ba%C4%9Fl%C4%B1l%C4%B1k-da65708db26a
canonical_url
https://medium.com/@sahinyelkenci/refactoring-mevcut-kodun-tasar%C4%B1m%C4%B1n%C4%B1-i%CC%87yile%C5%9Ftirme-sanat%C4%B1-b%C3%B6l%C3%BCm-7-code-smells-iii-ba%C4%9Fl%C4%B1l%C4%B1k-da65708db26a
author_url
https://medium.com/@sahinyelkenci
status
ok
fetched_at
2026-08-05 11:06:33