Kitap Özeti: A Philosophy of Software Design
Yazılım geliştirirken ortaya çıkan karmaşıklığı nasıl yönetmeliyiz?
Yazılım üzerine okuduklarımdan hiçbiri, bu kitaptaki kadar mantıklı ve gerçek hayatla örtüşen tavsiyeler vermemişti. Verilen örneklerden tutun da anlatımdaki akıcılığa kadar her bakımdan mükemmel. Fırsat bulursanız okuyun, istifade edin.
Kitabın ana fikri şu: Karmaşıklığı (complexity) modüllerde izole et, dış dünyaya basit bir arayüz sun.
Modül burada genel bir anlam ifade ediyor. Örneğin; bir sistemi oluşturan API’ların her biri veya bir proje içerisinde yer alan farklı sınıflar modül olarak düşünülebilir. Modülü kullanan her bir öge ise dış dünyadır. Modülün kullanılabilmesi için dış dünya tarafından bilinmesi gereken tüm detaylara da “arayüz” (interface) denir.
Yazılım süreçlerinde tüm planlama ve tasarımın en baştan yapıldığı “waterfall” anlayışının işe yaramadığı konusunda herkes hemfikir. Çünkü işe başlarken nasıl bir sistem tasarlanacağı konusunda kafalarda net bir resim olmuyor. Bundan kaçış yok. Tüm belirsizlikleri ilk günden giderme şansımız yok.
Geliştirme süreci boyunca tasarımlarımızın ve bu doğrultuda verdiğimiz kararların pek çok kere değişmesi gerekecek. İyi tasarımın amacı, bu değişiklikleri olabildiğince rahat yapabileceğimiz bir yapı kurmaktır. Bunun da ilk şartı, değişmesi muhtemel kararlarımızı küçük bir etki alanına hapsetmektir.
Modüller arası bağımlılıkları (dependency) minimumda tutarsak, değişiklik yapılacağı zaman elimiz kolumuz bağlanmamış olur.
Derin Modüller
Yazdığımız kodu kullanıcı gözünden değerlendirirsek, nasıl bir modül olsun isterdik? Kullanımı kolay olsun, fazla uğraştırmasın, fazla düşündürmesin ve işlevsel olsun. Yüzeyde basit görünsün, detayları ise kendi içinde çözsün. Kullanıcıyı uğraştırmamak için varsayılan (“default”) davranış genele hitap etsin. İhtiyaç varsa, özelleştirmeye fırsat tanısın.
Örnek olarak, unix dosya sistemini kullanmak için şu dört metod yeterli: open, close, read, write. Çünkü genelde I/O işlemleri “seri” şekilde yapılıyor. Eğer dosyanın rastgele bir yerine erişmemiz gerekiyorsa, o zaman lseek metodunu kullanabiliriz. Fakat sadece seri erişim olacaksa, bu metodu bilmemize gerek yok. Dosya sistemi gibi çetrefilli bir işlev, çok temiz ve kompakt bir arayüzle sunulmuş.
Derin modüllere bir diğer örnek de Java’daki GC (garbage collector). Arayüzü o kadar basit ki, yok. Arkada çaktırmadan çalışıyor, bellekte artık ihtiyaç duyulmayan nesneleri siliyor. Arayüzü basit ama yaptığı iş oldukça komplike; bütün referansları takip edip belleği düzenlemek kolay değil.
İdeal bir modülün arayüzü implementasyonundan çok daha basittir. Bu sayede:
Karmaşıklık modülün içinde kalır ve diğer modüllere bulaşmaz.
Modüllerde arayüz sabit kaldığı sürece, yapılan değişikliklerden bağımlı modüller etkilenmez. Eğer modülün arayüzü basitse, modül içerisinde değişiklik yapmak daha kolaydır. Çünkü değişikliklerin arayüze dokunma ihtimali azdır. Değişikliğe izin veren yapılar yazılım geliştirirken çok işimize yarar.
Bilgi Saklama
Bilgi saklama, “information hiding” veya “encapsulation” olarak adlandırılan bir kavram. Dış dünyaya bir işlev sunarken, bilmesi gereken en az bilgiyi vererek modüllerimizi derinleştirebiliriz. Detayları olabildiğince modülün içine gizleyebilirsek bunu başarırız.
Bilgi sızıntısı bunun tam tersidir, modüller arası bağımlılığı artırır. “A” modülünün dışarıya açtığı bilgiyi “B” modülü kullanmaya başladığı anda bağımlılıklara bir yenisi daha eklenmiş olur. Artık o bilgiyi ilgilendiren tüm değişiklikler her iki modülü de etkileyecektir.
Bu noktada, sızıntıyı önlemek için “A” ve “B” modüllerini birleştirmeyi düşünebiliriz. Eğer iki modülün sunduğu işlevi tek bir basit arayüzle dışarıya açabiliyorsak, derin bir modül elde edebiliriz.
Varsayılanlar
Modüllerimizi kullanacak olan dış dünyanın işini kolaylaştırmak için, tercihlerimizi en sık kullanım şeklini kolaylaştıracak biçimde yapmalıyız. Yukarıda unix dosya sisteminden verdiğimiz örnekteki gibi; dosya içeriğine erişirken varsayılan yöntem “seri erişim”, en sık duyulan ihtiyaca cevap veriyor.
Varsayılanlar sık duyulan ihtiyacı çözecek şekilde tasarlanmayınca, modülün içinde çözülmesi gereken tüm detaylar kullanıcının sırtına yüklenmiş oluyor. Kullanıcının koduna ve diğer modüllere sızıyor. Örneğin Java’da dosya okurken, “buffering” istiyorsak bunu ayrıca belirtmemiz gerekiyor. Neden varsayılanı bu şekilde değil ki? Bu tercihi niye kullanıcıya bırakıyorsun ve arayüzü karmaşıklaştırıyorsun?
new BufferedInputStream(new FileInputStream(new File(...Kullanım Kolaylığı
Eğer bir modülü kullanmak için kırk takla atmanız gerekiyorsa, bu durum modülün arayüzünün doğru tasarlanmadığını gösterir. Karmaşıklık modülün dışına taşmış demektir. Yazılım geliştirirken amacımız doğru soyutlamaları bulmaktır ki böylece kompleks işler tüm sisteme yayılmak yerine, modüllerin içine hapsolsun. Modül sahibi olarak önceliklerimizin başında, modülümüzü kullanacak olanların hayatını kolaylaştırmak gelmelidir.
Konfigürasyon
Karmaşıklığın kullanıcıya yüklendiği bir başka konu ise konfigürasyon parametreleridir. Bir API tasarladınız diyelim. Kullanıcıya da her ihtiyacını çözebilsin diye onlarca parametre sundunuz. Modülün içinde çözmeniz gereken konuları kullanıcıya bırakarak, aslında kullanıcının işini zorlaştırmış oldunuz.
Konfigürasyon parametreleri, çözümü zor problemleri veya yapamadığımız tercihleri kullanıcıya yüklemek bize kolay bir kaçış yolu sunuyor. Bu hataya düşmemeliyiz. Eğer konfigürasyonu kullanıcıya bırakmak zorundaysak da mutlaka mâkul varsayılan değerler belirlemeliyiz.
İdeal şartlarda modüller bir problemi “tamamen” çözmelidir. Konfigürasyonu kullanıcıya bırakıyorsak, çözüm yarım kalmış demektir ve buradaki karmaşıklık dış dünyaya sızmıştır.
Bizde hep şu düşünce vardır, “esnek bir yapı kuralım, parametrik olsun”. Bu çok yanlış. Esnek olsun, ama kendi içinde esnesin. Dışarıdan bakıldığında, esneklik ve ek parametreler kullanımı zorlaştıran ve kullanıcıyı tercih yapmak zorunda bırakan ek bir külfete dönüşüyor.
Kapanış
Kitaptan aklımda kalan temel birkaç fikri tekrar ederek özeti sonlandırıyorum.
Modüllerin dışarıdan görünen yüzleri (arayüz) basit, sunduğu işlev derin olmalı.
Kodda değişiklik yaparken sizi en çok zorlayan şey bağımlılıklardır. Soyutlama yaparken, çizgileri bağımlılıkları en aza indirecek şekilde çizin. Eğer basit bir arayüz tasarlayabiliyorsanız, gerektiğinde iki modülü birleştirmeyi bile düşünebilirsiniz.
Kod yazarken, kodu daha sonra okuyacak olan insanları düşünerek anlaşılır yazmalıyız. Kod bir kere yazılır, pek çok kere okunur.
Kitapta benim burada değinmediğim konular da var. Classlar ve fonksiyonlar nasıl yazılmalı, exception handling ve hatta commentlerle ilgili de pek çok faydalı bilgi var. Bu özetle kalmayın, kitabın kendisini de mutlaka okuyun.


