UI Mühendisliğinin İlkeleri

Gizem Korkmaz
7 min readApr 12, 2022

--

Bu yazı, Dan Abramov’un The Elements of UI Engineering adlı makalesinin çevirisidir.

Bir önceki yazımda bilgi açıklarımızı kabul etmekten bahsetmiştim. Ortalamaya razı olmayı önerdiğim sonucuna varmış olabilirsiniz. Hayır! Bu son derece geniş bir alan.

Kesinlikle “bir yerlerden başlayabileceğinize” ve teknolojileri belirli bir sırayla öğrenmenize gerek olmadığına inanıyorum. Fakat uzmanlık kazanmaya da oldukça değer veriyorum ve daha çok kullanıcı arayüzleri oluşturmakla ilgileniyorum.

Hakkında bilgi birikimim olan ve değerli olduğunu düşündüğüm bazı şeyler üzerine kafa yormaktayım. Bir takım teknolojilere (JavaScript ve React gibi) aşinayım tabii ancak deneyimlerden elde edilmiş, çok daha önemli olan bazı derslerin ifade edilmesi oldukça güç. Bunları daha önce kelimelere dökmeye çalışmamıştım. Bu yazı, bu deneyimlerin bir kısmını listeleyip açıklamak üzerine yaptığım ilk deneme olacak.

Teknolojiler ve kütüphaneler üzerine birçok “öğrenme yol haritası” mevcut. 2019'da hangi kütüphane moda olacak? Peki ya 2020? Vue mü öğrenmelisiniz yoksa React mi? Angular? Peki ya Redux ya da Rx? Apollo’yu öğrenmenize gerek var mı? REST ya da GraphQL? Tüm bunların içinde kaybolmak çok kolay. Ya yazan kişi yanılıyorsa?

En büyük öğrenme farkındalıklarım bir teknoloji ile alakalı değildi. En çok, bazı kullanıcı arayüzü problemlerini çözmeye çalışırken yaşadığım mücadelelerden öğrendim. Bazen bana yardımcı olan kütüphaneleri ya da yapıları keşfettim. Bazen de kendi çözümlerimi (iyi ya da kötü) buldum.

Problemleri anlamak, çözümler üzerine denemeler yapmak ve farklı stratejiler uygulamak bir araya geldiğinde hayatımdaki en tatmin edici öğrenme deneyimini oluşturmama sebep oldular. Bu yazı sadece problemlere odaklanmaktadır.

Bir kullanıcı arayüzü üzerinde çalıştıysanız, muhtemelen bu zorlukların en azından birkaçını doğrudan ya da bir kütüphane aracılığı ile çözmüşsünüzdür. Her iki durumda da, kütüphane kullanmadan küçük bir uygulama oluşturmanızı ve bu sorunları yeniden oluşturarak çözümler üretmenizi tavsiye ediyorum. Hiçbirinin tek bir doğru çözümü yok. Öğrenme, problemin alanını keşfedip çeşitli olası ödünleşmeleri (tradeoffs) denemekte başlar.

  • Tutarlılık: Bir “Beğen” butonuna tıkladınız ve metin güncellendi: “Siz ve diğer 3 arkadaşınız bu gönderiyi beğendi.” Tekrar tıkladınız ve metin eski haline döndü. Kulağa gayet kolay geliyor. Fakat belki, benzer bir şey sayfanın başka yerlerinde de mevcut. Belki görsel olarak değişmesi gereken bir şey (butonun arka planı gibi) var. Daha önce sunucudan alınan ve üzerine gelindiğinde görülen “beğenenler” listesi artık sizin adınızı da içermeli. Eğer başka bir sayfaya gidip geri dönerseniz, gönderi beğenildiğini “unutmamalı”. Sadece lokal tutarlılıklar bile başlı başına bir dizi zorluk yaratır. Üstelik diğer kullanıcılar da bizim görüntülediğimiz verileri değiştirebilirler (görüntülediğiniz bir gönderiyi beğenmeleri gibi). Aynı verileri ekranın farklı bölümlerinde nasıl senkronize tutabiliriz? Lokal verileri sunucuyla nasıl ve ne zaman tutarlı hale getiririz ve bunun tersini yapabiliriz?
  • Tepkisellik: İnsanlar eylemleri karşılığında görsel geri bildirim eksiklikliğini yalnızca sınırlı bir süre için tolere edebilirler. Hareketler ve kaydırma gibi sürekli eylemler için bu sınır daha da düşüktür. (16ms’lik tek bir kareyi atlamak bile “kalitesiz” hissettirir.) Tıklamalar gibi aralıklı eylemler için ise kullanıcıların 100ms’den kısa gecikmeleri eşit derecede hızlı algıladıklarını söyleyen araştırmalar mevcut. Bir eylem daha uzun sürerse, bunu bir gösterge ile göstermemiz gerekir. Ancak bunun sezgilere aykırı bazı zorlukları olacaktır. Sayfa düzeninin “sıçramasına” neden olan ya da birkaç yükleme “aşamasından” geçiren göstergeler, eylemi olduğundan daha uzun süreli hissettirebilir. Benzer şekilde, bir etkileşimi, bir animasyon karesini atlama pahasına 20ms içinde işlemek, 30ms içinde ve kare atlamadan işlemekten çok daha yavaş hissettirebilir. Beyin, ölçütümüz değildir. Uygulamalarımızı farklı tür girdilere karşı nasıl tepkisel halde tutabiliriz?
  • Gecikme: Hesaplamalar da, ağ erişimi de zaman alan şeylerdir. Hedef cihazımızdaki yanıt hızına zarar vermiyorsa, hesaplama maliyetini bazen göz ardı edebiliriz (uygulamanızı düşük kaliteli cihaz spektrumunda da test ettiğinizden emin olun). Ancak ağ gecikmesi sorununu halletmek kaçınılmaz olacaktır çünkü bu saniyeler sürebilir! Uygulamamız, veriler ya da kod yüklenirken öylece donup kalamaz. Bu yeni verilere, koda ya da assetlere bağlı herhangi bi eylemin potansiyel olarak asenkron olduğunu ve “yükleme” durumunu ele alması gerektiğini gösterir. Hemen hemen her ekranda gerçekleşebilir. Bu gecikmeyi nasıl “dönüp duran bir şeyler” ya da “boş halkalar” göstermeden, zarif bir şekilde halledebiliriz? “Sıçrayan” sayfa düzenlerinden nasıl kaçınırız? Ve asenkron bağımlılıkları, her seferinde kodumuzu “yeniden oluşturmadan” nasıl değiştiririz?
  • Navigasyon: Etkileşimde olduğumuz sürece kullanıcı arayüzünün “kararlı” kalmasını bekleriz. Bir şeyler öylece gözümüzün önünden kaybolmamalı. İster uygulamanın içinde başlatılsın (bir bağlantıya tıklamak gibi) ister harici bir olaya bağlı olarak başlasın (“geri dön” butonuna tıklamak gibi), navigasyon da bu ilkeye uymalıdır. Örneğin, bir profil sayfasında /profile/likes ve /profile/follows sekmeleri arasında geçiş yapmak, sekmeli görünüm dışındaki bir arama input’unu temizlememeli. Başka bir ekrana geçmek bir odaya girmek gibidir. İnsanlar daha sonra geri dönmeyi ve bıraktıkları şeyleri aynı şekilde (belki bazı yeni öğelerle) bulmayı umarlar. Bir feed’in ortasındaysanız, herhangi bir profile tıklayın ve geri dönün. Feed’deki konumunuzu kaybetmek ya da yeniden yüklenmesini beklemek çok can sıkıcıdır. Uygulamamızı, önemli contextleri kaybetmeden, plansız gezinmeyi ele alabilecek şekilde nasıl tasarlarız?
  • Geçersizlik: Lokal cache kullanarak “geri dön” butonunun navigasyonunu anında gerçekleştirebiliriz. Bu cache ile teorik olarak verileri geri çekebilsek bile hızlı erişim için bazılarını “hatırlayabiliriz”. Ancak caching kendi içinde bazı sorunlar barındırır. Cacheler geçersiz hale gelebilirler. Eğer bir avatar değişikliği yaparsam, cache’in de güncellenmesi gerekir. Yeni bir gönderi paylaşırsam, bunun cache’de hemen görünmesi ya da cache’in geçersiz hale gelmesi gerekir. Bu da zorlayıcı ve hatalara açık bir durum ortaya çıkarır. Gönderi paylaşımı başarısız olursa ne olur? Cache bellekte ne kadar süreyle kalır? Feed’i yeniden çektiğimizde, yeni gelen feed’i cache’deki ile “birleştirir” miyiz yoksa cache’i atar mıyız? Sayfalandırma ve sıralama cache’de nasıl temsil edilir?
  • Entropi: Termodinamiğin ikinci yasası “zamanla her şey karmakarışık bir hale gelir” gibi bir şey der (yani, tam olarak böyle olmayabilir). Bu kullanıcı arayüzleri için de geçerlidir. Kullanıcı etkileşimlerini ve bunların sıralarını birebir tahmin edemeyiz. Herhangi bir zamanda, uygulamamız akıllara durgunluk veren sayıda olası durumdan herhangi birinde olabilir. Sonucun öngörülebilir ve tasarımımızla sınırlı olması için elimizden gelenin en iyisini yaparız. Bir hatanın ekran görüntüsüne bakıp “bu nasıl oldu” diye merak etmek istemeyiz. N sayıda olası durum için, aralarında N x (N-1) sayıda olası geçiş vardır. Örneğin, bir buton 5 farklı durumdan birinde (normal, active, hover, danger, disabled) olabilirse, bu butonu güncelleyen kodun 5x4=20 tane olası geçişte doğru olması ya da bir kısmının engellenmesi gerekir. Olası durumların kombinasyonel artışını nasıl yavaşlatabiliriz ve görsel çıktıyı öngörülebilir bir hale getirebiliriz?
  • Öncelik: Bazı şeyler, diğerlerinden daha önemlidir. Bir diyalogun, kendisini oluşturan butonun fiziksel olarak “üzerinde” görünmesi ve kapsayıcısının sınırlarından “taşması” gerekebilir. Yeni planlanmış bir görev (tıklamaya cevap vermek gibi) uzun süredir devam eden bir görevden (ekranın altında sonraki gönderileri oluşturmak gibi) daha önemli olabilir. Uygulamamız büyüdükçe, kodun farklı kişiler ve ekipler tarafından yazılan bölümleri işlemci, ağ, ekran alanı ve paket boyutu bütçesi gibi sınırlı kaynaklar için rekabet eder. Bazen bu rakipleri, CSS’in z-index özelliği gibi bir “önem” ölçeğinde sıralayabilirsiniz. Ancak bunun sonu nadiren iyi bitecektir. Her geliştirici, kendi kodunun önemli olduğunu düşünmeye eğilimlidir. Ve eğer her şey önemli olursa, hiçbir şey önemli olamaz! Kaynaklar için savaşmak yerine işbirliği yapmak için bağımsız görsel bileşenleri (widgets) nasıl elde ederiz?
  • Erişebilirlik: Erişebilirlik eksikliği olan siteler çok niş problemler değiller. Örneğin, Birleşik Krallık’ta her 5 kişiden 1'i bir tür engelliliğin etkisindedir (burada güzel bir infografik mevcut). Bunu kişisel olarak ben de deneyimledim. Henüz 26 yaşında olmama rağmen, ince yazı tiplerine ve düşük kontrasta sahip web sitelerini okumakta zorlanıyorum. Trackpad’i daha az kullanmaya çalışıyorum ve bir gün kötü yapılandırılmış web sitelerinde klavye ile gezinmek zorunda kalmaktan çok korkuyorum. Uygulamalarımızı zorluk yaşayan insanlar için korkunç bir hale getirmekten çekinmeliyiz. Neyse ki bu, eğitimle ve doğru araçlarla başlayan oldukça kolay ulaşılabilir bir hedef. Fakat aynı zamanda, ürün geliştiricilerinin doğru şeyleri yapmalarını da kolaylaştırmamız gerekiyor. Erişebilirliği sonradan akla gelen bir düşünceden ziyade bir varsayılan haline getirebilmek için neler yapabiliriz?
  • Uluslararasılaşma: Uygulamamızın tüm dünyada çalışması gerekiyor. Sadece insanlar farklı dilleri konuştukları için değil, aynı zamanda ürün mühendisleri için işleri kolaylaştırarak sağdan sola yerleşimleri desteklememiz gerekiyor. Gecikme ve tepkisellikten ödün vermeden farklı dilleri nasıl destekleriz?
  • Teslimat: Uygulama kodumuzu kullanıcının bilgisayarına göndermemiz gerekiyor. Hangi taşıma ve formatı kullanıyoruz? Bu kulağa oldukça basit geliyor olabilir ancak burada pek çok ödünleşme var. Örneğin, native uygulamalar, büyük uygulama boyutlarına rağmen tüm kodu önceden yükleme eğilimindedir. Web uygulamaları, kullanım sırasında daha fazla gecikme pahasına daha küçük başlangıç yüküne sahip olma eğilimindedir. Gecikmeyi hangi noktada başlatacağımızı nasıl seçeriz? Kullanım yapılarına göre teslimatımızı nasıl optimize ederiz? Optimal bir çözüm için ne tür verilere ihtiyacımız var?
  • Dayanıklılık: Bir entomolojistseniz (böcekbilimci) böcekleri(bugs) seviyor olabilirsiniz ama onları programlarınızda görmekten muhtemelen hoşlanmazsınız. Her halükarda hatalarınızın bir kısmı kaçınılmaz olarak production’a geçeceklerdir. O zaman ne olacak? Bazı hatalar sıkıntı yaratırlar ancak sınırları belirli davranışlara neden olurlar. Örneğin, kodunuz bir koşulda yanlış görsel çıktısını gösteriyor olabilir. Peki ya render kodu çökerse ne olacak? O zaman anlamlı bir şekilde devam edemeyiz çünkü görsel çıktı tutarsız olacaktır. Tek bir gönderiyi oluşturan bir çöküş tüm feed’i “alt etmemeli” ya da onu daha fazla etkileyecek yarı-bozuk bir hale getirmemeli. Kodu, render işlemi ve veri çekme hatalarını izole edecek ve uygulamanın geri kalanını çalışır durumda tutacak şekilde nasıl yazarız? Kullanıcı arayüzleri için hata toleransı ne anlama geliyor?
  • Soyutlama: Küçük bir uygulamada, yukarıda bahsedilen sorunlar sebebiyle birçok özel durumu sabit kodlayabiliriz (hardcoding). Ancak uygulamalar büyüme eğilimindedirler. Kodumuzun parçalarını yeniden kullanabilmek, forklayabilmek, birleştirebilmek ve üzerinde ortaklaşa çalışabilmek isteriz. İnsanlara tanıdık gelen parçalar arasına net sınırlar çizmek ve sık sık değişen mantığı çok katı hale getirmekten kaçınmak isteriz. Belirli bir UI bölümünün oluşturulma ayrıntılarını gizleyen soyutlamaları(abstraction) nasıl oluştururuz? Uygulamamız büyüdükçe çözdüğümüz sorunların yeniden ortaya çıkmasını nasıl engelleriz?

Tabii ki daha sayamadığım birçok sorun var. Bu, o kadar da kapsamlı bir liste değil. Örneğin, tasarımcı ile mühendislik ve hatta debugging ile testing işbirliklerinden bahsetmedim. Belki başka bir zaman.

Tüm bunları aklınızda çözüm olarak belirli bir görüntüleme ya da veri çekme kütüphanesini düşünerek okumak cazip gelebilir. Ancak tüm bu kütüphaneler yokmuş gibi davranmanızı ve bu perspektiften tekrar okumanızı tavsiye ederim. O zaman bu sorunları çözmeye nasıl yaklaşırsınız? Küçük bir uygulama ile bir deneyin! (Deneyimlerinizi GitHub üzerinden görmeyi çok isterim, cevaplarınızı bana bir tweet atmaktan çekinmeyin.)

Bu problemlerin en ilginç yanı, çoğunun herhangi bir ölçekte karşınıza çıkabilir olmasıdır. Bunları hem typeahead ya da tooltip gibi ufak araçlarda hem de Twitter ve Facebook gibi büyük uygulamalarda görebilirsiniz.

Kullanmayı sevdiğiniz bir uygulamada önemsiz olmayan bir UI öğesi düşünün ve bu sorun listesini bir gözden geçirin. Geliştiriciler tarafından seçilen bazı ödünleşmeleri açıklayabilir misiniz? Benzer bir davranışı sıfırdan yeniden oluşturmaya çalışın!

Ben bu sorunları kütüphane kullanmadan, küçük uygulamalarda denerken UI mühendisliği hakkında pek çok şey öğrendim. Aynı şeyi, UI mühendisliğindeki ödünleşmeler üzerine daha derin bir farkındalık kazanmak isteyen herkese tavsiye ederim.

--

--