書いた人: 須藤 功平 (kou)
この記事は RubyKaigi 2013 の 3 日目の午後に私がした発表 Be a library developer! に関する記事です。発表のまとめに加えてたくさんの補足を詰め込んでいます。
発表資料や当日の動画は以下にあります。発表内容だけで十分という方はこれらを参考にしてください。
発表資料は以下にあります。どのサイトにも同じ PDF をアップロードしているので見やすいサイトを選んでください。
発表資料のソースは GitHub にあります。
当日の動画は以下にあります。どちらも同じ動画です。
なお、この記事は資料を見なくてもわかるようにまとめています。まとめの合間に何度か長めの補足が入っています。補足のセクションのタイトルには「補足:」と入れているので、まとめだけで十分という方は「補足:」と入っているセクションを飛ばしてください
この発表ではよいソフトウェアを開発する方法を 1 つ紹介しました。この方法は私の経験をベースとしています。
私がソフトウェアを書き始めてから 10 数年経ちました。書き始めた頃から書いたコードのほとんどをフリーソフトウェアとして公開しているので、たまに昔のコードを読む機会があります。そのとき、数年前よりも今の方がよいコードを書けるようになっていることに気づきます。この発表は、どうして自分は昔よりもよいコードを書けるようになっているのかをふりかえって得られた知見をまとめたものです。この知見がよいコードを書きたいと思っている人の参考になればよいなぁと思って発表しました。
この発表で伝えた「よいソフトウェアを開発する方法」は「ライブラリー開発者になること」です。どうしてこの方法につながるのか。キーとなる考えは「想像するよりも思い出す」です。
「よいソフトウェアを開発する方法」について考える前に、まずは「よいソフトウェア」について考えてみましょう。
こんな API について考えてみましょう。これは cairo gem というグラフィックスライブラリーの API です。
このコードは、save で現在の描画情報を保存し、restore で保存した状態まで戻しています。save と restore の間で circle と stroke を使って円を描いています。円を描くために変更された描画情報は restore のところで元に戻ります1。
では、このコードよりもよいコードで書けるようにするために API を改良できないか考えてみましょう。より「よい」の基準はいくつもあります。まずは自分のより「よい」の基準で考えてみてください。
考えましたか?
例えば、save と restore に注目してみるとどうでしょうか。
ここに注目するとブロックを使った書き方を思いつきます。こちらの方がよりよい API です。
では、どうしてブロックを使ったほうがよりよい API なのでしょうか。それはより Ruby らしい書き方だからです。
「Ruby らしい」とはどういうことでしょうか。「○○らしい」とは「他と似ている」ということです。「Ruby らしい」書き方だとまわりのコードと似たような記述になります。つまり、まわりのコードと統一感がでるということです。統一感がでると読みやすくなります。読みやすくなるとメンテナンスが楽になります。いきなりよいソフトウェアを作ることは難しいため、繰り返し繰り返し継続してソフトウェアを開発していく方法が現実的です。メンテナンスが楽になると開発を継続しやすくなり、よりよいソフトウェアにつながります。
つまり、「Ruby らしい書き方」にするとよりよいソフトウェアにつながるため、「Ruby らしい書き方」はよりよい基準のひとつと言えます。
では、このブロックの使い方は「Ruby らしい」のでしょうか。File クラスを思い出してください。
まず open でファイルを開きます。これは前処理です。read して使い終わったら閉じます。これは後処理です。
(最初の補足です。)
ところで、これを見て File.open ではなく Kernel.#open を使ったほうがよいと思う人もいることでしょう。理由は「記述が短い」や「ファイル以外にも対応できる」あたりでしょうか。しかし、私は File.open を使ったほうがよいと考えています。理由は「ファイルを開くことが明確なコードになるから」です。ファイルしか扱わない場所ではファイルしか扱わない API を使うことでコードで表現したい意図が明確になります。一方、ファイル以外にも様々な入力を受け付けたい場合は Kernel.#open を使います。
この考えは end_with?2 と /\z/3 の使い分けにも応用が効きます。
end_with? を使っているコードは「パスが .rb で終わっているなら Ruby スクリプト」と読めます。/\z/ を使っているコードは「パスが .rb で終わるパターンにマッチするなら Ruby スクリプト」と読めます。end_with? の方がやりたいことそのものに読めます。
この補足では、より多くのことができる機能よりもやりたいことそのものの機能があるなら、そのものの機能を使ったほうがやりたいことの意図を明確に伝えられるコードになるという例を示しました。
補足は長かったでしょうか? これから出てくる補足もこのくらいの長さになります。
話を戻します。こんなコードを考えていたのでした。
このように明示的に close を書くのは Ruby 初心者です。Ruby に慣れた人はこのようにブロックを使って書きます。
こうすることの利点は 2 つです。1 つは close のし忘れがなくなるということです。もう 1 つは後処理の詳細を意識しなくてよくなるということです。ファイルの場合の後処理は close で、Dir.chdir のときは元のディレクトリーに戻る、などと使いわける必要はありません。ブロックを抜けたら「いい感じ」に後処理をしてくれます。これが Ruby の組み込みライブラリーで使われている後処理のためにブロックを使う方法です。つまり、これと「似た」使い方をすれば「Ruby らしい」ということです。
では、もう一度 cairo gem の例を見てみましょう。
save が前処理の部分、ブロックを抜けたところで実行する restore は後処理の部分です。File と「似た」使い方ですね。ということでこの API は「Ruby らしい」といえます。
おさらいします。
「よいソフトウェアを開発する方法」について考える前に、まずは「よいソフトウェア」について考えました。「よい」の基準の 1 つは「Ruby らしい」です。「Ruby らしい」を言いかえると「他と似ている」なので、「よい」を「他と似ている」と言いかえることができます。つまり、「よいソフトウェアを開発する方法」は「似ているとはどういうことかを知って、それと同じようにすること」と言いかえることができます。
それでは、「似ているとはどういうことかを知って、それと同じようにすること」を実現するにはどうしたらよいか考えてみましょう。
まず、「似ているとはどういうことかを知って、それと同じようにすること」を実現するためのキーとなる考えを説明します。
キーとなる考えは、「想像するよりも思い出す」です。
「想像すること」は難しいことです。これはまだ知らないことを思いつかなければいけないからです。0 から 1 にすることは難しいことです。
では、「思い出すこと」はどうでしょうか。これは、想像することに比べれば簡単なことです。なぜなら、すでに知っていることの中から適切なことを選んでくるだけだからです。新しく思いつく必要はありません。すでにある 0 から 9 の中から一番よさそうな数 (例えば 7 とか) を選んでくるようなものです。
また、知っていることから選ぶ場合は、それを選んだ「結果」もわかる場合があります。「よい結果」とわかっていることを選んだ場合は、今回もうまくいく可能性が高くなります。
思い出せるようになるには知っている必要があります。では、どうやって知ればよいでしょう。
知るためには自分で経験する方法、人から聞く方法、観察して学ぶ方法などがあります。この中でも一番初めにやることは経験してみることです。経験すれば知っているので思い出せるようになります。
ということで、キーとなる考えは「想像するよりも思い出す」です。
それでは、「よいソフトウェアを開発する」ために「想像するよりも思い出す」というキーとなる考えを適用してみましょう。
「よいソフトウェアを開発する」には、「似ているとはどういうことかを知って、それと同じようにする」とよいのでした。それでは、「似ているとはどういうことかを知る」ために何を経験すればよいでしょうか。
それは「Rubyist」としての経験です。組み込みライブラリーや標準添付のライブラリーを何度も使っていればどのような API があるのかわかってきます。どのような API があるか知っていくと同じような使い方の API が見えてきます。○○の API が他の××の API と同じように使えたならそれらは似ています。例えば、File.open と Dir.open はどちらも同じように使えます。ブロックを受け取り、ブロックにファイル・ディレクトリーを渡し、ブロックの処理が終わったらファイル・ディレクトリーを close します。
これを読んでいる人であればある程度 Rubyist としての経験があるでしょう。それでは実際に Rubyist としての経験を活かして練習してみましょう。以下のコードを考えてみます。
これは gtk2 gem という GUI ライブラリーの API です。window オブジェクトの opacity プロパティーの値を取得しています。では、これをどうすればよりよい API になるか考えてみてください。この話の中では「Ruby らしい」かどうかをより「よい」の基準としていました。どうすればより Ruby らしい API になるでしょうか。
考えましたか?
以下のように「プロパティーの値を返す、プロパティーと同じ名前のメソッド」の方が Ruby らしい API、つまりよりよい API です。
オブジェクトのプロパティーを取得するためにプロパティーと同じ名前のメソッドを使うというのは Ruby ではよくやる方法なので、この方法は Ruby らしいです。プロパティーを属性と言いかえるとわかりやすいでしょう。Ruby には attr_reader という「属性の値を返す、属性と同じ名前のメソッド」を定義する、そのためのショートカットが用意されています。
発表時には触れなかったことについて補足します4。
gtk2 gem は GTK+ という GUI ライブラリーを Ruby から使えるようにしたライブラリーです。そのため、プロパティー名は GTK+ の規則に従います。プロパティー名として Ruby のメソッド名で使えない文字が使われていないか気になるところです。実はプロパティー名には「-」も使うことができます5。これだけが Ruby のメソッド名では使えない文字です。ただし、プロパティー名として「-」の代わりに「_」を使っても GTK+ 内部6で「-」に正規化するため、gtk2 gem で「default-height」プロパティー用のメソッド名として「default_height」を使えば、Ruby から見ても GTK+ から見ても問題も違和感もありません。
なお、仮にどうしても使えない文字があった場合はどうしますか? 私ならそのプロパティー用のメソッドは用意せず、用意できるものだけ用意します。メソッドを用意できないプロパティーはそもそも Ruby らしくない名前なので Ruby の属性と似せることはできません。
では、window[“default-height”]7のように取得できるようにするのはどうでしょうか。これについてはもう少し後の補足で触れます。そこでは [] を使うときはどんなときかを考えます。
なお、Ruby のメソッド名として使えないプロパティー名の値は get_property を使えば取得できるので、Ruby らしさから外れたものはそれらを使うという割り切りはアリです。ただし、これは多くのプロパティー名が Ruby のメソッド名としても使える場合に限ります。多くのプロパティー名が Ruby のメソッド名として使えない場合はプロパティーと Ruby の属性は似ていないということです。無理やり Ruby の属性と似せようとしてはいけません。思い出すものを間違っています。
補足はここまでです。
ところで、get_property のよりよい API を考えられましたか? 「思い出す」というのは意外と「難しい」と思いませんでしたか?8 そう、難しいんです。「思い出せ!?」「Ruby らしいって何!?」そう思ったことでしょう。
すでに知っているはずなのにどうして思い出すことが難しいのでしょう。それは、「想像するよりも思い出す」という経験をしていないからです。今のあなたの状態は「経験した」という状態ではなく、「読んだだけ」という状態です。
それでは、もう一度。「よいソフトウェアを開発する」、「似ているとはどういうことかを知って、それと同じようにする」を実現するためには何を経験したらよいのでしょうか。それは、ライブラリー開発者としての経験です。ここでようやくこの話のタイトルがでてきました。
ライブラリー開発者は Rubyist として使いやすい API とはどういう API だろうと考えたり、ライブラリーのユーザーとしてわかりやすいドキュメントはどんなドキュメントだろうと考えたりします。他にもいろいろ考えます。そして、これらを何度も何度もたくさん考えます。考える機会がたくさんあるのです。「たくさん」というのはとてもよい練習になります。そのため、「想像するよりも思い出す」をうまくやるためにはライブラリー開発者になることをオススメします。
それではいくつか練習してみましょう。
まずは簡単な練習です。プロパティーの値を取得するには以下のようにプロパティー名と同じ名前のメソッドを用意するのが Ruby らしいのでした。
それでは、visible というプロパティーの場合はどうでしょう。ヒントは visible は真偽値を返すということです。
考えましたか?
Ruby らしくするならメソッド名の最後に「?」をつけます。
では、なんでもメソッドにすればよいのでしょうか。この例ではどうでしょう。
ここの record はテーブルの中の 1 つのレコードです。このレコードのカラムの値にアクセスすることを考えます。Hash のようにアクセスする方法とメソッドでアクセスする方法ではどちらがよいでしょうか。レコードをカラムが集まったコレクションと考えるなら Hash のようにアクセスする方法が Ruby らしいですし、オブジェクトと考えるならメソッドでアクセスする方法が Ruby らしいです。
さて、それでは GUI のウィジェットであるウィンドウはプロパティーが集まったコレクションと考えられるでしょうか。ウィンドウの主要な機能はプロパティーの操作ではなく GUI 関連の機能です。そのため、コレクションと考えるよりもオブジェクトと考える方が妥当です。よって、プロパティーにアクセスするために [] を使うのは Ruby らしくありません9。
少し難しい例も考えてみましょう。この例は発表時には省略した例です。
この例の Gst.init をよりよくできないでしょうか。
通常のライブラリーは require すれば使えますが、この API では require しただけでは使えません。require した後に Gst.init を呼ぶ必要があります。
そもそも Gst.init はなんのために必要なのかというと、デバッグオプションを指定するなど高度な使い方に対応するためです。
変わった使い方をしたいときに通常の使い方と変わってしまうのはしょうがありませんが、それが通常の使い方をしたいときにも影響を与えてしまっては使い勝手が悪いです。どうしたらよりよい API になるでしょうか。
考えましたか?
高度な使い方をしなくてもよいときは Gst.init を呼ばなくても動くようにします。
Ruby では require するだけで (明示的な初期化をせずに) すぐにライブラリーを使えるようになることが多いので、今回も require だけで使えるようにする、ということです。他のライブラリーの使い方に似せたということです。
参考までに実装方法を紹介します。ポイントは const_missing と remove_method です。
必要なときだけ Gst.init を呼べばよいという API を「思い出す」ことはできるでしょうが、どうやって実装するかは「思い出す」ことはできないでしょう。これについては、もう少し後で触れます。
次は、API ではなくドキュメントについて考えてみましょう。ソフトウェアの開発はコードを書くだけではありません。
これは gtk2 gem のインストールドキュメントを書いた例です。
gtk2 gem は拡張ライブラリーなので事前に GTK+ という C のライブラリーをインストールしておく必要があります。ユーザーのことを考えると、ドキュメントには「gem install gtk2 する前に GTK+ のライブラリのインストールが必要なので、OS ごとのインストール方法を書いておかないと」となります。でも、これでよいのでしょうか。よりよいドキュメントならこうするべきです。
gem をインストールするときは「gem install gem」が普通のやり方です。これが RubyGems らしさです。普通はこれでインストールするなら、これでインストールできるようにするべきなのです。gtk2 gem は gem install gtk2 とやったら必要なパッケージを自動でインストールするようにして、インストールドキュメントは gem install gtk2 だけにしています。
発表時には触れなかったことについて 2 つ補足します。1 つはシステムのパッケージを自動でインストールするということについてです。もう 1 つはパッケージを自動でインストールするということをどうやって思いつくかということについてです。どちらも長めの補足になります。
まずは、システムのパッケージを自動でインストールするということについてです。
gem 以外のパッケージを勝手にインストールされるのがイヤだと思った人がいるはずです。私も昔はそう思っていましたが、以下の理由から自動でインストールしたほうがよいと思うようになりました。
最初の「インストールでつまづいている人をたくさん見た」ことが一番大きな理由です。gem をインストールしたい人が一番したいことは「使うこと」です。インストールが大変なために使う前に諦めたり、インストールできたとしてもインストールしたことに満足して使うことがどうでもよくなったりした人を見てきました。いくらよりよい API のライブラリーでも使われなければ API のよさは関係ありません。そのため、インストールでつまづかないようにすることを重視するようになりました。
セキュリティーについても考えました。自動でインストールすることでセキュリティーに対する脅威は増えるのか。
Linux 環境では sudo 経由でインストールコマンドを実行します。セキュリティーを重視している人は sudo を実行する前にパスワードを入力する設定にしているはずです。sudo のパスワード入力を促すプロンプトには「○○というパッケージをインストールするために sudo のパスワードを入力してください」とでるようにしています。つまり、セキュリティーを意識している人たちには適切に判断するタイミングと判断する材料を提供しています。そのため深刻な問題はないと考えています。もし、パスワードなしで sudo を実行できるようにしている場合はそれほどセキュリティーを意識していないと考えられるため、それほどケアしなくても問題ないと考えています。
OS X 環境で Homebrew を使っている場合は sudo を使わないでそのまま実行します。つまり、パッケージのインストールを確認するプロセスがありません。では、パッケージのインストールを確認するプロセスが必要なのかを考えてみましょう。
ユーザーは gem install … としているのでパッケージをインストールしたいという意思を示したと考えることができます。つまり、すでにインストールしたいと示したのでよきに計らうことの方がユーザーの意思を反映しているという考えです。強引でしょうか。多少強引かもしれません。
では、そもそも、一般ユーザー権限で gem install … することはそんなに安全なのでしょうか。ホームディレクトリーを削除する悪意のあるライブラリーをインストールして使ってしまうという危険性が考えられます。サーバー上の特定サービス用のユーザーであれば被害を最小限に抑えられるため安全と言えます。では、開発用のマシンや普段使いのマシンではどうでしょうか。/usr/ 以下を全部削除されるより、ホームディレクトリーを削除される方が被害が大きいのではないでしょうか。システムはインストールしなおせばよいですが、ホームディレクトリーは最後にバックアップしたところまでしか復旧できません。
このように考えると、自動でシステムにパッケージをインストールしようとしてもセキュリティーに対する脅威はそれほど変わらないと言えるでしょう。
自動でインストールしない場合についても考えました。自動でインストールしない場合は、使いたいなら手動でシステムにパッケージをインストールします。どうせ同じことをするのです。私は nokogiri gem をインストールするときに、gem install nokogiri して失敗して、sudo apt-get install … して、再度 gem install nokogiri をしていました。
どうせ手動でパッケージをインストールするのであれば、最初から自動でインストールしても同じことです。もし、パッケージを追加でインストールすることをシビアに考えている人はインストールする前に依存しているライブラリーを調べるはずです。それらの人のためには、インストール方法のドキュメントとは別の場所に依存ライブラリーをリストアップしておくことができます10。本当にシビアな人は調べるはずです。これは、パッケージでインストールしないで自分でビルドしてインストールする人にも当てはまります。
長くなりましたが、パッケージを自動でインストールするようにしても、よく考えてみれば、最初に抱くイメージほど変なことではないということです。
もう 1 つ補足します。これも長いです。
パッケージを自動でインストールするという方法をどうやって思いつくかということについてです11。
パッケージを自動でインストールする方法は他のライブラリーではやっていません。そのため、知っていることを「思い出す」ということはできません。目指したい場所を思い出してみつけることはできるがそこまでの道のりは思い出すことができない、ということはよくあります。少し前の Gst.init を呼ばなくても済むようにしたいという例もこのケースです。
では、今いる場所とゴールがわかっている状態で間をどうやって埋めていけばよいのでしょうか。残念ながら私はこの問いに対する答えをまだ持っていません。それでも自分はどうやっているだろうかと思い出して説明してみます。まず、ゴールを手前に近づけようとしている気がします。アスキーアート付きで説明します。
最初は「今いる場所」と「ゴール」がわかっていて、間をどう埋めればよいかわからない状態です。
この状態からゴールに到達するために、ゴールの少し手前にある「ここからゴールまではなんとかなりそうな場所」を探します。
そして、「今いる場所」から「ここからゴールまではなんとかなりそうな場所」にどうやっていくかを考えます。このなんとかなりそうな場所はゴールより近いので、ゴールまでいく方法を考えるよりアイディアが浮かんできます。
それでもダメなときは別の「ここからゴールまではなんとかなりそうな場所」を探します。一度探した「ここからゴールまではなんとかなりそうな場所」をベースにさらに近づけようとは考えていない気がします。
さて、みなさんは「今いる場所」と「ゴール」がわかっているとき、どうしているでしょうか12。
だいぶ補足が長くなって、なんの話だったか忘れていると思うのでおさらいします。
実際に「想像するよりも思い出す」というキーとなる考えを適用してみることを練習をしました。Rubyist として普通はどうやっているかを「思い出す」、そしてそれと同じようにする、ということがどういうことかを伝えたかったのです。
この発表では「よいソフトウェアを開発する方法」を説明しました。より「よい」とは「Ruby らしい」、言い換えると「他と似ている」ということです。これを実現するためのキーとなる考えが「想像するよりも思い出す」です。なぜなら、想像することは知らないことを閃かないといけないので難しく、思い出すことは知っていることから選ぶだけなので簡単だからです。ソフトウェア開発に当てはめてみると、思い出すためには Rubyist としての経験が必要です。あとはその経験を思い出せばよいのです。
しかし、「思い出す」という経験がないので、はじめは「思い出す」ことが難しいことでしょう。「思い出す」経験をするためにはライブラリー開発者になることをオススメします。ライブラリーを開発すると何度も何度も「思い出す」必要があり、とてもよい練習になります。
ライブラリー開発者となり、「思い出す」ことを練習してください。そうすれば、より簡単に、よりうまく「似ていることを思い出してそれと同じようにやる」ことができるようになるはずです。みなさんがよいソフトウェアを開発できるようになる参考になったらうれしいです。
須藤功平。フリーソフトウェアプログラマーで株式会社クリアコード代表取締役 (2 代目)。社名の命名者でもある。社名の由来は「クリアなコード」。その名の通りクリアなコードを書く会社であろうという意図を込めている。最近、「よいコードを書くことが当たり前なチーム」になることを支援するサービス「コミットへのコメントサービス」を始めた。サービスを受けたい開発チーム、クリアコードでこのサービスを担当したい開発者、どちらも募集中。
実は、この例の場合は ‘‘save’’ したときと ‘‘restore’’ 直前で描画情報が変わっていないので、’‘save’’ と ‘‘restore’’ は必要ありません。 ↩
http://doc.ruby-lang.org/ja/2.0.0/method/String/i/end_with=3f.html ↩
文字列の末尾にマッチする、正規表現のアンカー。 http://doc.ruby-lang.org/ja/2.0.0/doc/spec=2fregexp.html ↩
発表後に咳さんにこのことについてコメントをもらいました。 ↩
もう少し厳密に言うと、GTK+ が使っているオブジェクトシステムである GObject の内部 ↩
ここでは Ruby のメソッド名に使えない文字を含むプロパティー名を考えているので、シンボルでプロパティー名を指定するようにはしません。’‘window[:”default-height”]’’ という多くの場合は使わない記法を使わなければいけなくなり、Ruby らしさから外れてしまうからです。 ↩
ここは難しいと思って欲しいところです。 ↩
'’Struct’’ は ‘’[]’’ でメンバーにアクセスできるので、ものすごく Ruby らしくないというほどではありません。 ↩
インストール方法のドキュメントのところにリンクを張るのが親切です。 ↩
発表時に田中さんからこれに関する質問がありました。発表後に長永さんともこれに関する話をしました。 ↩
しまだ Ruby 会議に参加したとき、しまださんは「今いる場所」と「ゴール」がわかっている状態で間をどう埋めればよいかを見つけられる人なんだと感じました。しまださんに聞くとなにかよいやり方を教えてもらえるかもしれません。 ↩