---
title: Map Bloklarında Mantıksal İşlem Yapmayı Bırakalım
teaser: map bloğunda yaptığımız işlemler gezdiğimiz nesneye ait bir metot içerisinde
  olmalı.
tags: web,ruby,domain modeling
author: Joël Quenneville
published_on: 2023-05-26
---

Rubyciler olarak sık sık `map` blokları yazıyoruz ancak bu bloklar aslında başka yerde olması gereken işlemleri içermeye meyilli oluyor. İlk bakışta masum görünen aşağıdaki kod parçasını inceleyelim.

```ruby
class ShoppingCart
  # other methods

  # SMELLY
  def sub_totals
    items.map do |item|
      item.base_cost + item.bonus_cost
    end
  end
end
```

Biraz daha dikkatli baktığımızda birkaç farklı code smell olduğunu fark edebiliriz.

1. Bloğumuz [**Feature Envy**] dediğimiz farklı bir nesnenin özelliklerini barındırmaya çalışma davranışından muzdarip çünkü `Item` üzerindeki propertylerle haşır neşir olarak `Item`ın sorumluluklarını kendi üzerine almaya çalışıyor.
2. Tam tersine `Item` ise [**Anemic Model**] dediğimiz içinde veri bulunduran ama bu veriyle herhangi bir işlem yapmayan bir model olarak karşımıza çıkıyor.

[**Feature Envy**]: https://wiki.c2.com/?FeatureEnvySmell
[**Anemic Model**]: https://martinfowler.com/bliki/AnemicDomainModel.html

## Mantıksal İşlemleri Gezdiğimiz Objelere Taşıyalım

Map bloğuna gezdiğimiz her nesneye uygulanmasını istediğimiz kodu yazıyoruz. Peki bunun yerine `Item` modeline bu işlemleri yaptırsak nasıl olur? Böylece `Item` daha fazla domainiyle alakalı operasyon gerçekleştirmiş olacak ve `Item`la alakalı mantıksal işlemler yalnızca bir yerde toplanacak.

```ruby
class Item
  # other methods

  def total_cost
    base_cost + bonus_cost
  end
end
```

Şimdi bu domain operasyonuna yeni bir ad verdik ve kodumuz artık single source of truth prensibine uyuyor. Bu oluşturduğumuz yeni metodu çağırdığımız yer daha high-level hale geldi ve `Item`ın iç işlerine burnunu sokmayı kesti. Aynı zamanda bu şekilde [symbol to proc] syntaxini de kullanabiliyoruz.

```ruby
def sub_totals
  items.map(&:total_cost)
end
```

[symbol to proc]: https://thoughtbot.com/blog/blocks-procs-and-enumerable

## Karmaşık Bloklar

Peki ya bloğumuz daha karmaşık işlemler yapıyorsa ve başka nesnelerle de etkileşime geçiyorsa ne yapacağız? Bu durumda da yaptığımız işlemlerin çoğunun yeri muhtemelen yine `Item` nesnesi. Ne de olsa `map` bloğunun amacı belli bir işlemi gezdiğimiz listenin her elemanına yaptırmak. _Eğer bir nesne üzerinde mantıksal işlem yapıyorsak, bu aslında nesnenin kendisi yaptırdığımız işlemin davranışına sahip olmalı demek oluyor._

```ruby
# SMELLY
def sub_totals
  items.map |item|
    if coupon.applies_to_id == item.id
      (item.base_cost + item.bonus_cost) * coupon.percent_off
    else
      item.base_cost + item.bonus_cost
    end
  end
end
```

Yukarıda yaptığımız işlemler hep `Item` üzerinden yürüyor. `item` değişkeninin kaç kere kullanıldığına bir bakın! Ama bu sefer bloğun içerisinde `coupon` diye yeni bir değişken daha kullanılıyor. Bu karşılaşabileceğimiz bir durum. `Item` kendi toplam ücretini kupon uygulayarak da olsa hesaplayabilmeli. Bu işlemi `Item` üzerindeki bir metoda `coupon` argümanı vererek yapabiliriz.

```ruby
sub_totals = items.map { |item| item.total_cost(coupon: coupon) }
```

## Mantıksal İşlemleri Private Metoda Çekmekten Kaçınalım

Bu tarz karmaşık bir blokla karşı karşıya geldiğimizde ilk aklımıza gelen kodun mantığının bir kısmını `ShoppingCart` içindeki bir private metoda çekmek olabilir. Bu kodu daha okunaklı kılsa da bulduğumuz çözüm yine bahsettiğimiz code smellerden muzdarip. Onun yerine mantığı gezdiğimiz nesnelerin içine çekelim.

```ruby
class ShoppingCart
  # other methods

  # STILL SMELLY
  def sub_totals
    items.map { |item| sub_total_for(item) }
  end

  private

  def sub_total_for(item)
    if coupon.applies_to_id == item.id
      (item.base_cost + item.bonus_cost) * coupon.percent_off
    else
      item.base_cost + item.bonus_cost
    end
  end
end
```
