1.0.0リリース時のリビジョンがr2898で現在のリビジョンがr3001と、1.0.0リリース後も継続して開発されているmilter managerですが、5月は、開発だけではなくmilter managerの話をする機会もあります。5/19と5/26の2回です。
5/19の方は財団法人インターネット協会主催のIAjapan 第7回 迷惑メール対策カンファレンスです。
13:20-14:20の「3. 送信ドメイン認証活用に向けて」で「milterの有効活用 - milter manager」というタイトルで30分話します。同じ枠で、送信ドメイン認証SPF/Sender ID Framework/DKIM/DKIM ADSPに対応したmilterであるENMAの話も聞けます。
また、続く14:35-16:05の「4.【Q&Aパネルディスカッション】送信ドメイン認証を導入にあたって」にもパネリストとして参加します。
迷惑メール対策に関心のある方は参加してみてはいかがでしょうか?
トピックス: IAjapan第7回迷惑メール対策カンファレンスで講演
5/26の方はIPA主催のIPAX2009です。
milter managerはIPAのオープンソフトウェア事業による成果であるため、今回出展することになりました。オープンソフトウェア事業以外にも未踏に参加した人たちも出展しているので、技術的におもしろいことが聞けるのではないかと思います。
出展者のプレゼンテーション枠があり、5月26日11:55〜12:55の枠の中で10分間milter managerの話をします。ブースは両日とも出展していますので、ご来場の際は一声かけてください。
このような機会がきっかけとなって、milter managerに興味を持ってくれる人が増えると嬉しいですね。
Ruby 1.9.1付属のREXMLではXML宣言のエンコーディングの扱いに問題があるためvalidなXMLでもパースできない場合があるという話です。
Ruby 1.9では文字列や正規表現がエンコーディング情報を持つため、REXMLのように正規表現ベースでXMLをパースしている場合は、エンコーディングを適切に設定しないとパースに失敗することがあります。
例えば、tDiaryのseach-yahoo.rbプラグインがこの問題に遭遇しています。
REXMLは内部でUTF-8を用いています。そのため、パース対象のXMLのエンコーディングをUTF-8に変換しながらパースします。この処理はREXML::SourceまたはREXML::IOSourceで行われます。
しかし、REXML::IOSourceに問題があり、UTF-8に変換しないままパースしてしまう場合があります。これは、入力XMLのエンコーディングがUTF-8に設定されていない、かつ、XML宣言のエンコーディングがUTF-8になっている場合です。ちなみに、REXML::Sourceではこの問題は起きません。
tDiaryのsearch-yahoo.rbでは入力XMLのエンコーディングがASCII-8BITでXML宣言のエンコーディングがUTF-8になっていたため問題に遭遇しました。
search-yahoo.rbではopen-uriを使って入力XMLをHTTP経由で取得しています。open-uriはContent-Typeを見て適切なエンコーディングを設定してくれますが、今回はcharsetが指定されていなかったとのことです。このため、open-uriで取得した入力XMLがASCII-8BITになっていました。
1 2 3 |
xml = open("http://.../xxx.xml") {|f| f.read} xml.encoding # => ASCII-8BIT document = REXML::Document.new(xml) # => パースエラー |
この問題に遭遇してしまった場合は、以下のような解決法があります。
入力XMLのエンコーディングをUTF-8に設定する場合は以下のようになります。
1 2 3 |
xml = open("http://.../xxx.xml") {|f| f.read} xml.force_encoding("utf-8") document = REXML::Document.new(xml) |
REXML::Sourceを使う場合は以下のようになります。
1 2 |
xml = open("http://.../xxx.xml") {|f| f.read} document = REXML::Document.new(REXML::Source.new(xml)) |
修正されるのを待つ場合は、修正されるまで待ってください。
Ruby 1.9で正規表現ベースのコードがうまく動かない場合はマッチ対象の文字列のエンコーディングを確認しましょう。
ちなみに、REXML::IOSource#matchではエンコーディング関係のエラーを握りつぶしているため、実際に発生するREXML::ParseExceptionだけ見てもエンコーディングミスマッチがどこで起こっているかはわかりません。問題が発生したときは問題解決につながるエラーメッセージを提供したいものですね。
groongaは活発に開発が続けられており、リポジトリ上のgroongaでは性能改善だけではなくAPIも改善されています。APIの変更点を紹介しつつ、N-gramを用いた全文検索の仕方を紹介します。ただし、継続的に改善されているので、APIはこれからも変わっていきます。ここで紹介する内容もしばらくするとすぐに古くなることに注意してください。
ここでは、groogaのインデックスを自動更新で作成したサンプルアプリケーションを2009/05/14時点でのgroongaのAPIにあわせた上で、MeCabではなくN-gram(bi-gram)でインデックスを作成するように変更します。
GRN_OBJ_INIT()に引数が一つ増えて「ドメイン」も受けとるようになっています。ドメインとはそのオブジェクトがとりうる値の範囲を示しているオブジェクトのことです。例えば、カラムのドメインが<int>
であれば、そのカラムは32ビットの整数を持つということを表します。また、テーブルであれば、そのカラムはドメインに指定したテーブルのレコードIDを持つということを表します。カラムのドメインにテーブルを指定することにより、テーブル間の関連付けが行えます。
さて、サンプルアプリケーションではバルクオブジェクトを初期化するためにGRN_OBJ_INIT()を使っていました。バルクオブジェクトとは少し賢い文字列のようなものです。バイト列を持っていますが、バイト列の長さを取得できたり、バイト列に割り当てる領域を再利用できたりします。groongaでは値の受け渡しにバルクオブジェクトを使うことが多いので、実は、結構大事なオブジェクトです。
サンプルアプリケーション内では、単なるバイト列として使っていたのでバルクオブジェクトのドメインはGRN_ID_NIL
とします。ドメインにはオブジェクトそのものではなく、オブジェクトのIDを指定するのですが、GRN_ID_NIL
は存在しないオブジェクトを表すIDになります。LispやRubyを触ったことのある人なら、名前からすぐに想像がつきますね。
変更点は以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
--- groonga/auto-index-update.c 2009-04-26 17:20:28 +09:00 (rev 32) +++ groonga/auto-index-update.c 2009-05-14 17:35:56 +09:00 (rev 33) @@ -64,7 +64,7 @@ grn_id source_id; source_id = grn_obj_id(context, comment_column); - GRN_OBJ_INIT(&source, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY); + GRN_OBJ_INIT(&source, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); GRN_BULK_SET(context, &source, &source_id, sizeof(grn_id)); grn_obj_set_info(context, comment_index_column, GRN_INFO_SOURCE, &source); @@ -78,11 +78,11 @@ id = grn_table_add(context, bookmarks); - GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY); + GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); GRN_BULK_SET(context, &value, uri, strlen(uri)); grn_obj_set_value(context, uri_column, id, &value, GRN_OBJ_SET); - GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY); + GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); GRN_BULK_SET(context, &value, comment, strlen(comment)); grn_obj_set_value(context, comment_column, id, &value, GRN_OBJ_SET); } |
これまでは、テーブルやカラムなどオブジェクトを生成するときの引数にエンコーディング(grn_encoding)を指定していましたが、エンコーディングはコンテキスト(grn_ctx)から取得することになりました。エンコーディングの扱いは、メーリングリストに投稿された[groonga-dev,00056] Re: GRN_ENC_DEFAULTの扱いが詳しいです。
同一アプリケーション(同一コンテキスト)内ではエンコーディングを統一することが多いという観点からこのようになりました。引数が減ってすっきりしました。
この変更に対応するためには、単にエンコーディング引数を削除します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
--- groonga/auto-index-update.c 2009-05-14 17:35:56 +09:00 (rev 33) +++ groonga/auto-index-update.c 2009-05-14 17:37:01 +09:00 (rev 34) @@ -30,8 +30,7 @@ NULL, GRN_OBJ_TABLE_NO_KEY, NULL, - 0, - GRN_ENC_DEFAULT); + 0); uri_column = create_column(context, bookmarks, "uri", lookup(context, "<shorttext>"), 0); @@ -48,8 +47,7 @@ NULL, GRN_OBJ_TABLE_PAT_KEY, lookup(context, "<shorttext>"), - 0, - GRN_ENC_DEFAULT); + 0); comment_index_column = create_column(context, lexicon, "comment-index", bookmarks, @@ -127,8 +125,7 @@ NULL, GRN_OBJ_TABLE_HASH_KEY, lexicon, - 0, - GRN_ENC_DEFAULT); + 0); query = grn_obj_open(context, GRN_BULK, 0, 0); grn_bulk_write(context, query, word, strlen(word)); @@ -148,7 +145,7 @@ grn_ctx context; grn_init(); - grn_ctx_init(&context, 0, GRN_ENC_UTF8); + grn_ctx_init(&context, 0); grn_db_create(&context, NULL, NULL); define_bookmarks_table(&context); |
テーブルにはキーがあるテーブル(ハッシュテーブルとパトリシアトライ)とキーがないテーブル(配列)がありました。これまでは、キーがないテーブルにレコードを追加する場合はgrn_table_add()
を用いて、キーがあるテーブルにレコードを追加する場合はgrn_table_lookup()
を用いていました。
このAPIの変更でどのテーブルにもgrn_table_add()
でレコードを追加できるようになりました。grn_table_add()
でキーがあるテーブルにもレコードが追加できるようになったため、キー関連の引数が増えています。
一部では、grn_table_lookup()
でレコードが追加できるなんて気づかないよ!という声もあったのですが、この変更で機能と名前が一致したよいAPIになったのではないかと思います。
サンプルアプリケーションではキーがないテーブルにだけレコードを追加していました。キーがないレコードの場合はキー関連の引数にNULL
などを指定します。
1 2 3 4 5 6 7 8 9 10 11 |
--- groonga/auto-index-update.c 2009-05-14 17:37:01 +09:00 (rev 34) +++ groonga/auto-index-update.c 2009-05-14 17:40:35 +09:00 (rev 35) @@ -74,7 +74,7 @@ grn_id id; grn_obj value; - id = grn_table_add(context, bookmarks); + id = grn_table_add(context, bookmarks, NULL, 0, NULL); GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); GRN_BULK_SET(context, &value, uri, strlen(uri)); |
前半でも登場した実は結構大事なバルクオブジェクトですが、バルクオブジェクトを操作する便利マクロがGRN_BULK_プリフィックスからGRN_TEXT_プリフィックスに変更になっています。これらの便利マクロが文字列関連の機能を提供していたのでこうなったのだと思います。機能を反映した名前に変更されていると思います。
ただ、このAPIはまだまだ変更される可能性があるので、注意してください。
この変更に対応するにはBULKをTEXTに置換します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
--- groonga/auto-index-update.c 2009-05-14 17:40:35 +09:00 (rev 35) +++ groonga/auto-index-update.c 2009-05-14 17:41:26 +09:00 (rev 36) @@ -63,7 +63,7 @@ source_id = grn_obj_id(context, comment_column); GRN_OBJ_INIT(&source, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); - GRN_BULK_SET(context, &source, &source_id, sizeof(grn_id)); + GRN_TEXT_SET(context, &source, &source_id, sizeof(grn_id)); grn_obj_set_info(context, comment_index_column, GRN_INFO_SOURCE, &source); } @@ -77,11 +77,11 @@ id = grn_table_add(context, bookmarks, NULL, 0, NULL); GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); - GRN_BULK_SET(context, &value, uri, strlen(uri)); + GRN_TEXT_SET(context, &value, uri, strlen(uri)); grn_obj_set_value(context, uri_column, id, &value, GRN_OBJ_SET); GRN_OBJ_INIT(&value, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, GRN_ID_NIL); - GRN_BULK_SET(context, &value, comment, strlen(comment)); + GRN_TEXT_SET(context, &value, comment, strlen(comment)); grn_obj_set_value(context, comment_column, id, &value, GRN_OBJ_SET); } @@ -105,8 +105,8 @@ uri = grn_obj_get_value(context, uri_accessor, result_id, NULL); comment = grn_obj_get_value(context, comment_accessor, result_id, NULL); - GRN_BULK_PUTC(context, uri, '\0'); - GRN_BULK_PUTC(context, comment, '\0'); + GRN_TEXT_PUTC(context, uri, '\0'); + GRN_TEXT_PUTC(context, comment, '\0'); printf("%s\t | %s\n", GRN_BULK_HEAD(uri), GRN_BULK_HEAD(comment)); grn_obj_close(context, uri); grn_obj_close(context, comment); |
API変更に追従するための変更は以上です。それでは、MeCabでインデックスを作成してした部分をN-gram(bi-gram)で作成するようにします。
まず、デフォルトトークナイザとして<token:mecab>
を指定していた部分を<token:bigram>
に変更します。
1 2 3 4 5 |
--- old +++ new grn_obj_set_info(context, lexicon, GRN_INFO_DEFAULT_TOKENIZER, - lookup(context, "<token:mecab>")); + lookup(context, "<token:bigram>")); |
そして、ここがドキュメントに載っていない重要なことなのですが、インデックス用のカラムにGRN_OBJ_WITH_POSITION
を指定して位置情報も記録するようにします。これを指定しないと2文字の検索語にしかマッチしなくなります。(bi-gramで切り出しているため)
1 2 3 4 5 6 7 |
--- old +++ new comment_index_column = create_column(context, lexicon, "comment-index", bookmarks, - GRN_OBJ_COLUMN_INDEX); + GRN_OBJ_COLUMN_INDEX | + GRN_OBJ_WITH_POSITION); |
これで、リポジトリ上の最新groongaを用いたN-gramベースの全文検索ができるようになります。
変更後のソースコードはクリアコードのリポジトリにあります。
最新groongaではAPIが改善されていることを紹介したついでに、ドキュメントに書かれていないN-gram使用時の注意も紹介しました。
もちろん、Ruby/groongaのtrunkは最新groongaに対応しているので、最新groongaを最新Ruby/groongaから使うこともできます。
先日開催されたIAjapan 第7回 迷惑メール対策カンファレンスで、送信ドメイン認証の結果を利用した迷惑メール対策を行えるソフトウェアとしてmilter managerの紹介をしました。
講演資料: milterの有効活用 - milter manager
講演資料は、後日、カンファレンスのサイトでも公開される予定です。カンファレンスのサイトでは他の講演者の方の資料も公開されると思うので、公開されたらそちらもチェックするとよいと思います。
また、配布資料とは構成が変わっていて、図も増えてあります。公開した資料は講演時に利用した資料となっています。
自分でも感じていたり、まわりの方からもそういうコメントをもらったのですが、他の講演者の方とは少し違う感じの講演となりました。それでも、講演後に何人かの方が声をかけてくれました。ありがとうございます。
milter managerそのものについて、milter managerやその周辺技術の活用方法についてなど、興味を持ってもらえたようです。今回の講演では省略したりあまり深く触れなかった部分の中には、実はすごく伝えたい部分もあったのですが、そこに興味を持ってくれた方もいたのが嬉しかったです。
来週はIPAX2009に出展します。また、5/26(火)にある出展者プレゼンテーション1 企業の中で、12:45-12:55の10分間、milter managerについて話します。迷惑メール対策カンファレンスではあまり触れなかった技術的な面について話そうかと考えています。ブースも出していますので、もし、時間が合えば、IPAX2009に来てみてください。
先日、書きやすさとデバッグのしやすさを重視したC言語用テスティグフレームであるCutter 1.0.7がリリースされました。
Cutterでは、定義したテスト関数をフレームワークに登録する必要はありません。Cutterを用いたテストでは、共有ライブラリとしてテストを作成し、cutterコマンドでその共有ライブラリを読み込んで定義されているテスト関数を検出し実行します。
1.0.6までのCutterは、共有ライブラリから定義されているテスト関数を抽出するためにBFDライブラリを用いていました。しかし、共有ライブラリではなく静的ライブラリとしてBFDライブラリが提供されているプラットフォームがわりとあり、導入の障壁となる場合がありました。そこで、Cutter 1.0.7ではBFDライブラリに依存せず、共有ライブラリから定義されているテスト関数を抽出する機能を実装しました。
Cutter 1.0.7はELF/PE/Mach-Oに対応しているため、Linux, *BSD, Solaris, Cygwin, Mac OS Xなどの環境でもBFDライブラリなしで動作するようになりました。
ELFのフォーマットを解説しているページや、readelfなどのELF関連ツールを紹介しているページはあるのですが、ELFからシンボル名を抜き出すプログラムを紹介しているページがなかったので、Cutterで行っている、ELFから公開されている関数名を抜き出す方法を紹介します。
簡略化のためファイルの内容をすべてメモリに読み込んでから処理します。コツコツ資源を利用したい場合は少しづつ読み込みながら処理することになります。
ファイルの内容を読み込むにはGLibのg_file_get_contents()が便利です。
1 2 3 4 |
gchar *content;
gsize length;
g_file_get_contents(filename, &content, &length, NULL);
|
これで、contentの中にファイルの内容が格納されました。これを使って公開されている関数名を抜き出します。
ELFのフォーマットに関する情報はelf.h
で定義されています。ELF をパースするときはelf.h
を使うと便利です。ここでも、elf.h
を使います。
1 |
#include <elf.h> |
まず、ファイルがELFかどうかを判断します。
ELFは最初にヘッダが入っていて、それを見ることでELFかどうかを判断することができます。ここでは、64bit環境用のELFだけを対象とします。32bit環境用のELFを対象とする場合はコード中の「64」という箇所を「32」に変更します。どちらにも対応する場合はCutterのソースを参考にしてください。
1 2 3 4 5 6 7 |
Elf64_Ehdr *header = NULL; header = (Elf64_Ehdr *)content; if (memcmp(header->e_ident, ELFMAG, SELFMAG) == 0) { /* ELFファイル */ } |
「MAG」は「マジック」の略だと思います。
ELFであることが確認できたら、共有ライブラリかどうかを確認します。
1 2 3 |
if (header->e_type == ET_DYN) { /* 共有ライブラリ */ } |
.dynsymセクションには動的に解決されるシンボルが入っています。.dynstrセクションにはそれらのシンボルの名前が入っています。これらを見ることで共有ライブラリの中にあるシンボル名の一覧を取得することができます。
.textには関数の本体などが入っています。.dynsymにあるシンボルが.textセクションに関連していると、共有ライブラリ内で定義されているシンボルだということがわかります。
.dynsym/.dynstr/.textのセクションヘッダを探し出すコードは以下のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
Elf64_Shdr *dynstr = NULL; Elf64_Shdr *dynsym = NULL; uint16_t text_section_header_index = 0; gsize section_offset; uint16_t section_header_size; uint16_t i, n_headers; Elf64_Shdr *section_name_header; gsize section_name_header_offset; const gchar *section_names; /* ファイルの先頭からセクションの先頭までのバイト数 */ section_offset = header->e_shoff; /* 1つのセクションヘッダのバイト数 */ section_header_size = header->e_shentsize; /* セクション数 */ n_headers = header->e_shnum; /* ファイルの先頭からセクション名があるヘッダの先頭までのバイト数 */ section_name_header_offset = header->e_shoff + (header->e_shstrndx * header->e_shentsize); /* セクション名があるヘッダ */ section_name_header = (Elf64_Shdr *)(content + section_name_header_offset); /* セクション名が格納されている位置の先頭 */ section_names = content + section_name_header->sh_offset; for (i = 0; i < n_headers; i++) { Elf64_Shdr *section_header = NULL; gsize offset; const gchar *section_name; /* ファイルの先頭からセクションヘッダの先頭までのバイト数 */ offset = section_offset + (section_header_size * i); /* セクションヘッダ */ section_header = (Elf64_Shdr *)(content + offset); /* セクション名 */ section_name = section_names + section_header->sh_name; if (g_str_equal(section_name, ".dynstr")) { /* .dynstrセクション */ dynstr = section_header; } else if (g_str_equal(section_name, ".dynsym")) { /* .dynsymセクション */ dynsym = section_header; } else if (g_str_equal(section_name, ".text")) { /* .textセクションが先頭から何番目のセクションか */ text_section_header_index = i; } } |
.dynsym/.dynstr/.textのセクションヘッダが見つかったら、それらのセクションにアクセスして、共有ライブラリ内に定義されているシンボル一覧を取得できます。
公開されているシンボルが関数かどうかを判断する条件は、シンボルが.textセクションに関連付けられているかどうかです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
guint i, n_entries; gsize symbol_section_offset; gsize symbol_entry_size; gsize name_section_offset; /* ファイルの先頭からシンボルが定義されているセクションまでのバイト数 */ symbol_section_offset = dynsym->sh_offset; /* シンボル定義領域のバイト数 */ symbol_entry_size = dynsym->sh_entsize; /* ファイルの先頭からシンボル名が定義されているセクションまでのバイト数 */ name_section_offset = dynstr->sh_offset; /* シンボル定義領域の数 */ if (symbol_entry_size > 0) n_entries = dynsym->sh_size / symbol_entry_size; else n_entries = 0; for (i = 0; i < n_entries; i++) { Elf64_Sym *symbol; uint64_t name_index; unsigned char info; uint16_t section_header_index; gsize offset; /* ファイルの先頭からシンボル定義領域までのバイト数 */ offset = symbol_section_offset + (i * symbol_entry_size); /* シンボル定義 */ symbol = (Elf64_Sym *)(content + offset); /* シンボル名は何番目に定義されているか */ name_index = symbol->st_name; /* シンボルの情報 */ info = symbol->st_info; /* シンボルに関連するセクションは何番目のセクションか */ section_header_index = symbol->st_shndx; /* シンボルは関数に関連付けられている */ if ((info & STT_FUNC) && /* シンボルは公開されている */ (ELF64_ST_BIND(info) & STB_GLOBAL) && /* シンボルは.textセクションに関連付けられている */ (section_header_index == text_section_header_index)) { const gchar *name; /* シンボル名 */ name = content + name_section_offset + name_index; g_print("found: %s\n", name); } } |
elf.h
を使って、BFDライブラリに依存せずに、ELFから公開されている関数名を抜き出す方法を紹介しました。ELFを読み書きするlibelfというライブラリもあるのですが、ELFから情報を取得するだけなら、elf.h
で十分でしょう。
関数名が取得できたら、GModuleで関数本体を取得することができます。GLibは便利ですね。
いずれ、PEまたはMach-Oから公開されている関数名を抜き出す方法も紹介するかもしれません。
5/26,27の2日間開催されたIPAX2009にブース出展しました。立ち寄ってくれたみなさん、ありがとうございました。
このようなイベントに参加すると、新しくつながりができたり、つながりがある方たちと会うことができるのがよいところです。また、私たちが公開しているフリーソフトウェアのユーザの方と直接お話できることもあります。実際に便利に使っているという声を聞けると嬉しいものです。
さて、5/26にあった出展者プレゼンテーションで発表したので、その資料を公開します。
発表資料: milter-manager - 迷惑メール対策を柔軟に実現するためのmilterの開発
実は、今回の資料ではじめて「milterプロトコル」という単語を出しました。実装的には「milter managerがmilterプロトコルを実装しているので、うまくMTAとmilterの間に入ることができる」というのがおもしろいところなのですが、それを知らなくても便利に使うことができるので今までは触れずにいました。
他にも「milter manager内にRubyインタプリタを組み込み、IOまわりやプロトコル処理など速度を出したいところはC、設定ファイルや動的な条件判断など柔軟性が必要なところはRuby、というように適材適所で使い分けている」という実装面でおもしろいところがあるのですが、そこもいつか話せるとよいなぁと思っています。
5月は東京で開催されたIAjapan 第7回 迷惑メール対策カンファレンスとIPAX2009でmilter managerの紹介をしました。6月は東京以外の場所でmilter managerを紹介する予定です。興味のある方は足を運んでみてください。
肉の日なのでPikzie 0.9.4をリリースしました。
今回のリリースではとくに新機能はなく、 hexacosa.net::Pikzie (unittesting extention module)で教えてもらったバグが修正されている程度です。
Pythonでコードを書く機会があるとPikzieの機能も増えると思いますが、最近はなかなかPythonを使う機会がないため新機能が増えていません。新機能追加案やそのような機能を実装したパッチはWelcomeなので、使ってみて足りていない便利そうな機能があれば教えてください。
PikzieはPython Package Indexにも登録しているため、easy_installやpipを使って簡単にインストールすることができます。
easy_installを使う場合:
% sudo easy_install pikzie
pipを使う場合:
% sudo pip install pikzie
簡単に試すことができますね。
新機能はWelcomeなのですが、「テストを書きつづけること」に邪魔になりそうな機能は受け入れないかもしれません。邪魔になりそうな機能とは「デバッグしづらくなる機能」や「テストが読みづらくなる機能」などです。
Pythonで広く使われているであろうnoseはプラグイン機能があり、たくさんの機能を備えています。例えば、assert*
だけではなく、できるだけタイピング数を減らすためにok
やeq
といった機能も提供されています。
1 2 |
ok(a == b) # == assert(a == b) eq(a, b) # == assert(a == b, "%r != %r" % (a, b)) |
しかし、これらは「テストを書きつづけること」の邪魔になりそうな機能だと思います。そのため、もし、Pikzieにeq
やok
を追加してほしいという要望があった場合は断るでしょう。
ok
は「デバッグしづらくなる機能」だと思います。上記の例では、ok
が失敗したとき、aとbの値がなんだったのかを示してもらえません。問題を解決するためには、何が問題かを把握する必要があり、そのためには問題解決につながるエラーメッセージが非常に重要です。その情報を提供しないok
を簡単に使えるようにすると、デバッグしづらいテストを書いてしまいます。
eq
は「テストが読みづらくなる機能」だと思います。簡潔に書いてあるプログラムは読みやすいですが、省略した名前を使って短く書かれたプログラムは読みづらいものです。何を意図しているかが明確ではないからです。1つ省略した名前を使うと他でも省略した名前を使いたくなります。そのため、eq
を簡単に使えるようにすると読みづらいテストを書いてしまいます。
注意: 名前は長ければよいというものではありません。最小限の量で必要な情報が過不足なくこめられている名前がよい名前です。そのためには、その名前が使われている文脈を意識することが重要です。いつか、名前の話も書きたいものです。
noseもPikzieもテストを書きやすくすることに重点がおかれています。 そのため、noseもPikzieもたくさんの機能を提供しています。
noseとPikzieの違いは「テストを書きつづけること」にも重点がおかれているかどうかです。Pikzieは「テストを書きつづけること」にも重点がおかれているため、それを阻害するような機能を提供していません。
機能が多いことを重視する場合はnoseを選択するのがよいでしょう。しかし、テストを書きつづけることを重視する場合はPikzieもよい選択肢になると思います。
Pikzie以外にも高橋メソッドなプレゼンツール in XUL リターンズがリリースされています。
高橋メソッドなプレゼンツール in XUL リターンズの新機能は明日のMozilla Japan JP 10.0でわかるでしょう。