Ruby のコーディングスタイル

$Id: codingstyle.rd,v 1.6 2003/06/18 23:03:32 aamine Exp $

今日 (1999/02/01)、Linux 2.2.1 のソースをダウンロードしてきた ついでに始めてドキュメント群を読んでみたのだが、そこに CodingStyle なんて文書があった。Linux の作者の Linus 氏が Linux カーネルを 書く時のコーディングスタイルについて書いているのだが、これが なかなかおもしろい。さっそくまねをして、Ruby におけるコーディ ングスタイルについて書いてみようと思う。

とは言っても筆者はヒトのコーディングスタイルをどうこう言えるほど 偉くはないし、これを読む人に「ソースコードはこのように書け」と 言うつもりもない。この文書はあくまで「おれはこうじゃなきゃやだ!」 という自己主張である。

インデント

インデントは基本的に 2。インデントがでかすぎると end が離れて 美しくない。でも {....} のときは 4 のほうが格好いいようなので 4 だ。このへんは最近になってかなり趣味が変わった。

※ 有名な Ruby hacker の前田修吾氏はかつてインデントを「3」に していた。この理由について筆者(あおき)は

if true
   while true
      unless false
         return 1
      end
   end
end

のように end がピッタリそろうのが素敵かなあ、と評したのだが 実際の理由は全然違ったようだ ( [ruby-list:18603] 参照)。

※※ 素敵という言葉は江戸時代にできたそうだ。 筆者はこのステキな響きが気にいっている。

メソッド呼びだし

括弧の省略

Ruby ではメソッドの引数を囲う括弧を省略できる。最初はなんとなく 気にくわなかったが、一度味を知ってしまったらもうやめられない。 括弧はとってもつかれるキーなのだ。なにしろ、シフトがある。しかも ( と )で二回も。そのうえ隣りにあるのですぐ間違える。さらに、筆者は 106 キーボードで 101 キー配列を使っている都合上、ひとつだけずれて 印刷されていて気持ち悪い。そりゃー確かにほとんど自分のせいだったり するわけだけどとにかく括弧はいやなんだ。括弧なんてあっちいけー!

そうだ、書き忘れたがこれは呼びだしの場合だけだ。定義の時の仮引数の 括弧は絶対に省略しない。なぜならそれは単に見慣れなくて気持ちわるい から、ではなくて(それもあるが)、メソッド名を強調したいからである。 括弧をなくすとメソッド名と引数が並列に見えてしまう。ただし引数がない ときだけは省略する。

(追記 1) しばらく Ruby を書いていてときどき C を使ったりすると知らず 知らずのうちに括弧を省略して書いてしまい、パースエラーになって始めて 気付く……という恐ろしい噂がある。

(追記 2) そういうわけなので当然 Lisp は嫌いだ。でも Scheme は好きだ。

(追記 3) 同じ悩みを持つひとはキーボード最上段の記号キーと数字キーを 全部いれかえるとよいかもしれない。数字キーは普通はあまり使わない。 ……と書いてはみたものの、実際やってみたらすごく使いにくい。論理的 にはよろしくても、キーボードに関しては慣れがかなり影響する。

括弧の位置

例えば引数がたくさんあるメソッド呼びだしでは

this_is_method_call_with_many_argument(
  this_is_argument_no_1,
  this_is_argument_no_2,
  this_is_argument_no_3 )

のようにするのか、

this_is_method_with_many_argument( this_is_argument_no_1,
                                   this_is_argument_no_2,
                                   this_is_argument_no_3 )

のようにするのか。また閉じ括弧の位置は最後の引数のうしろか、独立の 行か。Array[]Hash{} でも同様の問題が発生する。この問題 は自分でも行ったり来たりしていて決定打がないのだが、現在の結論とし ては後の形式を使うことにしている。この書きかたのほうがずっと見やすい。 特にメソッドが値を返すときは後の形式のほうがイメージがとらえやすく なる。また、ifcase を右辺に書くときもこの形式(下図)。

val = case arg
      when String then arg
      when Fixnum then arg.id2name
      else
        arg.must String, Fixnum
      end

また閉じ括弧を独立させるのは絶対にやらないようになった。同じケタに 開き括弧と閉じ括弧を縦におくと、人間の目の錯覚で閉じ括弧のほうが 前にあるように見える。この錯覚のせいで見た目が気持ちわるい。また、 閉じ括弧が最後の文の後ろにあるほうが「かたまり感」があるような気がする。

すきま

それから、括弧のあとの空白を入れる・入れない も重要である。筆者はプ ログラムを書き始めた時からなぜか「括弧のあとには空白を入れるものだ!」 と信じていたのだが、C 言語を使い始めてからそうでもないことに気付いた。 特に K&R で空白を入れていなかったのがショックで、少なくとも C を使う ときは入れないようになった。

しかし Ruby ではやっぱり空白はあける。括弧を省略した場合とのつりあ い、見た目のよさなどを考えるとこれが一番よい。……と考えていたのだ が、最近はどんどん空白を消している。目が慣れると空白が間延びして見 えてくるみたいだ。

ちなみに、開き括弧の前に空白を入れたりするのは絶対にやってはいけない。 カスケードしたときに「かたまり感」がなくなるし、実際問題としても それを変な場所でやるとパースエラーになったりして不便である (これはまつもとさんが秘かに「思想をふりかけている」結果らしい)。

すきま (2)

メソッドの前につけるピリオドは

receiver   .    method_name( arg1, arg2 )

のようにまわりに空白をおくことができる。挙句のはてには

receiver   .
method_name( arg1, arg2 )

などと次の行にメソッドを書くこともできる。まあ前のものは桁を揃え たいとかその時々の理由があるなら許容範囲だが、後のほうの形式は絶 対に使ってはならない。例外は Just Another Ruby Hacker をつくると きと「Ruby 言語不明瞭コンテスト」に出典するときだけである。いつ もこんな邪悪なコーディングをしている人は Bignum がオーバーフロー してゼロに戻ってくるまで Ruby インタプリタに呪われるであろう (呪 われると一歩あるくたびに経験値が減る、わけではなくて、文末にセミ コロンを書かないとパースエラーが出るようになるらしい)。

と書いておいたら前田(修)さんからメールが。メソッドチェーンが長く なった時に継続するのに便利だという反論である。しかし、ピリオドが 一個だけ置いてあっても見落す可能性大だし、筆者はそこまでしてつな がなくてもいいじゃんと思うのでやっぱりダメー。それでもやっぱりつ なげたいときは、せめてバックスラッシュを置いて継続を明示するのが よいと思う。

イテレータ

Ruby の特徴と言えば、徹底したオブジェクト指向もさることながら、 イテレータを忘れてはならない。特にイテレータは二通りの書きかたが できるのでいかにも論議を呼びそうで楽しみである。

イテレータは、一行におさめるときは {....} で、二行以上のときは do....end、が基本。なんたって美しい。それに、ifwhileend で終わるので、それとそろえたほうがよい。ただし例外として、end が 重なり、かつ「範囲型」または「コンテキスト型」 のイテレータの時は {....} でもよいことにしている。

一行で書くときに {....} をつかうのは、そのほうが見た目がいいから である。まず、かっこのほうが文字の密度が低い。これは重要だ。だが もっと重要なのは式のバランスである。doend を見てなにか 気付かないだろうか。両方とも重心は下にあるくせに、上にのびる棒が 端にある。それゆえ一行に書くとどうしても「いまにも倒れそう」な イメージになるのだ。逆に、行をわけたときは do .... end のほうが バランスよく見える。

イテレータ括弧

ところで最近また悩みの種が増えた。{|i| ... } 形式のときに、

each{|i| ... }
each {|i| ... }

のどちらを使うかということだ。筆者は最近は下の形式が好みだ。 特に、イテレータへの引数がないときは断然下。 そのほうが密度が低くなって見やすい。

each{ |i| ... }

というのも一瞬考えてはみたんだけどこれは気持ちわるすぎるので却下。 なのでやっぱりメソッド名と {} の間には空白を入れる。まとめると以下。

ret = arr.map {|i| i.dup }
arr.map {|i| i.dup }.each {|i| p i }

うむっ。

イテレータ引数

イテレータブロックの引数の位置。大多数は

arr.each do |i|

だと思うが、ごくまれに

arr.each do
|i|

というのを目にする。これは聞くところによると Smalltalk の 流儀らしいのだが、おれは絶対にやらない。こんなの気持ちわるいし、 Ruby は Smalltalk ではないので Ruby 流儀でゆくのが人の道である。

イテレータ引数 (2)

イテレータの引数が二つ以上ある場合、間に空白は入れるべきだろうか。

arr.each_with_index do |item, idx|
arr.each_with_index do |item,idx|

これにはいまだ結論が出ていないがだいたいのルールはできてきた。 引数名が短い時は空けず、長い時は空ける。それから * が付く時も 空ける。それから最後に , が付く時 (それ以降の引数は捨てる、の印) にはその後にもスペース。

some_iterator do |a,b,c|
some_iterator do |a, b, *c|
some_iterator do |param, param, param, |

文字列

Ruby では文字列がいろんな書きかたができる。代表は「'」と「"」だが その他にも「% 文字列」や「ヒアドキュメント」もある。一番よく見かける のは、C や Perl の影響なのだろう、なんでもかんでも「"」にすること だが、そんなのは不可である。せっかくいろいろあるんだから全部場面に よって使いわけるのがよい。

まず、エスケープや式展開がなく、一行のときは常に「’」。このほう が見やすいしちょっと速い。式展開があるときは「”」。「”」が文字 列中に出てきてバックスラッシュが多くなるときは迷わず「%」を使う。 「%」は module_eval なんかと一緒に使うと特に相性がいい。最後に 使うのがヒアドキュメント。これを使うとインデントの最中にどーんと わりこんでくるのでめちゃくちゃ気持ちわるい。<<-HEREDOC を使えば インデントはできるが、インデント部分が取り除かれるわけではないので、 たとえば出力するための文字列には使えない。

コメント

コメントは # をつけて文のあとにちょこっとかく。これはかの有名な 「ソフトフェア作法」にもそれがよいと書いてあるからよいのだ。

内容についてはもうすでにいろいろ言われているので、少しだけにする。 コメントには自明のことはいちいち書かない。本当に重要なところ、 忘れてはいけないこと、注意をひきたいところに「だけ」書く。

Ruby ではソースコードがコメントである。 データ構造やコードの見かけに注意して、 自然と流れが読めてしまうコードを書くのがRuby はっかーとしての務めなのだ。 Ruby 作者のまつもとさんも 「ソースコードがドキュメントだ、バグも全て記述されている」 という名言を残しているではないかっ。

また Ruby ではクラス(モジュール)に特異メソッドを定義することで 定型メソッドを簡単に定義するメソッドを作ることができるので、 活用しよう。例えば def prop() @prop end より attr_reader :prop のほうが見やすい。 こういうことをやってコードを簡潔に、読みやすくできるということも 「Ruby ではソースコードがコメント」という主張の一部をなしているのである。

ちなみに埋め込みドキュメント ,(=begin...=end) は特に目的を持って 使われるもの(RD)なので、通常のコメントには # を使うほうがいい。 一時的に部分をまとめてコメントアウトしたいときは、,=begin c …… のように後ろに適当なもんを書いて使うと将来いいことがあるかもしれぬ。

(後記) しかし最近は RDoc が主流だ。

演算子とか

Ruby では演算子もいろいろ別の書き方ができる。and&& でもいいし、 not は ! でもいいし、Perl や sh や Lisp みたく ifand にする 記法も有効だ。どっちにするかちょっと考えちゃうのだが、まず基本は 「アルファベットで書く」。記号が多すぎるとわけわかになる。そんで、 結合順位上困るときだけ && || を使う。

と言っても結合順位の概念はなかなか初心者にはわかりにくいので、簡 単に判定する方法を教えよう。読みくだすのである。そしてその時、記 号類はあせって読む。アルファベットはゆっくり読む。それでちゃんと 文章が意味ごとにまとまって読まれていたなら、たぶんそれで大丈夫だ。

C なんかは演算子の優先順位が腐ってるので(これは作者も認めてるらしい) 括弧を頻繁に使わないと安心できないけど、Ruby ではそのへんはよく 考えられているのでそうそう括弧を使うことはないと思う。非常にあいまいで ないかぎり、無駄な括弧をつけるのは避けてできるだけ見ためがすっきり するようにしたい。

then と do

then や do は最近は全て省略するようになった。 後置 if に変更しやすいのが理由である。 以下は以前書いていたものだが、記録のために残しておく。

if・unless や while などにつく thendo。これは絶対に省略しては いけない。人間の美感の中では、バランスが非常に重要な役割を果たす。 ifunless はそのあとが(普通は)インデントされ、しかも最後の行が end だけになる。つまり、よほど条件が長くない限り、if 文全体は 「>」型になる。これは非常に見た目が悪い。 そんなことないというやつはバランス感覚がないのであるっ!

でも when だけは特別に省略する。when はずらずら縦に並ぶことがほ とんどなので > 型にはならないし、全部に then をつけるとくどくな り密度も濃すぎる。

ただしもちろんこれは条件式の直後にコードが続かない場合の話である。 同じ行にコードを書くときは必然的に then などが必要になるので考え る必要はない。……え? セミコロン? なんだっけそれ?

その他 if にからむ問題

本文が一文の時、修飾の if・unless と普通の if・unless、さらに andor のどれを使うべきか。まず、メソッドの最初に置くガード文は修 飾の if unless。メソッドの途中に置く異常値 returnand ornext break なんかが本文のときは修飾の if。それ以外で本文がメインアル ゴリズムの一部であるときは普通の if。そして普通の if 文はどんな に本文が短かくとも複数行に分ける。if 文を一行にまとめて書くとバ ランスが変。

条件中の代入。当然使うでしょ。

while line = arr.shift
  ...
end

とか、とっても便利。最近は ,-w つけても警告が出なくなった。わーい。

ifunless どっちを使うか。unless が使えるときは積極的に使う。 else も入ってきたら重要なほうが前に来るように使いわける。

a ? b : c

これは C にもある、あれのこと。これを嫌う人は(かつての筆者と同様) いっぱいいるのだが、使い方さえ間違えなければ便利なものだ。 例えばこんなふうに。

ret = (i > 0) ? i : 0

Ruby では if も値を持つのでそれでもいいのだけど、先に書いたように 筆者は if を一行に書くのは嫌いなので、どうしてもこっちになる。

ちなみに意味も考えればこの例は次のようにできる。

j = [i, 0].max

実際に使うときはこっちだな。

return

Ruby ではメソッドの最後の return は使っても使わなくても全く同じ 意味に書ける。よってこれは全部省略する。例えば次のように。

def expand( str )
  File.expand_path(str)
end

def ==( obj )
  MyClass === obj and @name == obj.name
end

理由は、まず簡潔さ。それから「Ruby ではすべての式が値を持つ」と いう基本思想を適切に表現するからである。ifwhile もイテレータ もメソッドも同様に値を持つ。ならば書式も全部同じにするのが適切で ある。あらゆる式の記述の中にオブジェクトの流れを見るのだ。

それと return なしのほうがちょっとだけ効率もいい。それはなぜかと いうと、Ruby Hacking Guide でも見てくれればわかるのだが、return は結局 longjmp だからだ。 breaknextlongjmp

self

Ruby では self はたいてい省略できる。あたりまえみたいだが Python なんかではそうではないらしい。実に情けない仕様である。 と書いているところからも想定できるように、self は全部省略する。

理由の第一は、その方がかっこいい。第二に、「デフォルトのターゲット は自分のいるところ」という規定は非常に人間の生理にかなったものであ る。たとえば、単に「郵便局に手紙だしてきて」と言った場合「郵便局」 はたぶん最寄りの郵便局であろう。「市役所どこですか」と言えばそれは その市内の市役所であろう。ニュースで「政府首脳が…」と言った場合、 普通は日本の政府首脳を指すであろう。だからこのようなデフォルト設定 は非常に「正しい」。同時に、それを利用するのも「正しい」。また第三 に、そうすることによってレシーバ指定の形式は全てオブジェクト外部か らのメソッドコールということになり、オブジェクトの隠蔽性を「体感」 できる。

ただし例外として、引数なしのメソッドの値を使うときは以下のように self を使う。

val = self.some_value

some_value() としてもいいけど、どうもこの ,() は好きになれない。 C なら全然気にならないんだが。

ちなみに、これは有名な話だけど、セッターメソッド ( foo= みたいなやつ) では self を省略できない。たとえば次のような場合。

def initialize( arg )
  self.value = arg
end

こういうとこで self を省略するとローカル変数の代入とみなされてしまう。 筆者ははまらないが他の言語から入った人ははまりやすいらしいので注意だ。

セミコロン

重大なことを忘れていた。文末の「;」(せみころん)について。 これは省略「しなければならない」。

空行

筆者は空行を入れるのが好きだ。メソッドの途中でもガンガン空行を入れ る。以前つい魔がさして「ソースコードの中のコードと空行とコメントの 割合」などというものを測定するスクリプトを書いてしまったのだが、い ままで書いたスクリプトの結果を合計すると(含んでないのもあるが)、そ の結果は な、なんと!

コード 8652 行、空行 2710 行、コメント 208 行

という恐るべきものであった。コメントの少なさもさることながら、コー ドと空行が 3 : 1 ってのはちょっとびびる。単純に言えば、三行コード を書いたら一行あけてるわけだ。

こういう結果が出てみれば確かに思いあたることはある。筆者の「空行追 加ルール」を書いてみよう。

とまあ、これだけやれば空行が多くなるのはあたりまえかもしれない。し かし、空行の多さは決して悪い結果を呼ばないと思う。わかりやすいコー ドを書くためには、内容がわかりやすいだけではいけない。見た目にもわ かりやすくなくてはいけないのだ。スペースがなく、ぎっちりつまった文 章が読みにくいように、漢字だらけの文章が読みにくいように、あるいは こういうふうに句読点が少ない文は意味が把握しにくいように、文字がぎっ しりつまったソースコードも読みにくくなる。

しかもソースコードはいわば「論理のかたまり」だ。つまり、普通の文章 以上に内容が濃いはずなのである。それならばソースコードを読みやすく するためには、普通の文章以上にレイアウトや字の密度に気を配らなけれ ばならないのは当然ではないか。

最近はちょっとこなれてきて空行は前より少なくなりつつあるのだが、そ れでもやっぱり重要な区切りには必ず空行を入れる。以前どこかでメソッ ドの間に空行を入れずにぎっちりつめている例を見たのだが、はっきり言っ てそんなの論外である。メソッド間には当然空行を入れるべし。クラス間 はもっと当然である。

あとひとつ書くと、aliasprivate のまわりの空行。メソッド定義の あと aliasprivate を書くときは以下のように alias では空行を入れ private では入れない。

def foo
  ....
end
private :foo

def bar
  ....
end

alias any bar

理由は、alias は別のメソッド(インターフェイス)の定義だから。 一方 private のほうはメソッド定義に付随する情報なのでくっつける。

alias

alias で思いだした。 最近 alias の引数にシンボルを渡しているコードを見かける。

alias :new :old

attr 系からの連想でこうしてしまうようだが……こんなことをしてはいけない。 alias はメソッド呼び出しではなくて構文である。 だからこそ引数の間にカンマがないのだ。 そこはわかっていながらなぜエセメソッドのような使いかたをするのか。 こんなのは def のメソッド名部分に文字列を渡すようなもんである。 絶対にやめよう。

for

for 文の話。知っていると思うが、 Ruby の for 文は each をラップするループ文である。 すなわち、

obj.each do |i|
  ....
end

は単純に

for i in obj do
  ....
end

に変形できる。違いは for のほうが新スコープを導入しない (結果としてちょっと速い) という点だけだ。 さて、foreach どっちを使うかだが、おれは常に each 派だ。 for はテスト以外では一回も使ったことがない。理由は特になし。 Perl や sh などで for を使った経験がないからかもしれない。

おまけ: is_a? と ===

知っているだろうか、is_a? が ,=== で代用できることを。 これを知ったのはメーリングリストで教えてもらったのが最初だが、 それ以来筆者は is_a? を一回も使っていない。

なにしろ is_a? は疲れるのだ。たかだか五文字にシフトが二回もある。 一方 === ならばシフトなしのうえに三文字ですむ。 ああ、=== はなんて偉いんだろう……と今日も思う。

(追記 1) ,=~ も使わないことにしている。 どこかに「,=~ は ,=== の古い書きかた」と書いてあった覚えがあるからだ。 そう言われると害はないとはいえついつい「新しい」ほうを使いたくなってしまう。 現代人の悲しい性である。

(追記 2) そのあと $' 系が使いたいときは ,=== でなく regexp.match にする。 Regexp.last_match って書くのは長くて疲れるからだ。 しかし when に正規表現を置くときだけはどうにもならないので あきらめて $' を使う。