Rubyの拡張ライブラリにYARD用のドキュメントを書く方法 - 2012-10-02 - ククログ

ククログ

株式会社クリアコード > ククログ > Rubyの拡張ライブラリにYARD用のドキュメントを書く方法

Rubyの拡張ライブラリにYARD用のドキュメントを書く方法

はじめに

YARDというRuby用のドキュメンテーションツールがあります。この記事ではCで書かれたRubyのライブラリにYARD用のドキュメントを書く方法を紹介します。

YARDはソースコード中にドキュメントを埋め込むタイプのドキュメンテーションツールです。ドキュメントはコメントとして書きます。ドキュメントに@タグ名という記法でメタデータを書けることが特徴1です。YARDに添付されているyardocというコマンドを使うことで、ソースコード中に書いたドキュメントからHTMLのリファレンスマニュアルを作成することができます。

Ruby2はライブラリをRubyでもCでも書けます3。Cでライブラリを書くと、処理を高速化したり、既存のC/C++で書かれたライブラリをRubyから使えるようにできます。例えば、rroonga4はC/C++で書かれた全文検索エンジンライブラリgroongaをRubyから使えるようにするライブラリです。

YARDはRubyで書いたライブラリもCで書いたライブラリもサポートしています5。Cで書いたライブラリにYARD用のドキュメントを書くには少しコツがいります。ここでは、例をつけながら、Cで書いたライブラリにYARD用のドキュメントを書く方法を紹介します。具体的には次の5つについて説明します。

  • YARD用のドキュメントをどこに書いたらよいか
  • メソッドの説明を書く方法
  • メソッドの引数の説明を書く方法
  • メソッドの戻り値の説明を書く方法
  • メソッドに引数として渡すHashに指定できるキーの説明を書く方法

例の説明

まず、例として使うCのコードを示します。このコードは、rroongaで実際に使われているコードの一部です。

static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    grn_ctx *context;
    grn_obj *database;
    int n_segments;
    VALUE options, rb_threshold;
    int threshold = 0;

    rb_scan_args(argc, argv, "01", &options);
    rb_grn_scan_options(options,
                        "threshold", &rb_threshold,
                        NULL);
    if (!NIL_P(rb_threshold)) {
        threshold = NUM2INT(rb_threshold);
    }

    rb_grn_database_deconstruct(SELF(self), &database, &context,
                                NULL, NULL, NULL, NULL);
    n_segments = grn_obj_defrag(context, database, threshold);
    rb_grn_context_check(context, self);

    return INT2NUM(n_segments);
}

void
Init_database ()
{
    VALUE mGrn;
    mGrn = rb_define_module("Groonga");
    rb_cGrnDatabase = rb_define_class_under(mGrn, "Database", rb_cObject);
    rb_define_method(rb_cGrnDatabase, "defrag", rb_grn_database_defrag, -1);
}

このコードで何をしているかを簡単に説明します。

このコードでは次の2つの関数を定義しています。

  • rb_grn_database_defrag()関数
  • Init_database()関数

1つ目のrb_grn_database_defrag()関数は、Groonga::Databaseオブジェクトのdefragメソッドの実体です。defragメソッドを呼ぶと、この関数が実行されます。

2つ目のInit_database()関数は、Groonga::Databaseオブジェクトのdefragメソッドとrb_grn_database_defrag()関数を結びつけています。初期化をしている関数です。

それでは、このCのコードにYARD用のドキュメントを書きながら、冒頭で挙げた次の5つについて説明します。

  • YARD用のドキュメントをどこに書いたらよいか
  • メソッドの説明を書く方法
  • メソッドの引数の説明を書く方法
  • メソッドの戻り値の説明を書く方法
  • メソッドに引数として渡すHashに指定できるキーの説明を書く方法

なお、この記事ではどこにどうタグ6を書くかに焦点を当てているため、個別のタグに対する詳細な説明は省いています。タグの詳細についてはYARDのドキュメント(英語)を参照してください。

YARD用のドキュメントをどこに書いたらよいか

YARD用のドキュメントはコメント内に書きます。ドキュメント用のコメントはメソッドの実体となる関数の直前に書きます。関数の直前に書くと、関数定義とメソッドのドキュメントが結びつきます。結びつけられるとYARDが生成するHTMLのリファレンスマニュアルでは「View source」のリンク先に関数定義が表示されます。

例を示します。以下のコード内の「ここにYARD用のドキュメントを書く」と書かれた部分にドキュメントを書きます。こうすることにより、これから書くdefragメソッドのドキュメントとrb_grn_database_defrag()関数の定義が結びつきます。

/*
 * ここにYARD用のドキュメントを書く
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    grn_ctx *context;
    grn_obj *database;
    int n_segments;
    VALUE options, rb_threshold;
    int threshold = 0;

    rb_scan_args(argc, argv, "01", &options);
    rb_grn_scan_options(options,
                        "threshold", &rb_threshold,
                        NULL);
    if (!NIL_P(rb_threshold)) {
        threshold = NUM2INT(rb_threshold);
    }

    rb_grn_database_deconstruct(SELF(self), &database, &context,
                                NULL, NULL, NULL, NULL);
    n_segments = grn_obj_defrag(context, database, threshold);
    rb_grn_context_check(context, self);

    return INT2NUM(n_segments);
}

void
Init_database ()
{
    VALUE mGrn;
    mGrn = rb_define_module("Groonga");
    rb_cGrnDatabase = rb_define_class_under(mGrn, "Database", rb_cObject);
    rb_define_method(rb_cGrnDatabase, "defrag", rb_grn_database_defrag, -1);
}

なお、これ以降、例にはrb_grn_database_defrag()関数の定義部分とドキュメントのみを載せます。それ以外の部分はYARD用のドキュメントとは関係ないため省略します。

それではメソッドのドキュメントを書いていきます。

メソッドの説明を書く方法

メソッド定義の直前のコメントにタグ7を使わずにドキュメントを書くと、YARDはその文章をメソッドの説明として扱います。メソッドの説明にはどのような処理をするメソッドかということを書きます。

defragメソッドの場合は以下のようになります。

/*
 * Defrags all variable size columns in the database.
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    /* ... */
}

このコードをexample.cというファイルに保存し、yardocコマンドを実行するとHTMLのリファレンスマニュアルを生成できます。

% yardoc example.c

リファレンスマニュアルには以下のようにコメントに書いたドキュメントがメソッドの説明として表示されています。

メソッドの説明を書いたリファレンスマニュアル

メソッドの説明は通常の文章として書きました。

メソッドの引数の説明を書く方法

メソッドの説明の後は、メソッドの引数についての説明を書きます。メソッドの引数の説明はタグを使って書きます。タグとはメタデータを指定するためのYARDの機能です。YARDではメソッドの引数をメタデータとして扱うため、統一感のある読みやすいリファレンスマニュアルを生成することができます8

引数の説明には@paramタグを使います。@paramタグの書式は以下の通りです。

@param [引数のクラス] 引数名 引数の説明

では、実際に@paramタグで引数の説明を書きましょう。@paramタグを書く位置はメソッドの説明の下がよいでしょう。HTMLのリファレンスマニュアル上では順序は関係ありませんが、コード中のドキュメントを読む場合に読みやすくなります。この順序にすると、メソッド全体の説明を読み、次に引数の説明に入る、という順序になります。メソッドの全体像を把握してから細部を読めるのでドキュメントを理解しやすくなります。

/*
 * Defrags all variable size columns in the database.
 *
 * @param [Hash] options custom options.
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
   /* ... */
}

「引数の説明」の「custom options.」の最後には常に「.」を付けておいたほうがよいでしょう。説明が「custom options. Optional」と2文以上になった場合に最後の文だけ「.」がついていないと、もやっとするからです。

例のdefragメソッドのシグニチャー9はRubyで書くと以下の通りです。

def defrag(options={})
  # ...
end

options引数はHashなので、@paramタグで[Hash]と書いてその情報を伝えています10

ここまでで書いたドキュメントからyardocコマンドでHTMLのリファレンスマニュアルを生成すると次のようになります。

引数の説明を追加したリファレンスマニュアル

リファレンスマニュアルに引数の説明が追加されています。引数の説明を追加するために@paramタグを使いました。

メソッドの戻り値の説明を書く方法

戻り値の説明には@returnタグを使います。@returnタグの書式は以下の通りです。

@return [戻り値] 戻り値の説明

では、実際に@returnタグで戻り値の説明を書きましょう。@returnタグを書く位置は@paramタグの下がよいでしょう。入力を読んでから出力を確認する、という順序で読めます。

/*
 * Defrags all variable size columns in the database.
 *
 * @param [Hash] options custom options.
 * @return [Integer] the number of defraged segments.
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    /* ... */
}

例のdefragメソッドは戻り値として整数を返すので、@returnタグで[Integer]と書いてその情報を伝えています。

ここまでで書いたドキュメントからyardocコマンドでHTMLのリファレンスマニュアルを生成すると次のようになります。

戻り値の説明を追加したリファレンスマニュアル

リファレンスマニュアルに戻り値の説明が追加されています。戻り値の説明を追加するために@returnタグを使いました。

メソッドに引数として渡すHashに指定できるキーの説明を書く方法

最後に、Hashを引数として受け取るメソッドのドキュメントを書きます。Rubyでは、Pythonのキーワード引数相当のことを実現するために、引数をHashとして受け取り、メソッド内で必要な値を取り出します。このようなメソッドを使う側は、Hashに指定できるキーと、その値が何を意味するのかが気になります。これをドキュメントに書いておくことで、有用なドキュメントになります。

Hashにどんなキーを指定できるのかというドキュメントを書くには、次の3つのタグを使います。

  • @overloadタグ
  • @paramタグ
  • @optionタグ

まず、@overloadタグを使ってメソッドのシグニチャーを指定します。@overloadタグの書式は以下の通りです。

@overload メソッド名(メソッドの引数)

なお、@overloadタグは引数にHashを指定しない場合でも常に指定することをオススメします。Rubyで書かれたメソッドは@overloadタグを書かなくても引数名などの引数の情報がつきますが、Cで書かれたメソッドには@overloadタグを書かないと引数の情報がつかないからです11

では、実際に@overloadタグでメソッドのシグニチャーを書きましょう。@overloadタグを書く位置は@paramタグの上がよいでしょう。まず、引数全体を確認してから個々の引数を確認する、という順序で読めます。

/*
 * Defrags all variable size columns in the database.
 *
 * @overload defrag(options={})
 * @param [Hash] options custom options.
 * @return [Integer] the number of defraged segments.
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    /* ... */
}

ここまでで書いたドキュメントからyardocコマンドでHTMLのリファレンスマニュアルを生成すると次のようになります。

シグニチャーの説明を追加したリファレンスマニュアル

リファレンスマニュアル内の、「Instance Method Summary」と「Instance Method Details」にあるdefragメソッド名のところに、「(options = {})」が追加されています。

次に、@paramタグでHashで指定するオプション全体についての説明を書きます。@paramタグの説明では「Hashでオプションを渡すことができる」ということを説明するのがよいでしょう。

@paramタグの書き方で注意するポイントは、必ず@overloadタグよりも下に書き、さらにその@overloadタグよりもインデントして書かなければいけないという点です。インデントして書くことで、YARDがその@paramタグは@overloadタグで書いたシグニチャーに対応していると認識します。

実は@overloadタグを複数指定することにより複数のシグニチャーを指定することができます。もし、@paramタグが@overloadタグと同じインデントレベルにある場合は「すべての@overloadタグで共有される@paramタグ」と認識されます。多くの場合はそれぞれのシグニチャー毎に引数の説明は異なるため、@overloadタグ毎に@paramタグが認識される書き方の方が適切です。

例ではすでに@paramタグが書かれていますが、@overloadタグと同じインデントレベルになっています。そのため、@overloadタグよりもインデントして@paramタグを書くように修正します。同様に@returnタグもインデントします。インデントする理由は@paramタグと同じです。

/*
 * Defrags all variable size columns in the database.
 *
 * @overload defrag(options={})
 *   @param [Hash] options custom options.
 *   @return [Integer] the number of defraged segments.
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    /* ... */
}

これで、@paramタグと@returnタグが@overloadタグで書いたシグニチャーに対応しているとYARDが認識するようになります。

ここまでで書いたドキュメントからyardocコマンドでHTMLのリファレンスマニュアルを生成すると次のようになります。

@paramタグと@returnタグが@overloadタグで書いたシグニチャーに対応したリファレンスマニュアル

@returnタグに書いた戻り値の説明が、メソッドの説明の後ろに追加されています。@paramタグに書いた引数の説明は追加されていませんが、これは、@overloadタグが1つしかないため、1つの@overloadタグで使われているかすべての@overloadタグで共有されているかの見分けがつかないためです。12 @overloadタグで書いたシグニチャーに@paramタグと@returnタグが対応しているとYARDに認識させるために、@paramタグと@returnタグをインデントしました。

最後に、@optionタグでHashに指定できるキーとその説明について書きます。@optionタグの書式は以下の通りです。

@option Hash引数の名前 Hashのキー名 (デフォルト値) 値の説明

ここでの「デフォルト値」とは、Hashにキーを指定しなかったときに、そのキーに対応する値として使用される値のことです。

@optionタグは@paramタグの下に同じインデントで書きます。

では、実際に@optionタグを書きましょう。

/*
 * Defrags all variable size columns in the database.
 *
 * @overload defrag(options={})
 *   @param [Hash] options custom options.
 *   @option options [Integer] :threshold (0) the threshold to
 *     determine whether a segment is defraged. Available
 *     values are -4..22. -4 means all segments are defraged.
 *     22 means no segment is defraged.
 *   @return [Integer] the number of defraged segments
 */
static VALUE
rb_grn_database_defrag (int argc, VALUE *argv, VALUE self)
{
    /* ... */
}

ここまでで書いたドキュメントからyardocコマンドでHTMLのリファレンスマニュアルを生成すると次のようになります。

Hashのキーの説明を追加したリファレンスマニュアル

オプションについての説明が追加され、引数のHashに指定できるキーが:thresholdであることと、:thresholdに対応する値は整数を指定することと、指定しなかったときには0が使われることがわかります。Hashでオプションを指定する場合のドキュメントには@overloadタグ、@paramタグ、@optionタグを使いました。

まとめ

Cで書いたライブラリにYARD用のドキュメントを書く方法を説明しました。ポイントは@overloadタグを使うことです。Rubyで書いたライブラリの場合は@overloadタグは必須ではありませんが、Cで書いたライブラリの場合は必須と言ってよいでしょう。YARD用のドキュメントを書くことで、ユーザーにとって有用なドキュメントを書いてみてはいかがでしょうか。よいソフトウェアを書くためにドキュメントを書くことが役に立つこともありますよ。

  1. RDocでは:XXX:という記法でディレクティブを指定できます。ディレクティブはメタデータを指定するというよりは出力を制御するものです。ただし、ディレクティブの中には:category:などメタデータを指定するものもあります。

  2. CRubyやMRIと呼ばれている実装。

  3. Cで書かれたRubyのライブラリを拡張ライブラリと呼びます。

  4. rubyforge.orgからranguba.orgに移動しました。

  5. RDocもRubyとCを両方サポートしています。

  6. 後述。

  7. もっと後述。

  8. RDocはドキュメントを書く人それぞれが引数の説明っぽくドキュメントを書くという方式で、RDoc自身は引数の説明を特別扱いしません。そのため、書く人により表示のされ方は様々です。

  9. メソッドの名前、引数、戻り値に関する情報のこと。Rubyのメソッド定義の構文には戻り値に関する情報は含まれない。

  10. options引数が省略可能という情報は後で指定します。

  11. Rubyで書かれたメソッドの場合はメソッド定義から引数の情報を抽出しているが、Cではそもそも引数の情報が書かれていないため。

  12. もう少し言うと、もう1つ@overloadタグがあると、その@overloadタグの下には現在ある@paramタグの説明が表示されないので見分けがつきます。