WEB+DB PRESS plusシリーズAPIデザインケーススタディ
――Rubyの実例から学ぶ。問題に即したデザインと普遍の考え方

書籍の概要

この本の概要

Rubyを題材に,APIデザインの各種事例を取り上げた技術解説書。実用のための言語のAPIデザインには,使いやすさ,一貫性,速度,互換性など,さまざまな点でバランスの良さが求められます。それらの点で,長きにわたり定評がある言語の一つは「Ruby」ではないでしょうか。本書ではRubyの事例をベースに,APIデザインの過程と考え方を丁寧に解説。言語の中核機能を担う「I/O」「ソケット」「プロセス」「時刻」「数,文字列」をカバーし,APIデザインの核心に迫ります。熟達のRubyコミッタによる全面書き下ろし。

本書の構成

本書は,Rubyで実際に行われたデザインを具体的に解説することによって,使いやすいプログラミング言語やライブラリをデザインする方法を述べています。そのため,Rubyのデザイン自体の詳細と,デザインのやり方について知ることができます。

本書は全5章から成り,Rubyの機能のカテゴリ別に分割されています。各章は,個々の機能のデザインを説明する節が並んでいます。

各節は基本的には独立しているので,どこから読んでもかまいません。とくに,その節で述べている機能を使ったことがあるのであれば,理解しやすいでしょう。ただ,関連する話題はなるべくわかりやすいものから連続して並べてあるので,そのようなものは連続して読んだ方が理解しやすいでしょう。

各章は,以下のような内容です。

第1章─I/O

IOクラスのデザインについて,述べています。C言語の標準ライブラリのstdioの問題を多く取り上げています。

第2章─ソケット

ネットワーク通信やプロセス間通信に使われるソケットのデザインについて,述べています。ソケット周りは,OSでさまざまな拡張が行われます。そのような拡張を,Rubyアプリケーションにうまく提供するデザインなどについて取り上げています。

第3章─プロセス

プロセスの起動に関するデザインについて,述べています。低レベルAPIのspawnと高レベルAPIのopen3の話題を多く取り上げています。

第4章─時刻

Timeクラスのデザインについて,述べています。暦や夏時間は人間社会の都合によって定義されるため,ソフトウェアの機能でも人間社会の都合を考慮せざるを得ません。人間社会の都合とソフトウェアの都合をどう折衷させるか,という問題を多く取り上げています。

第5章─数,文字列

数や文字列に関する機能のデザインについて,述べています。数に関して数学的な背景をどうデザインに反映させるか,などといった問題を取り上げています。

本書に関するお知らせ

本書に関連する記事を公開しております。

本書のハッシュタグは,以下です。

  • #APIデザインケーススタディ
  • #APIAKR

目次

  • 本書に寄せて
  • 本書について
  • 本書の構成
  • 目次

第1章 I/O

1.01 RubyのIOクラスとC言語のstdioライブラリ ——プログラマが知っている名前を利用する

  • FILE構造体とIOクラスの対応
  • IOクラス以外のC言語とRubyの対応
  • まとめ

1.02 feof関数とIO#eof?メソッド ——過去にEOFに出会ったのか,それとも今現在EOFなのか

  • C言語とPascalにおけるファイルの終端
  • ユーザにとってわかりやすいファイルの終端
  • まとめ

1.03 IOバッファが空でなければsysreadは例外 ——明らかに危険で役に立たない動作は禁止する

  • sysreadメソッドの由来
  • sysreadメソッドの危険性
  • Rubyでは混用禁止
  • まとめ

1.04 EOFフラグの除去 ——モードで挙動が変化するのは良くない

  • stdioのEOFフラグ
  • RubyにおけるEOFフラグ
  • EOFフラグの再実装の試み
  • まとめ

1.05 0バイト読んだときに何を返す? ——用例を探して良い挙動を判断する

  • readメソッド
  • read(0)の挙動
  • CGIでPOSTされたデータを読み込む
  • CGI以外の用法
  • 固定長レコード
  • 可変長レコード
  • 用法を探す
  • Rubyの実際の挙動
  • まとめ

1.06 selectとstdioのバッファ ——無理をしても使いやすくする

  • I/Oの多重化
  • selectとstdioのバッファリング
  • RubyのIO.select
  • まとめ

1.07 readpartial ——I/Oを多重化したときに適切なメソッド

  • I/Oの多重化と読み込み/書き込み
  • 適切な読み込み/書き込みサイズはわからない
  • バッファを考慮したreadシステムコールが欲しい
  • readpartialの動作
  • まとめ

1.08 ノンブロッキングI/O ——モードで挙動が変化するのは良くない

  • ノンブロッキングI/Oはfdのフラグ
  • ノンブロッキングI/Oと1行入力
  • ノンブロッキングモードと競合状態
  • まとめ

1.09 ノンブロッキングI/Oメソッドの導入 ——read_nonblockやwrite_nonblock

  • ノンブロッキングI/Oの用途
  • 書き込みの多重化
  • 読み込みの多重化
  • 1. ノンブロッキングI/Oの設定
  • 2. 複数スレッドからの読み込み
  • 3. 圧縮されたストリームや,SSLなストリームからの読み込み
  • ノンブロッキングI/Oメソッドを用いたI/Oの多重化
  • 読み込むために書き込み可能になるのを待つこともある
  • 4. Linux固有の問題
  • ノンブロッキングI/Oの他プロセスへの影響
  • まとめ

Column ノンブロッキングI/OとRubyの歴史

  • UnixにおけるノンブロッキングI/Oの歴史
  • RubyにおけるノンブロッキングI/Oの歴史

1.10 PTY.open ——ptyを利用するためのプリミティブ

  • 擬似でない端末
  • tty
  • 制御端末
  • 擬似端末
  • PTY.spawnメソッド
  • 擬似端末が必要になる状況
  • 擬似端末の利用の仕方
  • PTY.openメソッド
  • まとめ

1.11 IOによるエンコーディング変換 ——正しく処理すべきだが,速度も重要

  • IOクラスによるエンコーディング変換
  • 改行の変換
  • IOでの変換と文字列の変換の違い
  • ファイルの現在位置の扱い
  • 現在位置を扱える変換手法
  • まとめ

第2章 ソケット

2.01 Addrinfoクラスの導入 ——関連して扱う情報をまとめてオブジェクトにする

  • Addrinfo.getaddrinfoメソッドとSocket.getaddrinfoメソッド
  • Addrinfoクラス
  • 逆引きの有無
  • プロトコル非依存
  • アドレスの種類を判定するプレディケートメソッド
  • ソケットアドレス構造体を返すメソッドと互換性
  • まとめ

2.02 Socketクラスの勧め ——使いやすく,かつ,低レベルな操作も可能

  • ソケットのクラス階層
  • 細分化されたクラスの問題
  • Socketクラスの強化
  • クラスメソッド
  • インスタンスメソッド
  • initialize
  • 特定の種類のソケットのためのメソッド
  • ソケットアドレスを返すメソッド
  • まとめ

2.03 Socket.ip_address_list ——自ホストのIPアドレスを正しく簡単に得る

  • 自ホストのIPアドレスの必要性
  • 自ホストのIPアドレスを得る方法
  • Socket.ip_address_listメソッド
  • Socket.ip_address_listの実装
  • まとめ

2.04 ソケットオプション ——関連して扱う情報をまとめてオブジェクトにする

  • ソケットオプションの使い方の例
  • ソケットオプションの内容の表示
  • ソケットオプションの型の詳細を隠蔽
  • Socket::Optionの内容
  • Socket::Option導入に伴う非互換性
  • クラスを細分化しない
  • まとめ

2.05 send_ioとrecv_ioによるfd passing ——ポータブルで引数の少ないAPI

  • fd passing
  • recvmsgとsendmsgシステムコール
  • recv_ioとsend_ioメソッド
  • まとめ

2.06 recvmsgとsendmsg ——多機能なシステムコールを工夫して提供

  • recvmsgとsendmsgシステムコール
  • recvmsgとsendmsgメソッド
  • 「不連続なバッファを扱える」機能は提供しない
  • 「MSG_OOBなどのフラグを指定できる」機能
  • 「recvmsgではMSG_TRUNCなどのフラグが返ってくる」機能
  • 「recvmsgで送信元を得られ,sendmsgで送信先を指定できる」機能
  • 「バイト列を読み書きできる」機能
  • 「補助データを扱える」機能
  • まとめ

2.07 getpeereid ——簡単確実なユーザ認証

  • ユーザ認証
  • マシン内のユーザ認証システムコール
  • getpeereidメソッド
  • まとめ

第3章 プロセス

3.01 プロセス起動プリミティブspawnメソッド ——ポータブルで高機能で簡単なプロセス起動

  • forkとexecはポータブルでない
  • プロセス起動の悩ましい選択
  • spawnメソッド
  • まとめ

Column async-signal-safe関数

  • async-signal-safeでない関数
  • forkで生成された子プロセス
  • 経緯と現状

3.02 close-on-execフラグ ——意図しないfdの継承を防止する

  • 子プロセスへのさまざまな継承
  • fdを継承する用途
  • 意図せざるfdの継承
  • デフォルトでclose-on-execにする
  • Rubyにおけるclose-on-exec
  • POSIXとRubyのデザインの違い
  • すべてのfdをcloseする実装
  • まとめ

3.03 Open3.popen3の修正 ——互換性を保って問題を解決する

  • Ruby 1.8のopen3ライブラリ
  • open3ライブラリの改善
  • double forkの除去
  • まとめ

3.04 open3における標準エラー出力の扱いと

  • デッドロック ——用途に応じたメソッドの追加
  • メソッドの追加
  • popen3,popen2,popen2eメソッド
  • デッドロック
  • capture3,capture2,capture2e
  • まとめ

3.05 open3のパイプラインサポート ——パイプで接続したプロセス起動

  • パイプで接続されたプロセスの起動
  • パイプの使われ方
  • まとめ

Column PerlとPythonでパイプラインを作る

  • 使う例について
  • Rubyのopen3
  • PerlのIPC::Open2
  • Pythonのsubprocess
  • fdがオープンされたまま残る
  • クローズして問題を解決する

3.06 双方向popenのソケットによる実現 ——ソケットペアによる実装の失敗

  • コマンドとの単方向通信
  • コマンドとの双方向通信
  • 1つのIOオブジェクトで2つのfdを扱う問題
  • ソケットペアによる双方向popenの実現
  • 2つのfdの問題への対応
  • まとめ

3.07 forkは他のスレッドを子プロセスに残さない ——用途に適した挙動が重要

  • マルチスレッドとfork
  • 子プロセスですべてのスレッドが実行されることの問題
  • まとめ

第4章 時刻

4.01 POSIXの時刻機能とRubyのTimeクラス ——プログラマが知っているPOSIXの機能を提供する

  • POSIXの時刻機能で用いる型
  • POSIXの時刻機能とRubyの対応
  • まとめ

4.02 Time.utcと閏秒 ——POSIXが提供していなくても必要なら提供する

  • Time.utcメソッド
  • timegm関数と閏秒
  • Rubyによるtimegm関数のエミュレーション
  • まとめ

4.03 Time#monとTime#ydayの範囲 ——実際の用法を検討してAPIをデザインする

  • 「月」を整数でどう表すか
  • 「年初からの日数」を整数でどう表すか
  • まとめ

4.04 時刻に関するOSの制限 ——本質的でない制限を取り除く

  • C言語における時刻表現の制限
  • time_tの制限
  • struct tmの制限
  • 関数の制限
  • RubyのTimeクラス
  • まとめ

4.05 localtimeの逆関数 ——mktimeに依存しない

  • 地方時とタイムゾーンデータベース
  • mktimeの不安定性
  • mktimeをlocaltimeと2分探索で実現する
  • まとめ

4.06 localtimeの外挿 ——2038年問題への対応

  • 2038年問題への対応
  • 未来の地方時の推定
  • いくつかの地方時の規則
  • localtime関数の外挿
  • 過去の地方時の推定
  • その他の制約の除去
  • まとめ

Column さまざまなタイムゾーン

  • キューバと夏時間
  • ブラジルと夏時間
  • イスラエルと夏時間
  • モロッコと夏時間
  • イランと夏時間
  • イギリスと夏時間
  • 夏時間の時差の例外
  • 夏時間と歴史的観点
  • 日付変更線付近の夏時間
  • 夏時間と極地
  • 日付をまたぐ例
  • 時刻を取り巻くさまざまな理由

4.07 UTCからの時差と夏時間 ——対象を確実に表現するデータ構造

  • C言語とRubyにおける時刻表現の違い
  • C言語における時刻の扱いの問題
  • Rubyにおける時刻の扱いの問題
  • Time#utc_offsetメソッドの追加
  • Time#strftimeメソッドの強化
  • まとめ

4.08 UTCからの時差を指定 ——メンテナンスを増やさない範囲で表現を広げる

  • UTCと地方時の扱い
  • さまざまな地方時とタイムゾーンデータベース
  • 時差が一定のタイムゾーン
  • まとめ

4.09 秒未満の表現 ——有理数による表現

  • 外部から時刻が伝達される状況
  • Timeオブジェクトの内部表現
  • Marshalフォーマットの問題
  • まとめ

4.10 タイムゾーンの略称 ——問題が多過ぎるので避ける

  • 略称はタイムゾーンに1つでない
  • タイムゾーンの略称は複数のタイムゾーンで共有される
  • タイムゾーンの略称の生成はOSによっては期待されない結果になる
  • UTCからの時差を直接使う
  • まとめ

Column 一方,PHPはタイムゾーンデータベースを同梱した

  • PHPのタイムゾーンデータベースの同梱
  • タイムゾーンデータベースの更新
  • OSのタイムゾーンの検出
  • まとめ

Column Pythonの時刻 ——naiveとaware

  • datetimeの2種類の時刻
  • naiveなdatetimeとawareなdatetime
  • PythonとRuby

4.11 time.rbが提供するメソッドの意図 ——正しい方が簡単になるようにしておく

  • time.rbが追加するメソッド
  • 特定用途のためのメソッド
  • Time#strftimeメソッド
  • strftimeのlocale依存性の問題
  • strftimeの%zの問題
  • RFC 2822の時刻を生成する正しい方法
  • プログラマを正しい方法に誘導する
  • まとめ

4.12 Time.localとTime.utcの引数順 ——あからさまに奇妙だが互換性のために残っている

  • Time.localとTime.utcの奇妙な引数順
  • 引数順の理由と歴史
  • 問題を修正するかどうか
  • まとめ

第5章 数,文字列

5.01 Math.gammaのメソッド名 ——慣習は無視することもある

  • ガンマ関数と対数ガンマ関数
  • Rubyのメソッド名とC言語の関数名
  • まとめ

5.02 Integer#nonzero?の返り値 ——意外な動作だけど役に立つ

  • Integer#nonzero?の返り値
  • Enumerable#sort
  • Enumerable#sortでInteger#nonzero?を使う
  • まとめ

5.03 有理数のビット演算 ——一貫性を拡張するのは無理かもしれない

  • Rubyにおける数の演算
  • 有理数のビット演算は交換法則が成り立たない
  • 有理数のビット演算で交換法則を実現できるか
  • まとめ

5.04 Integer#bit_lengthメソッド ——用途と前例を調べる

  • メソッドの用途
  • 仕様決定の難しさ
  • 用途を検討する
  • 前例を検討する
  • 既存のメソッドの問題
  • まとめ

5.05 文字列中の式展開構文の一貫性 ——一貫性を優先

  • バージョンによる式展開の違い
  • 式展開の変更の影響
  • まとめ

5.06 URI.encode_www_formとURI.decode_www_form ——間違いにくいAPI

  • URI.decode_www_formとURI.encode_www_formメソッド
  • 間違いに気がつきやすいAPI
  • まとめ
  • おわりに

著者プロフィール

田中 哲(たなか あきら)

2000年北陸先端科学技術大学院大学博士後期課程修了。同年より電子技術総合研究所。2001年に産業技術総合研究所に改組され現在に至る。プログラミング言語に興味を持ち,実践としてRubyの開発を行うコミッタでもある。Rubyへの貢献は細かいものまで含めると数えきれないが,大きなものは本書にかなり書いてある。