Eczaneden aldığınız en sıradan ağrı kesicinin bile kutusunu açtığınızda, içinden akordeon gibi katlanmış, küçük puntolarla basılmış upuzun bir kâğıt çıkar: İlaç Prospektüsü.
Çoğu insan o kâğıdı okumadan çöpe atar. Oysa o minik kâğıt, farmakoloji biliminin insan hayatına duyduğu saygının en somut belgesidir. Orada sadece ilacın baş ağrınızı nasıl geçireceği yazmaz. Sayfalar boyunca şunlar sıralanır:
"Karaciğer enzimlerini yükseltebilir, mide mukozasında kanamaya yol açabilir, tansiyonu düşürebilir, astım hastalarında bronkospazmı tetikleyebilir..."
Farmakolojide bir kural vardır: Yan etkisi olmayan bir ilaç, etki de etmiyordur.
Bir kimyasalı biyolojik bir organizmaya soktuğunuzda, onun sadece tek bir organı hedef alıp geri kalan her şeyi tamamen masum bırakmasını bekleyemezsiniz. Başarılı bir hekim, sadece ilacın tedavi edici ana etkisine (terapötik etki) odaklanan değil; o ilacın vücudun diğer köşelerinde tetikleyeceği olası "yan etkileri" (side effects) öngören ve yöneten hekimdir.
Hastanın baş ağrısını kesip böbreğini iflas ettiren bir tedavi, tıp değil; cinayettir.
Peki yazılım dünyasına dönelim: Yazdığımız fonksiyonların, sınıfların ve modüllerin yan etkilerini ne kadar biliyor ve yönetiyoruz?
Masum Görünen Fonksiyonların Gizli Suçları
Fonksiyonel programlamanın (Functional Programming) en temel ve zarif kavramlarından biri "Pure Function" (Saf Fonksiyon) tanımıdır.
Saf bir fonksiyon, tıpkı matematiksel bir denklem gibidir ():
- Aynı girdi verildiğinde her zaman ama her zaman aynı çıktıyı üretir.
- Kendi kapsamı (scope) dışındaki hiçbir şeyi değiştirmez. Dış dünyayla gizli bir temasa girmez.
Peki gerçek hayattaki kod tabanlarında ne görüyoruz? Tam bir farmakolojik kaos.
Bir projeyi incelersiniz. Karşınıza şöyle bir fonksiyon çıkar:
getUserProfile(userId)
Fonksiyonun ismine ve imzasına bakarsınız; ne kadar masum, değil mi? Beklenti çok basittir: Kullanıcının ID'sini ver, sana kullanıcının profil bilgilerini içeren bir nesne dönsün.
Ama fonksiyonun gövdesini (implementasyonunu) açtığınızda dehşete düşersiniz:
- Kullanıcının profilini çekerken aynı anda arka planda veritabanındaki
last_active_atsütununu güncelliyordur. - Yetmezmiş gibi, global bir
activeUserCountsayacını bir artırıyordur. - O da yetmezmiş gibi, üçüncü parti bir analitik servisine sessizce bir HTTP isteği gönderiyordur.
- Ve eğer analitik servisi zaman aşımına uğrarsa, profil bilgisini de getiremeyip hata fırlatıyordur!
İşte bu, prospektüsü olmayan ve arkasında gizli cinayetler işleyen bir fonksiyondur.
Siz sadece hastaya bir vitamin verdiğinizi sanırsınız; ama o vitamin gizlice hastanın kalbini durdurur.
"Bunu Değiştirdim, Neden Orası Patladı?"
Geliştiricilerin hayatını cehenneme çeviren o meşhur soruyu hatırlayın:
"Ben sadece kullanıcı profil sayfasındaki küçük bir detayı değiştirdim; ana sayfadaki sipariş modülü neden çöktü?"
Cevap basittir: Yönetilmeyen, belgelenmemiş ve gizlenmiş yan etkiler (unintended side effects).
Bir fonksiyon, kendi parametreleri ve dönüş değeri dışındaki bir dünyayı kurcalamaya başladığında (global bir değişkeni değiştirmek, dosya sistemine bir şeyler yazmak, paylaşılan bir önbelleği geçersiz kılmak); sistemin öngörülebilirliği (determinism) yok olur.
Tıpkı aynı anda 5 farklı ilaç kullanan ve hangi ilacın hangi organı bozduğunu doktorların bir türlü çözemediği polifarmasi (çoklu ilaç zehirlenmesi) hastaları gibi; sisteminiz de kendi yan etkilerinin yarattığı kimyasal fırtınada boğulmaya başlar.
Bir kodu test edememenizin (untestable code) ana sebebi de budur. Bir fonksiyonun çalışması için arka planda 10 tane gizli servisin ayakta olması gerekiyorsa, o fonksiyon bir mühendislik harikası değil; bulaşıcı bir patojendir.
Yan Etkileri Sınırlarda Tutmak
Bir yazılım sisteminin tamamen yan etkisiz olması elbette imkânsızdır. Çünkü en nihayetinde bir yazılımın varoluş sebebi yan etki üretmektir: Veritabanına kayıt atmak, ekrana piksel basmak, ağ üzerinden paket yollamak birer yan etkidir. Yan etki olmazsa yazılım hiçbir işe yaramaz.
Ancak gerçek yazılım mimarisi, tıpkı iyi bir hekimlik gibi şu prensiple çalışır:
Yan etkileri olabildiğince tecrit et (isolate), sınırda tut (boundaries) ve mutlaka belgele.
- İş Mantığını (Core Domain) Saf Tutun: Şirketinizin en kritik hesaplamaları, fiyatlandırma algoritmaları ve veri dönüşümleri saf fonksiyonlardan oluşmalıdır. Ne veritabanı bilmeli, ne HTTP bilmelidir. Sadece ham veriyi almalı, işlemeli ve sonucu dönmelidir. Böylece 10 yıl sonra bile o fonksiyonu gözünüz kapalı test edebilirsiniz.
- Yan Etkileri Dış Çepere İtin (I/O at the Edges): Veritabanı yazımları, ağ çağrıları ve kuyruk işlemleri sistemin en dış katmanında (Controller veya API handler) açık ve şeffaf bir şekilde yapılmalıdır. Fonksiyonun içinde gizlenmemelidir.
- Fonksiyonun Prospektüsünü Yazın: Bir fonksiyon dış dünyada bir şeyleri değiştiriyorsa, bunu isminde veya tip tanımında haykırmalıdır.
getUser()değil, gerekiyorsagetUserAndTrackAnalytics()olmalıdır. Geliştirici o ilacı sisteme verdiğinde başına ne geleceğini bilmelidir.
Ağrıyı kesmek maharet değildir. Maharet; hastanın karaciğerini, böbreğini ve kalbini koruyarak o ağrıyı dindirebilmektir.
Kod yazarken kendinize sorun:
"Bu fonksiyon sadece vaat ettiği işi mi yapıyor, yoksa arkasında gizlice bir organı iflasa mı sürüklüyor?"
Eğer bu sorunun cevabını veremiyorsanız; yazdığınız şey bir teknoloji değil, patlamaya hazır kimyasal bir zehirdir.