【Ruby】より"よい" FizzBuzzを書く

# はじめに

自分でルールを拡張できるFizzBuzzを、より`"よく"`書きます。
ダブルクオートがついているのは、**「よさとは一体……」**という深淵に対する畏怖の現れです。

コードのよさは**個別の状況**によって変わるものです。
さまざまな**評価軸**を知り、それぞれのよさを高める書き方・考え方を身につけましょう。


## 対象読者

- （前提） Ruby で一応 FizzBuzz を書ける人
- より`"よい"`コードを書きたい人


# 問題

標準入力から以下のように実行できる `fizzbuzz.rb` を作成します。

`"3:Fizz"` のような文字列がいくつか与えられ、最後にFizzBuzzの対象となる数値が入力されます。

```console:
ruby fizzbuzz.rb 3:Fizz 5:Buzz 25
Buzz

ruby fizzbuzz.rb 3:Fizz 5:Buzz 15
FizzBuzz

ruby fizzbuzz.rb 3:Fizz 5:Buzz 7:Guzz 35
BuzzGuzz

ruby fizzbuzz.rb 3:Fizz 5:Buzz 13
13
```

追記： 0をルールとして含めた場合の結果は特に指定しません。

```
ruby fizzbuzz.rb 0:Zero 0
```


# 前置き コードのよさ

解答のまえに、コードのよさについてちょっと考えます。

本記事は**「良いコードをかくぞ！」**という気持ちになってもらうのが狙いです。

網羅的に書くのはすごい本たちにまかせます。

👇すごい本たち

- [リーダブルコード ―より良いコードを書くためのシンプルで実践的なテクニック (Theory in practice)](http://u0u0.net/pgw9)
- [UNIXという考え方―その設計思想と哲学](http://u0u0.net/xaQW)
- [プリンシプル オブ プログラミング3年目までに身につけたい一生役立つ101の原理原則](http://u0u0.net/Uh00)


## コードのバリエーション

ある問題を解決するコードには無数のバリエーションがあります。

全体を貫くアルゴリズムによる違いもありますが、こまかい文法による違いもあります。

後者については、たとえば、以下のように `fizzbuzz` の結果を戻り値にしたいとき。

```ruby:
def fizzbuzz(number)
  #...

  if result == ""
    return number
  else
    return result
  end
end
```

これは、Rubyの `if` が文ではなく式であることを知っていればこう書けます。

```ruby:
def fizzbuzz(number)
  #...

  return if result == ""
    number
  else
    result
  end
end
```

さらに、Rubyのメソッドは必ず最後の値を戻り値にするので、 `return` が省略できます。

```ruby:
def fizzbuzz(number)
  #...

  if result == ""
    number
  else
    result
  end
end
```

さらにさらに、今回なら三項演算子を使うのもいいでしょう。

```ruby:
def fizzbuzz(number)
  #...

  result == "" ? number : result
end
```

条件を反転させることもできます。

```ruby:
def fizzbuzz(number)
  #...

  result != "" ? result : number
end
```

`String` クラスの `empty?` メソッドを使うという手もあります。

```ruby:
def fizzbuzz(number)
  #...

  result.empty? ? number : result
end
```

このように、同じ内容でも書き方はさまざまあります。

これらのコードの優劣は、コード単体では評価できません。
つまり、いついかなるときもこの書き方はこの書き方よりよい、なんてことはありません。

たとえば、三項演算子は短くかけて便利ですが、知らない人に対してはコードをいかつく見せる機能をもちます。
僕が初学者に見せるコードを書くなら、いきなり三項演算子は使わないでしょう。

`empty?` メソッドも、 `String` クラスに `empty?` メソッドがあると知らなければ読みにくくさせてしまうかもしれません。


## コードの評価軸

上にみた「いかつさ」のような**コードの評価軸**はたくさんあります。

- 実行速度
- メモリ使用量
- 可読性
- 拡張性
- 安全性
- 堅牢性
- テスト可能性
- 凝集度と結合度

`empty?` メソッドや三項演算子は、人によって可読性がかわります。

コードそのもののなかに「読みにくさ」や「遅さ」があるわけではありません。

このようにコードは、「誰が読むのか」などの**個別の状況**を踏まえて評価されます。

- コードを読む**人**
- コードを使う**期間**
- コードを使う**頻度**
- 入力される値の**範囲**
- 不正な入力の**可能性**

などなど……。


## 言語選択

上にあげたコードの評価にもっとも覆しがたく影響を与える「状況」は、選択する**言語**です。

たとえばRubyは、C言語とは比べ物にならないくらい実行速度が遅いです。
逆立ちしてもかなわないので、実行速度が大事ならRubyで書くのがそもそも間違いです。

だからといって、RubyがC言語の下位互換というわけではありません。
評価軸はたくさんあるのです。

そんな「遅い」Rubyの強みは、ずばり、**可読性**と**開発速度**です。
Rubyで書くのなら、このふたつを意識しましょう。

Rubyの長所短所については、たくさんの人がすばらしい記事を書いています。
特に以下のQuoraの回答はおすすめです。

👉[なぜrubyは他の言語と比べて遅いのでしょうか？](https://jp.quora.com/%E3%81%AA%E3%81%9Cruby%E3%81%AF%E4%BB%96%E3%81%AE%E8%A8%80%E8%AA%9E%E3%81%A8%E6%AF%94%E3%81%B9%E3%81%A6%E9%81%85%E3%81%84%E3%81%AE%E3%81%A7%E3%81%97%E3%82%87%E3%81%86%E3%81%8B)


よいコードへの道は、言語そのものについて知るところから始まります。


# 解答例

前置きが長くなりましたが、問題の解答例です。

```ruby:
def main(argv)
    m = argv.last.to_i
    argv.delete_at(-1)
    a = []
    s = []
    argv.each do |v|
        a << v.split(":")[0].to_i
        s << v.split(":")[1]
    end

    output = ""
    a.each.with_index do |a, i|
        output << s[i] if m % a == 0
    end

    if output == ""
        return m
    else
        return output
    end
end

puts main(ARGV)
```

~~ツッコミどころ~~ リファクタリングのやりがいがたくさんあります。


# 個別の評価軸について

評価は評価軸ごとに独立のものではなく、可読性を上げると実行速度が下がるといったように、互いに結びついています。

状況に応じたチューニングをする前に、個別の評価軸ごとにできることを考えます。

評価軸はたくさんあってキリがないので、以下の点に絞ります。

- 可読性
- 実行速度
- 拡張性
- 安全性


## 可読性


### インデントはスペースふたつ

Rubyの慣習的に、インデントはスペースふたつです。

強制ではないから従わなくてもいい、というものではありません。
読み慣れたコードの見た目をしていないだけで脳は負荷を感じ、可読性は下がるのです。

エディタの設定を見直して、必ずインデントをスペースふたつにしましょう。


### 適切なメソッド名をつける

`main`は、C言語などでは意味のある名前ですが、Rubyでは必要ありません。[^main]

今回は fizzbuzz のためのメソッドなので、素直に`fizzbuzz`としておきましょう。

```ruby:
def fizzbuzz(argv)
  # ...
end
```


### 変数名は内容をあらわすものを

`m` や `a` のような変数名は、「短く書く努力」ではなく**「適切な命名の怠慢」**です。
十分に体をあらわす名をつけましょう。

命名の妥当性を判断する方法としては、**その一行だけを読んで意味がわかるか？**をチェックすると良いです。

たとえばこの一行。
`m`が何のための変数なのかわかりません。

```ruby:
m = argv.last.to_i
```

コードの意味がわかるようにする一つの手は、コメントを書くことです。

```ruby:
# m: fizzbuzzの対象となる整数値
m = argv.last.to_i
```

コメントは雄弁で、なんでも伝えることができますが、それだけ書く手間と読む手間を要求します。

コメントを書く暇があったら、わかりやすい命名を考えましょう。

```ruby:
target = argv.last.to_i
```


### 適切な命名ができないとき

```ruby:
argv.each do |v|
  a << v.split(":")[0].to_i
  s << v.split(":")[1]
end
```

このうち、以下の部分について…。

```ruby:
a << v.split(":")[0].to_i
```

この`a`や`v`にも適切な名前をつけたいのですが、なかなか思いつきません。

名前をつけにくいときには、以下の状況が考えられます。

- 一行で多くのことをやりすぎている
- そもそもまわりのコードがおかしい

順に見ていきます。


#### 一行で多くのことをやりすぎている

```ruby:
a << v.split(":")[0].to_i
```

この一行では

1. 文字列を`:`で区切り、
2. その`0`番目をとりだし、
3. 整数に変換し、
4. 配列`a`の最後尾にいれる

なんと４つもの仕事をこなしています。

名前をつけられるレベルまで分解しましょう。

```ruby:
argv.each do |rule|
  splitted = rule.split(":")
  numbers << splitted[0].to_i
  strings << splitted[1]
end
```

さらに、`splitted`のような一時変数を意識したくないので、[多重代入](https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#multiassign)を使いましょう。

```ruby:
argv.each do |rule|
  num, str = rule.split(":")
  numbers << num.to_i
  strings << str
end
```

#### そもそもまわりのコードがおかしい

`"3:Fizz"`という、FizzBuzzのルールにおいて、数字の`3`と文字列の`Fizz`は密接な関係にあります。
関係をこわして数字だけ配列にいれるというような操作は、そもそもするべきではありません。

より意味を表した操作をしましょう。

```ruby:
rules = []
argv.each do |rule|
  num, str = rule.split(":")
  rules << [ num.to_i, str ]
end
```


### 適切なメソッドをつかう

標準で用意されているメソッドの使い所を知りましょう。

以下の場面では`Array#pop`メソッドが使えます。

```ruby:
# bad
target = argv.last.to_i
argv.delete_at(-1)

# good
target = argv.pop.to_i
```

`pop`は、もともと配列をスタックとして使うために用意されたメソッドです。

`pop`メソッドを言葉で説明するなら、
「自身の末尾から要素を取り除き、取り除いた要素を返すメソッド」です。
なんだか複雑そうにみえます。

しかし、一度スタックという概念と結びついているんだとわかれば、スタックも`pop`も忘れないでしょう。

概念同士の結びつきは、お互いの記憶を長持ちさせます。
次からは自由に使いこなせるはずです。

**メソッドや文法を学ぶときには、ぜひその歴史や存在意義を調べてください**。


## 実行速度

ずばり計算量を意識しましょう。
計算量とは、アルゴリズムの実行速度がざっくりどのくらいかを試算するための値です。

理論的に考えはじめるとわりと面倒なので、ざっくりわかればいいです。

とりあえず、以下の２つに気をつけましょう。

- ループ
- 配列とハッシュの違い


### ループ

ループとは、配列などの要素にたいして順番に処理をおこなうことです。
たとえば、`Array.each`や`Array.map`はループです。

ループの重要な特徴は、ループの対象となる要素が増えれば増えるほど実行時間が長くなることです（当然です）。
ですから、**ループ回数は減らしたほうが速い**です。

今回の例では、ループが２ヶ所で実行されています。

```ruby:
def fizzbuzz(argv)
  # ...

  # 標準入力からルールを取り出すループ
  argv.each do |v|
    # ...
  end

  # 結果を計算するループ
  a.each.with_index do |a, i|
    # ...
  end

  # ...
end
```

ループを減らしてみます。

```ruby:
def fizzbuzz(argv)
  # ...

  result = ''

  # 標準入力からルールを取り出し、結果を計算するループ
  argv.each do |rule|
    num, str = rule.split(':')
    result << str if target % num.to_i == 0
  end

  # ...
end
```

すっきりしました。

ここで、`Array#reduce`を使うこともできます。

```ruby:
def fizzbuzz(argv)
  # ...

  # 標準入力からルールを取り出し、結果を計算するループ
  result = argv.reduce('') do |res, rule|
    num, str = rule.split(':')
    res << str if target % num.to_i == 0
    res
  end

  # ...
end
```

`Array#reduce`の動きはすこしわかりにくいですね。
`Array#each`ではなく`Array#reduce`を使うメリット（デメリット）は何でしょうか？
調べてみてください。

まとめると、ループは少ないほうが実行速度が早くなります。
また一般に、ループの中でやることが増えるとコードの可読性が下がります。
バランスが肝心です。


### 配列とハッシュの違い

データを入れているのが配列かハッシュかで、実行速度に大きくかわる場合があります。

いくつかのルールの中で`7`に対応する文字列を調べてみます。

```ruby:配列の場合
rules = [[3, 'Fizz'], [5, 'Buzz'],  [7, 'Bizz']]

rules.each do |rule|
  if rule[0] == 7
    puts rule[1] 
    break
  end
end
```

```ruby:ハッシュの場合
rules = {
  3 => 'Fizz',
  5 => 'Buzz',
  7 => 'Bizz'
}

puts rules[7]
```

最大の違いはループがあるかないかです。

ハッシュは配列と違って、`7`に対応する値をとりだすためにループを必要としません。
キーがわかれば、すぐにそれに対応する値にアクセスできるのです。

キーとなる値をもとに取り出すようなデータは、配列でも表現できますが、実行速度の観点からみればハッシュ一択です。

…

複数の値を保持しておくものには、配列やハッシュのほかにもいくつかの形式があります。
これらの形式のことを**データ構造**といいます。

データ構造にはほかにもキューやスタック、リスト、グラフなどがあります。
配列やハッシュに比べると、より限定的な目的のために特化したデータ構造です。

データ構造は、実行速度だけでなくメモリ使用量にも大きく影響します。
しかし、Rubyを使う以上どちらも気にしすぎることはないでしょう。


## ここまでのまとめ

ここまでの内容をまとめると、たとえば以下のようなコードになります。

```ruby:
def fizzbuzz(argv)
  target = argv.pop.to_i

  result = ''
  argv.each do |rule|
    num, str = rule.split(":")
    result << str if target % num.to_i == 0
  end

  result == '' ? target : result
end

puts fizzbuzz(ARGV)
```

これでも問題の解答としては十分ですが、つづけて他の評価軸についても検討してみましょう。


## 拡張性

拡張性とは、コードの変更のしやすさです。

そのためには**不要な依存関係を断ち切る**ことが必要です。


### 入力形式に依存させない

引数が `["3:Fizz", "5:Buzz", 15]` のような特殊な入力形式に依存しているのは拡張性がさがるポイントです。
コードの可読性からみても望ましくありません。

そこで、FizzBuzzを解くメソッドと、標準入力を分解する**アダプター**としてのメソッドを分離する方法があります。

```ruby:
# より使いやすい入力形式を受け取るfizzbuzz
# fizzbuzz( 15, { 3=>'Fizz', 5=>'Buzz', ...} )
def fizzbuzz(target, rules)
  result = ''
  rules.each do |num, str| # RubyのHash#eachは順番が保証される
    result << str if target % num == 0
  end

  result == '' ? target : result
end

# 標準入力のargvをfizzbuzzメソッドにあわせるアダプター
def fizzbuzz_argv(argv)
  target = argv.pop.to_i
  rules = {}
  argv.each do |x|
    num, str = x.split(':')
    rules[ num.to_i ] = str
  end

  fizzbuzz(target, rules)
end

puts fizzbuzz_argv(ARGV)
```

これにより、`fizzbuzz`メソッドは息の長いメソッドになります。
今後、FizzBuzzを計算するウェブサイトを作ることになったとして、この`fizzbuzz`メソッドはなんの変更もなく使い回せるでしょう。

また、コード全体としてループの回数が増えた点は興味深いです。
これも実行速度とのトレードオフになっています。


### クラスを定義する

`fizzbuzz` をとことん抽象化すると、「数字を入力して、ルールにもとづいて文字か数字を出力するメソッド」です。

倍数しかルールとして登録できないのは、拡張性が低いですね。

自分の好きなルールを登録できるようにしましょう！

Rubyならクラスを定義するのがいいでしょう。
マジで定義するのは面倒なので読者への宿題とします😤

```ruby:
class FizzBuzz
  # ...
end

fizzbuzz = FizzBuzz.new
fizzbuzz.multiple_of(3, 'Fizz')
fizzbuzz.multiple_of(5, 'Buzz')
fizzbuzz.divisor_of(360, 'Divv') # 360の約数

fizzbuzz.culc(15) # FizzBuzzDivv
```

また、入力形式にあわせたイニシャライザを用意することもできます。

```ruby:
class FizzBuzz
  # ...
end

fizzbuzz = FizzBuzz.from(argv: argv)

fizzbuzz.culc(15) # FizzBuzzDivv
```


## 安全性

安全性とは、エラーの生じにくさ、あるいはエラーを適切にハンドリングできている度合いです。

```Ruby:
def fizzbuzz(target, rules)
  result = ''
  rules.each do |num, str|
    result << str if target % num == 0
  end

  result == '' ? target : result
end
```

さきほど入力形式に依存しないように変更した`fizzbuzz`メソッドです。

このメソッドは第一引数に数字がはいることを想定していますが、間違えて文字列を入れてしまいました。

すると・・・。

```ruby:

puts fizzbuzz( 15, { 3=>'Fizz', 5=>'Buzz' })
# FizzBuzz

puts fizzbuzz( '15', { 3=>'Fizz', 5=>'Buzz' })
# 15
```

結果は`15`になりました。
これは数字の`15`ではなく文字列の`"15"`です。
いずれにせよ、望ましくない結果です。

Rubyは**動的型付け言語**なので、引数に予期せぬ値が入るということは往々にしてあります。

間違った引数を与えた場合にどう振る舞うかを考えておくべきでしょう。

方針としては２つあります。

- エラーを出す
- エラーを出さない

（驚くことに、すべての方針はこの２つに分類できます！）


### エラーを出す

俺が決めた挙動以外はみとめない。
そんな場合はエラーを出しましょう。

以下では、引数のクラスを判定し、それぞれエラーを出しています。

```ruby:
def fizzbuzz(target, rules)
  unless target.kind_of? Integer
    raise ArgumentError, 'targetの値はIntegerを継承したクラスのインスタンスだけ！'
  end
  unless rules.kind_of? Hash
    raise ArgumentError, 'rulesの値はHashを継承したクラスのインスタンスだけ！'
  end

  result = ''
  rules.each do |num, str|
    result << str if target.to_i % num == 0
  end

  result == '' ? target : result
end
```


### エラーを出さない

エラーを出さずに、できる限り違う入力にも対応するという方針です。
ちょっとややこしいですが、Rubyらしい方法です。

```ruby:
def fizzbuzz(target, rules)
  result = ''
  rules.each do |num, str|
    result << str if target.to_i % num == 0 # target が target.to_i になった
  end

  result == '' ? target : result
end
```

`target.to_i`になりました。

これにより、`target`に文字列を入れても`fizzbuzz`を実行できるようになりました。

```ruby:
puts fizzbuzz('15', { 3=>'Fizz', 5=>'Buzz' })
# FizzBuzz
```

`String#to_i`と`Integer#to_i`がそれぞれ実装されているからです。
`target.to_i`が実行される段階では、`target`が文字列なら`String#to_i`が、整数なら`Integer#to_i`が呼び出されます。

つまり、整数に変換する`to_i`メソッドを呼び出すことができるオブジェクトならなんでも`target`にすることができます。

```ruby:
def fizzbuzz(target, rules)
  # ...
end

class Box
  attr_accessor :item, :count

  def initialize(item, count)
    @item = item
    @count = count
  end

  def to_i
    @count
  end
end

box = Box.new('りんご', 15)

puts fizzbuzz(box, { 3=>'Fizz', 5=>'Buzz' })
# FizzBuzz
```

`Box`クラスは、あるアイテムをいくつか入れておけるクラスです。

このクラスには`to_i`メソッドを定義してあるので、`fizzbuzz`メソッドはその内部で`box`を`15`に変換します。
見事`FizzBuzz`という結果を得られました。[^box]

Rubyでは、あるメソッドが呼び出せるかどうかは、呼び出した瞬間に判定されます。
実際に実行されるのが`String#to_i`だろうと`Integer#to_i`だろうと`Box#to_i`だろうと、とにかく`to_i`というメソッドが実行できればいいのです。
`to_i`が期待するのは、ただ整数値に変換できること、それだけです。

**オブジェクトのクラスよりも、オブジェクトがメソッド呼び出しに応えられるかが肝心**です。

このような考えのもとプログラムを書くことを、**ダックタイピング**といいます。
「それがアヒルかどうかよりも、アヒルのように鳴くかどうかが肝心」ということです。

ダックタイピングをうまく行えば、Rubyらしく柔軟なコードが書けるようになります。


# 個別の状況について

これは本当にさまざまです。

状況の判断材料を再掲します。

- コードを読む**人**
- コードを使う**期間**
- コードを使う**頻度**
- 入力される値の**範囲**
- 不正な入力の**可能性**


## コードを読む人

どれくらいコードが書ける人が読むのかによって、書くべき内容は変わります。
初心者に`Array#reduce`など見せても混乱するだけです。

少なくとも、読んでもらう必要があるコードはできるだけ**可読性**の高いコードにすべきです。
他人だけではなく自分もコード読むことに注意してください。

**書くときは謙虚に、読むときは傲慢に**。
これは今思いついた名言です。

プログラマというものは、よりテクニカルに問題を解決しようとしたがるものです。

しかし、よりよいコードを書く人はいくらでもいます。
**「自分には最善のコードは書けない」**と謙虚になりましょう。

あとで自分が読んだときにわからなくなる可能性もあります。
そのときはなんとしても読み解いて、その場でリファクタリングするのです。
**「自分はよりよいコードに改善できる」**と、傲慢になりましょう。


## コードを使う期間

コードを使う期間が長いほど、特に**拡張性**が重要になります。
思わぬ変更はすぐにやってきます。

逆に、コードを使う期間が短いなら、正常に動きさえすればぐっちゃぐちゃのコードでも構いません（と言い切るのもなんですが）。


## コードを使う頻度

頻繁に使うコードなら、**実行速度**が重要になるかもしれません。
１日１０回実行するコードの実行時間をたった１秒短縮するだけで、１年間で１時間の短縮になります。


## 入力される値の範囲

10万件のデータをあつかうような場合は、**計算量**や**メモリの消費量**についてよく検討する必要があります。


## 不正な入力の可能性

コードの**安全性**が問題になります。

コードを一般に公開する場合や、いろんな場所でコードを使い回す場合にとくに注意しましょう。


# まとめ

FizzBuzzを題材に、いいコードについて考えました。

コードにはたくさんの**評価軸**があり、互いに複雑にからまりあっています。
評価軸ごとに最善のコードを考え、さらに評価軸同士のバランスをとりましょう。

また、いいコードは**個別の状況**に応じて変わるものです。
コードに何が求められているのかを判断できるようになりましょう。

この記事をきっかけに、コードを見る目がかわったら嬉しいです！


[^main]: Rubyで`main`といえば、トップレベルの`self`のことです。
[^box]: `fizzbuzz(box.count, { 3=>'Fizz', 5=>'Buzz' })`のように書けばいいじゃん、というのは野暮です。