React, Redux ve Durum Yönetimi (State Management) Üzerine

“Biz veri taşıyoruz”

React çıktığından beridir bir gündemdir gidiyor, “state”i nasıl yöneteceğiz. Bu gerçekten çok şaşırtıcı!

Çünkü bilgisayar ilk ortaya çıktığından beri yaptığımız şey “state management” yani durum yönetmek. Bunun yeni bir gündem, yeni bir sorun olarak önümüze gelmesi, bu soruya cevap aramanın bir moda haline gelmesi, pıtır pıtır durum yöneten kütüphanelerin çıkması şaşırtıcı. Redux’lar, MobX’ler, yüzlerce tutorial, binlerce StackOverflow sorusu…

Yazıda hem bu noktaya gelinmesinin sebeplerini inceleyeceğim, hem de state nasıl yönetilir sorusuna kendimce cevap vereceğim.

Program Nedir?

Durum yönetimi, React’e özel bir konu veya bir kütüphaneye ihale edilecek harici bir iş değildir. Tüm yazılım işleri özünde durum yönetimi işleridir. Bugüne kadar ne kadar kod yazdıysanız o kadar durum yönetimi yaptınız.

Günümüz bilgisayarları kuş bakışı bir değerlendirmeyle Von Neumann mimarisine uyar. Yani yazdığınız kod dahil herşey veridir. Komut ile durum iç içedir. Çalışan programın bakış açısıyla olmasa bile, çalıştıran işlemci açısından çalışan kod dahi bilgisayarın o anki durumudur (state’idir).

bilgisayar(durum_1, girdiler_1) = durum_2

Bilgisayarı kullanma amacımız durumda yaptığı değişikliklerdir. Girdiler üzerinden birşeyler hesaplayıp sonuçlarını kullanmayan bir program anlamsız olurdu. Burada durum kavramının dış dünyaya da taşabildiğini belirtmek lazım.

Yan etki (side-effect) veya çıktı denilen olaylar da durumla beraber gruplanır, örneğin: ağ kablosuna bir paket göndermek, ekranda bir pikselin rengini değiştirmek, disk’e bir blok veri yazmak. Bu şekilde düşünmek için bilgisayarın dışındaki evreni komple bir durum belleği (store) olarak kabul etmek gerekir. İsterseniz bir kez de durum kavramını bilgisayarın içine izole edip, dış dünyayla kurulan ilişkiyi ayrı kategorize edelim:

bilgisayar(durum_1, girdiler_1) = { durum_2, çıktılar_2 }

Bu denklemde durum dediğimiz şey bilgisayarın belleği, girdiler ve çıktılar ise çevresel birimlerin (disk, ekran, ağ kartı vb.) parçası olmaktadır. Von Neumann’a göre durum (bellek) aynı zamanda durumun nasıl dönüştürüleceği bilgisini de içermektedir (program kodu), işlemci bir sonraki durumu program koduna, yani yine duruma bakarak hesaplar.

Özetlersek; assembly yazarken bellek adreslerine atama yaptığımızda, C yazarken pointer’larla oynadığımızda, Javascript’te bir değişkenin değerini değiştirdiğimizde aslında hep bir durum değişimi yapmış oluyoruz. Peki Redux kullandığımızda ne oluyor?

reducer(store_1, actions_1) = store_2

Redux mimarisiyle adeta Javascript ile kendi komut seti olan bir sanal makine tasarlayıp programımızı o sanal makine ile işletmiş oluyoruz. Buradan bize bir ders, bir de soru çıkıyor: Yazılım geliştirme, veri modelleme adına bildiğimiz genel bilgilerin hepsi (örneğin SOLID prensipleri) aslında React dünyasında da geçerli. Elimizdeki problem ve çözmek için kullandığımız araçlar aslında her zamankinin aynısı. Sadece birileri değilmiş taklidi yapıyor. İkinci olarak: Javascript dili ve işleten VM’si bize zaten aynı imkanları sunarken neden kulağımızı bu kadar tersten tutuyoruz? Nedir bilgisayara karşı bu paralel yapılanmanın sebebi?

Bize bir durum makinesi sağlamanın ötesinde Redux bize Observer Pattern ile (gözlemci deseni) çalışma alanı tanıyor. Yani gözlemlediğimiz bir objenin değerindeki değişiklikten hemen haberdar oluyoruz. Hatta denilebilir ki Redux’tan hoşlanma sebeplerinizin büyük kısmı bir durum makinesi sunması değil bir gözlemlenebilir nesne altyapısı sunmasıdır.

Zaten şu sıfırdan JS ile durum makinesi kurgulama olaylarından hazzetmediğim için ben projelerde Redux tercih etmiyorum. Onun yerine, çok daha hafif bir gözlemci API’si sunan MobX’i tercih ediyorum. Bence Redux’u tasarlayanlar nerede duracaklarını bilememişler ve çok çirkin bir API ortaya çıkartmışlar. Tıpkı Google’ın Angular’da yaptığı gibi. Javascript’in Redux’u aşma vakti de gelecek umarım.

Fonksiyonel Programlama

React’ı güzel kılan hemen hemen herşey fonksiyonel programlamadan alıntı olan fikirlerdir. React’ı beğendiniz ama fonksiyonel programlama ile hiç ilgilenmediyseniz çok şey kaçırıyorsunuz, benden söylemesi.
 
Fonksiyonel programlamada mümkün olduğunca durum değiştirmeyi programın uç noktalarına iter, izole eder, dar alana sıkıştırırsınız. Yazdığınız kodun büyük kısmının program durumuna değil, sadece kendi parametrelerine bağımlı olmasını istersiniz. Benzer şekilde mümkün olduğunca en az miktarda kodun program durumunu dönüştürmesini istersiniz.

fonksiyon(girdiler) = çıktılar

Sadece verili parametreleri dönüştüren kod, anlamak için programın geri kalanını akılda tutmayı gerektirmeyen koddur. Girdi ve çıktı ihtimalleri bellidir, dolayısı ile üzerinde rahat düşünülür. Tersine bakarsak; Programın durumuna bağlı sonuç üreten her kod parçasını anlamak, geliştirmek, debug etmek programın tüm veri modelini aklınızda bulundurmayı gerektirir. Dolayısı ile hataya çok açıktır.

Durumdan bağımsız, kendi kapsamında anlaşılabilen fonksiyoncuklardan oluşan bir program üzerinde çalışmak zihinsel olarak kolaydır. Durum değişimlerini mümkün olduğunca sade tutarsanız komplike bir projeyi bile başınız ağrımadan idare edersiniz.

React fonksiyonel yaklaşımlarla tasarlanmış bir görüntüleme (view) katmanı kütüphanesi. React yokken görüntüleme katmanına tarayıcı DOM’u ile bakıyorduk:

script(DOM_1, durum_1) = {DOM_2, görüntü_2}

React’ın temel önermesi ise şudur:

komponent(durum_1) = görüntü_1

Yani React ile ekranda birşeyleri göstermek bir durum değişimi olmaktan çıkar, browser DOM’unun durumu hakkında düşünmezsiniz, görüntü program durumunun doğrudan bir sonucu haline gelir.

Veri Yapıları 101

Bir verinin ihtiyaç duyduğunuzdan uzun süre yaşaması kötüdür. Anlaşılması, akılda tutulması gereken durumu büyütür. Miadından uzun yaşayan veri bir sorun kaynağıdır. Misal, global değişkenlere bu yüzden karşı çıkılır. Global değişkenler üzerine kurulu bir program büyüdükçe anlaşılmazlaşır.

İşin ilginci, mevcut durum kütüphaneleri tuttukları verinin ömrünü kısıtlama kapasitesine sahip değil. Store’lara attığınız veri window’a atanmış bir global değer gibi. Zihinsel zorluk yaratma açısından hiç bir farkı yok. Özünde Redux mimarisi gözlemlenebilir global değişkenler sağlıyor. Yaşam süresi kullanıldığı yerle kısıtlı olmayan bu değişkenler bir karmaşa yumağına dönüşmeye çok müsait. Ama bu konuda genel yazılım bilgimizi uygulamak yerine modayı takip ediyoruz ve “yaşasın global değişkenler” diyoruz.

React açısından bahsedilebilecek en az iki yaşam süresi çeşidi daha var (global olan hariç). Biri komponent yaşam süresi, diğeri de navigasyon yaşam süresi (bunun için bir router kütüphanesi kullanmanız lazım tabi ki). React size komponent kadar yaşayan bir gözlemlenebilir veri yapısını hazır olarak sunuyor (evet, this.setState’ten bahsediyorum).

Navigasyon yaşam süresi için de hemen hemen tüm router kütüphaneleri bir durum nesnesi alabiliyor: this.props.history.go(“/url”, { … state … }) gibi. İstenirse history.replace() gibi araçlarla bu durumu güncellemek mümkün.

Bu iki yaşam süresinden verimli olarak faydalanmak programınızın akışını kavramayı kolaylaştırır, karmaşıklığını azaltır. Bunları yok görüp, “sene 2017, herşeyi store’a doldurmamız lazım” demek bizi 1970’lerin bilgisayar bilimlerinden alınacak derslerden dahi mahrum bırakır.

Ne İşe Yaradı Şimdi Bu?

Bitirirken özetlemek istiyorum. Durum (state) React’a özel bir durum değil, sorunları da çözümleri de genel bilgisayar bilimleri ve yazılım mühendisliği kültürüne dair şeyler. Veri yapıları ve modelleme üzerine bildiğiniz herşey burada da geçerli, mesela SOLID prensipleri gibi. Nasıl React olmasa herşeyi window’a atmazdıysanız, bence React ile de öyle yapmamalısınız.

Observer pattern (gözlemci deseni) React’a çok yakışıyor. Çünkü veri modelindeki değişiklikleri görüntü katmanındaki değişikliklere otomatik olarak bağlamanızı sağlıyor. Böylece görüntü katmanı durumu diye ayrı bir derdiniz tasanız olmuyor. Daha fonksiyonel düşünebiliyorsunuz. Ama her gözlemlenebilir veri sonsuza kadar yaşamak zorunda değil. Uygun yerlerde Komponent ve Route durumlarından faydalanmak kodunuzu daha anlaşılır kılabilir.

Vakit ayırdığınız için teşekkürler:)