Data-Oriented Programming(DOP)— Ufak Bir Giriş

İbrahim Kürce
6 min readAug 15, 2023

--

Selamlar, son zamanlarda okuma fırsatını bulduğum ve bende ufuk açan bir kitap hakkında size bir şeyler karalamak istiyorum. Data-Oriented Programming(DOP) kitabının orjinalini şuradan alabilirsiniz.

DOP’dan bahsetmeye geçmeden önce, bunu en iyi uygulayan örneklerden olan Clojure dilinden bahsetmeden olmaz. Kendi projelerimde kullanmasam da, daha önce Clojure dilini ve tasarımcı Rich Hickey’in ilginç anılarını dinlemiştim.

Söz Rich Hickey’den açılmışken, yakın zamanda çalışma hayatından emekli olduğunu ama Clojure’ı geliştirmeye devam edeceğini şurada belirtti. Kendisine iyi emeklilikler dilerim.

Clojure dili(veya genel olarak DOP) birçok özelliği ile beraber değişmezlik(immutability) üzerine kuruluydu ve object-oriented programlamanın(OOP) getirdiği karmaşıklığı bir nebze azaltmayı hedefliyordu. Lisp dili sentaksını kullanır ve JVM üzerinde çalışabilir. 2009 yılında 1.0 versiyonu ile çıkan, nispeten genç bir dil sayılabilir. Türkiye’de çok fazla popüler olmasa da, dünya çapında kullanılan büyük yerler var. Nubank, Clojure’ı kullanan en büyük örneklerden.

Günlük hayatta genelde sürekli OOP dillerini kullandığımız için, Clojure gibi DOP dillerini kullanma fırsatını pek bulamıyoruz. DOP kitabında, data-oriented programlamayı uygulamak için illa bu tür bir dil kullanmaya gerek olmadığını, Java veya JavaScript gibi dillerde de DOP mantığını uygulayabileceğimizi belirtiyordu. Bu da açıkçası, beni biraz ümitlendirdi. Kitabın bitmesinden sonra, Spring Boot gibi bir framework ile bu mantığı nasıl biraraya getirebileceğimi araştıracağım.

Ufaktan kitaba giriş yapalım, vira bismillah.

DOP ile ufak çaplı bir, Kütüphane Yönetim Sistemi(KYS) gerçekleştireceğiz. Bu sistemin şu gibi gereksinimleri olacak:

  • İki tür kullanıcısı var: üyeler ve kütüphaneciler,
  • Kullanıcı sisteme e-posta ve şifre ile login olacak,
  • Üyeler ödünç kitaplar alabilecek,
  • Tüm kullanıcılar, yazara veya başlığa göre kitapları arayabilecek,
  • Kütüphaneciler, kitapları geç iade edenleri bloklayabilecek veya bloğu kaldırabilecek,
  • Kütüphaneciler, ödünç verilen kitapları listeyebilecek,
  • Bir kitabın bir çok kopyası olabilecek,
  • Bir kitap, fiziksel bir kütüphaneye ait olacak

Bölüm 1: OOP’in Karmaşıklığı

DOP’un detaylarına girmeden önce aynı uygulamayı OOP ile yapmış olsaydık nasıl bir proje çıkardı onu inceleyelim biraz. Bu sayede OOP’nin karmaşıklığını biraz inceleyebiliriz. “Object-Oriented Programming — The Trillion Dollar Disaster” diye internette çeşitli makaleler bulunmaktadır.

OOP sistemlerinin karmaşık olma eğilimi vardır. Programdaki durumların(state), metodlar ile içiçe olması bu karmaşanın başlıca sebeplerindendir. Bu arada, OOP’yi tamamen kötüleme gibi bir niyetimiz yok, DOP ile birlikte farklı paradigmaları bir nebze olsun tanımış olacağız.

Tasarım Aşaması

OOP’de her şeyi nesneler olarak düşünürüz. Kütüphane Yönetim Sisteminin bazı sınıfları şunlar olabilir:

  • Library — Sistem tasarımının merkezi kısmı
  • Book — Kitap sınıfı
  • BookItem— Bir kitabın birden fazla kopyası olabilir, her bir kopyayı ayrı bir öğe diye düşünürüz
  • BookLending— Bir kitap ödünç verildiğinde, bu nesne oluşturulur
  • Member— Kütüphane üyesi
  • Librarian— Kütüphaneci
  • User— Librarian ve Member için üst sınıf
  • Catalog— Tüm kitaplarının listesini tutar
  • Author— Yazar

Sınıfların ne olacağını çıkarmak kolay iş; işin zor tarafı bu sınıflar arasındaki ilişkiyi kurabilmek. Kaba bir tasarımla şöyle bir sınıf diagramı çıkarabiliriz.

Sınıf Diagramı

UML 101

Composition Relation: Bir nesne öldüğünde, diğer nesne de ölür. Library ve Catalog arasında bire-bir(one-to-one) composition ilişkisi varken, Library ve Member arasında bire-çok(one-to-many) bir ilişki vardır.

Association Relation: İlişkideki her nesnenin kendine ait bir yaşam döngüsü vardır. Book ve Author arasında çoka-çok(many-to-many) bir ilişki vardır.

Usage Relation: Bir sınıf diğerinin metodunu kullandığında, kullanım ilişkisi ortaya çıkar. Örnek olarak, Librarian::blockMember metodu Member::block metodunu kullanır.

Inheritance(Kalıtım) Relation: Member ve User arasında böyle bir kalıtım ilişkisi vardır.

Gerçekleştirme Aşaması

OOP’nin zamanla karmaşıklaşma yönünde eğilimi olduğunu söylemiştik. OOP’de de karmaşıklığı hafifleten yollar tabi ki vardır. Buradaki amacımız OOP’nin kritiğini yapmak değil, DOP ile bazı şeyleri daha basit yapabileceğimizi göstermektir. DOP’a göre OOP’de sistemi karmaşıklaştıran bazı maddeler:

  • Kod ve veri karıştırılması: Sınıfların birçok ilişkiye sahip olması eğilimi yaratır.
  • Nesnelerin değişebilir(mutable) olması: Kodu okurken ekstra efor gerektirir, çoklu threadli bir ortamda ekstra senkronizasyon gerektirir.
  • Verinin nesneler üzerinde üye olarak saklanması
  • Kodun nesneler içinde metodlar olarak saklanması: Sınıf hiyerarşilerini karmaşıklaştırır.

Üye değişkenleri(members) ve metodları ihmal etsek bile, yine aşağıdaki gibi karmaşık bir yapımız oluyor.

Bunu veri varlıkları ve kod varlıkları olarak ikiye bölmek istesek, 2 bağımsız yapı ortaya çıkar.

Birden fazla basit bağımsız parçadan oluşan bir sistem, tek bir karmaşık parçadan oluşan bir sistemden daha az karmaşıktır.

Tahmin Edilemeyen Kod Davranışı

isBlocked değişkenin true olduğu zaman, bu metod ekrana ne yazdırır?

İki defa true olarak yazdırır değil mi?

Peki, bu kod ne yazdırır ekrana?

Doğru cevap, tek threadli bir ortamda true yazdırır ama çok threadli bir ortamda, tahmin edilemez. Çok threadli bir ortamda, 2 console.log çağrısı arasında, nesnelerin bir durum(state) değişikliği olabilir. Hatta, tek threadli JavaScript ortamında bile, isBlocked verisi async olarak değiştirilebilir ve bu da kodu tahmini zor bir hale getirebilir. Sonuç olarak, veri değişebilir(mutable) olduğunda, kod tahmin edilemez bir hal alır.

OOP’de primitif tiplere değişmez, nesne tiplerine değişebilir olarak bakarken, DOP’da her türlü tipi değişmez olarak ele alır.

Karmaşık Sınıf Hiyerarşileri

Bir kodu iki kere yazmadan kaçınmak için OOP’de kullanılacak yöntemlerden biri de sınıf veya arayüz(interface) kalıtımıdır. Librarian ve Member kullanıcılarının login olabilmesi için User sınıfından türetilmeleri gerekiyor. Bu bizi başlangıçta kurtarır ama yeni gereksinimler gelmeye devam ettiğinde işler karışmaya başlar.

Projenin teslim tarihine 2 gün kala iş birimi, ufak(!) bir istekle geldi. VIPMember kullanıcısına ihtiyaç var denildi. Bu kullanıcı, kütüphaneye yeni bir kitap ekleyebilecekti. Geliştirici, bu durumdan hoşlanmadı. Ne de olsa Librarian::addBookItem metodu yazılmıştı, bunu neden VIPMember için kullanamıyorduk? Buradaki sorun, kodun sınıflar içinde kilitli olmasıydı.

OOP’de, kod sınıflar içinde kilitlidir.

Yeni bir VIPMember sınıfı oluşturduk. Bunu Member sınıfından türetmek mantıklıydı ama Librarian sınıfından da türetemezdik, yaptığı işler farklıydı. Bir bakımdan kütüphaneci gibiydi, yeni kitap ekleyebiliyordu; diğer bakımdan kütüphaneci gibi değildi, üyeleri bloklama yetkisi yoktu. Yapılacak şey, kitap ekleme özelliğinin Librarian sınıfı dışına çıkarılmasıydı. Şu değişikliklere gidildi:

  • UserWithBookItemRight sınıfı oluşturduk ve User dan türettik.
  • addBookItem metodu Librarian dan UserWithBookItemRight a taşıdık.
  • VIPMember ve Librarian , UserWithBookItemRight dan türettik.

Yeni sınıf diagramımız şöyle oldu:

Kolay değildi ama zamanında yetiştirmeyi başardık.

Teslime 14 saat kala, iş birimi ufak(!) bir değişiklik daha istedi. SuperMember kullanıcısı ile diğer üyelere ödünç verilen kitapları listeleyebilsin. Bunu da sisteme dahil etmek istesek şuna benzer bir sınıf diagramı ortaya çıkardı.

SuperMember ile sistem çok daha karmaşık hal aldı ve “Deadly Diamonds of Death” durumu oluştu. D’nin B ve C’den türediği, onların ise A’dan türediği durumda bir belirsizlik oluşması durumu.

Java ve C# gibi diller, buna benzer durumlardan dolayı çoklu-kalıtımı desteklemezler ama C++ dilinde bu sorun oluşabilir.

Bu durumdan kaçınmak için “composition over inheritance” tasarım kalıbını kullanabiliriz ama son dakika değişiklikleri daha büyük sorunlar doğurabilir.

OOP’de, kalıtım yerine bileştirme(composition) tercih edilir.

Neyse ki, iş birimi SuperMember fikrinden vazgeçti ve bu durumdan yırttık.

Bölüm 2: Kod ve Veri Arasındaki Ayrım

Yazının devamı için tıklayınız…

--

--