Rubyのendは美の観点から必要だ。END HELLは要リファクタへの警告である。メソッド分離、{ }、Guard、三項、ポリモーフィズムで回避せよ!
ブログを下記に移転しました。デザイン変更により移転先では記事が一層読みやすくなっていますので、よろしければ移動をお願い致します。
Rubyのendは美の観点から必要だ。END HELLは要リファクタへの警告である。メソッド分離、{ }、Guard、三項、ポリモーフィズムで回避せよ! : melborne.github.com
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Rubyのendは構文上の欠点だとされ
一部のRubyistから
END HELLと忌み嫌われている
その一方でRubyのendを愛し
endを綴り続けることで
悟りの境地に達したRubyistもいる
Rubyistは一日に何度もendと書くことで、
何事にも終わりがあることを日々確認しているのである
by @nalsh*1
そしてこの私はというと
見習うべきRubyistの姿がそこにあるのに
defと打つと私のエディタが勝手にendと補完するので
物事の終わりも確認できず
醜いendの連なりだけを毎日目にする
という虚しい日々を送っている
結局未熟な私はendの悟りを開くことができず
かつて誤った道に足を踏み入れた
Ruby1.9でもEND HELLを解消したい! - hp12c
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