2011-10-29

ドキュメント書きを効率化した5つの方法

API の仕様書なるものを書いている過程で、繰り返し作業が多く、いくつかの作業を工夫したのでまとめておきます。前提として、私はコードを一切書かず、依頼先にドキュメントを渡し、成果物としてコードを受け取ります。reST で書いて、Sphinx で HTML をビルドし、Wed-DAV 上のディレクトリに置くことで共有します。

1. 継続ビルド

Sphinx によるドキュメントを継続ビルドしています。omake を使わずに、Sphinx ドキュメントの継続的ビルド に具体的な方法を書きました。明示的なビルドをしなくなったので、書くときにはひたすら文書自体に集中して書いて、ブラウザでHTMLを確認する、というのを続けていけます。

make html をいちいち手でやっていると、どうしても文書ではなくて、ビルドするっていう行為に集中してしまうのです。継続ビルドなら書く方に集中できます。

欠点として、sphinx でビルドするとき、リンクエラーなんかは2回目以降のビルドではでなくなるので、エラーはどんどん流れていってしまいます。大きなコミットする前には make clean; make html  します。

2. ひな形

Komodo Edit の snippet に、ひな形を登録しておき、ワンクリックで挿入します。各 API は、だいたい同じフォーマットで記述するので、便利です。キーボードショートカットも割り当てられますが、そこまで使わないので、クリックで十分でした。

3. 進捗の測定

Komodo Edit のコマンド機能を使って、時刻、文書中に現れる "..todo::" の個数と、ソースファイルの文字数を出力するコマンドを登録しています。作業が一段落したところで、実行して、スプレッドシートにコピペします。スプレッドシートではそれをグラフにしています。

この工程は、作業と関係ないのですが、だらけるのを防止するために、自分の活動が成果物の量としてどのくらい反映されているのかを可視化していました。TODO は書いている途中で増えたりしますし、共通した部分をまとめると、ざくっとソースが小さくなるのですが、それは別に構いません。短期的なやる気の問題です。

echo -e `date "+%Y-%m-%%d %H:%M"`\\t`grep todo:: api/*.rst *.rst | wc -l`\\t`cat \`ls *.rst api/*rst\` | wc -c`

4. Mercurial のログを取り出す

現在の Mercurial ブランチの各リビジョンのメッセージを取り出すというコマンドも作りました。

いきなり完成版のドキュメントを渡すことができなかったので、追記や変更した版を出していくことにしました。そのとき変更履歴を changelog.rst に書くわけですが、コミットの度に編集していると面倒ですし、だいたい内容は mercurial のログと重複します。

そこごで、リリースごとにブランチをきっているので、ひととおり作業が終わったら、このコマンドを実行し、まとめて整形するようにしました。作業後半はこのコマンドがいちばん作業時間の軽減になりました。

hg log -b `hg branch` -v | awk '$1 !~ /(changeset|branch|user|date|files|description|tag):/ {print}'

5. Makefile に upload ターゲット

Makefile に upload ターゲットを追加して、make upload で反映します。これはすぐですね。



以上は、作業の効率化であって、仕事全体の効率化とは別の問題です。こうしたらいいとか、あったらぜひ教えてください。まだまだ書くのが、というか、要件を頭で整理するのが遅いです。困ったものです。

2011-10-14

omake を使わずに、Sphinx ドキュメントの継続的ビルド


Sphinx つかってドキュメント書いているのですが、自動的にビルドできないかなぁと思ったのが、1ヶ月くらい前です。そのときは、EUnitとOMakeでテスト駆動開発とかを読んで omake の P オプションがよさそうだったのですが、実現に至らず。

Mac OS X Lion で Komodo Edit 使って編集しています。omake -P すると、最初に1回ビルドが走ったあと、polling for filesystem changes ってなったまま表示が変わらず、ソースを編集すれどもすれでも、ビルドされません。ターミナルで vim とかで編集/保存すると omake -P はちゃんと変更を検出してくれて、期待通りに継続ビルドになります。

もうファイルシステムとか分かんないので、Makefile を適当に追記して生きていくことにしました。以下追記部分。

auto:
     @while :; do make html_silent; sleep 1; done

html_silent: .built
     @echo > /dev/null

.built: *.rst img/*
     $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
     @touch .built
     @echo
     @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."

「make auto」して使います。

auto と html_silent は、.PHONY に入れてあります。

毎秒、.built ファイルと、ソースファイルの日付を比較して、ソースファイルのほうが新しければビルドして、touch .built するだけです。ださくてすみません。

2011-10-13

@tokibito さんの TestBedTestCase をいじる

@tokibito さんが、Google App Engine のスタブを使う TestCase クラスを紹介しています。私の仕事でも使っています。

サブクラスを作ったときに、TestBedTestCase の setUp と tearDown を明示的に呼び出さないといけないのがちょっと面倒です。

class MyTestCase(TestBedTestCase):
    def setUp(self):
        super(MyTestCase, class).setUp()
        # … 準備 ...

    def tearDown(self):
        # … 片付け …
        super(MyTestCase, class).tearDown()

Pythonic なので、これはこれで別に問題ないんだろうけど、これ結構めんどくさい。あと、nose 使えば、モジュールごとに setUp を定義しておくと、こういうのもやってくれるはずだけど、老害じじいは nose とか分かんないので、とりあえずスルー。

という訳で、ベースクラスの setUp と tearDown を自動的に呼び出すようにして使っています。setUp の呼び出し順は、基底クラスの setUp の後で、派生クラスの setUp。tearDown は逆です。それぞれ、C++ なんかのコンストラクタとデストラクタと同じにしました。

class CascadingTestCaseMeta(type):
    """Metaclass for TestCase class

    This metaclass make setUp method calls all setUp methods 
    in base classes and then calls defined setUp method.  
    Likewise tearDown but in opposite order.
    """

    def __init__(cls, name, bases, ns):
        for method_name, reverse in [('setUp',False), ('tearDown', True)]:
            setattr(cls, method_name,
                    cls._create_method(method_name, bases, ns, reverse=False))

    @classmethod
    def _create_method(self, method_name, bases, ns, reverse=True):
        """return a method that calls all methods with given 
        name in class hierarchy
        """
        # create method sequence in parent and current classes
        methods = [getattr(base, method_name, lambda self: None)
                   for base in bases]
        methods.append(ns.get(method_name, lambda self: None))
        # reverse order if necessary
        if reverse:
            methods.reverse()
        # define method to call all methods
        def call_methods(self):
            for method in methods:
                method(self)
        # return the caller method
        return call_methods

class CascadingTestCase(unittest.TestCase):
    __metaclass__ = CascadingTestCaseMeta

class TestBedTestCase(CascadingTestCase):
    # 以下、@tokibito さんのコード

TestBedTestCase のメタクラスを直接指定していないのは、testbed の setUp よりも「前に」何かしたいときと、「後に」何かしたいときがあると思ったからです(でも今のところない…)。

class MyTestCase(TestBedTestCase, MyBaseTestCase):
    def setUp(self):
        ...

と定義すると、TestBedTestCase.setUp, MyBase.setUp, MyTestCase.setUp の順に呼び出されます。

TestBedTestCase のsetUp と tearDown が呼び出されるのが明示的じゃない気がしますが、そこの明示性は諦めました。

やっぱ nose かなぁ…。