Clean Architecture Kitabından Notlar – Solid Prensipleri Üzerine
SOLID PRENSIPLERI
Günümüzde yazılım projelerinin problemleri kodun kırılgan ve kaskatı olması, bir noktayı değiştirdiğinizde başka yerlerin değişmesi, modüler yazmadığınızda bir şeyleri reuse ettiğinizde başka yerlerin bozulması ve çok fazla karmaşıklıktır. Solid prensipleri aslında Lemi orhanın tanımıyla bağımlılıkları yöneten prensiplerdir. Bir uygulamanın da artık çürüdüğünü gösteren şeylerden biri zaten budur. Birbiriyle alakalı olan şeylerin dağınık, alakasız şeylerin bir arada olması. Yani uygulamayı yazdığınızda çalışır ama çalışan bir uygulamayı değiştirmek istediğiniz zaman sorunlar başlar. Solid prensipleri uygulamayı değişime hazırlar. Değişimi yönetmeniz için SOLID’i anlamanız gerekir.
1. SINGLE RESPONSIBILITY – COHESION
Single responsibility en yanlış anlanan solid prensiplerinden birisidir. Yazılım mühendisleri tarafından fonksiyonları olabildiğinde küçük parçalara ayırmak olarak algılanır ve bir fonksiyonu ne kadar küçültüp parçalayabilirsek, single responsibility’e o kadar sadık kalmışız gibi bir algı oluşur. Ancak Robert C. Martinin de söylediği gibi single responsibility prensibi daha çok insanlarla ilgilidir.
Single responsbilitynin tanımı bir modülün değişmek için yalnızca bir sebebi olmasıdır. Peki burda değişmek için sebepten kasıt nedir? Sebep actor yani paydaştır. Bir modül yalnızca bir actor ya da paydaş grubuna refere olmalıdır. Yani bir modül finansal işlemlerden sorumlu ise sadece finans ekibinin talepleri üzerine, muhasebesel işlemlerden sorumlu ise sadece muhasebe ekibinin istekleri üzerine değişmelidir. Bir modül hem finans hem de muhasebe ekibinin değişim talepleri üzerine değişime uğramayı gerektirecek kodlar bulundurmamalıdır.
Peki hiç mi örnekteki gibi iki ekibin yani hem finans hem de muhasebe ekibinin kullanabileceği türden business fonksyonlarımız yok? Tabiki var. Ancak single responsibility prensibi, bu gibi fonksiyonların ileri zamanlarda finans ve muhasebe ekibinin isteklerine göre özelleşecek değişecek potansiyelleri olduğunu ve gerekirse çoklanması gerektiğini söyler.
2. OPEN-CLOSED PRENSIBI – COUPLING
Bir yazılım genişlemeye açık ancak değişime kapalı olmalıdır. Başka bir deyişle bir yazılım değiştirilmeye gerek duyulmadan genişleyebilmelidir. Peki bunu nasıl sağlarız? Doğru bir plug-in mimarisi inşaa ederek. Aslında bu da bir yazılım mimarisi inşaa ederken dikkate almamız gereken en önemli kriterlerden biridir. Ufak bir geliştirme bile yazılımda büyük değişikliklere yol açıyorsa yakın zamanda hataların geleceği kaçınılmazdır.
3. DEPENDENCY INVERSION – COUPLING
Dependency inversion bağımlılıkları nasıl yöneteceğimizden bahseden prensiptir. Yazılım bileşenlerimizi low level componentler ve high level componentler olarak düşünebiliriz. Öncelikle component derken neyi kastettiğimizden bahsedelim. Component yazılımda class, dll hatta web service gibi herhangi bir cohesive birleşen olabilir. Low level component ise ufak belli işlemleri gerçekleştiren, dış bağımlılıkları düşük subsystemlerdir. High level componentler(sık değişen) ise low level componentlerdeki(stabil) operasyonları call edip bir akış gerçekleştiren yapılardır.
Domain driven design yaklaşımı üzerinden örneklemek istersek, domain objelerimiz hiç bir bağımlılığı olmayan içerisinde critical business rulelar barındıran low level objelerdir. Application serviceler ise bu domain objelerini call ederek belli işlemler gerçekleştiren high level componentlerdir diyebiliriz.
Bu konseptte bakıldığı zaman high level componentlerin low level componentlere bir bağımlılığı olduğu görünmekte. Dependency inversion prensibi de tam olarak burada söze giriyor ve High level componentlerimizi low level componentlerimize bağımlı kılmamamız gerektiğini söylüyor.
Aşağıdaki, dependency inversion prensibi uygulanmamış akışa baktığımızda, high level componentlerden low level componentlere doğru bir bağımlık olduğunu görüyoruz. Hangi modülün hangi modülü kullanarak iş yaptığını gösteren akış’ı flow of control olarak adlandırırız. Aşağıda da gördüğümüz üzere, Dependency inversion kullanmadığımız takdirde bağımlılıkların yönünü flow of control dikte ediyor.
Peki bağımlılıkları tersine çevirmek için ne yapmalıyız?
Öncelikle high level componentlerimiz ile low level componentlerimiz arasına bir interface ekleyerek başlayabiliriz. Bunun sonucunda bağımlılıklar aşağıdaki şekilde yön değiştirecektir ve high level component artık low level component’e dair birşey bilmeyecek ve sadece konuştuğu ara yüzü tanıyacaktır. Bu arayüz arkadasında hangi low level componentin olduğu, bunun değişip değişmediği yerine başka bir yapı konup konmadığıyla ilgilenmeyecektir. Yani artık low level component’e bağlı olmayacaktır. Bu da bize bir plugin mimarisi inşa etme fırsatı verecektir.
Buradan da varacağımız nokta, esnek yazılım sistemlerinde esnek sistemler concerete classlara değil abstractionlara refere eden componentler içermelidir. Daha da teknik bir dille, java, c#gibi dillerdeki use, import include gibi statementlar sadece interface ya da abstract class gibi soyut classları içeri almaldıır. Concreteleri değil.
4. LISKOV SUBSTITUON – COUPLING
“Derived classes must be substitutable for their base classes.”
Liskov Substitution Principle MIT programlama metodolojileri grup liderliği yapan Barbara Liskov tarafından öne sürülmüştür. LS’nin bize söylediği olay, kodlarımızda herhangi bir değişiklik yapmadan alt sınıfları türedikleri üst sınıfların yerine kullanabilmeliyiz. Türeyen sınıf yani alt sınıflar ana(üst) sınıfın tüm özelliklerini ve metotlarını aynı işlevi gösterecek şekilde kullanabilme ve kendine ait yeni özellikler barındırabilmelidir.
5. INTERFACE SEGREGATION – COHESION
Kodu kullanan clientler gerekmeyen metotları kullanmaya zorlanmamalı ve coupling surface’si minimize edilmedilir.
Yukarıdaki figuru baz aldığımızda User1’in yalnızca op1i, user2’nin op2’y, user3ün de op3ü kullandığını düşünelim. Ancak yukarıdaki senaryoda User1 de op2 ve op3’e hiç kullanmayacak olsa bile bağımlı haldedir. Bu problem aşağıdaki gibi operasyonları interfacelere ayrıştırarak çözülebilir. Aşağıdaki şekilde ise artık User1 yalnızda U1Ops ve op1’e bağlı ancak OPS’e bağlı değildir. Böylece OPS’teki değişimler User1′, etkilemez.
Nesneler asla ihtiyacı olmayan property/metot vs içeren interfaceleri implement etmeye zorlanmamalıdır.