← Back to list

Refactoring — Mevcut Kodun Tasarımını İyileştirme Sanatı — Bölüm 12: Veriyi Organize Etmek —…

Kısım III — Katalog: Refactoring Mekaniği

Sahin Yelkenci · 2026-07-28 19:16 · 0 claps · 16.1 min read
#refactoring #encapsulation #value-objects #clean-code #object-oriented-design
Open on Medium ↗
Wiki topics: 💻 · Programming

Refactoring — Mevcut Kodun Tasarımını İyileştirme Sanatı — Bölüm 12: Veriyi Organize Etmek — Encapsulation ve Veri Yapıları

Kısım III — Katalog: Refactoring Mekaniği

İçindekiler

Kataloğun üçüncü ailesine geçiyoruz. ↗ Bölüm 10'da davranışı, ↗ Bölüm 11'de sorumlulukları düzenledik; şimdi sıra verinin kendisinde. Bu aile, ham ve dağınık veriyi disipline eden refactoring’leri içerir: erişimi kontrol altına almak, çıplak ilkelleri akıllı değer nesnelerine yükseltmek, ve veriyi değişmez kılmak. ↗ Bölüm 6'da teşhis ettiğimiz Primitive Obsession, Data Class ve Mutable Data kokularının baş tedavileri buradadır.

» Veri Ailesi — Kapsülleme ve Değer Nesneleri » Encapsulate Variable — Veriye Erişimi Kontrol Et » Replace Primitive with Object — İlkeli Değer Nesnesine Yükselt » Encapsulate Collection — Koleksiyonu Koru » Change Reference to Value — Değişmez Değer Nesnesi » Replace Derived Variable with Query — Türetilmiş Veriyi Hesapla » Değer Nesnesi Felsefesi — Veriyi Akıllı Kılmak » Aileyi Bir Arada Görmek — Ham Veriden Akıllı Nesneye » Üç Gözlem Sorusu » Felsefi Kapanış » Sonraki Bölüm » *https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis*

Veri Ailesi — Kapsülleme ve Değer Nesneleri

Yazılımda veri ve davranış birbirinden ayrılamaz; ama bu serinin başından beri gördüğümüz gibi, kötü organize edilmiş veri, üzerinde çalışan tüm davranışı zehirler. ↗ Bölüm 6'da bunun kokularını teşhis etmiştik: çıplak ilkellerle taşınan domain kavramları (Primitive Obsession), davranışsız veri kabukları (Data Class), ve kontrolsüz mutasyon (Mutable Data). Bu aile, o kokuların reçete dolabıdır; verinin nasıl paketlendiğini düzelterek, üzerinde çalışan her şeyin temelini sağlamlaştırır.

Bu ailenin iki büyük fikri vardır ve bölüm bu ikisinin etrafında döner. Birincisi kapsüllemedir (encapsulation): Veriye erişimi metotlar üzerinden kontrol altına almak. İkincisi değer nesnesidir (value object): Çıplak ilkelleri, kendi kurallarını ve davranışını taşıyan tiplere yükseltmek. Bu iki fikir birbirini tamamlar; kapsülleme veriye bir zırh giydirir, değer nesnesi ise o veriye bir akıl verir.

Kapsüllemenin neden bu ailenin temeli olduğunu vurgulamak gerekir. ↗ Bölüm 11'de Move Field’ın önkoşulunun kapsülleme olduğunu görmüştük; aslında kapsülleme, veriyle ilgili neredeyse her şeyin önkoşuludur. Veriye doğrudan (çıplak alan üzerinden) erişiliyorsa, onu değiştiremez, taşıyamaz, doğrulayamaz, izleyemezsiniz — çünkü erişim her yere dağılmıştır. Kapsülleme, verinin etrafında tek bir kontrol noktası (choke point) yaratır; o noktadan geçmeyen erişim olmadığı için, orada doğrulama ekleyebilir, gösterimi değiştirebilir, mutasyonu denetleyebilirsiniz. Veriyi yeniden organize etmenin ilk adımı, her zaman onu kapsüllemektir.

Bu bölümün ilerleyişi, ham veriden akıllı nesneye doğru bir yolculuktur. Önce veriyi kapsülleriz (erişimi kontrol et); sonra ilkelleri değer nesnelerine yükseltiriz (veriye davranış ve kural ver); sonra o değerleri değişmez kılarız (mutasyon hatalarını kökten yok et). Ham veri → kontrollü veri → akıllı veri → güvenli veri. Bu yolculuğun her durağı, ↗ Bölüm 7'de baştan beri kullandığımız Para ve Oran gibi değer nesnelerinin neden bu kadar güçlü olduğunu adım adım açıklayacak.

Veri ailesini bilmenin dersi şudur: Kötü organize edilmiş veri üzerinde çalışan tüm davranışı zehirler; bu aile iki büyük fikirle — kapsülleme (erişimi kontrol et) ve değer nesnesi (veriye akıl ver) — Primitive Obsession, Data Class ve Mutable Data kokularını tedavi eder; kapsülleme her şeyin temelidir çünkü verinin etrafında tek bir kontrol noktası yaratarak onu değiştirilebilir, taşınabilir ve doğrulanabilir kılar.

Encapsulate Variable — Veriye Erişimi Kontrol Et

Bu ailenin temel taşı, Encapsulate Variable’dır (Değişkeni Kapsülle; alanlar için Encapsulate Field). Veriye doğrudan erişimi, getter ve setter metotlarının arkasına alarak, o verinin etrafında bir kontrol noktası kurar. Bu, hem ↗ Bölüm 11'deki Move Field’ın önkoşulu hem de veriyle ilgili her ileri hamlenin başlangıç adımıdır.

Özet. Veriye doğrudan erişimi, erişimci metotların arkasına al: public alan; nesne.alanprivate alan; nesne.getAlan().

Motivasyon. Doğrudan erişilen veri — çıplak (public) bir alan ya da yaygın kullanılan bir değişken — değiştirilmesi, taşınması ve denetlenmesi zor olandır, çünkü erişim her yere dağılmıştır. Kapsülleme, bu erişimi tek bir noktadan geçirir, ve o nokta size bir güç verir: orada doğrulama ekleyebilir, loglama yapabilir, tembel yükleme uygulayabilir, ya da verinin iç gösterimini — çağıranları hiç etkilemeden — değiştirebilirsiniz. Kapsülleme, veriye bir tutamak (seam, ↗ Bölüm 4) yaratır; bu tutamak olmadan veriyi güvenle yeniden organize edemezsiniz. İlişkili: Move Field, Replace Primitive with Object (ikisinin de önkoşulu).

Mekanik.

  1. Değişken için bir getter (ve gerekiyorsa setter) oluştur.
  2. Tüm doğrudan erişimleri bul.
  3. Bunları erişimcilerle değiştir — her birinden sonra test.
  4. Değişkeni private yap — testleri çalıştır.

Örnek. Bahis sınıfında çıplak bir oran alanı var ve her yerden doğrudan erişiliyor:

// ÖNCE — çıplak alan, doğrudan erişim her yere dağılmış
public class Bahis {
    public BigDecimal oran;                            // public, kontrolsüz
}
// çağıranlar:  bahis.oran = yeniOran;   ...   x = bahis.oran;
// SONRA — Encapsulate Variable: erişim tek noktadan, doğrulama eklenebilir
public class Bahis {
    private BigDecimal oran;                           // private
    public BigDecimal getOran() { return oran; }
    public void setOran(BigDecimal oran) {
        if (oran.compareTo(BigDecimal.ONE) <= 0) {     // artık DOĞRULAMA mümkün!
            throw new GecersizOranException();
        }
        this.oran = oran;
    }
}

Kapsüllemenin asıl ödülü, setter’ın içindeki doğrulamadır: Artık geçersiz bir oran atamak imkânsızdır, çünkü her atama o tek kontrol noktasından geçer. Çıplak alan döneminde bu kontrolü her atama yerinde tekrarlamak (ya da unutmak) gerekiyordu. Bir nüans: Değişmez (immutable) veri için yalnızca bir getter yeterlidir; setter eklemeyin. Setter’ları en aza indirmek, ↗ Bölüm 6'daki Mutable Data kokusunu baştan önler — çünkü atanamayan bir alan, kontrolsüz mutasyona uğrayamaz. Kapsülleme, sonraki iki refactoring’in de — Replace Primitive ve Change Reference to Value — kapısını açar.

Encapsulate Variable’ı bilmenin dersi şudur: Veriye doğrudan erişimi erişimci metotların arkasına almak, verinin etrafında tek bir kontrol noktası yaratır — orada doğrulama, loglama ya da gösterim değişimi yapabilirsiniz, çağıranları hiç etkilemeden; bu, veriyle ilgili her ileri hamlenin temelidir, ve değişmez veride yalnızca getter bırakıp setter eklememek Mutable Data kokusunu baştan önler.

Çalışan örnek kodlara erişmek için: » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis

Replace Primitive with Object — İlkeli Değer Nesnesine Yükselt

Veriyi kapsülledikten sonra, onu yükseltebiliriz. Replace Primitive with Object (İlkeli Nesneyle Değiştir), ↗ Bölüm 6'daki Primitive Obsession kokusunun baş tedavisidir: bir domain kavramını çıplak bir ilkelden alıp, kendi kurallarını ve davranışını taşıyan bir değer nesnesine yükseltir.

Özet. Çıplak bir ilkelle taşınan domain kavramını, kurallı ve davranışlı bir değer nesnesine yükselt: BigDecimal oranOran.

Motivasyon. ↗ Bölüm 6'da gördüğümüz gibi, çıplak ilkeller dört zarar verir: doğrulama kaybolur, davranış kaybolur, tip karışıklığı doğar, mantık dağılır. Bir değer nesnesi bu dördünü de çözer: kavrama bir tip vererek doğrulamayı yapıcıya toplar (geçersiz nesne var olamaz), davranışı metot olarak ekler, ve tip güvenliği sağlar. Bu, baştan beri Para ve Oran ile kullandığımız tedavidir. Tedavi eder: Primitive Obsession. Nüans: Her ilkeli sarmayın; yalnızca kuralı, davranışı ya da kimliği olan domain kavramlarını.

Mekanik.

  1. Önce ilkeli kapsülle (Encapsulate Variable) — bu önkoşuldur.
  2. İlkeli saran basit bir değer sınıfı oluştur.
  3. Getter/setter’ı, değer sınıfını kullanacak şekilde güncelle — test.
  4. Değer sınıfına doğrulama ve davranış ekle — test.
  5. Çağıranları güncelle — test.

Örnek. ↗ Bölüm 6'daki çıplak BigDecimal oranOran nesnesine yükseltelim:

// ÖNCE — Primitive Obsession: oran çıplak BigDecimal, kuralı her yerde tekrar
public class Bahis {
    private BigDecimal oran;
    public BigDecimal getOran() { return oran; }       // kapsüllü ama hâlâ ilkel
}
// SONRA — Replace Primitive with Object: Oran kuralını ve davranışını taşır
public record Oran(BigDecimal deger) {
    public Oran {                                      // compact constructor: kural BİR yerde
        if (deger == null || deger.compareTo(BigDecimal.ONE) <= 0
                || deger.compareTo(MAKSIMUM_ORAN) > 0) {
            throw new GecersizOranException();
        }
    }
    public Oran carp(Oran diger) { return new Oran(deger.multiply(diger.deger)); }
}

public class Bahis {
    private Oran oran;       // artık akıllı değer nesnesi
    public Oran getOran() { return oran; }
}

Mekaniğin 1. adımına dikkat: Önce kapsülleme. Bu önkoşul boşuna değildir; ilkele her yerden doğrudan erişiliyorsa, onu nesneye çevirmek tüm o erişimleri kırardı. Ama erişim getter/setter’dan geçiyorsa, dönüşümü güvenle yaparsınız. Tedavinin ödülü, ↗ Bölüm 6'da vurguladığımız gibi en güçlü garantidir: Geçersiz bir Oran artık hiç var olamaz, çünkü doğrulama yapıcının içindedir; nesne oluştuysa, geçerlidir. Bu, geçerlilik yükünü her hesaplama noktasından alıp tipin kendisine taşır. Ve bu örnek, kapsülleme ile değer nesnesinin nasıl zincirlendiğini gösterir — biri diğerinin önkoşuludur, ↗ Bölüm 9'daki zincirleme.

Replace Primitive with Object’i bilmenin dersi şudur: Çıplak bir ilkelle taşınan domain kavramını kurallı ve davranışlı bir değer nesnesine yükseltmek Primitive Obsession’ı tedavi eder — doğrulamayı yapıcıya toplar (geçersiz nesne var olamaz), davranışı ekler, tip güvenliği verir; mekaniğin önkoşulu önce kapsüllemektir, çünkü ilkele doğrudan erişim varsa onu nesneye çevirmek tüm erişimleri kırardı.

Çalışan örnek kodlara erişmek için: » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis

Encapsulate Collection — Koleksiyonu Koru

Encapsulate Variable, tekil bir değeri kapsüllüyordu; ama koleksiyonlar özel bir tehlike taşır ve kendi hamlesini hak eder. Encapsulate Collection (Koleksiyonu Kapsülle), bir koleksiyon alanını, çağıranların onu doğrudan değiştirmesini engelleyecek şekilde korur. Çünkü bir koleksiyonu basit bir getter ile açığa çıkarmak, kapsüllemenin en sık fark edilmeyen sızıntısıdır.

Özet. Bir koleksiyonu, doğrudan değiştirilemeyecek şekilde koru; ekleme/çıkarma için açık metotlar sun: getBahisler() (canlı liste döner)getBahisler() (değiştirilemez görünüm) + bahisEkle().

Motivasyon. Bir koleksiyon alanını sade bir getter ile döndürürseniz — return bahisler; — çağıran, sahibin canlı iç listesine doğrudan erişir ve onu sahibin haberi olmadan değiştirebilir: kupon.getBahisler().add(yeniBahis). Bu, kapsüllemeyi tamamen kırar; sahip artık kendi değişmezlerini (örneğin maksimum bahis sayısı, para birimi tutarlılığı) zorlayamaz, çünkü ekleme onun kontrol noktasından geçmez. Encapsulate Collection, getter'ı değiştirilemez bir görünüm ya da kopya döndürecek şekilde değiştirir ve kuralları zorlayan açık ekle/çıkar metotları sunar. Tedavi eder: kapsülleme sızıntısı, gizli Mutable Data.

Mekanik.

  1. Sahibe açık ekle/çıkar metotları ekle (kuralları içlerinde zorla).
  2. Getter’ı, değiştirilemez bir kopya/görünüm döndürecek şekilde değiştir.
  3. Getter üzerinden değiştiren çağıranları bul, onları ekle/çıkar'a yönlendir — test.
  4. Testleri çalıştır — yeşil olmalı.

Örnek.

// ÖNCE — sızıntı: çağıran canlı listeyi doğrudan değiştirebilir
public class Kupon {
    private final List<Bahis> bahisler = new ArrayList<>();
    public List<Bahis> getBahisler() { return bahisler; }   // SIZINTI!
}
// çağıran kuralları atlayarak:  kupon.getBahisler().add(yeniBahis);
// SONRA — Encapsulate Collection: getter korumalı, ekleme kuraldan geçer
public class Kupon {
    private final List<Bahis> bahisler = new ArrayList<>();
    public List<Bahis> getBahisler() {
        return Collections.unmodifiableList(bahisler);      // değiştirilemez görünüm
    }
    public void bahisEkle(Bahis bahis) {
        if (bahisler.size() >= MAKSIMUM_BAHIS) {            // KURAL artık zorlanıyor
            throw new MaksimumBahisAsildiException();
        }
        bahisler.add(bahis);
    }
}

Tedavi edilmiş versiyonda, bir bahis eklemenin tek yolu bahisEkle'dir, ve o metot maksimum bahis kuralını zorlar; getter ise yalnızca okumaya izin veren, değiştirilemez bir görünüm döndürür. Bir nüans: Bu kokunun en sinsi tarafı, alanın private olmasının yetmemesidir. Çoğu mühendis alanı private yapar ve kapsüllediğini sanır; ama getter canlı, değiştirilebilir referansı döndürdüğü sürece, private hiçbir koruma sağlamaz — kapı arkadan açıktır. Gerçek kapsülleme, getter'ın da değiştirilemez bir şey döndürmesini gerektirir.

Encapsulate Collection’ı bilmenin dersi şudur: Bir koleksiyonu sade bir getter ile döndürmek, çağıranın sahibin canlı iç listesini doğrudan değiştirmesine izin vererek kapsüllemeyi kırar; tedavisi değiştirilemez bir görünüm döndürmek ve kuralları zorlayan açık ekle/çıkar metotları sunmaktır — ve en sinsi nokta şudur: alanı private yapmak yetmez, getter canlı referans döndürdüğü sürece kapı arkadan açık kalır.

Çalışan örnek kodlara erişmek için: » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis

Change Reference to Value — Değişmez Değer Nesnesi

Veriyi kapsülledik ve nesneye yükselttik; şimdi onu güvenli kılalım. Change Reference to Value (Referansı Değere Çevir), küçük bir nesneyi değişmez (immutable) ve değerine göre eşit kılar — kimliğine göre değil. Bu, değer nesnesi tasarımının temelidir ve ↗ Bölüm 5'teki Mutable Data kokusunun en güçlü panzehiridir.

Özet. Değiştirilebilir, kimliğine göre karşılaştırılan bir nesneyi, değişmez ve değerine göre eşit bir nesneye çevir.

Motivasyon. Değer nesneleri — Para, Oran gibi — değişmez olmalı ve kimliklerine göre değil, değerlerine göre karşılaştırılmalıdır. Değiştirilebilir bir referans nesnesi paylaşıldığında tehlikelidir: Onu bir yerde değiştirmek, ona referans veren her yerde değiştirir (aliasing hatası). İki farklı Kupon aynı değiştirilebilir Para nesnesine referans veriyorsa, birinin tutarını değiştirmek diğerininkini de sessizce bozar. Nesneyi değere çevirmek — değişmez yapmak ve equals/hashCode'u alanlara göre tanımlamak — bu hata sınıfını tamamen ortadan kaldırır ve nesne hakkında akıl yürütmeyi kolaylaştırır. Tedavi eder: Mutable Data, aliasing hataları. Tersi: Change Value to Reference.

Mekanik.

  1. Tüm alanları final yap, setter'ları kaldır (değişmez kıl).
  2. equals ve hashCode'u alanlara göre tanımla (değer eşitliği).
  3. Her “değişiklik” işleminin, mevcut nesneyi değiştirmek yerine yeni bir nesne döndürmesini sağla.
  4. Testleri çalıştır — yeşil olmalı.

Örnek.

// ÖNCE — değiştirilebilir, kimliğe göre eşit: aliasing hatası riski
public class Para {
    private BigDecimal tutar;
    public void setTutar(BigDecimal t) { this.tutar = t; }   // paylaşılırsa tehlike!
}
// SONRA — Change Reference to Value: değişmez, değere göre eşit (record bedava verir)
public record Para(BigDecimal tutar, ParaBirimi birim) {
    public Para arti(Para diger) {                           // değiştirmez, YENİ döner
        if (birim != diger.birim) throw new ParaBirimiUyumsuzException();
        return new Para(tutar.add(diger.tutar), birim);
    }
}
// p1.equals(p2) artık tutar ve birim aynıysa true — kimlik değil değer

Java’da record, equals ve hashCode'u alanlara göre bedava verir ve alanları final yapar — yani değer nesnesi tasarımını dilin kendisi destekler. Tedavinin gücü, aliasing hatasının kökten yok olmasıdır: Değişmez bir Para paylaşılsa bile, kimse onu değiştiremez; "değişiklik" her zaman yeni bir nesne üretir, eskisi olduğu gibi kalır. Bir nüans: Bu refactoring'in bir tersi vardır — Change Value to Reference. Bazen paylaşılan, değiştirilebilir bir kimlik tam da istediğiniz şeydir; örneğin bir Musteri, sistemde tek ve paylaşılan bir varlık olmalıdır, kopyalanan bir değer değil. Yargınızı, "bu kavram bir değer mi (Para, Oran) yoksa bir kimlik mi (Musteri, Hesap)?" sorusuna göre verin.

Change Reference to Value’yu bilmenin dersi şudur: Küçük bir nesneyi değişmez ve değerine göre eşit kılmak, paylaşılan değiştirilebilir referansların yol açtığı aliasing hatalarını kökten yok eder — değişiklik her zaman yeni bir nesne ürettiği için eskisi güvende kalır; ama bu yalnızca değerler (Para, Oran) içindir, paylaşılan bir kimlik gereken kavramlar (Musteri, Hesap) için tersi, Change Value to Reference uygulanır.

Çalışan örnek kodlara erişmek için: » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis

Replace Derived Variable with Query — Türetilmiş Veriyi Hesapla

Mutable Data ile savaşın son cephesi, türetilmiş verilerdir. Replace Derived Variable with Query (Türetilmiş Değişkeni Sorguyla Değiştir), başka verilerden hesaplanabilen ama bir alanda saklanan bir değeri kaldırır ve onu gerektiğinde hesaplanan bir sorguya çevirir. Çünkü saklanan türetilmiş veri, senkronizasyon hatalarının klasik kaynağıdır.

Özet. Başka verilerden türetilebilen, saklanan ve elle güncellenen bir alanı, gerektiğinde hesaplayan bir sorguyla değiştir: saklanan alansorgu metodu.

Motivasyon. Bir alan, başka alanlardan türetilen bir değeri saklıyorsa (örneğin bahislerden hesaplanan toplam ödeme), o bir hata kaynağıdır: Kaynak veri değişip de türetilmiş alanı güncellemeyi unuttuğunuzda, ikisi senkronizasyondan çıkar ve sistem tutarsız bir duruma düşer. Bu, elle korumanız gereken bir değişmeze (invariant) sahip bir Mutable Data biçimidir. Onu bir sorguyla — her seferinde taze hesaplanan bir metotla — değiştirmek, senkronizasyon problemini ortadan kaldırır: artık tek bir doğru kaynak vardır. Tedavi eder: Mutable Data, türetilmiş-durum hataları.

Mekanik.

  1. Türetilmiş değerin başka verilerden tam olarak hesaplanabildiğinden emin ol.
  2. Alanın okumalarını, değeri hesaplayan bir sorguyla değiştir — test.
  3. Alanı ve onu güncelleyen kodu kaldır — test.

Örnek.

// ÖNCE — türetilmiş alan, elle senkron tutulmalı (hataya açık)
public class Kupon {
    private final List<Bahis> bahisler = new ArrayList<>();
    private BigDecimal toplamOdeme = BigDecimal.ZERO;       // türetilmiş, saklanan
    public void bahisEkle(Bahis b) {
        bahisler.add(b);
        toplamOdeme = yenidenHesapla();   // güncellemeyi UNUTURSAN tutarsızlık!
    }
    public BigDecimal getToplamOdeme() { return toplamOdeme; }
}
// SONRA — Replace Derived Variable with Query: tek doğru kaynak, hep doğru
public class Kupon {
    private final List<Bahis> bahisler = new ArrayList<>();
    public void bahisEkle(Bahis b) { bahisler.add(b); }     // senkron derdi yok
    public BigDecimal toplamOdeme() {                        // her zaman taze, doğru
        return bahisler.stream()
            .map(Bahis::getiri)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}

Tedavi edilmiş versiyonda toplamOdeme artık saklanmaz, hesaplanır; bu yüzden onu güncellemeyi unutmak diye bir şey yoktur — değer her zaman bahislerden taze türetilir, dolayısıyla her zaman doğrudur. Bir nüans, ↗ Bölüm 8'deki performans uyarısını hatırlatır: Eğer hesap gerçekten pahalıysa ve çok sık çağrılıyorsa, dikkatli bir geçersizleştirmeyle önbelleğe alınmış türetilmiş veri haklı olabilir. Ama varsayılan tercih her zaman sorgudur; önbelleği yalnızca kanıtlanmış bir performans ihtiyacı varsa eklersiniz, erken optimizasyon olarak değil.

Replace Derived Variable with Query’yi bilmenin dersi şudur: Başka verilerden türetilip bir alanda saklanan değer, kaynak değişince güncellenmeyi unutursa senkronizasyondan çıkar ve tutarsızlık yaratır; onu her seferinde taze hesaplayan bir sorguyla değiştirmek tek doğru kaynak kurar ve bu hatayı kökten yok eder — varsayılan tercih sorgudur, önbellek yalnızca kanıtlanmış bir performans ihtiyacında, erken optimizasyon olarak değil.

Çalışan örnek kodlara erişmek için: » https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis

Değer Nesnesi Felsefesi — Veriyi Akıllı Kılmak

Bu bölümdeki refactoring’lerin hepsi, tek bir derin amaca hizmet eder: aptal veriyi akıllı nesneye dönüştürmek. Bu felsefeyi açıkça kurmak önemlidir, çünkü o, bu ailenin bireysel hamlelerini birleştiren ve onlara anlam veren şeydir. Kapsülleme, değer nesnesine yükseltme, değişmez kılma, türetilmiş veriyi sorguya çevirme — bunların hepsi, verinin kendi kurallarını ve davranışını taşımasını sağlamak için atılan adımlardır.

Karşıtlığı net görelim. ↗ Bölüm 6'daki anemik model (Data Class), veriyi aptal bir kap olarak tutar; o verinin mantığı — doğrulaması, davranışı, kuralları — etrafa, onu kullanan prosedürlere dağılır. Değer nesnesi yaklaşımı ise tam tersini yapar: veriyi, kendi mantığıyla aynı yere koyar. Oran nesnesi, hem oran değerini hem de "oran 1'den büyük olmalı" kuralını birlikte taşır. Para, hem tutarı hem de "farklı para birimleri toplanamaz" kuralını. Aptal veride mantık dağınıktır; akıllı veride mantık verinin yanındadır.

Bu yaklaşımın ödülleri, bu bölümün her başlığında gördüğümüz şeylerdir, ama şimdi bir arada: Doğrulama sınırda yapılır (geçersiz durumlar var olamaz), davranış veriyle birlikte yaşar (“sorma, söyle”), değişmezlik akıl yürütmeyi kolaylaştırır (aliasing yok), ve tek doğru kaynak türetilmiş-durum hatalarını yok eder. Tüm bu ödülleri tek bir ilkede toplayabiliriz: “geçersiz durumları temsil edilemez kıl” (make illegal states unrepresentable). İyi bir değer nesnesi, geçersiz bir değer oluşturmayı imkânsız kılar — 1'in altında bir Oran yapamazsınız, para birimlerini karıştıran bir Para toplaması kuramazsınız. Hata, çalışma zamanında yakalanacak bir şey olmaktan çıkar; tip sistemi onu en baştan imkânsız kılar.

Bu felsefe, bu serinin başka yerlerine de bağlanır: Değer nesnesi, Alan Güdümlü Tasarım’ın (Domain-Driven Design) temel yapı taşlarından biridir, ve bir yazılım mimarının domain modelini ifade etmedeki en güçlü araçlarındandır. Ama her felsefe gibi, bu da bir dogmaya dönüşebilir ve ↗ Bölüm 6'daki nüansı tekrarlamak gerekir: Her ilkeli akıllı bir nesneye çevirmeyin. Bir döngü sayacı, bir teknik bayrak, kendi tipini hak eden bir domain kavramı değildir. Sanat, hangi verinin akıllı olmayı hak ettiğini — yani hangisinin gerçek bir domain kavramı, kuralı ve davranışı olduğunu — tanımaktır. Akıllı veri güçlüdür; ama her veriyi akıllı yapmaya çalışmak, gürültü üretir.

Değer nesnesi felsefesini bilmenin dersi şudur: Bu ailenin tüm hamleleri aptal veriyi akıllı nesneye dönüştürmeye hizmet eder — mantığı verinin yanına koyarak, en güçlü ilkeyi hayata geçirir: geçersiz durumları temsil edilemez kılmak, böylece hata çalışma zamanında yakalanacak bir şey değil, tip sisteminin en baştan imkânsız kıldığı bir şey olur; ama her ilkeli akıllı yapmaya çalışmak gürültü üretir, sanat hangi verinin gerçek bir domain kavramı olduğunu tanımaktır.

Aileyi Bir Arada Görmek — Ham Veriden Akıllı Nesneye

Bu ailenin hamlelerini tek tek tanıdık; şimdi onları bir yolculuk olarak görelim. Çünkü bu refactoring’ler izole hamleler değil, ham veriden akıllı nesneye giden bir ilerlemenin durakarıdır. Aşağıdaki tablo, bir durumla karşılaştığınızda hangi hamleye uzanacağınızı gösterir:

Durum / Koku Hamle Veriye doğrudan, kontrolsüz erişim Encapsulate Variable Domain kavramı çıplak ilkel (Primitive Obsession) Replace Primitive with Object Koleksiyon getter’la sızıyor Encapsulate Collection Paylaşılan değiştirilebilir değer (aliasing) Change Reference to Value Saklanan türetilmiş veri senkron koparıyor Replace Derived Variable with Query

Bu hamlelerin bir sıra oluşturduğuna dikkat edin, çünkü her biri bir öncekinin üzerine kurulur. Önce Encapsulate Variable ile veriye erişimi kontrol altına alırsınız — bu, geri kalan her şeyin önkoşuludur, çünkü kontrol noktası olmadan veriyi ne yükseltebilir ne taşıyabilirsiniz (↗ Bölüm 11'deki Move Field’ın da önkoşuluydu). Sonra Replace Primitive with Object ile ilkeli akıllı bir nesneye çevirirsiniz. Sonra Change Reference to Value ile o nesneyi değişmez kılarsınız. Encapsulate Collection ile koleksiyonları korur, Replace Derived Variable ile türetilmiş durumu yok edersiniz. Her adım, verinin yanlış gidebileceği bir yolu kapatır: kontrolsüz erişim, eksik doğrulama, tip karışıklığı, aliasing, senkron kopması.

Ailenin altında yatan tek tema, veri disiplinidir. ↗ Bölüm 6'da teşhis ettiğimiz üç veri kokusu — Primitive Obsession, Data Class, Mutable Data — bu ailenin hamleleriyle sistematik olarak tedavi edilir. Ve aile, ↗ Bölüm 9'daki tersine çevrilebilirliği de taşır: Change Reference to Value ↔ Change Value to Reference. Bir kavramın değer mi yoksa kimlik mi olduğuna göre, iki yöne de hareket edebilirsiniz. Tüm bunların sonunda elde ettiğiniz şey, üzerinde çalışan davranışın güvenle inşa edilebileceği sağlam bir temeldir; çünkü iyi paketlenmiş veri, ↗ Bölüm 6'da söylediğimiz gibi, kendi kurallarını ve davranışını taşıyan akıllı bir nesneye dönüşür.

Aileyi bir arada görmenin dersi şudur: Veri organize etme refactoring’leri izole hamleler değil, ham veriden akıllı nesneye giden bir ilerlemenin duraklarıdır — kapsülleme her şeyin önkoşuludur, sonra yükseltme, değişmez kılma, koruma ve türetilmişi sorgulama gelir; her adım verinin yanlış gidebileceği bir yolu kapatır, ve ortak tema, üzerinde çalışan tüm davranışın temeli olan veri disiplinidir.

Üç Gözlem Sorusu

Bu veri ailesini, kendi kodunuzdaki veri disiplinini sınayan üç soruyla kapatalım.

Birinci Gözlem Sorusu. Kod tabanınızda bir koleksiyon getter’ı bulun — getX() bir List ya da Set döndüren bir metot. O getter, iç koleksiyonun canlı referansını mı, yoksa değiştirilemez bir görünümünü mü döndürüyor? Eğer canlıysa, bir çağıran onu doğrudan değiştirerek sahibin hangi kuralını sessizce atlayabilir?

İkinci Gözlem Sorusu. Sisteminizde bir para tutarını, bir telefon numarasını ya da bir kimlik numarasını çıplak bir String veya BigDecimal olarak taşıdığınız bir yer seçin. O kavramın doğrulaması (geçerli olup olmadığı kontrolü) kaç farklı yerde tekrarlanıyor? Onu ↗ Replace Primitive with Object ile bir değer nesnesine yükseltseydiniz, "geçersiz bir değer var olamaz" garantisi kaç hatayı baştan önlerdi?

Üçüncü Gözlem Sorusu. Bir sınıfta, başka alanlardan hesaplanabilen ama yine de saklanan ve elle güncellenen bir alan var mı — bir toplam, bir sayı, bir durum? O alanı güncellemeyi unutabileceğiniz bir kod yolu var mı? Onu ↗ Replace Derived Variable with Query ile bir sorguya çevirseydiniz, bu senkronizasyon riski tamamen ortadan kalkar mıydı?

Felsefi Kapanış

Bu bölümde, davranışın ve sorumluluğun altındaki en temel katmana — verinin kendisine — indik. Ve gördük ki, iyi yazılım veriye saygı duymakla başlar: Veriyi açık bir kap olarak bırakmak yerine, ona bir zırh (kapsülleme), bir akıl (değer nesnesi) ve bir güvenlik (değişmezlik) vermek. Çünkü üzerine inşa ettiğiniz her davranış, ancak altındaki veri sağlamsa sağlam olabilir.

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

Aptal veri, dilini bilmediğiniz bir ülkeden gelen bir mektup gibidir: Onu okumak için her seferinde bir tercümana — onu doğrulayan, biçimlendiren, kurallarını bilen bir prosedüre — koşmanız gerekir. Akıllı veri ise kendi dilini konuşur; ona “geçerli misin?” diye sorduğunuzda cevap verir, “kendini topla” dediğinizde toplar, ve onu yanlış kullanmaya kalktığınızda kibarca reddeder. İyi mühendis, veriyi pasif bir yük olmaktan çıkarıp, kendi onurunu koruyan bir vatandaşa dönüştürür. Çünkü bir sistemin sağlamlığı, en alttaki tuğlaların — verinin — ne kadar dürüst olduğunda başlar; çürük bir temelin üstüne ne kadar zarif bir bina dikerseniz dikin, bina çürük temelin kaderini paylaşır.

Sonraki Bölüm

***↗ Bölüm 13: Koşullu Mantığı Sadeleştirmek — Guard Clauses, Polimorfizm ve Null Object ile kataloğun dördüncü ailesine geçiyoruz. ***İş kurallarının çoğu koşullu mantıkta yaşar — ve canlı bahis gibi bir sistemde, bir bahsin kabulü, bir ödemenin hesabı, bir kuponun durumu, hepsi düzinelerce if ve switch ile örülüdür. Bu aile, o karmaşık koşullu mantığı sadeleştiren en güçlü hamleleri içerir: Decompose Conditional ile koşulları isimlendirmek, Replace Nested Conditional with Guard Clauses ile ↗ Bölüm 5'teki doom piramidini düzleştirmek, ve en güçlüsü, Replace Conditional with Polymorphism ile ↗ Bölüm 7'deki dağınık switch'leri nesnelere dönüştürmek. Ayrıca Introduce Special Case ve Null Object ile null kontrollerini zarif bir şekilde ortadan kaldırmayı göreceğiz. Veriyi akıllı kıldık; şimdi o verinin üzerinde çalışan kararları akıllı kılma zamanı.

**İçindekiler… « Önceki [Bölüm 11: Özellikleri Nesneler Arasında Taşımak — Move Method, Move Field] » Sonraki *[Bölüm 13: Koşullu Mantığı Sadeleştirmek — Guard Clauses ve Polymorphism]https://gitlab.com/sahin.yelkenci2/refactoring-canli-bahis


메타데이터
post_id
1ea0da98f7fc
slug
refactoring-mevcut-kodun-tasarımını-i̇yileştirme-sanatı-bölüm-12-veriyi-organize-etmek-1ea0da98f7fc
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-12-veriyi-organize-etmek-1ea0da98f7fc
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-12-veriyi-organize-etmek-1ea0da98f7fc
author_url
https://medium.com/@sahinyelkenci
status
ok
fetched_at
2026-08-11 18:06:04