Örneklerle SOLID Prensipleri: Temiz Kod (Clean Code) Yazma Sanatı

SOLID prensipleri, nesne yönelimli kodu değişime dayanıklı hale getiren beş tasarım kuralıdır. 2012'den beri web projeleri, e-ticaret altyapıları ve kurum içi panellerle çalışıyorum; bir projenin ikinci yılında ne kadar rahat nefes aldığını çoğu zaman bu beş kuralın ilk günden ne kadar ciddiye alındığı belirliyor. Bu yazıda her prensibi kısa Python örnekleriyle, önce bozuk sonra onarılmış haliyle anlatıyorum.
Sınıf, kalıtım, kapsülleme ve çok biçimlilik gibi nesne yönelimli programlamanın temel kavramlarını bildiğinizi varsayıyorum. Onları tekrar anlatmıyorum; odak noktam, bu kavramları kullanırken hangi kararların kodu ileride kilitlediği ve hangilerinin serbest bıraktığı.
SOLID prensipleri nedir ve temiz kod için neden hâlâ önemli?
SOLID prensipleri, Robert C. Martin'in derlediği beş nesne yönelimli tasarım ilkesinin baş harflerinden oluşan kısaltmadır: tek sorumluluk, açık kapalı, Liskov yerine geçme, arayüz ayrımı ve bağımlılığın tersine çevrilmesi. Amaçları, yeni bir özellik eklerken eski kodu kırmadan ilerleyebilmenizi sağlamaktır.
Peki neden hâlâ önemli? Çünkü yazılımın asıl maliyeti ilk yazımda değil, bakımda ortaya çıkar. Bir fonksiyonu bir kez yazarsınız ama onu yıllarca okur, değiştirir ve test edersiniz. SOLID prensipleri tam olarak bu ikinci aşamayı ucuzlatmayı hedefler.
Ayrıca bu ilkeler dilden bağımsızdır. Örnekleri Python ile yazdım; ancak aynı mantık Java, C#, TypeScript, PHP ya da Kotlin için de geçerlidir. Değişen tek şey sözdizimidir.
SOLID prensipleri kimden çıktı, kısaltma nasıl oluştu?
İlkelerin çoğu Robert C. Martin'den önce de vardı. Açık kapalı ilkesini Bertrand Meyer 1988'de "Object-Oriented Software Construction" kitabında tanımladı. Liskov yerine geçme ilkesi ise Barbara Liskov'un 1987'deki bir konuşmasına ve Liskov ile Jeannette Wing'in 1994'te yayımladığı davranışsal alt tip makalesine dayanır.
Martin bu fikirleri 2000 yılında "Design Principles and Design Patterns" başlıklı makalesinde bir araya getirdi. SOLID kısaltmasını ise birkaç yıl sonra Michael Feathers önerdi. Yani SOLID bir kişinin icadı değil, onlarca yıllık deneyimin akılda kalan bir özetidir.
- S: Single Responsibility Principle, tek sorumluluk ilkesi.
- O: Open/Closed Principle, açık kapalı ilkesi.
- L: Liskov Substitution Principle, Liskov yerine geçme ilkesi.
- I: Interface Segregation Principle, arayüz ayrımı ilkesi.
- D: Dependency Inversion Principle, bağımlılığın tersine çevrilmesi ilkesi.
Temiz kod ile SOLID arasındaki ilişki ne?
Temiz kod, okuyan kişinin fazla çaba harcamadan anlayabildiği ve güvenle değiştirebildiği koddur. SOLID ise bu hedefe sınıf ve modül seviyesinde ulaşmanın yöntemidir. İsimlendirme, kısa fonksiyonlar ve anlamlı testler satır seviyesindeki temizliği sağlar; SOLID ise parçaların birbirine nasıl bağlandığını düzenler.
Şöyle düşünebilirsiniz: değişken adları çok güzel olan bir kod bile, her sınıf her şeye bağlıysa temiz olmaz. Tersine, mimarisi düzgün ama fonksiyonları 200 satırlık bir kod da okurunu yorar. Dolayısıyla ikisine birlikte ihtiyacınız var.
Benim sahada gördüğüm en yaygın sorun şudur: ekip isimlendirmeye özen gösterir ama bağımlılıkları hesaba katmaz. Sonuçta test yazmak zorlaşır, her küçük değişiklik beş dosyaya dokunur ve ekip değişiklikten korkmaya başlar.
Tek sorumluluk ilkesi (SRP) tam olarak ne söyler?
Tek sorumluluk ilkesinin klasik tanımı şudur: bir sınıfın değişmek için yalnızca tek bir nedeni olmalıdır. Martin 2014'te bu tanımı netleştirdi ve "sorumluluk" kelimesinin aslında bir paydaşı, yani değişiklik talep eden bir kişi ya da ekibi ifade ettiğini yazdı.
Bu ayrım önemli, çünkü SRP sık sık "bir sınıf tek bir iş yapsın" diye yanlış anlaşılıyor. Oysa asıl soru şudur: bu sınıfı değiştirmemi kim ister? Muhasebe ekibi, raporlama ekibi ve veritabanı yöneticisi aynı sınıfta değişiklik talep ediyorsa, o sınıfın üç sorumluluğu var demektir.
Örneğin bir fatura sınıfı hem tutarı hesaplıyor, hem PDF üretiyor, hem de veritabanına kaydediyorsa üç farklı paydaş aynı dosyada çalışır. Böylece birinin değişikliği diğerinin özelliğini bozabilir.
SRP'yi bir kod örneğiyle nasıl uygularsınız?
Önce ilkeyi ihlal eden bir sınıfa bakalım. Aşağıdaki sınıf hesaplama, biçimlendirme ve kalıcı kayıt işlerini aynı yerde topluyor:
class Fatura:
def __init__(self, kalemler, kdv_orani):
self.kalemler = kalemler
self.kdv_orani = kdv_orani
def toplam(self):
ara = sum(k.fiyat * k.adet for k in self.kalemler)
return ara * (1 + self.kdv_orani)
def pdf_olustur(self):
... # sayfa düzeni, font, logo
def kaydet(self, baglanti):
baglanti.execute("INSERT INTO faturalar ...")
Bu sınıfı üç parçaya bölersiniz. Hesaplama iş kuralı olarak kalır; biçimlendirme ve kaydı ise ayrı sınıflara taşırsınız:
class Fatura:
def __init__(self, kalemler, kdv_orani):
self.kalemler = kalemler
self.kdv_orani = kdv_orani
def toplam(self):
ara = sum(k.fiyat * k.adet for k in self.kalemler)
return ara * (1 + self.kdv_orani)
class FaturaPdfYazici:
def yaz(self, fatura): ...
class FaturaDeposu:
def __init__(self, baglanti):
self.baglanti = baglanti
def kaydet(self, fatura): ...
Artık PDF tasarımı değiştiğinde yalnızca yazıcı sınıfına dokunursunuz. Ayrıca hesaplama mantığını veritabanı olmadan, saniyeler içinde test edebilirsiniz. Kısacası SRP'nin ilk ödülü test edilebilirliktir.
Açık kapalı ilkesi (OCP) neden "değiştirme, genişlet" der?
Açık kapalı ilkesi, bir yazılım biriminin genişletmeye açık ama değiştirmeye kapalı olması gerektiğini söyler. Yani yeni bir davranış eklemek istediğinizde, çalışan ve testi yazılmış kodu açıp içini değiştirmek yerine yeni bir parça eklemeyi tercih edersiniz.
Bu kural ilk duyduğunuzda imkânsız gibi gelebilir. Ancak pratikte anlamı basittir: sık değişen noktaları önceden tahmin edip onları bir soyutlamanın arkasına koyarsınız. Böylece değişiklik geldiğinde yeni bir sınıf yazarsınız, eskisine dokunmazsınız.
Uyarı olarak şunu ekleyeyim: her şeyi genişletilebilir yapmaya çalışmak da bir hatadır. OCP'yi yalnızca gerçekten değişen ya da değişeceğine dair somut işaret olan yerlerde uygulayın. Aksi halde gereksiz katmanlar üretirsiniz.
OCP örneği: yeni bir ödeme yöntemi eklerken hangi kod değişir?
E-ticaret projelerinde en sık gördüğüm OCP ihlali, ödeme yöntemlerini bir if zinciriyle seçen fonksiyondur:
def odeme_al(yontem, tutar):
if yontem == "kart":
return kart_ile_ode(tutar)
elif yontem == "havale":
return havale_bildir(tutar)
elif yontem == "kapida":
return kapida_odeme_kaydet(tutar)
raise ValueError("Bilinmeyen yöntem")
Her yeni yöntem bu fonksiyonu açmanızı gerektirir. Bu yüzden yöntemleri ortak bir arayüzün arkasına alırsınız:
from abc import ABC, abstractmethod
class OdemeYontemi(ABC):
@abstractmethod
def ode(self, tutar): ...
class KartOdeme(OdemeYontemi):
def ode(self, tutar): ...
class HavaleOdeme(OdemeYontemi):
def ode(self, tutar): ...
def odeme_al(yontem: OdemeYontemi, tutar):
return yontem.ode(tutar)
Artık cüzdan ile ödeme eklemek istediğinizde yalnızca yeni bir sınıf yazarsınız. Üstelik mevcut kart ve havale testleri hiç değişmez. Python'daki soyut temel sınıfların ayrıntıları için resmi abc modülü belgesine bakabilirsiniz. Ödeme akışının genel kurgusunu ise e-ticaret danışmanlığı sayfamda anlatıyorum.
Liskov yerine geçme ilkesi (LSP) neyi korur?
Liskov yerine geçme ilkesi şunu söyler: bir alt sınıfın nesnesi, üst sınıfın beklendiği her yerde programın doğruluğunu bozmadan kullanılabilmelidir. Başka bir deyişle, alt sınıf üst sınıfın verdiği sözleri tutmak zorundadır.
Bu sözleri üç başlıkta toplayabiliriz. Alt sınıf girdiler konusunda daha katı olamaz, çıktılar konusunda daha gevşek olamaz ve üst sınıfın koruduğu kuralları (değişmezleri) bozamaz. Örneğin üst sınıf "negatif bakiye olmaz" diyorsa alt sınıf bunu esnetemez.
- Alt sınıf ön koşulları güçlendirmez; üst sınıfın kabul ettiği girdiyi alt sınıf da kabul eder.
- Son koşulları zayıflatmaz; üst sınıfın garanti ettiği sonucu alt sınıf da garanti eder.
- Alt sınıf değişmezleri korur; nesnenin her zaman doğru olması gereken durumu bozmaz.
- Beklenmedik istisna da fırlatmaz; üst sınıfın hiç fırlatmadığı bir hatayı alt sınıf eklemez.
LSP ihlali genellikle derleyiciden geçer, testte de çoğu zaman gözden kaçar. Sorun üretimde, alt sınıfın beklenmedik bir yerde kullanıldığı gün ortaya çıkar. Bu nedenle LSP en sinsi ihlaldir.
Kare ve dikdörtgen örneği LSP'yi nasıl bozar?
Matematikte her kare bir dikdörtgendir. Bu yüzden şöyle bir kalıtım kurmak mantıklı görünür:
class Dikdortgen:
def genislik_ayarla(self, g): self.g = g
def yukseklik_ayarla(self, y): self.y = y
def alan(self): return self.g * self.y
class Kare(Dikdortgen):
def genislik_ayarla(self, g): self.g = self.y = g
def yukseklik_ayarla(self, y): self.g = self.y = y
def test_alan(d: Dikdortgen):
d.genislik_ayarla(5)
d.yukseklik_ayarla(4)
assert d.alan() == 20
Bu test bir dikdörtgenle geçer ama bir kareyle başarısız olur, çünkü kare yükseklik ayarlanırken genişliği de değiştirir. Dikdörtgenin verdiği "genişlik ve yükseklik bağımsızdır" sözü bozulmuştur.
Çözüm, kalıtımı davranışa göre kurmaktır. Kare ve dikdörtgeni ortak bir "Sekil" soyutlamasının kardeş sınıfları yaparsınız; ikisi de alan hesaplar ama birbirinin yerine geçme iddiasında bulunmaz. Kısacası "bir X'tir" ilişkisini gerçek dünyaya değil, koddaki davranışa göre kurarsınız.
Arayüz ayrımı ilkesi (ISP) nedir?
Arayüz ayrımı ilkesi, hiçbir istemcinin kullanmadığı metotlara bağımlı olmaya zorlanmaması gerektiğini söyler. Büyük ve her şeyi kapsayan arayüzler yerine, belirli rollere odaklanan küçük arayüzler tasarlarsınız.
Şişman bir arayüzün bedeli şudur: onu uygulayan her sınıf, işine yaramayan metotlar için boş gövde ya da "desteklenmiyor" hatası yazar. Üstelik arayüzün kullanılmayan bir kısmı değiştiğinde, o kısmı hiç kullanmayan istemcileri de yeniden derlemeniz ve test etmeniz gerekir.
ISP'nin LSP ile yakın bir bağı da var. Bir sınıf bir metodu "desteklenmiyor" diye hata fırlatarak uyguluyorsa, büyük ihtimalle hem şişman bir arayüz hem de bir yerine geçme ihlali ile karşı karşıyasınız demektir.
ISP'yi Python'da Protocol ile nasıl uygularsınız?
Bir yazıcı sistemi düşünelim. Tek bir büyük arayüz yazdırma, tarama ve faks işlerini birlikte istiyor:
class OfisCihazi(ABC):
@abstractmethod
def yazdir(self, belge): ...
@abstractmethod
def tara(self, belge): ...
@abstractmethod
def faks_gonder(self, belge): ...
class BasitYazici(OfisCihazi):
def yazdir(self, belge): ...
def tara(self, belge): raise NotImplementedError
def faks_gonder(self, belge): raise NotImplementedError
Basit yazıcı iki metodu uygulayamıyor. Bunun yerine rolleri ayırırsınız. Python'da bunun için yapısal alt tiplemeye izin veren typing.Protocol sınıfı kullanışlıdır:
from typing import Protocol
class Yazdirabilir(Protocol):
def yazdir(self, belge) -> None: ...
class Taranabilir(Protocol):
def tara(self, belge) -> bytes: ...
class BasitYazici:
def yazdir(self, belge) -> None: ...
def rapor_bas(cihaz: Yazdirabilir, rapor):
cihaz.yazdir(rapor)
Artık rapor basan fonksiyon yalnızca yazdırma yeteneğine bağlı. Böylece çok işlevli bir cihaz da basit bir yazıcı da aynı fonksiyonla, gereksiz metot yükü olmadan çalışır.
Bağımlılığın tersine çevrilmesi ilkesi (DIP) ne anlatır?
Bağımlılığın tersine çevrilmesi ilkesinin iki maddesi vardır. Birincisi, üst seviye modüller alt seviye modüllere bağımlı olmamalıdır; ikisi de soyutlamalara bağımlı olmalıdır. İkincisi, soyutlamalar ayrıntılara bağlı olmamalıdır; ayrıntılar soyutlamalara bağlı olmalıdır.
Somut bir örnekle anlatayım. Sipariş servisi iş kuralıdır, yani üst seviyedir. MySQL bağlantısı ya da bir SMS sağlayıcısı ise ayrıntıdır. Sipariş servisi doğrudan MySQL sınıfını oluşturursa, veritabanı değiştiğinde iş kuralını da açmanız gerekir.
class SiparisDeposu(Protocol):
def kaydet(self, siparis) -> None: ...
class SiparisServisi:
def __init__(self, depo: SiparisDeposu):
self.depo = depo
def siparis_ver(self, siparis):
# iş kuralları burada
self.depo.kaydet(siparis)
class MySQLSiparisDeposu:
def kaydet(self, siparis) -> None: ...
Burada bağımlılık oku tersine döndü: arayüzü iş kuralı tanımlıyor, veritabanı sınıfı ona uyuyor. Dolayısıyla testte gerçek veritabanı yerine bellekte çalışan sahte bir depo verebilirsiniz.
DIP ile bağımlılık enjeksiyonu aynı şey mi?
Hayır, aynı şey değiller ama birbirlerini tamamlıyorlar. DIP bir tasarım ilkesidir ve bağımlılıkların hangi yöne akması gerektiğini söyler. Bağımlılık enjeksiyonu ise bir tekniktir: nesnenin ihtiyaç duyduğu parçaları kendisinin oluşturması yerine dışarıdan almasıdır.
Yukarıdaki örnekte deponun yapıcı metot üzerinden verilmesi bir enjeksiyondur. Ancak enjeksiyon yapmak tek başına DIP'e uyduğunuz anlamına gelmez. Eğer enjekte ettiğiniz tip yine somut MySQL sınıfıysa, bağımlılık yönü değişmemiş demektir.
Bir de şu yanılgıyı düzeltmek istiyorum: DIP için ağır bir enjeksiyon çerçevesine ihtiyacınız yok. Küçük ve orta projelerde, nesneleri uygulamanın giriş noktasında elle birbirine bağlamak çoğu zaman yeterli ve daha okunaklıdır.
SOLID prensipleri tek tabloda nasıl özetlenebilir?
Beş ilkeyi yan yana görmek, hangisinin hangi sorunu çözdüğünü hatırlamayı kolaylaştırır. Aşağıdaki tabloyu kod incelemesi sırasında hızlı bir kontrol listesi olarak da kullanabilirsiniz:
| İlke | Tek cümlelik özet | Tipik ihlal belirtisi | Yaygın çözüm |
|---|---|---|---|
| SRP | Bir sınıfın değişmek için tek nedeni olsun | Farklı ekipler aynı dosyada çakışıyor | Sınıfı paydaşlara göre bölmek |
| OCP | Genişletmeye açık, değiştirmeye kapalı | Her yeni tür için if/elif zinciri uzuyor | Soyutlama ve çok biçimlilik |
| LSP | Alt sınıf üst sınıfın yerine geçebilsin | isinstance kontrolleri, NotImplementedError | Kalıtımı davranışa göre kurmak |
| ISP | Kullanılmayan metoda bağımlılık olmasın | Boş gövdeli metotlar | Rol bazlı küçük arayüzler |
| DIP | İş kuralı ayrıntıya değil soyutlamaya bağlansın | Servis içinde doğrudan veritabanı nesnesi oluşturma | Arayüz ve bağımlılık enjeksiyonu |
Tablodaki belirtiler kesin kanıt değildir, yalnızca bakmanız gereken yeri gösterir. Örneğin bir if zinciri bazen tamamen masumdur, özellikle iki ya da üç sabit seçenek varsa.
SOLID prensipleri ne zaman aşırıya kaçar?
İlkeleri öğrendikten sonraki ilk tuzak, her şeye uygulamaktır. Tek bir uygulaması olan arayüzler, üç satırlık iş için beş sınıf ve takip etmesi zor dolaylılık katmanları bu tuzağın tipik ürünleridir. Kodu daha esnek yapmaya çalışırken okunması daha zor bir hale getirirsiniz.
Benim kullandığım basit kural şu: bir soyutlamayı ikinci somut ihtiyaç ortaya çıktığında eklerim, ilkinde değil. Yazılım topluluğunda buna "üç kuralı" ya da YAGNI (You Aren't Gonna Need It) yaklaşımı denir. Yani esnekliği spekülasyona değil, gerçek bir değişikliğe göre kurarsınız.
- Küçük betikler ve tek kullanımlık araçlarda SOLID'i zorlamayın.
- Prototip aşamasında hız önceliklidir; yapıyı ürün netleşince kurun.
- Tek uygulaması olan bir arayüz görürseniz, gerçekten gerekli olup olmadığını sorgulayın.
- Soyutlama sayısı arttıkça isimlendirmeye daha fazla özen gösterin.
Martin da 2020'deki "Solid Relevance" yazısında ilkelerin kural değil, yol gösterici olduğunu vurguluyor. Bence bu yaklaşım doğru: SOLID bir hedef değil, bakım maliyetini düşürmek için bir araçtır.
Mevcut bir kod tabanına SOLID prensipleri adım adım nasıl uygularsınız?
Çalışan bir projeyi baştan yazmak neredeyse hiçbir zaman doğru karar değildir. Bunun yerine küçük ve güvenli adımlarla ilerlersiniz. Benim projelerde izlediğim sıra şöyle:
- Değiştireceğiniz bölgenin davranışını karakterizasyon testleriyle sabitleyin.
- En çok değişen dosyaları sürüm kontrol geçmişinden bulun; önce onlara odaklanın.
- Büyük sınıfları paydaşlara göre bölün, yani önce SRP ile başlayın.
- Dış bağımlılıkları (veritabanı, e-posta, ödeme) arayüzlerin arkasına alın.
- Tekrar eden if zincirlerini, yeni tür eklendikçe çok biçimliliğe dönüştürün.
- Her adımdan sonra testleri çalıştırın ve küçük commit'lerle ilerleyin.
Bu sırayı önermemin nedeni basit: SRP ve DIP test edilebilirliği hemen artırır. Test güvencesi olmadan OCP ya da LSP düzeltmelerine girişirseniz, iyileştirme sırasında yeni hatalar üretme riskiniz yükselir.
Kod incelemesinde SOLID ihlalini nasıl fark edersiniz?
Kod incelemesi, ilkeleri ekip alışkanlığına dönüştürmenin en etkili yeridir. Ben inceleme sırasında birkaç soruyu sırayla soruyorum ve bu sorular çoğu ihlali erkenden yakalıyor.
- Bu sınıfı değiştirmek isteyecek kaç farklı kişi ya da ekip var?
- Yeni bir tür eklemek için mevcut bir fonksiyonu açmak gerekiyor mu?
- Kodda isinstance ya da tip kontrolüyle dallanma var mı?
- Bir sınıf, arayüzün bazı metotlarını boş ya da hata fırlatarak mı uyguluyor?
- İş kuralı, veritabanı ya da HTTP istemcisini kendi içinde mi oluşturuyor?
Bu sorular bir suçlama değil, bir sohbet başlatır. Ayrıca inceleme notlarında ilkenin adını anmak yerine somut etkisini yazmayı tercih ediyorum: "Bu sınıf SRP'yi ihlal ediyor" demek yerine "Rapor formatı değişince fatura hesaplamasını da test etmemiz gerekecek" demek, karşı tarafı çok daha kolay ikna ediyor. Böylece tartışma ilke ezberinden çıkar, gerçek bakım maliyetine odaklanır. Üstelik yeni katılan geliştiricilere ilkeleri teoriden değil, kendi kodları üzerinden öğretmenin de en iyi yoludur.
SOLID ilkeleri web projelerinde nerede karşınıza çıkar?
Kurumsal web siteleri ve paneller, SOLID'in gündelik sınandığı yerlerdir. Örneğin bir iletişim formu bugün e-posta gönderir, yarın CRM'e kayıt açar, öbür gün WhatsApp bildirimi ister. Bildirim kanallarını tek bir arayüzün arkasına aldıysanız, her yeni kanal yalnızca yeni bir sınıftır.
Ön yüz tarafında da durum farklı değil. Büyük ekiplerin ön yüzü parçalara böldüğü mimarileri micro frontend yazısında ele almıştım; oradaki sınır çizme mantığı SRP'nin sistem ölçeğindeki halidir.
Ayrıca temiz mimari performansı dolaylı olarak etkiler. Sorumluluğu belli bir kodda darboğazı bulmak kolaydır. Performans ölçümü tarafında Lighthouse ile site performans testi ve site hızının SEO'ya etkisi yazılarım işinize yarayabilir.
Test yazmak SOLID ile neden kolaylaşır?
Birim testi yazmanın en büyük engeli, test etmek istediğiniz kodun kendi bağımlılıklarını içeride oluşturmasıdır. Sipariş servisi veritabanına bağlanıyor, e-posta gönderiyor ve bir API çağırıyorsa, tek bir iş kuralını test etmek için üç dış sistemi ayağa kaldırmanız gerekir.
DIP ve SRP bu engeli ortadan kaldırır. Bağımlılıklar dışarıdan geldiği için testte sahte nesneler verirsiniz; sınıflar küçük olduğu için her testin kurulumu birkaç satırdır. Böylece testler hızlı çalışır ve ekip onları gerçekten çalıştırır.
class BellekDeposu:
def __init__(self):
self.kayitlar = []
def kaydet(self, siparis):
self.kayitlar.append(siparis)
def test_siparis_kaydedilir():
depo = BellekDeposu()
SiparisServisi(depo).siparis_ver({"id": 1})
assert len(depo.kayitlar) == 1
Bu test milisaniyeler içinde biter ve hiçbir dış sisteme ihtiyaç duymaz. Aynı depo arayüzünü üretimde MySQL, testte bellek, ileride belki bir bulut veritabanı karşılar; iş kuralı bu değişimlerin hiçbirinden haberdar olmaz. Benim deneyimimde bu tür hızlı testler, ekiplerin her commit öncesinde testleri gerçekten çalıştırmasının en önemli nedenidir. Yavaş testler ise zamanla atlanır ve güvence ortadan kalkar. Kısacası iyi tasarım ile kolay test aynı madalyonun iki yüzüdür.
SOLID ilkeleri tasarım desenleriyle nasıl ilişkilidir?
Tasarım desenleri, SOLID ilkelerinin tekrar tekrar karşılaşılan sorunlara uygulanmış hazır kalıplarıdır. Strateji deseni, OCP örneğindeki ödeme yöntemlerinin ta kendisidir: ortak bir arayüz ve değiştirilebilir uygulamalar. Adaptör deseni ise dış bir kütüphaneyi kendi arayüzünüze uydurarak DIP'i destekler.
Benzer şekilde dekoratör deseni, mevcut bir sınıfı açmadan ona davranış eklemenizi sağlar; bu da açık kapalı ilkesinin başka bir uygulamasıdır. Fabrika deseni ise nesne oluşturma sorumluluğunu tek bir yere toplar ve iş kuralını somut sınıflardan ayırır.
Yine de desenleri ezberleyip her yere yerleştirmek, ilkeleri aşırıya kaçırmanın bir başka biçimidir. Ben önce sorunu tarif etmeyi, sonra ona uyan deseni seçmeyi öneriyorum. Sorun yoksa desene de gerek yoktur.
- Strateji: değişen algoritmayı arayüzün arkasına alır, OCP ile uyumludur.
- Adaptör: dış kütüphaneyi kendi soyutlamanıza bağlar, DIP'i destekler.
- Dekoratör: sınıfı değiştirmeden davranış ekler, OCP ile uyumludur.
- Fabrika: nesne oluşturmayı tek noktada toplar, SRP ve DIP'e yardım eder.
SOLID fonksiyonel programlamada da geçerli mi?
İlkeler nesne yönelimli dünyada doğdu, ancak özleri paradigmadan bağımsızdır. Fonksiyonel bir kod tabanında sınıf yerine modül ve fonksiyon konuşursunuz; yine de tek sorumluluk, bağımlılıkları parametre olarak geçirme ve küçük sözleşmeler aynı değeri üretir.
Örneğin bir fonksiyonu başka bir fonksiyonu parametre olarak alacak şekilde yazmak, DIP'in fonksiyonel karşılığıdır. Benzer şekilde, bir fonksiyon tipinin imzasına uyan her fonksiyonun beklenen davranışı sergilemesi LSP ile aynı fikri taşır. Yani kavramlar değişmez, yalnızca uygulama biçimi değişir.
Python gibi çok paradigmalı dillerde ben ikisini birlikte kullanıyorum. Durum tutan ve paydaşı belli kavramlar için sınıf, saf dönüşümler için fonksiyon yazıyorum. Bu denge, kodu hem esnek hem sade tutuyor.
Yazılım ekibi seçerken SOLID bilgisini nasıl sorgularsınız?
Dışarıdan bir yazılım ekibiyle çalışacaksanız, SOLID'i ezbere sayabilmek iyi bir işaret değildir. Asıl işaret, ekibin ilkeleri kendi kodunda nasıl uyguladığını ve nerede bilinçli olarak esnettiğini anlatabilmesidir.
Görüşmede örneğin şu soruyu sorabilirsiniz: "Yeni bir ödeme yöntemi eklemek istesek kaç dosyaya dokunursunuz?" Cevap "bir yeni sınıf ve bir kayıt satırı" ise mimari büyük ihtimalle sağlıklıdır. "Duruma göre" cevabı ise ayrıntılı bir konuşmanın habercisidir.
Web projelerinde bu tür teknik kararları proje başında netleştirmeyi önemsiyorum; web tasarım hizmetimde kod yapısı da teslim kapsamına giriyor. Teknik tarafın arama motoruna yansıyan kısmını ise teknik SEO ipuçları yazısında topladım.
Son söz: temiz kod bir kural listesi mi, bir alışkanlık mı?
Bana göre temiz kod bir alışkanlıktır ve SOLID bu alışkanlığın iskeletidir. İlkeleri ezberlemek birkaç saat sürer; onları doğru yerde uygulayıp yanlış yerde bırakmayı öğrenmek ise yıllar alır. Bu yüzden her ilkeyi önce küçük bir örnekte deneyin, sonra gerçek projenizde bir modülle sınayın.
Son olarak şunu hatırlatayım: amaç kusursuz bir mimari değil, değişikliğin ucuz kalmasıdır. Yeni bir istek geldiğinde ekibiniz korkmadan "tamam" diyebiliyorsa, doğru yoldasınız demektir. Yazılım konusundaki diğer yazılarım için yazılım kategorisine göz atabilirsiniz.




