Rubyのendは構文上の欠点だとされ、一部のRubyistからEND HELLと忌み嫌われている。
その一方でRubyのendを愛し、endを綴り続けることで悟りの境地に達したRubyistもいる。
Rubyistは一日に何度もendと書くことで、 何事にも終わりがあることを日々確認しているのである by @nalsh1
そしてこの私はというと、見習うべきRubyistの姿がそこにあるのに、defと打つと私のエディタが勝手にendと補完するので、物事の終わりも確認できず、醜いendの連なりだけを毎日目にする、という虚しい日々を送っている。
結局、未熟な私はendの悟りを開くことができず、かつて誤った道に足を踏み入れた。
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を回避せよ!
blog comments powered by Disqus