Declarative Yaklaşım ve Tasarım

Tasarım #6Yazılım #35React #1SwiftUI #3Flutter #2

Suat Karakuşoğlu yazdı.

Figure 1: Photo by Artem Maltsev

İçerik

Merhabalar,

Arayüz tasarlamak yazıyla resim yapabilmek.

Yalnızca, resmi sürekli güncel bilgilere göre düzenlemeniz, bazende etkileşimli hale getirmeniz gibi bir ressamdan daha fazlasını gerektirebiliyor.

Uzun zamandır türlü diller ve araçlar ile farklı tuvallare arayüz tasarımları yapmaktayız.

Bu araçlar hakkında ve arayüz tasarımında değişen pratiklerin nihayetinde nasıl declarative yaklaşımda buluştuğundan bahsedeceğim.

Imperative Tasarım

Declarative taraftan bahsetmeden önce daha çok pratik ettiğimiz imperative yaklaşımdan bahsedelim.

Bir ekranın nasıl çizilmesi gerektiğine dair tüm adımları özellikle belirterek uyguladığımız tasarım yaklaşımı imperative tasarım olarak değerlendirilebilir.

Declarative veya Imperative yaklaşım yalnızca UI’da karşımıza çıkmıyor.

Henüz üniversite yıllarında sıralama algoritmalarına denk geldiğimde heyecanlanmıştım, bubble’indan merge sort’una, timsort’undan quick sort’una.

Hepsi ile örnekler yapmış görselleştirmeler yapmaya çalışmıştım. Sonrasında ilk staja başladığım yerde kullanıcıları hangi algoritma ile nasıl sıralıyorsunuz gibi bir soru sorduğumda ’SORT’ yazıyoruz SQL’de ve o bu işi yapıyor diye cevap almıştım :).

Beyan ve Indirection

Sadece beyan / declare ederek işinizin ne olduğunu belirtip, sistemin bunu yapmasını beklemenin elbette bir çok avantajı var.

Protocol veya Interface dediğimiz yapılar ile dependency inversion yapıyorsunuz ve daha sonra yeni bir sıralama algoritması keşfedildiğinde, sistem sizi herhangi bir değişikliğe gerek bırakmadan daha verimli hale geliyor.

Declarative yani ’ol’ de olsun ’sırala’ de sıralasın ve nasıl yaptığına çok karışma felsefesini orada daha iyi anlamıştım.

Görsel tasarımdan örnek vermemiz gerekir ise; bir alert popup’i tasarlayıp ekranda istediğiniz hangi noktadan çıkacağını hesaplayıp animasyonlarını belirleyip gerçeklemek imperative iken, sisteme ben alert göstereceğim ve verilerim şunlar gerisini sen hallet demekte declarative tarafı.

Bu sayede belki büyük ekran’lı iPad’de farklı bir yerden çıkarabilecekken popup, ufak ekranda daha uygun bir yerden çıkarma kararını işletim sistemi kendisi verebilir.

Yazılım’ın temel teoremi indirection beyan’a dayalı declarative yaklaşımın izdüşümü.

“We can solve any problem by introducing an extra level of indirection.”

Andrew Koenig

Declarative Tasarım ve Veri’nin ilişkisi

Declarative tasarım matematiksel ifadeyle veriyi alıp arayüze dönüştüren bir fonksiyondur:

declarativeTasarim(veri) -> Arayuz

Facebook’un etkisi

Siyasal mecrada skandallara varabilecek derecede toplumu yönlendirme gücüne sebep olabilecek büyüklükte veriyi elinde bulunduran firma diyince diyince ilk akla gelen şirket elbette Facebook.

React belgeselinde geçen, reklam takımında ortaya çıkan arayüz geliştirme süreçlerindeki hantallık ve hatalar facebookta radikal bir çözümü gerekli kılmış.

React ve devamı

Reklam takımındaki bir product engineer’a garip gözlerle bakılmasına sebep olan sorusu şöyle:

’Tüm’ arayüzü herhangi bir ’veri’ değiştiğinde silip en baştan çizelim.

Declaratıve tasarım fonksiyonu için olan bu matematiksel yaklaşımların gerçek dünyada ekonomik bir şekilde tatbik edilebilir hale getirmek zor olabiliyor.

Performanslı bir şekilde tüm tuvali tek bir değişiklikte sil baştan çizmek delice gelsede kulağa, pratikte bunu yapıcak yolların keşfiyle hiçte mantıksız değilmiş ampulunu kafalarda yakmıştır.

Diffing ve Faydası

Ekonomik bir şekilde verinin istenilen görüntüye dönüşmesini sağlamanın en önemli yolu olabilecek en az değişiklik ile bunu başarabilmek.

React’in kabaca karmaşıklığı O(n)3 ten heuristic yaklaşımla O(n)’e cektigi ve ilham aldığı bazı pratikler bunu mümkün kıldı.

Buradaki diffing’e yardımcı olarak daha akıcı olarak veriyi ekrana yansıtabilecek yazılımı ortaya çıkabiliriz.

Nelere dikkat etmeliyiz

O nedenle kullandığınız framework’un buna yardımcı olan yaklaşımlarını iyi anlamamız gerekiyor.

Reconcilation olarak geçen bu yaklaşımda yeniden kullanalabilecek tipte elemanlar var ise yok edilmiyor. Listeler gibi tekrarlı görsellerin olduğu noktalarda key’ler kullanılıyor.

SwiftUI kütüphanesi ise yine static typing’den faydalanarak view’lerin yokedilmesi ve tekrar kullanılabilir olması konusundan faydalanıyor. View’lerin animasyonlu bir şekilde ekranda görüntülenmesini sağlamak istiyorsak ilgili tiplerin yeni renderde kaybolmadığına dikkat etmek önemli.

Çok basit iki örnekle açıklayalım:

  // Burada farklı bir branching izlediğinden
  // yok olması ve oluşturulması gereken view'ler oluyor
  // Animasyon verdiğinizde düzgün geçiş göremezsiniz.
  struct AnswerStateView: View {
      @State var isCorrect: Bool

      var body: some View {
          if isCorrect {
              Text("Correct").foregroundColor(Color.green)
          } else {
              Text("Wrong").foregroundColor(Color.red)
          }
      }
  }
  // Burada farklı bir branching izlemeden aynı view olduğunu
  // anlayan sistem rahatlıkla animasyonu gerçekler ve gereksiz bir view yaratıp yok etmez.
  struct AnswerStateView: View {
      @State var isCorrect: Bool

      var body: some View {
          Text(isCorrect ? "Correct" : "Wrong")
            .foregroundColor(isCorrect ? Color.green : Color.red)
      }
  }

Bu örneklerde görüldüğü üzere view eşlemeleri tipler üzerinden gerçekleşebilir.

Key/Id kullanımı

Listeler gibi view elemanlarında ise id’ler üzerinden dinamik arayüz elemanları çizip eşleştirilebilir.

O nedenle ForEach gibi viewlerde SwiftUI Identifiable protokolu ile id parametresi gerektiriyor, ve onun uzerinden reconciliation yapiyor.

  import SwiftUI

  struct City: Identifiable {
      let id: Int
      let name: String
  }

  struct ContentView: View {
      let cities = [
        City(id: 34, name: "Istanbul"),
        City(id: 6, name: "Ankara"),
        City(id: 35, name: "Izmir"),
        City(id: 16, name: "Bursa"),
        City(id: 7, name: "Antalya")
      ]

      var body: some View {
          List {
              ForEach(cities) { city in
                  Text(city.name)
              }
          }
      }
  }

Flutter gibi kütüphanelerde ise key’ler üzerinden ilgili veri için yeni bir view widget’i gerekiyor mu sorusu cevaplanıyor. Dikkat edilmesi gereken noktalardan bir tanesi liste elemanlarında index’i id veya key olarak kullanmamak. Reorder durumlarında hatalara sebep olucaktır.

Bazende eşsiz rastgele bir id kullanarak bunu yapabiliriz diye düşünebiliriz. O durumda ilgili framework’e yardım etmemiş ve performansı düşük bir kod yazmış oluruz.

Eğer elimizde bir ’id’ yok ise mümkün mertebe veri’ye ait bir ’key’ çıkarma yoluna gidebiliriz, misal şehrin ’id’leri yok ise şehir ismini ’id’ olarak düşünüp diğer verilerle hash’leyip kullanabiliriz.

Verilerin declarative yaklaşımlı framework’lerle çizilmesinde state management yani veri yönetimi bir başka saç ayağı.

Bu yazıda Veriden => Ekrana giden yolda tasarımın declarative yollar ile görselleştirilmesinden bahsettik.

İyi eğlenceler.

Kaynakça