Rubyのendは構文上の欠点だとされ、一部のRubyistからEND HELLと忌み嫌われている。

その一方でRubyのendを愛し、endを綴り続けることで悟りの境地に達したRubyistもいる。

Rubyistは一日に何度もendと書くことで、 何事にも終わりがあることを日々確認しているのである by @nalsh1

そしてこの私はというと、見習うべきRubyistの姿がそこにあるのに、defと打つと私のエディタが勝手にendと補完するので、物事の終わりも確認できず、醜いendの連なりだけを毎日目にする、という虚しい日々を送っている。

結局、未熟な私はendの悟りを開くことができず、かつて誤った道に足を踏み入れた。

Ruby1.9でもEND HELLを解消したい!

endの必要性

しかし私は対称性という様式美の観点から、Rubyのendは重要な役割を果たしていることを知っている。

開いたら閉じなければいけない。左足を下ろしたら右足も下ろさなければいけない。おもちゃを出したら片付けなければいけない。

つまり対称性とは礼儀の作法である。つまりRubyのendはニッポンの心であり、私はRubyistがその文化の継承者であることを知っている2

このように美の観点から論じると、endはその構造的文化的背景によりプログラミングには欠かせない必須のエレメントであることが理解できるだろう。

問題の所在

しかしそうは言っても次のようなendの連なりには、若干の問題を感じざるを得ない。

module MyModule
  class MyClass
    def my_method
      10.times do
        if rand < 0.5
          p :small
        else
          p :large
        end
      end 
    end      <- ここ
  end
end
MyModule::MyClass.new.my_method # => 10
# >> :large
# >> :large
# >> :large
# >> :large
# >> :small
# >> :small
# >> :large
# >> :small
# >> :large
# >> :large

このようなendの連なりはむしろその形式美を破壊し、その可読性を著しく阻害する。

しかしこの問題の責務は言語としてのRubyにあるのではなく、実のところあなたにあるのである。そう、あなたの書いたコードに問題があるのである。先のコードはリファクタリングが必要なのである。

実はこれはRubyには意図されたことである。つまりRubyはあなたがEND HELLを量産すると、コードの美が破壊されるように設計されており、これによってあなたに要リファクタリングの警告を発していたのである!

許容end個数

さて先のコードにリファクタリングが必要なことはわかった。リファクタリングに際しまずは許容end個数すなわち美を破壊しないendの連なり個数がいくつであるのかを知ることが重要である。

一般に許容end個数はend対語平均語長未満とされるから、以下のように求められるだろう。

heads = %w(class module def if unless case while until for begin do)
heads.map(&:size).inject(:+)/heads.size.to_f # => 4.181818181818182

つまりendの連なりは4つ、理想的には3つ以下である。詳細はJIS X 3017(8.7.2: キーワード)を参照されたい3

END HELLの回避方法

具体的なコードによってEND HELLの回避方法は変わるが、ここでは先のコードにおけるEND HELLの回避方法をいくつか示すに留める。

その1: 三項条件演算子

if else end式に代えて?:三項条件演算子を使う。

module MyModule
  class MyClass
    def my_method
      10.times do
        p rand < 0.5 ? :small : :large
      end 
    end
  end
end

その2: { }(curly braces:波括弧)

do endに代えて{ }を使う。

module MyModule
  class MyClass
    def my_method
      10.times { p rand < 0.5 ? :small : :large }
    end
  end
end

その3:メソッド呼び出し

if else end式に代えてメソッド呼び出しを使う。

module MyModule
  class MyClass
    def my_method
      10.times { p [:small, :large][rand.round] }
    end
  end
end

その4:メソッド分割

メソッドを分割する。

module MyModule
  class MyClass
    def my_method
      10.times { p small_or_large(0.5) }
    end
    
    private
    def small_or_large(weight)
      rand < weight ? :small : :large
    end
  end
end

その5:ガード文

if else end式に代えてガード文を使う(ここではnext)。

module MyModule
  class MyClass
    def my_method
      10.times do
        next p :small if rand < 0.5
        p :large
      end 
    end
  end
end

その6:ポリモーフィズム

if else end式に代えてポリモーフィズムを使う。

class Float
  def size
    [:small, :large][self.round]
  end
end
module MyModule
  class MyClass
    def my_method
      10.times {
        p rand.size
      } 
    end
  end
end

その7:定数スコープ演算子

定数のスコープ演算子::を使う。

module MyModule; end
class MyModule::MyClass
  def my_method
    10.times {
      p rand.size
    } 
  end
end

以上まとめると、END HELLはRubyのせいではなく、あなたのせいであり、したがってあなたのコードをリファクタして直ちにEND HELLを回避せよ!

  1. [twitter](https://twitter.com/#!/nalsh/statuses/105432772570656768)
  2. まったく訳がわからない
  3. [JIS X 3017](http://www.webstore.jsa.or.jp/webstore/Com/FlowControl.jsp?lang=jp&bunsyoId=JIS+X+3017%3A2011&dantaiCd=JIS&status=1)


blog comments powered by Disqus
ruby_pack8

100円〜で好評発売中!
M'ELBORNE BOOKS