# 重構
---
不改變代碼的外部行為情況下而修改原始碼
不修正錯誤,又不增加新的功能性
為了讓將來以後更好維護
---
Uncle Ben:
「With great power comes great responsibility」
---
"fat model, skinny controller"
---
Obese Models
---
* Value Objects
* Service Objects
* Form Objects
* Query Objects
---
What is a Value Object?
---
As stated by Martin Fowler a value object is:
> A small simple object, like money or a date range, whose equality isn’t based on identity.
---
### How can we identify them?
1. Arguments together all the time
2. One attribute with behaviour
3. Two inseparable attributes value and unit
4. Class enumerable
---
Arguments together all the time:
```ruby=
class DateRange
attr_reader :start_date, :end_date
def initialize(start_date, end_date)
@start_date, @end_date = start_date, end_date
end
def include_date?(date)
date >= start_date && date <= end_date
end
def include_date_range?(date_range)
start_date <= date_range.start_date && end_date >= date_range.end_date
end
def to_s
"from #{start_date.strftime('%d-%B-%Y')} to #{end_date.strftime('%d-%B-%Y')}"
end
end
```
---
```ruby=
class Event < ActiveRecord::Base
def date_range
DateRange.new(start_date, end_date)
end
def date_range=(date_range)
self.start_date = date_range.start_date
self.end_date = date_range.end_date
end
end
```
---
```ruby=
> event = Event.create(name: 'Ruby conf', start_date: Date.today, end_date: Date.today + 1.days)
> event.date_range.include_date?(Date.today)
=> true
```
---
One attribute with behaviour:
```ruby=
room = Room.create(degrees: 10)
room.cold?
room.hot?
```
---
```ruby=
class Temperature
attr_reader :degrees
COLD = 20
HOT = 25
def initialize(degrees)
@degrees = degrees
end
def cold?
self < COLD
end
def hot?
self > HOT
end
def to_s
"#{degrees} °C"
end
end
```
---
```ruby=
> room = Room.create(degrees: 10)
> room.temperature.cold?
=> true
> room.temperature.hot?
=> false
```
---
### Two inseparable attributes value and unit
temperature (degrees and unit)
money (cents and currency)
distance (value and unit)
---
https://github.com/RubyMoney/money
---

---
Class enumerable
```ruby=
class Event < ActiveRecord::Base
SIZE = %w(
small
medium
big
)
end
```
---
```ruby=
class Size
SIZES = %w(small medium big)
attr_reader :size
def initialize(size)
@size = size
end
def self.to_select
SIZES.map{|c| [c.capitalize, c]}
end
def valid?
SIZES.include?(size)
end
def to_s
size.capitalize
end
end
```
---
透過寫一些簡單的 Ruby 類別,把一些判斷的邏輯放在裡面。這樣的純 Ruby 類別,又稱之 PORO(Plain Old Ruby Object)
---
把跟該 Model 看起來不相關功能的程式碼都拆出來,除了可增加彈性且更容易重複使用外,將來也容易維護修改,也因為程式碼另外拆出來而變得容易測試
---
[Value Objects in Ruby on Rails](https://revs.runtime-revolution.com/value-objects-in-ruby-on-rails-9df64bc8db34)
[Top Tips for Refactoring Fat Models in Rails](https://airbrake.io/blog/rails/top-tips-for-refactoring-rails-models)
[5 Common Mistakes in Rails Development](https://airbrake.io/blog/rails/5-common-mistakes-rails-development)
{"metaMigratedAt":"2023-06-15T09:58:17.361Z","metaMigratedFrom":"Content","title":"重構","breaks":true,"contributors":"[{\"id\":\"a1061309-7a7f-42b9-b144-e7e479bb7747\",\"add\":3336,\"del\":229}]"}