Rubyで学ぶ１年目に知っておきたいプログラミング技法８選

# はじめに

駆け出しプログラマだった頃の自分に教えたい、８つのプログラミング技法について纏めます。本エントリでは主にRubyを用いますが、ここで紹介する技法はあらゆる言語に適用できます。

８つの技法は**リーダビリティ**と**関心の分離**の２つに分類できます。サンプルコードを添えて**可能な限り具体的に**解説していきます。

## 対象読者

- コードを書くのに慣れてきた人
- けどもっと良い書き方はないか？と考えている人

# リーダビリティ - Readability

リーダビリティとは、アーキテクチャに関係しない純粋なコーディングの綺麗さです。可読性とも呼ばれます。私達は仕事時間の相当量をコードの読解に当てています。コードの読解は短期記憶をフル活用するため、コーディング作業そのものよりも知的負担が大きくなりがちです。同僚の書いたコードは勿論、先週の自分が書いたコードでさえ記憶の奥底から掘り出すのに時間が掛かることもしばしばです。ましてや一年前、二年前に書いたコードなら尚更でしょう。

リーダビリティの低下は、バグを生む、機能追加のコストが上昇する、仕様変更に弱くなる、メンバーの精神が不安定になるなど、種々の厄災の温床になります。ではリーダビリティを維持するためにどのようなテクニックがあるのでしょうか。

## 1. Guard Clause

boolean型の２つの引数`flag_a`, `flag_b`を取るメソッド`f1`を考えます。`f1`は`flag_a`がtrue且つ`flag_b`がfalseの場合にだけメソッド`f2`をコールします。
駆け出しの私は恐らく次の様なコードをCVS[^1]にコミットしていたことでしょう。

[^1]: Linus TorvaldsがGitを創造する前の世界であまねく使われていたバージョン管理ソフト。突っ込んだバイナリファイルがしばしば壊れる。

```ruby:悪いコード.rb
def f1(flag_a, flag_b)
  if flag_a
    unless flag_b
      f2
    end
  end
end
```

これは不必要にネストを深くしてしまっている悪いコードです。もう少しマシなバージョンは次のようなコードです。

```ruby:少しマシなコード.rb
def f1(flag_a, flag_b)
  if flag_a && !flag_b then
    f2
  end
end
```

ネストを下げられましたが、まだ読み手の脳に充分に優しいとは言えません。なぜなら条件文が論理演算子（andやor）を含むとリーダビリティは途端に下がるからです。
今の私ならこのように書きます。

```ruby:良いコード.rb
def f1(flag_a, flag_b)
  return unless flag_a
  return if flag_b
  f2
end
```

私達の脳もコンパイラと同様、コードを一行ずつ読んで理解します。そのため一行に多くの状態が含まれていると、理解に要する時間が長くなってしまいます。Guard Clauseは平易な条件文を用いて即座にメソッドの処理を終える（returnする）ことで、深いネストや難しい条件文を排除してくれます。


## 2. 表現的命名

> はじめに神は0と1を創造された。
> 神は「アセンブルあれ」と言われた。するとアセンブルがあった。
> 神はアセンブルを見て、良しとされた。神はアセンブルを自動アセンブルとハンドアセンブルに分けた。
> 神は「手続き型言語」と言われた。すると手続き型言語があった。
> 神は手続き型言語を見て、良しとされた。神は手続き型言語をFORTRANとLISPに分けた。
> 第一日である。
> 
> 旧約聖書 〜創世記〜 第1章

プログラミング言語の歴史は置き換えの歴史でありました。0と1で構成された機械語はアセンブリに置き換えられ、アセンブリは英文に近い手続き型言語に置き換えられた後、現代の私達の手に馴染む言語へと変貌を遂げました。お蔭で`def is_money_important_than_lives?; true; end`のような人間に優しい書き方でプログラミング出来るのです。これを活かさない手はありません。

送金元ユーザ、送金先ユーザ、金額、通知メールの送信可否の４フラグを受け取る送金メソッドを考えます。

```ruby:悪いコード.rb
def submit(number, sendmail, userA, userB)
  userA.minus(number)
  userB.add(number)
  userB.notify if sendmail
end
```

`submit`や`number`など、広い意味を持つ英単語が使われています。変数名やメソッド名を適当に決めてしまうことは、駆け出しプログラマが必ず通る道です。

上のコードを読んだ同僚（もしくは半年後の自分）は、この`submit`メソッドをコールしている箇所を探し出し、前後の文脈から`submit`メソッドの用途を推測しなくてはいけません。

```ruby:良いコード.rb
def send_money(amount, should_send_email, sender, receiver)
  sender.pay_money(amount)
  receiver.get_money(amount)
  receiver.notify if should_send_email
end
```

限定的な意味を持つ英単語に置き換えました。それでもまだ意味の絞り込みが不十分な場合は`動詞原形+目的語`の英文にしています。またboolean値の場合は接頭辞`should`を付けることで、変数名だけでその変数がboolean型であると察知できます。


## 3. 説明変数

書いている本人は理解できるものの、同僚にとっては理解に時間が掛かるコードは沢山あります。例えば次のようなものです。

```ruby:悪いコード.rb
if Time.parse(params[:datetime]) < DateTime.now.prev_year.to_time
  # params[:datetime]が一年以上前の時に実行する処理
end
```

条件文が複雑でどういう意図のif文なのかすぐには理解できません。そこで説明変数の出番です。

```ruby:良いコード.rb
is_over_a_year_ago = Time.parse(params[:datetime]) < DateTime.now.prev_year.to_time
if is_over_a_year_ago
  # params[:datetime]が一年以上前の時に実行する処理
end
```

分かりやすい名前の変数に一旦代入してあげることで、コードの意図を読み手に伝えることができます。


## 4. インデント

インデントはタブであるべきかスペースであるべきかという宗教戦争は、未だ決着を見ず[インターネットの各地で繰り広げられています](https://softwareengineering.stackexchange.com/questions/57/tabs-versus-spaces-what-is-the-proper-indentation-character-for-everything-in-e)。非プログラマ達は「死ぬほど興味がない」と思いこの戦争を眺めていますが、筆者の見解も「どっちでもいいんじゃない」です。それよりも重要視するべきは**一貫性**です。赤軍も白軍もこの点においては同意してくれることでしょう。

```ruby:悪いコード.rb
class Product
attr_reader :id, :category_id
attr_accessor :name, :price, :created_at

def initialize(params)
@id = params[:id].to_i
@category_id = params[:category_id].to_i
@name = params[:name]
@price = params[:price].to_i
@created_at = Time.parse(params[:created_at].to_s)
end
end
```

どこまでがクラス定義でどこまでがメソッド定義か、使われているキーワードを読まないと判別できません。ここまで読み辛いコードなら、新人プログラマであっても何が悪いか気付けるでしょう。しかし最初はみんな上のようなコードを書くものです。

```ruby:マシなコード.rb
class Product
  attr_reader :id, :category_id
  attr_accessor :name, :price, :created_at

  def initialize(params)
    @id = params[:id].to_i
    @category_id = params[:category_id].to_i
    @name = params[:name]
    @price = params[:price].to_i
    @created_at = Time.parse(params[:created_at].to_s)
  end
end
```

これならコードレビューを担当した先輩プログラマもGitHubのApproveボタンを押下してくれるでしょう。しかしもう少しやれることは残っています。

```ruby:良いコード.rb
class Product
  attr_reader   :id,
                :category_id

  attr_accessor :name,
                :price,
                :created_at

  def initialize(params)
    @id          = params[:id].to_i
    @category_id = params[:category_id].to_i
    @name        = params[:name]
    @price       = params[:price].to_i
    @created_at  = Time.parse(params[:created_at].to_s)
  end
end
```

インデントを足して、アクセサの属性に改行を入れました。これだけで読むのが非常に楽になりますね。

ただしここまで多くのインデントと改行の挿入には批判もあります。批判は`行数が増える`と`インデントを調整するのに時間が掛かる`の２つに集約できそうです。それぞれの批判については次にような反論が可能ですが、やり過ぎかどうかは個人の好みの問題になってきます。

### 批判1. 行数が増える

原則として、1ファイル当りの行数は少ないのが正義です。例えばRubocopというRubyのLinter[^2]は、1クラスが100行を超えるとデフォルトで警告を出します。
しかしRubyを始めとする主要なオブジェクト指向プログラミング言語は、後述するMixinを備えています。Mixinにより1つのクラスファイルを複数のファイルに分割可能なので、行数を気にせずジャンジャン改行できます。

[^2]: 【Linter…糸くずを除く機械】という語源通り、ソースコードの構文をチェックし、より良いコードを提案してくれる優れもの。プロジェクトの始動から半年経ってLinterを導入し、Warning件数が10,000件を超えるという様式美が存在する。

### 批判2. インデントを調整するのに時間が掛かる

インデントの調整時間はリーダビリティと明白にトレードオフの関係にあります。リーダビリティと書く速さのどちらを優先すれば開発効率が上がるか定量的に判断することは難しいです。しかし一般に、コードは書かれる時間よりも読まれる時間の方が長いものです。


# 関心の分離 - Separation of Concerns

ソフトウェア開発は複雑性との闘いです。放っておくとコードベース[^3]は加速的に複雑化し、もはや誰も触ることはできないが何故か動いているブラックボックスと化すのです。

[^3]: １つのアプリケーションを構成するソースコード群のこと。単にソースコードを格好良く言いたいときにも使う。

関心の分離はプログラムを関心（＝担う責任）毎にモジュールと呼ばれるソースファイルに分割し、ソフトウェアをそれらモジュールの総体として捉える概念です。

それでは関心の分離に分類される技法にどのようなものがあるか、確認していきましょう。

## 5. Do one thing well

UNIXのコミッターは巨大なコードベースを相手にしている根っからのハッカーです。そんな彼等が共有している設計思想をUNIX哲学といいます。`Do one thing well`はUNIX哲学の中でもあらゆる開発現場で有効という点で特に重要な設計思想です。

例えば、次のUNIXコマンドは`今いるディレクトリの.jsonファイルの個数を出力`してくれます。

```unix
$ ls
bar.json	foo.json	fuga.gif	hoge.txt	piyo.json
$ ls -l | grep '\.json$' | wc -l
3
```

UNIXではプログラムを小さな粒度に保つべきという掟があります。複雑な処理を行いたいときは、上の例のように、それら小さなプログラムをパイプ`|`で連結して実現するのです。

つまり個々のプログラムは、小さな責任を冴えたやり方で果たせ、すなわち`Do one thing well`であれという訳です。

一人の技術者が設計の全てを把握できていた古き良き時代は遠に過ぎ去り、現代は複雑なシステムを複数人が分担する構造になっています。さらにシステムは常に機能の追加と変更、そして不具合に襲われます。したがって、現代のシステムは、`上手く動く細かいプログラムを組み合わせて作る`という設計が望ましいとされています。`Do one thing well`はそのための思想と言えるでしょう。

## 6. Fat model, Skinny controller

Ruby on Rails等のMVCデザインパターンで定石とされる考え方です。Model層にビジネスロジックを集め、Controller層は薄く保ちます。ControllerはModelやService(後述)のメソッドをコールし、自身でビジネスロジックを持ちません。

```ruby:悪いusers_controller.rb
class UsersController
  def create
    # 新規User作成
    user = User.create!(params)
    
    # 作成したUserに通知
    mailer = Mailer.new
    mailer.to = user.email
    if user.admin? then
      mailer.subject = '管理者さん、ようこそ'
    else
      mailer.subject = 'ユーザーさん、ようこそ'
    end
    mailer.send
  end
end
```

Userを作成した後、そのUserにメールを送信しています。が、Userインスタンスの属性で条件分岐をしたりと、Controllerがビジネスロジックを持ってしまっています。

```ruby:良いusers_controller.rb
class UsersController
  def create
    user = User.create!(params)
    user.sendmail
  end
end
```

Userへのメールの送信処理は、Userクラスに作ったインスタンスメソッド`sendmail`に移しました。お蔭でControllerには一行のメソッドコールを書いておくだけで済みます。

## 7. Mixin

前項でビジネスロジックはModel層に寄せるのがセオリーであると書きました。しかし１つのModelクラスにメソッドを書いていたのでは、Modelクラスの行数が嵩みます。そこでメソッド定義はModelクラスからモジュールとして外に追い出し、Modelクラスはそのモジュールを読み込むように作ります。これがMixinです[^4]。

[^4]: Mixinは定義が曖昧で、プログラミング言語ごとに意味が微妙に異なる。広義の定義は「それ自体では動作せず、他のクラスから継承されることでそのクラスに機能を提供する」というようなもの。

```ruby:悪いコード.rb
class User
  attr_accessor :family_name, :first_name, :age, :hobby

  def initialize(params)
    @family_name = params[:family_name]
    @first_name  = params[:first_name]
    @age         = params[:age]
    @hobby       = params[:hobby]
  end

  def full_name
    "#{family_name} #{first_name}"
  end

  def introduce_myself
    "#{full_name}です。#{hobby}が好きな#{age}歳です。"
  end
end

user = User.new family_name: '秋山', first_name: '雅之', age: 29, hobby: 'パソコン'
p user.introduce_myself
# "秋山 雅之です。パソコンが好きな29歳です。"
```

４つの属性を持つクラスです。フルネームと自己紹介文を返す２つのインスタンスメソッドがクラス内に定義されています。

```ruby:良いコード.rb
# mixins/greetable.rb
module Greetable
  def full_name
    "#{family_name} #{first_name}"
  end

  def introduce_myself
    "#{full_name}です。#{hobby}が好きな#{age}歳です。"
  end
end

# models/user.rb
class User
  include Greetable

  attr_accessor :family_name, :first_name, :age, :hobby

  def initialize(params)
    @family_name = params[:family_name]
    @first_name  = params[:first_name]
    @age         = params[:age]
    @hobby       = params[:hobby]
  end
end

user = User.new family_name: '秋山', first_name: '雅之', age: 29, hobby: 'パソコン'
p user.introduce_myself
# "秋山 雅之です。パソコンが好きな29歳です。"
```

２つのインスタンスメソッドを`Greetable`モジュールとしてクラス外に追い出しました。クラスは`Greetable`モジュールをinclude文によってMixinしています。これによりModel層にビジネスロジックを集約させても、クラスファイルの肥大化を避けられます。

ちなみにRuby on RailsにはConcernというフレームワーク独自のMixin機構が備わっており、通常はそちらを使用します。

参考：[[Rails] ActiveSupport::Concern の存在理由](https://qiita.com/castaneai/items/6dc121ce6ff100614f42)

## 8. Service layer

Mixinにも弱点はあります。主に次の２つです。

### 内部状態に依存する

理想的な関数とは、`内部状態`を持たず、出力が入力だけに依存する関数です。しかしソフトウェアは理想的な関数だけでは構築できません。ここでいう`内部状態`とは、関数に渡される引数以外の情報という程度の意味です。データベースであったり、外部APIであったり、ログイン中のユーザーの性別であったりします。

Mixinはクラスを拡張するという性質上、出力が入力以外、特にクラスが持つインスタンス変数に依存するケースが多いです。

では、出力が入力以外に依存すると何故困るのでしょう。それはコードの見通しが悪くなるためです。入力以外に依存する関数の挙動を把握するために、プログラマは依存箇所をコードベースからあちこち探す必要があります。Mixinにビジネスロジックを書いたけど、そのロジックはMixinを継承するクラスによって動きが変わるというケースは多いです。Mixinだけで処理が閉じていない以上、テストケースを書くコストまで上昇します。

### 名前空間の共有

Mixinのもう一つの弱点は名前空間の共有です。クラスにメソッドや属性を追加するというMixinの性質上、それらのメソッドや属性は同じ名前空間を共有します。そのためメソッドや属性の命名は冗長になりがちです。つまりMixinモジュールは他のMixinと名前が被らないように、メソッド名を`add`ではなく`addFriend`にしなくてはいけないということです。

### そこでService Layer

Service layerはこの２つの弱点を低減させる力があります。**複数のModelを使う** または **単に複雑** なビジネスロジックは、Service layerと呼ばれるMVCとは独立した層にクラスとして実装されます。

例えば、ユーザーから別のユーザーへ送金するControllerメソッドがあったとします。

```ruby:悪いコード.rb
# controllers/banks_controller.rb
class BanksController
  def transfer
    User.transaction do
      sender   = User.find(params[:sender_id]) # 出金元
      receiver = User.find(params[:target_id]) # 入金先
      sender.bank.minus(params[:price]) # 出金
      receiver.bank.add(params[:price]) # 入金
      Log.store!(receiver, sender, params[:price])
    rescue => e
      ...
    end
  end
end
```

トランザクションしたり、ユーザーを２件取得したり、入出金したり、ログを作ったりしていますね。これだけならまだ見通しは良いですが、送金先が複数件になったり、ユーザーにメールを送ったりという要件が加わると、途端にロジックが複雑になります。

それではService layerに処理を任せてしまいましょう。

```ruby:良いコード.rb
# controllers/banks_controller.rb
class BanksController
  def transfer
    sender   = User.find(params[:sender_id]) # 出金元
    receiver = User.find(params[:target_id]) # 入金先
    Transfer.new(sender, receiver, params[:price]).do
  end
end

# services/transfer.rb
class Transfer
  def initialize(sender, receiver, price)
    @sender   = sender
    @receiver = receiver
    @price    = price
  end

  def do
    User.transaction do
      sender.bank.minus(@price)
      receiver.bank.add(@price)
      log
    end
    true
  end

  private

  def log
    Log.store!(@receiver, @sender, @price)  
  end
end
```

行数は嵩みましたが、Controllerからビジネスロジックを排除することができました。
送金処理をクラスとして実装できたので、入力と出力の関係が分かりやすくなり、テストが容易になっています。コードの再利用性も向上しました。名前空間も気にせず分かりやすく短い命名が可能です。

これなら仕様変更で送金処理が複雑化しても、そんなに怖くないですね[^4]。精神が安定するのが分かります。

[^4]: 仕様変更は無いほうが良い。

# おわりに

使用頻度が高い技法を独断と偏見を持って８つに絞り紹介しました。「それを挙げるならこれも要るだろ」「その技法はむしろバッドプラクティス」など反対意見もあるはずです。一つの仕様でも十人いれば十通りの設計があるのがプログラミングです。時と場合によって引き出しから最適な技法を取り出す必要があります。

ここで紹介した技法は極々一部に過ぎません。`SOLID原則`, `DRY原則`, `リスコフの置換原則`, `YAGNI`, `GoFの23のデザインパターン` 等など、様々な原則、デザインパターン、テクニックが存在します。それらを武器に複雑なソフトウェアをクリーンなアーキテクチャとして構築することは、職業プログラマの責任であると共に最高の遊びです。

Twitter: [@aki202](https://twitter.com/aki202)