たごもりすメモ

コードとかその他の話とか。

「研鑽Rubyプログラミング」はライブラリ作者の知識・技術の幅と深さを拡大する1冊

研鑽Rubyプログラミング」を読んだので、その感想を書く。

なお本書の訳者である角谷さんに本をお贈りいただきました。が、その前から同書のβ版(電子版)を購入していたため、実際にはほとんどをそちらで読みました。*1

本エントリの言いたいことは、様々な状況に対応してコードを書くには知識・技術の幅と深さが重要で、本書はそのための重要なインプットとなるでしょう、です!

総論: さまざまなRubyの書きかたを学べる

さて、この本は初手から対象読者を「中級から上級のRubyプログラマー」としており、本そのものの目的は「まえがき」の先頭にもはっきり書かれています。少し長めに引用します。

 本書の目的は、中級から上級のRubyプログラマーが従うべき有用な原則を伝えることです。解決策をどのように実装するかだけでなく、さまざまな実現方式と、それらの間のトレードオフ、ある方式が特定の状況下ではなぜ有効なのかといった観点も重視します。原則を伝えることが本書の主な目的ですが、Rubyプログラミングの発展的な技法を説明することもあります。

自分はこのまえがきを読み、本文を読み進めていく上で、この本はトレードオフのある選択肢をさまざまに提示する本だというように理解しました。Rubyに限らずどんなプログラミング言語でも同じことですが、ある処理を書くときに、方法がひとつしか無いということはまずありません。成瀬さんの感想エントリにもあるように、"There is more than one way to do it."なのです。この本がすべてのwayを示せているということもありません。しかし、間違いなく高い技術力を持つJeremyの"way"を一例として(あるいはもっと多くの例として)インプットできるのは本書ならではでしょう。

併存する複数のプログラミングスタイル

ところで、いくつも way があってどうすんだ、という話については、それでいいのです、と言いたい。自分の話をちょっと挟ませてもらうと、実際に自分がコードを書くときには、意図してあるいは意図せずして、複数のやりかたを使い分けています。典型的なユースケースを元に名付ければ、以下の3つが代表例です。

例えばアプリケーションコードを書く場合には、他のプログラマ*2に理解しやすいコード、あるいはビジネスロジックを発見・修正しやすいコードにすることを第一に考えます。一方でOSSのライブラリについては、もちろんメンテナンス性は非常に重要なものの、性能的に妥協ができないところで走るコードも多くなります。そういったホットスポットとなりうる部分のコーディングでは余計なオブジェクトはできるだけ生成したくないし、メソッド呼び出しの回数もできるだけ少なくなるようなコードを書くよう、自分のモードが自動的に切り替わっているなと思います。

一方でメタプログラミング面白い!!!!みたいな面白コードを書くときであれば、普通には実現が難しい機能をどうにかして実装するために、効率は二の次でなんとかして処理系の裏をかく、みたいな発想になります。このときは言語機能そのものをどれだけ知っているか、どの機能とどの機能を組み合わせれば面白い効果が得られるか、みたいなことをアイデア幅優先探索するみたいな思考過程になるでしょう。

もちろんこんなにかっちり分かれているわけではなく、グラデーションになっていて、これらの典型的なケースの間で、本書の言葉のとおりに言えば「トレードオフ」を考慮してバランスをとります。OSSのライブラリでも頻繁に呼び出されないような部分はユーザビリティ・可読性が最優先になったり、アプリケーションコードでもアプリ内ライブラリで性能最優先のコードを書くことだってあるでしょう。テストコードを書くときには、厄介な部分をどうにかしてモックするためにメタプログラミングを駆使することもあります。

技術・知識の幅を広げ、同時に深める

自分の話として書きましたが、これら複数のスタイルのプログラミングは、重点を置く場所の違い、幅・深さの差などはあれ、プログラマであれば誰でもやっていることだと思います。そしてこの幅と深さは自然に身につくものではなく、色々なところからのインプットを通じて継続的にメンテナンスする必要があります。1冊の教科書があればよいというものではまったくありません。

その意味で、「研鑽Rubyプログラミング」は非常に良い1冊です。本書の1部・2部では、ライブラリの設計・実装における様々な状況について、主に性能面でのトレードオフを解説しつつ、単純な実装からより効率的な実装まで複数のコード例を出して解説しています。特にライブラリ側の実装について、これほど具体的に複数の例示を並べて見られる本はそうありません。

率直に言って、本書で例示されているコード例は、自分にとって「いやこれはちょっと自分では書かないな」と思えるものもあります。例えば180ページ、第7章「自分のライブラリを設計する」に以下のようなコードがあります。ひとつのメソッド定義で複数のユースケースに対応するためのメソッド引数の定義方法についてです。(説明の簡単のため、ふたつのコード例ブロックを結合)

引数なしでobj.first_recordを呼ばれたときに1オブジェクトのみを返し、かつobj.first_n_records(number: 2)のように呼ばれたときは最初の2オブジェクトをArrayで返すことを目的としたものです。

def first_n_records(number: (only_one = 1), offset: 0)
  reset
  offset.times{next_record}
  ary = []

  while record = next_record
    if !block_given? || yield(record)
      ary << record
      break if ary.length >= number
    end
  end

  only_one ? ary[0] : ary
end

alias first_record first_n_records

このコードのキモは、Rubyのメソッド引数デフォルト値には任意の式が書け、そこでローカル変数の定義と代入もできるという特性を利用しているところで、本文ではこのテクニックの詳細が紹介してあります。

で、このコードを見せられて自分がこう書くかというと、まあ書かないかな、正直意図が読みにくいし……。配列が欲しければobj.first_record(number: 1)とも指定できる、と本文に解説されていますが、numberキーワード引数のデフォルト値は1のはずなのに明示すると返り値が変わるというのはあまりユーザに優しくない気がします。自分が設計するなら以下のようにすると思います。 *3

def first_n_records(number: 1, return_value: (number == 1 ? :obj : :array), offset: 0)
  raise ArgumentError, "2 or more values should be in array" if number > 1 && return_value == :obj
  # 途中省略
  if return_value == :obj
    ary[0]
  else
    ary
  end
end

alias first_record first_n_records

こうすれば、1要素のArrayが欲しい場合にはnumber: 1return_value: :arrayの両方を明示させることで、呼び出し側のコードだけを見て何が返ってくるかが誰にでもわかります。

とはいえ、異論があるからといって、本書のこのパートの価値が落ちるかといえば、そんなことはまったくありません。本書のこのコード例はあくまで人工的な例ですし、そもそも本書がなぜ中級以上のプログラマーを対象としているかというと、そんな対象読者は、このインプットを取り込んだ上で「自分でどう書くかを判断できる」人だという信頼があるからでしょう。

何より、自分は本書を読むまで、メソッド引数のデフォルト値指定においてローカル変数を定義する、というテクニックがまったく頭にありませんでした。これは何かに使えそうです。何に使えるかはまだまったく分かりませんが……何でしょうね。えっへっへ。これは将来書くコードのための、本当に価値のあるインプットです。自分にとっては。

面白小ネタ

前半を読んでいったときの3章、4章が変数およびメソッドの話で、これがウケました。なぜかって、3章でグローバル変数の使いどころに以下のようなコード例が。ブロック内コードの実行時に警告をオフにするやつ。

def no_warnings
  verbose = $VERBOSE
  $VERBOSE = nil
  yield
ensure
  $VERBOSE = verbose
end

で、4章のメソッドについてはけっこうな紙幅をつかって移譲(delegate)の解説が。これってさあ、この組み合わせってさあ、完全にLFAの実装テクニックに自分が書いてたやつじゃん!!!

まあでもdelegateとの組み合わせはともかく、警告をオフにするための便利メソッドはテストヘルパーとかでよく書きますよね。ライブラリとか書いてるとね。拙作ライブラリmaccroにだってもちろんありますよ。ウケるくらい一致してるな。真っ黒……。。。。

本書を読む上での注意点

本書については、タイトルなどで特に言明されていることではないですが、おおむね「Rubyによるライブラリ開発を中心とする」というコンテキストは頭において読んでもいいのではないかな、という気がします。例えば定数の可視性について『一般原則としては、「ユーザーに見せたくない定数はすべてprivate_constantにしておく」のが最善です』という記述がありますが、ユーザー(コードの呼び出し側開発者)と(アプリ内ライブラリ側)開発者が同一となるWebアプリケーションにおいて、定数をそこまでprivate_constant指定して回る必要があるかというと、まあ無いかなと思えます。このように、本書の記述が自分の開発にそのまま適用できるかどうかは常に考えながら読む必要があるでしょう。

また、Jeremy作のSequalAPIを見てもわかるとおり、メソッド命名において#[]を多用する傾向があったりするなど、著者の好みがかなり強く出ている章もあります。特に3部のWebアプリケーションについては、著者の意見が強く前に出ている部分も多く、自分の立場からは異論のある記述も多かったことは書いておきます。

とはいえ、それも想定読者である中〜上級者であれば、インプットの1バリエーションとして受け入れられるのではないでしょうか。

まとめ

ということで、本書は技術を飯の種にするRubyプログラマにとって、インプットとして非常に価値のある一冊だと言えます。全面的にお手本にする教科書ではありませんが、それでも確実に、我々のRubyプログラミングについて、知識と技術を押し広げてくれることでしょう。

*1:内容は完全に同一、ということになっているはず。 link

*2:後年の自分も含む

*3:あるいはaliasをやめて、first_n_recordsを呼び出したときはnumberにかかわらずreturn_valueのデフォルト値はArray、いっぽう def first_record = first_n_records(number: 1, return_type: :obj) のように定義するのもありかなと思います。