uchan note

プログラミングや電子工作の話題を書きます

Windows で GCC 6.2.0 をビルドするメモ

f:id:uchan_nos:20161208235327j:plainこれは 自作 OS Advent Calendar 2016 の 8 日目の記事です。

概要

現時点で最新バージョンの GCC 6.2.0 を Windows 上でビルドするメモです。 このメモでは、i386-elf をターゲットにした GCC を作ります。 コンパイラ本体のみが対象で、標準ライブラリ等はビルドしません。 (将来、標準ライブラリも自作 OS 向けにビルドできるようになったら、また記事を書こうかと思います。っていつだよ!)

続きを読む

レベル変換の仕組み(オームの法則からMOS-FETを使ったレベル変換回路まで)

レベル変換

レベル変換とは、電圧の異なる信号線をつなぐ際に電圧を変換することです。

例えば Raspberry Pi という小さな Linux ボードコンピュータの電源電圧は 3.3V です。 そのため GPIO の入出力電圧も 3.3V となっており、電子工作でよくある 5V 駆動の装置を直接つなぐことができません。 著者は最近、秋月電子通商で売っている中で最安のキャラクタ液晶表示器 SC1602Raspberry Pi から制御しようと思ってレベル変換回路を作りました。

また、いわゆる「L チカ」といって LED を点滅させるだけの回路でも、LED によっては 3.5V 程度の電圧がないと十分に光らない品種があり (赤色 LED に比べ、青色 LED は高い電圧を求める傾向があります)、3.3V の出力では十分でないケースがあります。

レベル変換は低電圧側の回路から高電圧側の回路を制御するだけでなく、逆に高電圧側から低電圧側の回路を制御するのに用いる場合もあります。

ハードウェアエンジニアにとっては良く知られた回路ですが、ソフトウェア専門の人がちょっと電子工作しようとするとハマることが多いと思います。 この記事では、オームの法則を復習しつつ、レベル変換回路の基礎を見てみます。

続きを読む

osdev-jp という自作 OS のコミュニティを作りました

これは 自作 OS Advent Calendar 2016 の初日の記事です。

自作 OS Advent Calendar といいつつ、初日は技術的なことは書かずにコミュニティの紹介をします。期待してくださった方にはすみません(^^;)

自作 OS コミュニティの昔と今

私が自作 OS というものを知ったのは 2003 年ごろのことです。 そのころ、ウェブには活発な自作 OS コミュニティがいくつかありました。 私は主に OSASK コミュニティで活動していましたが、周りには dev-j、OS-Wiki などの OS 系の情報サイトや Mona OS、MEG-OS もありました。

時は流れて 2016 年。 私はしばらく自作 OS から離れていましたが、あるきっかけでまた自作 OS 関連の活動を再開させようと思いました。

久しぶりの OS 熱に侵されながら、おぼろげな過去の記憶をもとに dev-j や OS-Wiki などを再訪してみました。 しかし、そこは荒れた荒野のような状態でした。 dev-j にいたっては、トップページさえ 403 エラーでアクセスできない状態になっています。

osdev-jp の紹介

そこで私は、自作 OS に再びかかわろうと思うきっかけを与えてくれた 2 人の若者 ―― @liva_jy さんと @hikalium さん ―― とともに osdev-jp というコミュニティを作ることにしました。

発足したのは 2016 年 3 月です。 発足当初、このコミュニティは(OS-Wiki のように)OS 関連の情報を集めるサイトの名前でした。 今は情報収集はあまり捗っていませんが、「自作 OS もくもく会」というオフ会を定期的に開催し、自作 OS 好きの人同士のつながりを作り出しています。

osdev-jp の主要なリソースは以下の通りです。

「自作 OS もくもく会」は今のところ 2 カ月に 1 回のペースで開催しています。 開催予告は GitHub Wikiイベントカレンダー に載せていくつもりです。

nfcpy で複数の System Code を持つ NFC タグを扱う方法

Linux (Ubuntu 16.04) で nfcpy を使ってみました。落とし穴が多い気がしたので、ここにまとめます。 特に、複数の system code を持つような NFC タグの扱いが複雑ですので、詳しく説明します。

概要

LinuxNFC タグを読み書きするには nfcpy という Python ライブラリを使うのが割とよく用いられる方法のようです。 この記事では、nfcpy で NFC タグに記録されたブロックを取得する方法を説明します。

nfcpy に関する記事はいくつもあるのですが、どれも複数の System Code を持つ NFC カードの扱い方は書いてないようでした。 また、対応する nfcpy のバージョンが古かったりするので、この記事を書こうと思いました。

この記事で対象とする nfcpy のバージョンは 0.11.1 です。 また、動作検証は Sony 製の NFC リーダー・ライター PaSoRi RC-S380 を使って行いました。

nfcpy のインストール

公式ドキュメントは http://nfcpy.readthedocs.io/en/stable-0.11/ にあります。 (ただし、このドキュメントは少し古いです。現在 nfcpy は GitHub に移行したのですが、ドキュメントは lauchpad 時代のままです。)

nfcpy は PyPI に登録されているので、pip でインストールできます。 (nfcpy は現時点でまだ Python 3 系に対応してないので、pip3 は使わないでください。)

$ sudo pip install -U nfcpy

pip でインストールするとサンプルコードが手に入らないので、別途 GitHub からダウンロードします。

$ git clone git@github.com:nfcpy/nfcpy.git
$ cd nfcpy
$ git checkout stable/0.11

ここまでできたら、サンプルコードを実行して PaSoRi でタグを読み取ることができるはずです。

$ sudo examples/tagtool.py
[nfc.clf] searching for reader on path usb
[nfc.clf] using SONY RC-S380/P NFC Port-100 v1.11 at usb:002:013
** waiting for a tag **
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108

NFC タグをタッチすると検出され、製造ID(IDm)、製造パラメータ(PMm)、システムコードが表示されます。(最終行)

おまけ:sudo を使わないで作業する方法

環境によって、USB 接続の NFC リーダーに一般ユーザが接続することができない場合があります。 毎回 sudo を使えば問題ないのですが、それは嫌だという方には公式ドキュメントで説明されている通り、回避策があります。

まず、一般ユーザで NFC リーダーが扱えないことを確認します。

$ examples/tagtool.py --device usb
[nfc.clf] searching for reader on path usb
[nfc.clf] no reader available on path usb
[main] no contactless reader found on usb
[main] no contactless reader available

この時点でちゃんとリーダーを検出できていれば、何もする必要はありません。 検出できないことが確認できたら、lsusb コマンドでデバイス ID を調べます。

$ lsusb
...
Bus 002 Device 013: ID 054c:06c3 Sony Corp.
...

それっぽいデバイスを見つけたら 054c:06c3 のところの番号を使って tagtool.py を実行しなおします。

$ examples/tagtool.py --device usb:054c:06c3
[nfc.clf] searching for reader on path usb:054c:06c3
[main] access denied for device with path usb:054c:06c3
[main] first match for path usb:054c:06c3 is usb:002:013
[main] usb:002:013 is owned by root but you are xxxxx
[main] members of the root group may use usb:002:013
[main] you may want to add a udev rule to access this device
[main] sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'
[main] no contactless reader available

「指定した ID のデバイスは root の所有なのでアクセスできないよ」てなことが書いてあります。 下から 2 行目に解決策が提示されていますので、そのコマンドを実行します。

$ sudo sh -c 'echo SUBSYSTEM==\"usb\", ACTION==\"add\", ATTRS{idVendor}==\"054c\", ATTRS{idProduct}==\"06c3\", GROUP=\"plugdev\" >> /etc/udev/rules.d/nfcdev.rules'

マシンを再起動すれば、一般ユーザで使えるようになっているはずです。

NFC タグの中身を見てみる

nfcpy をインストールしたことですし、NFC タグの中身を表示するプログラムを作りましょう。 以下、NFC といいつつ FeliCa Standard を対象としたプログラムになっています。 他の規格のカードではうまく動かない可能性があります。

dump_simple.py は NFC タグ(の先頭 System)が持つサービスをすべて列挙するプログラムです。

import nfc


def check_services(tag, start, n):
    services = [nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f)
                for i in xrange(start, start+n)]
    versions = tag.request_service(services)
    for i in xrange(n):
        if versions[i] == 0xffff: continue
        print services[i], versions[i]


def on_connect(tag):
    print tag
    n = 32
    for i in xrange(0, 0x10000, n):
        check_services(tag, i, n)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

取りあえず実行してみます。サービスの全列挙には実行には時間がかかりますのでしばらく待ちましょう。

$ python dump_simple.py
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108
Service Code 0000h (Service 0 Type 000000b) 4097
Service Code 0100h (Service 4 Type 000000b) 4097
...

プログラムを説明します。

nfc.ContactlessFrontend('usb') で USB 接続の NFC リーダーを開きます。

clf.connectNFC リーダーを起動し、NFC タグのタッチ検出を開始します。 connect の引数で、NFC タグがタッチされたときに呼び出すコールバック関数 on_connect を指定します。

ドキュメントにはコールバック関数を設定しない(clf.connect(rdwr={}))場合、NFC タグをタッチするまでブロックするような記述がありますが、実験したところブロックしませんでした。 きちんと on-connect コールバックを設定する必要があるようです。

NFC タグがタッチされると on_connect にはそのタグを表すオブジェクト tag が渡ってきます。 以降、tag を使って NFC タグと通信し、様々なデータを読み取っていきます。

dump_simple.py プログラムは、タッチした NFC タグが持つすべてのサービスを列挙します。

tag.request_service にサービスコード(ServiceCode)を渡すと、タグがそのサービスを持つかどうかを調べられます。 タグが指定したサービスを持つなら 0xffff 以外の値が返ってきます。

サービスコードは 16 ビットの値で、上位 10 ビットがサービス番号、下位 6 ビットが属性値です。 16 ビット値ですから、0 から 0x10000 までを調べればすべてのサービスを網羅できます。 for i in xrange(0, 0x10000, n) はそういう意味です。

nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f) は、16 ビットの整数 i から ServiceCode オブジェクトを生成するやり方です。 i >> 6 で上位 10 ビットを取り出します。i & 0x3f で下位 6 ビットを取り出します。

tag.request_service は複数のサービスを一度に指定することができます。 実際に check_services 関数では n 個の ServiceCode のリストを作り、request_service 関数に渡しています。 1 つずつ調べるよりまとめて調べる方が速いので、32 個ずつ調べることにしたのです。

複数の System を扱う

dump_simple.py では System を切り替える処理を入れていなかったため、暗黙的に NFC タグの先頭の System を扱うことになります。 複数の System を持つタグの場合、明示的に扱う System を指定する処理が必要です。

今度は、すべての System を網羅的に調べるように改造した dump_all_systems.py を示します。

import nfc


def check_services(tag, start, n):
    services = [nfc.tag.tt3.ServiceCode(i >> 6, i & 0x3f)
                for i in xrange(start, start+n)]
    versions = tag.request_service(services)
    for i in xrange(n):
        if versions[i] == 0xffff: continue
        print services[i], versions[i]


def check_system(tag, system_code):
    idm, pmm = tag.polling(system_code=system_code)
    tag.idm, tag.pmm, tag.sys = idm, pmm, system_code
    print tag
    n = 32
    for i in xrange(0, 0x10000, n):
        check_services(tag, i, n)


def on_connect(tag):
    system_codes = tag.request_system_code()
    for s in system_codes:
        check_system(tag, s)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

実行するとこんな感じになります。複数の SYS= を調べているのが分かりますね。

$ python dump_all_systems.py
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=8108
Service Code 0000h (Service 0 Type 000000b) 4097
Service Code 0100h (Service 4 Type 000000b) 4097
...
Type3Tag 'FeliCa Standard (RC-S915)' ID=xxxxxxxxxxxxxxxx PMM=yyyyyyyyyyyyyyyy SYS=FE00
Service Code 1748h (Service 93 Random RW with key) 4097
Service Code 174Bh (Service 93 Random RO w/o key) 5963
...

プログラムの変更のポイントはこんな感じです。

  • tag.request_system_code 関数で NFC タグが持っている System の一覧を得る
  • 今まで on_connect でやっていた処理を check_system 関数に移して、polling 処理を加えた

polling の処理がミソとなる部分ですので、少し詳しく説明します。

ポーリングは、NFC リーダーが NFC タグを探す処理のことです。 ポーリング時に System Code を指定することで衝突防止を実現しています。 指定したコードと同じコードを持つ NFC タグだけがポーリングに反応することで、複数の NFC タグが探索範囲に存在しても衝突しないようになっています。

さて、tag.polling 関数に System Code を渡すと、そのコードを持つ NFC タグの IDm, PMm を取得できます。 取得した IDm と PMm、それからポーリングに使った System Code を tag.idm, tag.pmm, tag.sys に設定することで、以降のデータ通信をその System に対して行えるようになります。

データブロックの読み込み

NFC タグがもつすべての System とすべてのサービスが分かったので、次はいよいよ任意のサービスが持つデータブロックを読み込んでみます。

dump_block.py

import nfc
from binascii import hexlify


def on_connect(tag):
    idm, pmm = tag.polling(system_code=0xfe00)
    tag.idm, tag.pmm, tag.sys = idm, pmm, 0xfe00

    sc = nfc.tag.tt3.ServiceCode(93, 0x0b)  # 174B
    bc = nfc.tag.tt3.BlockCode(1, service=0)
    data = tag.read_without_encryption([sc], [bc])
    print 'str:', data
    print 'hex:', hexlify(data)


def main():
    with nfc.ContactlessFrontend('usb') as clf:
        clf.connect(rdwr={'on-connect': on_connect})


if __name__ == '__main__':
    main()

このプログラムはシステム 0xfe00 内のサービス 0x174B が持つブロック 1 のデータを読み込みます。 システムコードとサービスコードは、先ほどの探索で得た値を使っています。

このあたりは read_without_encryption のドキュメント に載っているサンプルを参考にしています。 ドキュメントにもありますが、BlockCode の生成時に渡す service=0 はサービスコードそのものではなく、read_without_encryption の引数 service_list 内でのインデックスです。

read_without_encryption([sc1, sc2, ..., scN], [...]) などと N 個のサービスを指定したとき、BlockCodeservice は 0 から N-1 までの値を取れます。

1 つのサービスは 16 バイトの大きさのブロックを複数個持ち、サービスの中で 0 から連番が付いています。

| Service X   |
|    ---------|
|   | Block 0 |
|    ---------|
|   | Block 1 |
|    ---------|
|   |  ...    |

サービスコードの下位 6 ビットは属性値で、次のようになっています。 この中で、パスワードなしで読み書きできるのは "w/o key" (without key = キーなし)となっているサービスだけです。 例えば 2 つのサービス "1748h" と "174Bh" のうち、読み取れるのは "174Bh" の方だけです。

ビット 意味
001000 Random RW with key
001001 Random RW w/o key
001010 Random RO with key
001011 Random RO w/o key
001100 Cyclic RW with key
001101 Cyclic RW w/o key
001110 Cyclic RO with key
001111 Cyclic RO w/o key
010000 Purse Direct with key
010001 Purse Direct w/o key
010010 Purse Cashback with key
010011 Purse Cashback w/o key
010100 Purse Decrement with key
010101 Purse Decrement w/o key
010110 Purse Read Only with key
010111 Purse Read Only w/o key

Python で Markdown 文書を HTML へ変換する

python-markdown2, py-gfm, pygments というライブラリを使って Markdown 文書を HTML へ変換するメモです。

GitHub Flavored Markdown (GFM) の特徴である Fenced Code Block にも対応しています。

インストール

$ pip3 install --user python-markdown2 py-gfm pygments pygments-style-github

スクリプト

md2html.py

#!/usr/bin/python3

import sys
from textwrap import dedent

from markdown2 import markdown


CHARSET = 'utf-8'


def main():
    if len(sys.argv) < 2:
        print('Usage: md2html path/to/file', file=sys.stderr)
        sys.exit(1)

    with open(sys.argv[1], encoding=CHARSET) as f:
        source = f.read()

    body = markdown(source, extras=['fenced-code-blocks'])
    html = dedent('''\
        <html>
        <head>
            <meta charset="{charset}">
            <link rel="stylesheet" type="text/css" href="github_pygments.css">
        </head>
        <body>{body}</body>
        </html>
        ''').format(charset=CHARSET, body=body)
    print(html)


if __name__ == '__main__':
    main()

CSS の準備

$ ~/.local/bin/pygmentize -S github -f html > github_pygments.css

変換

$ ./md2html.py hoge.md > hoge.html

おまけ:ブラウザから確認

手軽にブラウザから確認するには Python で HTTP サーバを立てると便利です。 この程度の静的 HTML なら直接ブラウザで開いてもいいのですが、HTTP サーバを立てればリモート開発している時も便利です。

$ python3 -m http.server 8000

8000 番ポートで起動します。

deb パッケージングメモ - alien 編

Ubuntudeb パッケージを作ろうとして調べ、実験したメモです。

なぜ自作ツールdeb パッケージを作るのか

開発したソフトウェアを Chef や Ansible などでデプロイするのに、apt-get install できると便利です。 deb にすることで、プログラム本体だけでなく、正しく動かすために必要なもろもろを deb 製作側の責任で作り込むことができます。 依存関係を考慮してくれたり、アップデートやアンインストールが容易なことも魅力です。

自作のプログラムに加え、Oracle Java のように tar.gz または rpm でしか提供されていないものを deb 化したいという需要もあります。

deb 化の方法の選択肢

プログラムを deb 化するに当たって、幾つかのやり方があります。

  • alien コマンドを使う
  • 正攻法でビルドする

alien

alien は tar.gz や rpm 形式のアーカイブを deb に変換するコマンドです。

tar.gz も rpmdeb も、基本的にはインストールするファイルをまとめて圧縮したものです。 したがって、ビルド済みのプログラムを含むアーカイブなら、簡単に変換できます。 Oracle Javadeb 化にはこの方法が合っていると思われます。

正攻法

dh_make や pdebuild を使い、ソースコードから deb パッケージを生成する方法です。

正攻法を正しく行うには事前の学習がそれなりに必要ですが、ビルド済みアーカイブがなくても良い点や、コンテナを用いたクリーンビルドができる点が優れています。 また、alien ではメタデータを細かく調整できなかったりしますが、正攻法ではその心配はありません。

正攻法はこの記事では扱いません。後日、別記事で書くかもしれません。書かないかもしれません。

alien を使って tar.gz から deb を作る

Oracle Java 8 JDK を例に、ビルド済みバイナリの tar.gz アーカイブを deb に変換する方法を紹介します。

まずはそのまま変換してみる

変換元のアーカイブが jdk-8u73-linux-x64.tar.gz であるとします。まずファイル名を希望するパッケージ名に変えます。

$ mv jdk-8u73-linux-x64.tar.gz oraclejdk8-jdk.tar.gz

Ubuntuリポジトリにある OpenJDK 7 のパッケージ名は openjdk-7-jdk なので、それに合わせて oraclejdk-8-jdk というように 8 の前にハイフンを入れたくなります。しかし、alien の制約で、数字の前にハイフンがあるとその数字以降が無視されてしまいます。恐らく、バージョン番号だから無視すべきと判断しているのでしょう。したがって、8 の前にはハイフンを入れない名前にしました。

いよいよ deb に変換します。

$ sudo alien -v --version=1.8.0.73 --bump=0 oraclejdk8-jdk.tar.gz

alien コマンドは root ユーザを要求します。sudo または fakeroot とともに実行してください。

出来上がったパッケージのバージョンを指定するのに --version オプションを使います。また、パッケージのリビジョン(debian バージョン)を指定するのに --bump オプションを使います。

--bump は変換元のリビジョンに加算する数値を指定するオプションで、デフォルトでは 1 です。tar.gz にはもともとリビジョン(およびバージョンも)がありませんから、リビジョンとして 1 が指定されているとみなされます。したがって、--bump を指定せずに実行すると oraclejdk8-jdk_1.8.0.73-2_amd64.deb というファイルが生成されます。

最初のパッケージなのにリビジョンが 2 になるのは気持ち悪いですね。ということで、例では --bump=0 として、リビジョンを増加させないように指示しています。oraclejdk8-jdk パッケージを一度でも外部に出した後、同じバージョンでビルドしなおすときは --bump を増やしましょう。リビジョンを上げておけば、APT の仕組みでアップグレードされるようになります。

alien コマンドがどのような処理をしているかを見たいので -v オプションを付けましたが、これは付けなくても動作は変わりません。

最後に出来上がったパッケージの中身を確認してみます。

$ dpkg -c oraclejdk8-jdk_1.8.0.73-1_amd64.deb | less

パッケージをインストールすると、ここで表示されたパスにファイルが配置されます。すべてのファイルが /jdk-1.8.0_73/ 以下に配置されることが分かりました。このパスは通常使われるインストール先ではありません。後ほど、ルートディレクトリを調整する方法を紹介します。

インストールスクリプトを含ませてみる

rpmdeb はインストール前後に実行するスクリプトを含むことができます。スクリプトを使うと例えば、配置したバイナリを update-alternatives に登録したりできます。

これをやるには、一旦アーカイブを展開し、スクリプトを含めた DEBIAN ディレクトリとともに圧縮しなおします。(アーカイブのファイル名は元に戻した前提で話を進めます。)

$ tar xzf jdk-8u73-linux-x64.tar.gz
$ mkdir DEBIAN
$ vim DEBIAN/postinst
$ chmod +x DEBIAN/postinst

postinst はインストールの後処理で呼ばれるスクリプトです。ファイルがすべて配置し終わった後に呼び出されます。 postinst の中身はなんでもいいですが、例えばこんな感じにしてみましょうか。

#!/bin/sh
for f in $(ls /jdk-1.8.0_73/bin/*); do
    update-alternatives --install /usr/bin/$f $f /jdk-1.8.0_73/bin/$f 1
done

このスクリプトはインストールされたすべてのコマンドを update-alternatives に登録します。update-alternatives は、/usr/bin/java などをシンボリックリンクにし、実際に指すコマンドを手軽に切り替えられる仕組みです。

スクリプトを含めた deb パッケージを生成してみます。

$ tar czf oraclejdk8-jdk.tar.gz jdk-1.8.0_73/ DEBIAN/
$ sudo alien -v --version=1.8.0.73 --bump=1 oraclejdk8-jdk.tar.gz

oraclejdk8-jdk_1.8.0.73-2_amd64.deb をインストールしてみると、最後の方で update-alternatives が実行されるのを確認できます。

インストール先を調整してみる

これまでのやり方では、インストール先が /jdk-1.8.0_73 となってあまり綺麗ではありません。標準的には /usr/opt にインストールすることになっていますから、それに従うように調整してみます。

これには、tar で展開するときにオプションを使って調整してしまうのが楽です。

$ mkdir -p opt/oraclejdk8-jdk
$ tar xzf jdk-8u73-linux-x64.tar.gz -C opt/oraclejdk8-jdk --strip-component 1

tar コマンドは -C オプションで展開先を指定できます。さらに --stript-component 1 で先頭から 1 つのパスを削りつつ展開するよう指示しています。これらを組み合わせると opt/oraclejdk8-jdk/bin/java などに展開できます。

インストール先を変えたので、postinst も変更しておきましょう。ls /jdk-1.8.0_73/bin/*ls /opt/oraclejdk8-jdk/bin/* に変えれば OK です。

あとはアーカイブを作り alien を実行するだけです。

$ tar czf oraclejdk8-jdk.tar.gz opt/ DEBIAN/
$ sudo alien -v --version=1.8.0.73 --bump=2 oraclejdk8-jdk.tar.gz
$ dpkg -c oraclejdk8-jdk_1.8.0.73-3_amd64.deb | less

どうでしょうか。/opt/oraclejdk8-jdk 以下にインストール先を変更できたことが分かるはずです。

楽しい仕様書と生産性に関する考察

結論:楽しい仕様書は将来にわたってチームの生産性を向上させる。

仕様書を書く効果

『Joel on Software』(オーム社 2005)でジョエルはこんなことを書いている。

仕様書の最も重要な役割はプログラムをデザインすることだ。」

仕様書を書くには設計することを強いられる。 仕様書段階での設計は、実装しながらする設計に比べてはるかに簡単にレビューでき、手直しできるので、生産性の向上につながる。 ジョエルはこうも言っている。

「大きな理由の2番目はコミュニケーションにかかる時間を節約できるということだ。」

仕様書を1度書くだけで、実装時やテスト時に何度も参照され、コミュニケーションコストを大幅に節約するし、認識の齟齬を防げる。 このとき、ちゃんと皆に仕様書を読んでもらうには、楽しく(可笑しく)読める仕様書が大切になってくる。

先ほどの設計の話と合わせて、仕様書を書くということは開発からテスト、保守まで含めた生産性を向上させることは明らかだ。

期日を設けない場合の生産性

話は変わって『Peopleware』(日経BP社 2013)を取り出そう。ニューサウスウェールズ大学での調査の結果として、こんなことが書いてある。

「上司が日程的なプレッシャーを少しもかけなかったプロジェクトは、最高の生産性を示した」

私の個人的な経験から、生産性が高いのはやるべきことがはっきりしている、つまりどうやって実装したらいいか道筋が見えているときだ。 そういうときは、期日の目標値を定められないで放任されるほど、高品質なものが早く完成する傾向にある。 仕事への誇りからくる高生産性だと思う。

逆に、生産性が低いのは、対象の理解ができておらず、何から手を付けるか分からないときである。 こういうときは、タスクをやらなくちゃと思いつつ、何も進まずに非生産的な時間を過ごしてしまう。 強制的に期日の目標値を定めてもらわないと仕事が進まないケースだ。

このように、上司が強制的に期日を設定するのが必要なときもある。 しかし、その生産性は、やるべきことが見えているときに放任される場合の生産性よりずっと低い。

チームの生産性を最高に保つ

チームの生産性を常に最高に保つにはどうすればよいか。 自分たちのチームに新たに配属された人が、そのチームで作っているソフトウェアをよく理解できるようにしておくことだと思う。 もちろん、そうしておけば新人だけでなく既存メンバーがよりよくソフトウェアを理解することにもつながる。

メンバーが対象ソフトウェアをよく理解していれば、ソフトウェアの修正タスクに対する実現方法が思いつきやすくなる。 この状態になれば、期日を設定せずタスクを割り当てることができ、自動的に最高の生産性を発揮するはずである。

つまり、楽しく読め、きちんと対象を理解できるような仕様書をいつでもちゃんと書いておくことは、 後々、そのチーム全体の生産性を最高の状態に保つことにつながる。