2011-12-31

翻訳: Google App Engine で、アプリケーションの応答をよくする 15 の方法

大規模ウェブサービスのプラクティスを紹介している、High Scalability の記事「15 Ways To Make Your Application Feel More Responsive Under Google App Engine」を訳しました。


記事は Java 前提で、私は Python で Google App Engine を使っているのですが、それでも面白いなと思ったので、有効な方法は使っていこうとしています。ついでなので翻訳したものを残しておくことにしました。




軽量な査定用フィードバックサービスを提供している Small Improvements が、Performance issues on GAE, and how we resolved them という素晴らしい記事を書いている。どのようにして、ほとんどのリクエストを 300ms から 800ms でさばき、memcache が効いていなくても2秒、速いページなら150ms 程度でさばけるようになったかの事例になっている。全体として、ものすごく速くしているわけではないけれど、PaaS として GAE が気に入っているなら、検討すべき内容だろう。

パフォーマンスが悪いときに、できることが限られているというのが、PaaS のやっかいなところだ。でも、Small Improvements の人たちは賢くかつ辛抱強く改良をして、詳細な方法とその結果を提供してくれている。アドバイスはGAE に特化したいるけれど、別の状況にも当てはまるだろう。以下に、彼らが実施した、パフォーマンスの小さな向上(small improvemens) の15の方法をあげる。

  1. App Engine が遅い日もある、と知る。 App Engine はパフォーマンスが悪い日がある。設計段階で、潜在的にレイテンシが変化することを考慮しておく必要がある。常にベストな状況であることを想定しないこと。
  2. クエリよりも GET する。 Datastore から、ID で直接取得するのは 250ms 。クエリするのには 800ms かかる。
  3. GET のバッチと、スレッドローカルキャッシュを組み合わせる。キーごとに個別にGETせず、ひとつのバッチにまとめて GETする。結果を HashMap に保存しておいてレンダリングに使う。モジュール化したアプリケーションでは、内部状態の小さな断片を、引き回すのは避けたいだろうから、ThreadLocal に置いておき、いつでも各モジュールからアクセスできるようにする。
  4. クエリの結果は Memcache に保存する。 appstats ツールを使ってどのクエリが、キャッシュするに値するほど使われているからを見つけ出す。
  5. ログイン前にmemcache を温める。 ユーザが完全にログインするより前に Ajax を発行して、データをキャッシュしておく。
  6. ログイン後に memcache を温める。 ユーザがログインしたら、次に読み出しそうな5ページをキャッシュしておく。
  7. 救いようがないほど遅いときには、つなぎのページを表示する。 最初のクエリの時間を測り、もしも遅ければより速いページへ遷移させる。そのページが閲覧されている間に、元のページをキャッシュする。
  8. memcache を使えないといは、非同期に読み出すことで、行儀よくしょぼくする。クエリが遅いなら、その場では全情報をレンダリングしない。クエリが終わったところから順番に描画する。
  9. アクティブでないときにも再キャッシュ。ユーザがしばらくアクティブでない状態が続いたら、キャッシュが失効しそうなデータを再キャッシュするようなリクエストを、ブラウザから飛ばす。こうすればユーザが戻ってきたときにキャッシュに残っている。
  10. データを非正規化する。「このマネージャの部下は誰か」のような 300ms かかるようなクエリは、結果を予め保存しておき、人同士の関係が変わったときに再計算する。こうすれば GET で結果を取得できるので、速い。
  11. JAR を使う。 これ最強。何千ものクラスがあったら、ロードするのに 400ms はかかる、これはディスクアクセスがあるからだ。すべてのクラスを JAR に入れておけば、ロード時間が飛躍的に向上する。
  12. ウォームアップリクエスト。 できるかぎり GAE の warmup 機能を使う。コードパスを吟味して memcache にデータを書き、主な UI を描画し、鍵(キー)となるクエリを発行し、ログインをシミュレートし、時刻の計算をする。こういう処理は VM とデータをウォームアップする。
  13. 書込は後回しにする。 時間がかかったり、たくさんのクエリを発行した後は、書込処理をタスクキューにいれる。
  14. 非同期でメールする。 メール送信はタスクキューから行う。
  15. 非同期でクエリする。複数のクエリを並列に実行する。

2011-12-12

six

2011 Python アドベントカレンダー のエントリです。Python 2 と Python 3 の差異を吸収するラッパ集として six というライブラリを触ってみました。

Python 3 に同梱されている 2to3 というツールは、ひとつの Python 2 のソースから、 Python 3 用のソースを自動生成します。したがって、もとの Python 2 用コードと Python 3 用コードのふたつのバージョンを持つことになります。

一方、six がやろうとしているのは、ひとつのソースコードを Python 2 と Python 3 からも呼び出せるようにすることです。提供されるのは、基本的な定数や、ラッパ関数です。

文字列リテラルのフェイク

Python 3 と言えば文字列です。

# coding: utf-8

print(u'うほ')

Python 3 ではこれはエラーになります。six.u() 関数は、文字列リテラルのフェイクになってくれるそうです。

# coding: utf-8

import six
print(six.u('うほ'))

実行してみましょう。

$ python2.7 foo.py

ãã»
$ python3.2 foo.py
うほ

えー!six.u() の引数は、 unicode-escape でエンコードされた文字列じゃないとだめだそうです。「うほ」の場合は "\\u3046\\u307b" です。これはあんまりじゃないかと。しかも、

$ python2.7 foo.py
うほ
$ python3.2 foo.py
\u3046\u307b

ええぇぇ。そりゃまあ、リテラル表記を関数で代用するんですから、大変だと思いますよ。

見なかったことにして、six.b() に行きましょう。これは b'…' のフェイクです。

x = six.b('AB')
print(x[0]+x[1])
$ python2.7 foo.py
AB
$ python3.2 foo.py
131

oh… このあたりの挙動は 石本さんのブログ で書かれていますが、リテラルのフェイクは難しいようです。

気をとりなおして別の関数を見ましょう。それは文字列なのか、それともバイト列なのかを知りたいとき、isinstance とかを使うわけです。が、Python 3 では、このあたりの型や継承関係が変わっています。

x = u'foo'
if isinstance(x, basestring):
    print(x.encode('ascii'))

y = b'ABC'
if isinstance(y, str):
    print(y.decode('ascii'))

Python 2 では、上のように書いたりしたんですが、Python 3 では basestring は存在しませんし、str はバイナリを表しません。そこで、six の text_type と binary_type を使います。

x = six.u('foo')
if isinstance(x, six.text_type):
    print(x.encode('ascii'))

y = six.b('ABC')
if isinstance(y, six.binary_type):
    print(y.decode('ascii'))

実行してみると…

$ python2.7 foo.py
foo ABC $ python2.7 foo.py b'foo' ABC

お、これはわりとちゃんと動きますね。text_type は unicode (py2) か、str (py3) のエイリアスになっています。binary_type は同じく str と bytes です。

辞書のイテレータ

Python 2 の iteritems, iterkeys, itervalues は、Python 3 では items, keys, values に名前が変わっています。そして、Python 2 の items, keys, values はなくなっています。

この差異を吸収するのが、 six.iteritems などの関数です。

for k,v in six.iteritems({'spam':99, 'ham':0}):
    print(k,v)
$ python2.7 foo.py ('ham', 0) ('spam', 99) $ python3.2 foo.py ham 0 spam 99

辞書のメソッドを使わずに、six.iter* を使うことで、必ずイテレータを返すようになります。

表示が違うのは、print(k,v) の解釈の違いです。 Python 2 では、タプル (k,v) をプリントしています。一方、Python 3 では、k と v ふたつの引数をとって、それぞれをスペース区切りで表示します。

six には、これを吸収する関数もあります。

six.print_(1,2,3)

実行すると…

$ python2.7 foo.py
1 2 3
$ python3.2 foo.py
1 2 3

まとめ

言語仕様が変わって、オフィシャルに 2to3 が用意されるくらいですから、ラッパくらいでは吸収し切れない、というのがよく分かりました。個人的な感想は、2to3のほうが筋がいいんではないか、と思いました。

あしたは @nonNoise さんにバトンタッチです。よろしくお願いします。

2011-12-13 追記: pre タグがおかしかったのなど、諸々修正。blogspot って wysiwig エディタでコード書くのがいまいち。

2011-12-04

mock.patch をデコレータ、コンテクストマネージャとして使う

patch はコンテクストマネージャでもあるので、with を使った書き方ができます。with でつくられたブロックの中でだけ、モックが機能するようになります。
>>> from mock import patch
>>> with patch('random.random') as m:
...     import random
...     random.random()  # もうモックになっている。戻り値は Mock オブジェクト
...     m.return_value = 0.5
...     random.random()  # 戻り値は 0.5
... 
<mock.Mock object at 0x423ed0>
0.5
>>> random.random()  # モノホンの random 関数
0.84432022018162511
with ブロックの中に入った時点で、random.random はモックになってしまいます。必須ではありませんが、 as m のように書いて、変数 m でモックへアクセスできます。patch().start() の戻り値と同じです。
with ブロックを抜けるときに、パッチャー の stop() メソッドが実行されたのと同じ状態になり、モックではなくなります。
複数のモックを使いたいときには、with ブロックをネストするわけですが、それはちょっと鬱陶しいですよね。インデントが深くなりすぎるかも、なので。そんな時は 標準ライブラリの contextlib.nested を使います。
>>> from contextlib import nested
>>> from mock import patch
>>> from __future__ import with_statement
>>> with nested(patch('random.random'), patch('random.randint')) as (m, n):
...     m.return_value = 0.5
...     n.return_value = 3  # random.randint のモック
patch は、デコレータとしても使えます。
>>> from mock import patch
>>> @patch('random.random') 
... def func(m):
...     import random
...     print random.random()
...     m.return_value = 0.5
...     print random.random()
... 
>>> func()
<mock.Mock object at 0x429730>
0.5
>>> random.random()
0.94254256426687633
関数を patch でデコレートすると、元の関数にMock オブジェクトが渡されるようになります。上の例では、第1引数 m が random.random に対応するモックです。このモックが使われているスコープ内、つまりこの関数内でモックが有効になっています。関数を抜けると(正確には、関数を抜けて、デコレートしている外側のスコープを抜けると)、モックではなくなります。したがって、 func() を呼び出した後で、random.random() を呼び出すと本来の戻り値になります。
unittest モジュールを使ったテストを書く時には、テスト関数に、デコレータからモックを受け取るように書きます。
class MyTest(TestCase):
 @patch('random.random')
 def test1(self, m):
  import random
  m.return_value = 10.0
  self.assertEqual(random.random(), 10.0)
というわけで mock オブジェクトの機能でした。機能を知ることと、使えることはまた別なので、実際にテストでどうするのがいいか、は、別の機会に。私自身、試行錯誤しております。

2011-11-28

mock.patch


mock ライブラリの patch 関数の挙動を書きます。

パッチャーの start()/stop() メソッドを使う

標準ライブラリの random.random 関数を例にとります。

>>> import random
>>> random.random()
0.90675850364670885
>>> random.random()
0.9838226858480108


patch 関数を実行してみましょう。

>>> from mock import patch
>>> p = patch('random.random')
>>> random.random()
0.27552766919082217

とくに、何も変わったことは起こりません。patch 関数の戻り値は _patch オブジェクトです。ドキュメントにはパッチャーと書いてあります。パッチャーの start() メソッドを呼ぶと変化が起こります。

>>> m = p.start()
>>> m
<mock.Mock object at 0x366f30>
>>> random.random
<mock.Mock object at 0x366f30>

random.random という名前の参照先が、元の乱数生成関数ではなく、Mock オブジェクトに置き換わっています。random.random と p.start() の戻り値は同一の Mock オブジェクトです。

random.random は Mock オブジェクトなので戻り値を書き換えることもできます。

>>> random.random() >>> m.return_value = 100 >>> random.random() 100 >>> random.random.return_value = 0 >>> m() 0 >>> random.random() 0

パッチャーには stop() メソッドがあり、これを実行すると元に戻ります。

>>> p.stop()
>>> random.random
<built-in method random of Random object at 0x6b3b6210>
>>> random.random()
0.029689653478273681

テストで使う

def foo(x):
    return random.random() * x


関数 foo をテストする場合を考えます。テストすべきは、 

  1. random.random を引数なしで 1 度呼び出したこと。
  2. random.random() の戻り値に、x をかけたものが返ること。
です。random 関数の戻り値がわかっていれば、テストできますね。モックしましょう。

import random
import unittest
import mock

def foo(x):
    return random.random() * x

class MyTestCase(unittest.TestCase):
    def test(self):
        # random.random が常に1を返すようモックる
        p = mock.patch('random.random')
        p.start()
        random.random.return_value = 1
        # テスト対象関数を呼び出す
        result = foo(2)
        # random.random() を1度呼んでいることを確認
        self.assertEqual(random.random.call_count, 1)
        self.assertEqual(random.random.call_args, ((), {}))
        # 戻り値をテスト
        self.assertEqual(result, 2)
        # モックを戻す
        p.stop()  

if __name__ == '__main__':
    unittest.main()


これでテストできるようになりました。ですが、問題がありまして、テスト中に例外が出ると p.stop() が呼ばれません。random.random はモックのままなので、他のテストが実行されたきに、意図しない結果になることもあります。

というわけで、必ず実行されるように、setUp と tearDown で、パッチャーの start() と stop() 使います。

class MyTestCase(unittest.TestCase):
    def setUp(self):
        # random.random が常に1を返すようモックる
        self.p = mock.patch('random.random')
        self.p.start()
        random.random.return_value = 1

    def tearDown(self):
        # モックを戻す
        self.p.stop()

    def test(self):
        # テスト対象関数を呼び出す
        result = foo(2)
        # random.random() を1度呼んでいることを確認
        self.assertEqual(random.random.call_count, 1)
        self.assertEqual(random.random.call_args, ((), {}))
        # 戻り値をテスト
        self.assertEqual(result, 2)

つづき

patch をデコレータや、コンテクストマネージャとして使えるのですが、それは、また今度。

2011-11-25

mock ライブラリの Mock クラス

mock ライブラリを使ったテストのことを書こうと思ったのだけれど、テストの仕方なんて教えてもらう立場なんだから、いい例を思いつくわけもなく、挫折。

というわけで、mock ライブラリで提供されている機能を、簡単に書きだしていくことにします。mock を使ってテストする具体的な方法は、また今度。

今回は Mock クラスの基本機能です。ちなみにこれだけでは、テストを書くにあたってのメリットは限定的です。

Mock クラスは引数なしで、インスタンス化できます。以後、Mock オブジェクトと呼びます。

>>> from mock import Mock
>>> wozozo = Mock()
>>> wozozo
<mock.Mock object at 0x106fc4390>

Mock オブジェクトにドットでつなげると、別の Mock オブジェクトが取り出せます。

>>> wozozo.foo
<Mock name='mock.foo' id='4412162832'>
>>> wozozo.bar
<Mock name='mock.bar' id='4412163024'>
>>> wozozo.bar
<Mock name='mock.bar' id='4412163024'>

同じプロパティ名を指定すると、同じ Mock オブジェクトが返ってきます。上の例では、wozozo.bar は必ず同じ Mock オブジェクトを参照しています。

もちろん、任意の属性に、任意のオブジェクトを代入できます。

>>> wozozo.baz = 3
>>> wozozo.baz
3

Mock オブジェクトを、関数のように呼び出すこともできます。また、 call_count プロパティは呼び出した回数を返します。

>>> wozozo()
<mock.Mock object at 0x106fc44d0>
>>> wozozo.call_count
1
>>> wozozo()
<mock.Mock object at 0x106fc44d0>
>>> wozozo.call_count
2

Mock オブジェクトなら呼び出せるので、ドットでつないで得られるプロパティも呼び出せます。引数を渡すこともできます。

>>> wozozo.unko(1)
<mock.Mock object at 0x106fc4590>

call_count の他にも呼び出し系のプロパティがあります。call_args は、最後に呼び出されたときの引数を返します。また、call_args_list は呼び出されたときの引数の履歴を返します。

>>> wozozo.unko.call_count
1
>>> wozozo.unko.call_args
((1,), {})
>>> wozozo.unko(1, 'a', foo='hoge')
<mock.Mock object at 0x106fc4590>
>>> wozozo.unko.call_count
2
>>> wozozo.unko.call_args
((1, 'a'), {'foo': 'hoge'})
>>> wozozo.unko.call_args_list
[((1,), {}), ((1, 'a'), {'foo': 'hoge'})]

戻り値を指定することもできます。下の例では、wozozo.unko() で 999 を返すようにしています。

>>> wozozo.unko.return_value = 999
>>> wozozo.unko()
999

任意の関数を割り当てることもできます。

>>> def f(x, y):
...     return x + y
... 
>>> wozozo.unko = f
>>> wozozo.unko(2, 3)
5

てな具合です。

何が嬉しいかと言うと、簡単に何かのフリをさせることができる、ということです。すぐに思いつくのは (1) 生成するのがだるいインスタンスのフリをさせる、 (2) 関数やメソッドのフリをさせる、の2つです。

1つめの例として datetime オブジェクトを受け取って、年の情報だけを使うような関数を考えます。

>>> def next_year(today):
...     return today.year + 1
... 
>>> next_year(datetime.now())
2012
>>> today = Mock()
>>> today.year = 2000
>>> next_year(today)
2001
datetime なら、年月日を直接指定できるので大したことないですが、複雑なプロセスを経て、他のオブジェクトへの参照をしているようなオブジェクトで、かつ、一部のプロパティしか使わないのであれば、 Mock オブジェクトで代替すると便利です。

2つめの例としては random 関数を考えます。戻り値が決定論的に定まらないので、テストするのが大変です。そこで random の名前のくせに、常に0.5を返すようにしてみます。

>>> random = Mock()
>>> random.random.return_value = 0.5
>>> random.random()
0.5
>>> random.random()
0.5
>>> random.random()
0.5

こうすると、テストがやりやすくなりますね。

問題は、何かの関数の「中で」random 関数が呼び出される、ということです。もちろん、random 関数を引数に与えるような設計にするといいのでしょうが、人からもらったコードはそうなってないこともあるでしょう。

それを、うまくやってくれるのが mock.patch() 関数です。

眠い。寝よ寝よ。つづきは、またこんど。

2011-11-24

Python 温泉で開発プロセスの教えを乞う


初めて参加した Python 温泉で、 @voluntas と @aohta に開発プロセスの教えを乞いました。いろいろ教えてもらった中で、実際に手を動かし始めたことを書きます。@voluntas のブログ記事をベースに書いていますが、ふたりに教えてもらったことを混ぜています。

前提は、
- 自社サービス開発
- エンジニアの人数は不足気味
- 使用する言語は Python のみ
- ウェブ API 開発がメイン

環境の構築
開発環境を簡単に作れるというのは実はとても重要なファクターです。
これを目指すのがオススメです。git clone | hg clone して make だけたたけばあとは全部用意してくれるが理想ですね。

これは私が苦手なこと(そういうものが私には多い)のひとつです。というわけで、本当に基本的なことだけやりました。

人生初の buildout を使いました。いままで、便利そうなんだけど、よく分かんないってことで敬遠していました。が、今回は 「Python ライブラリを入れる」ことだけを書きました。

Makefile
.PHONY: env

env:
     python2.7 bootstrap.py --distribute
     bin/buildout

buildout.cfg
[buildout]
parts = env

[env]
recipe = zc.recipe.egg
eggs =
    nose==1.1.2
    mechanize==0.2.5
    simplejson==2.2.1
interpreter = python


これだけ。これで hg clone して make したら環境をつくれます。なんで今までやらなかったんだろう。無用な混乱を避けるためにバージョン番号を指定してしてあります。
単純だけど動く buildout.cfg を書けるようになったことが、今回のツール知識の中で最大の成果です。

機能テスト


上の環境は、開発しているアプリケーション本体ではなくて、その機能をテストするためのものです。@voluntas のブログでいうところの外部テストです。なので、nose が入ってるんですね。アプリケーションは HTTP で JSON を返すので、戻り値のチェックのために simplejson を入れています。
import urllib
import simplejson as json

def setup():
    # いろいろ初期化
    __SERVER = …
    ...

def test_my_api():
    res = app.call_api('/my/api', x=1, y=2)
    assert res['status'] == 0

def call_api(path, **kw):
    """API を呼び出して、レスポンスを辞書で返す"""
    params = urllib.urlencode(kw)
    fin = urllib.urlopen('%s%s' % (__SERVER, path), params)
    body = fin.read()
    return = json.loads(body)
上のようなファイルを作っておいて、 bin/nosetest を実行すると、setup して test_my_api を実行してくれます。 nose を使うのも初めてですが簡単でした。

自社ライブラリ


前述の call_api 関数は別のモジュールに切り出してあります。ゆくゆくは自社ライブラリとして格上げの予定。
大したコードではないので自社ライブラリにしなくていいんじゃ、と思ったのです。が、「call_api のテストだけ書いておけば、[simplejson などの] 依存先の使わない機能の不具合を無視できるのだから、自社ライブラリの動作確認だけすればよいでしょ。だから自社ライブラリにしちゃえ」という教えでした。これが一番私にとって重要な考え方でした。

次の課題


buildout でインストールした mechanize は OAuth 機能のテストに使います。リダイレクトやブラウザ上で操作などがあるので、自動テストにはブラウザを抽象化してくれるライブラリが必要でした。

def signup_with_twitter(screen_name, password):
    """Twitter アカウントでログイン"""
    def userop(browser):
        # Twitter にログインして承認
        browser.select_form(nr=0)
        browser["session[username_or_email]"] = screen_name
        browser["session[password]"] = password
        browser.submit()
    return _oauth('twitter', userop)

def _oauth(authority, userop):
    browser = mechanize.Browser()
    browser.set_handle_robots(False)
    # OAuth 開始の URL を開く
    browser.open("%s/oauth?authority=%s" % (__SERVER, authority))
    # ユーザ操作
    userop(browser)
    # レスポンスパラメータを取得
    # example.com/path?foo=1&bar=x => {"foo":["1"], "bar":["x"]}
    url = browser.geturl()
    data = cgi.parse_qs(urlparse.urlparse(url).query)
    return data


これはこれで Twitter を使った OAuth のテストができるので問題ありません。問題は、Twitter に問題があると、テストが FAILすることです。それは Twitter の問題であって、私が開発しているアプリケーションの問題ではありません。
なので、OAuth が成功した or 失敗したふりをしてくれるモックが必要です。それが次の課題だろうな、と考えています。


おわりに


何をしようとしているかというと、継続的インテグレーションをしたいのです。サーバサイドのエンジニアが少ないので、できるだけ自動化したいわけです。人間は創造的なことに時間を使うべきだ、と個人的に信じています(それでまあ、例の対談とかの流れになるわけです)。そのためには自動的に環境構築して、テストできるようにする必要があるのですが、ずーっと止まっていました。今回の温泉で、一歩目を踏み出せたのは大きな収穫でした。

おまけ


35歳ごろ(定年だというのに)ソフトウェア開発業界に入りたい、と思うようになりました。このとき、Python Code Reading で発表 → BPStudy に行く → Python 温泉に行く → 顔を知ってもらう → どっかの会社に潜り込む、という戦術を妄想していました。そして、37歳にしてやっと Python 温泉に行けました。順番がずれていますが、自分の人生で計画通りにいったことなどないので、自分にしては上出来です。

2011-11-13

python の mock ライブラリを使ってみる


最近 mock ライブラリを使うようになりました。

能書き

(ここは単体テストとモックの意義が分かってる人には、価値ゼロです。)

単体テストというのは、本来、あるコンポーネントの依存先に影響されないように、対象をテストします。が、これまでは比較的てきとーで、依存先のテストが通っていれば、依存先を完全に分離せずにやってきました。

これには2つ問題があって、(1) そもそもそれは単体テストではない、(2) 依存先が外部だったらどうするんだ、と。例えば、Twitter からタイムラインをとってくる、とかですね。

想定するテスト対象

def get_user_timeline(user)
    """タイムラインをとってきて、辞書で返す"""
    twitter = Twitter()
    response = twitter.get_timeline(user.id, user.access_token)
    timeline = [{'text': tweet.text} for tweet in response['tweets']]
    return timeline

辞書にレンダリングを分離すべきとかありますが、いまは Twitter から取ってくる箇所だけ考えます。

テストの度に本当に Twitter にアクセスしていては単体テストになりません。ってなわけで、 mock ライブラリを使います。

インストール

easy_install mock 

で、おk。

Mock クラス

Mock のインスタンスのプロパティは適当にやってくれます。事前に定義する必要はありません(定義することはできます)。しかも foo.bar にアクセスするとき、いつも同じオブジェクトが返されます。


>>> import mock
>>> x = mock.Mock()
>>> x.foo
>>> x.foo
>>> x.foo == x.foo
True

Mock インスタンスは常に呼び出し可能です。

>>> y = mock.Mock()
>>> y()

>>> y.hoge()


テストを書いてみる


get_user_timeline 関数の仕事は、(1) 引数 user を twitter.get_timeline() に渡すこと、(2) その戻り値から辞書を作成すること、です。user オブジェクトの作成や get_timeline() で何が起こっているかは、別の単体テストでやることです。なので、 user と get_timeline はモックにしちゃいましょう。

任意のモジュール内の、任意のクラスのメソッドを入れ替えるときは、patch 関数を使います。

class GetUserTimelineTest(unittest.TestCase):
    def test(self):
        # モックのコンテクストで実行
        # 動作を変えたいメソッドを文字列で指定する。
        with mock.patch('mymodule.Twitter.get_timeline') as m:
            # レスポンスで使うダミーの tweet を作る
            tweet0 = mock.Mock()
            tweet1 = mock.Mock()
            # get_timelineメソッドを呼び出したときの戻り値をモック
            m.return_value = {"tweets":[tweet0, tweet1]}
           
            # 引数で渡すオブジェクトもモックにする
            user = mock.Mock()

            # 単体テスト対象関数呼び出し
            result = get_user_timeline(user)

            # モックが1度呼ばれていることを確認
            self.assertEqual(m.call_count, 1)
            # モックが呼ばれたときの引数を確認
            self.assertEqual(m.call_args,
                             ((user.id, user.access_token),{}))
            # 戻り値の確認
            self.assertEqual(len(result), 2)
            self.assertEqual(result[0]['text'], tweet0.text)


わかりにくいなぁ。もっと時間かけて丁寧に書かないと、知らない人には伝えられない気がしてきた。

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 かなぁ…。

2011-09-24

Google App Engine の backends インスタンスをデータベース的に使おうとしてみた

ウィルスとかフィッシングとかにやられる奴ってなんなの? ひっかかる努力をしないと、ひっかからないだろあんなもん、と思っていた時期が私にもありました。まんまとフィッシングに引っかかった情弱です。

現実を見たくなかったので、Google App Engine の backends インスタンスを、DB サーバ的に使えないかなぁと、試してみました。単純なケースでは、パフォーマンス的にも、コスト的にもあまりメリットがないです。

ソースコードは https://github.com/torufurukawa/backendtest に置いてあります。

コード

backends.yaml
backends:
- name: memdb
  start: memdb.py
「memdb」という名前で識別するインスタンスで、起動時には memdb.py を見るように設定します。

memdb.py
from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
DATA = {}
class InitializeHandler(webapp.RequestHandler):
    def get(self):
        global DATA
        DATA['foo'] = 'foo'
application = webapp.WSGIApplication([('/_ah/start', InitializeHandler)], debug=True)
util.run_wsgi_app(application)

memdb インスタンスが起動するときに呼ばれる /_ah/start に対してハンドラを設定します。ここでは動作確認用に、DATA 辞書をちょっといじっています。

memdb モジュールの DATA 辞書を、 Key-Value ストアとして使うことにします。

まずデータを書き込むとき用のハンドラ群。

main.py (抜粋)

MEMDB_BACKEND_ID='memdb'
MEMDB_HOSTNAME=backends.get_hostname(MEMDB_BACKEND_ID)
DATA='spam'*10 
def stop_watch(op_name):
    """ロギングと共通レスポンス用のデコレータ。
    関数を実行して、実行時間をログとレスポンスに書き出す。
    """
    def outer(func):
        def wrapper(self):
            start_at=time.time()
            func(self)
            end_at=time.time()
            log='[%s] %s'%(op_name,end_at-start_at)
            logging.info(log)
            self.response.out.write(log)
        return wrapper
    return outer

class BackendWriteHandler(webapp.RequestHandler):
    @stop_watch('backend:write')
    def get(self):
        hostname=MEMDB_HOSTNAME
        response=fetch('http://%s/memdb/set/%s/%s'%(hostname,get_key_name(),DATA))
class MemdbSetHandler(webapp.RequestHandler):
    """/memdb/set/(.+)?/(.+) で呼ばれるハンドラ"""
    d ef get(self,key,value):
        importmemdb
        memdb.DATA[key]=value
        self.response.out.write(value)
def get_key_name():
    return''.join([random.choice('abcdefghijklmnopqrstuvwzyz')for i inrange(10)])


外部から、通常のインスタンス上 の BackendWriteHandler の get が呼ばれます。ランダムに作ったキーと定数値を指定して、memdb インスタンスの /memdb/set/<key>/<value> を GET します。

memdb インスタンスのハンドラが、MemdbSethandler です。モジュールグローバルの DATA 辞書を、key と value で更新します。これで memdb 上の値の更新ができます。

続いて、呼び出しです。main.py から抜粋。

class BackendReadHandler(webapp.RequestHandler):
    @stop_watch('backend:read')
    def get(self):
        hostname = MEMDB_HOSTNAME
        response = fetch('http://%s/memdb/get/%s' % (hostname, get_key_name()))
        data = response.content
class MemdbGetHandler(webapp.RequestHandler):
    """/memdb/set/(.+)?/(.+) で呼ばれるハンドラ"""
    def get(self, key):
        import memdb
        value = memdb.DATA.get(key)
        self.response.out.write(value)
外部からアプリの通常のインスタンスへは、BackendReadHandler が呼ばれます。memdb インスタンスに対して /memdb/get/<key> を呼び出します。

memdb のハンドラは MemdbGetHandler で、DATA 辞書から値を取り出して返す、というものです。

実行してみた

Datastore、Memcache、backends で読み書きをしてみました。きちんとやってなくてさーせん。なんどかブラウザからちょこちょこ URL にアクセスして、安定していたあたりの10件の平均時間 [ミリ秒] と標準偏差です。

Storage     Write     Read
Datastore     14±1     4±0
Mecache     2±0     2±0
Backend     49±83     87±88

かなり遅いのと、所要時間がえらく不確定です。アクセスの感覚もゆっくりとったので、Datastore よりも早いんじゃないのかくらいの期待をしていたのですが、ぜんぜんです。

コストのほうを計算してみました。

100ミリ秒(0.1秒)ごとに読み書きのいずれかが発生するとしましょう。楽観的に考えて、スパイクはなし、と。
そうすると、1ヶ月での読み書き回数は、
0.1 [秒] x 3600 [秒/時間] x 24 [時間/日] x 30 [日/月] = 25,920,000 [回/月]

デフォルトの B2 クラスのインスタンスの1ヶ月の使用料は、
0.16 [ドル/時間] x 24 [時間/日] x 30 [日/月] = 115 [ドル/月]
です。

同じ回数のアクセスを、Datastore に対して行うと、
書き込みのみ費用 = 0.01 [ドル/10k回] x 25,920,000 [回/月] = 26 [ドル/月]
読み込みのみ費用 = 0.07 [ドル/10k回] x 25,920,000 [回/月] = 181 [ドル/月]

読み込みだけ発生すれば、backends のほうが安いです。が、実際にはそんなわけないですしねぇ。うーむ。

とは言え…

Datastore から複数のエンティティを取得すると、その分読み込み回数のカウントが増えます。アプリによっては、それを backendsではうまくハンドルできるかも知れません。memcache のデータは揮発する可能性がありますが、backends はメモリに気をつけていれば datastore ほどではないにしてもなんちゃって永続化できます(そのあたりの監視や処理にCPU時間が必要でしょうけど)。

なので、この使い方がすなわちダメではないんでしょうが、個人的にもっとあからさまな速度差や、コスト差が出るのかなぁと妄想していたので、少しざんねんです。今日は残念な日なのでしょう。

2011-09-19

半島を出よ探訪

村上龍の「半島を出よ」の舞台あたりに行く機会がありました。えーっと博多のほうですよ。



ホテルから野営地までは、団体出入り口から外に出て四車線の広い道路を渡るのが最短の道筋だ。しかし高麗遠征軍制圧区域に出入りする車はほとんどないので、道路はまるで夜の平壌のように換算としている。狙撃の危険があるわけではないのだが、車の行き来も遮蔽物もまったくない幅三十メートルの道路を横断するのは気持ちが悪かった。チェ・ヒョイルは、ホテルから野営地に戻るときはいつも福岡ドームの正面ゲートのほうから迂回することにしていた。


ホテルから野営地のモデルとなる広場を見た様子。前の道路が、チェ・ヒョイルが横断するのをためらっている道路。右側は、まっすぐ伸びていってて、遮蔽物はまったくない。正面に見える大きな建物が、九州医療センター。

作品では、開発がとまってこの広場は放置されていた、と書かれています。若干手入れされていますが、それほど積極的に使われていないようです。ヒルトンと病院の間のこの空間「どーすんだよ、ここw」みたいな感じになっているのかも知れません。




左のほうは道が右にゆっくり曲っていってきます。チェ・ヒョイルはこちらのほうから、迂回して渡っている。カラフルな建物群がホークスタウンで、物語の中では、高麗遠征軍による広場の制圧後は、営業していない、という設定。


なるほど、というようにチェ・ヒョイルは真剣な顔を崩さずにうなずき、橋を指差して、ずいぶん長い橋ですね、と言った。 [...] こんなに眩しいのにどうしてブラインドを下ろさないのだろうか。チェ・ヒョイルがそのことを不審に思ったとき、突然窓ガラスが割れ、外の空気が流れこんできた。チェ・ヒョイルが思わず身をかがめたとき、缶入りコーヒーに似た黒い金属の筒が前方の床に転がった。

観月橋から、岸の方を見た様子。右側の2階建ての建物に、戦闘劇の現場になったレストランが入っています。窓は南向きです。左側の岸の向こうのほうに、駐車場の車が見えます。

岸にいると、バルコニーに誰かが隠れているかどうかは、「誰かがいるんじゃないのか?」っていうつもりで覗きこまないと見えません。池にかかっている橋まできて、やっと見えます。

海からの風で煙がこちら側に漂ってきて、ホテルの根元の黒い穴が露になり、キム・ヒャンモクは奇妙な感覚にとらわれた。自分のからだが左に傾いていくような感じがしたのだ。視界が右に傾いていくようだった。カタツムリのような形をしたガラス張りの建築物が粉々になり、ガラスの細かな破片が飛び散るのを見て、ホテルが右に倒れているのだと気づいた。ナイフのよう形を保ったままホテルはゆっくりと傾いていき、地鳴りのような音と猛烈な風が起こった。 



作中でキム・ヒャンモクがいた病院から道をはさんだ右側、ホークスタウンからの様子です。左から、ヒルトン福岡シーホーク、同じく4Fのアトリウム(作中/当時ラグナグ)、ヤフードーム(作中/当時福岡ドーム)です。

こんなものが倒壊したら、ひどいもんです。この写真をとっていた場所や、すぐ近くの野営地にいたら、爆風と飛んでくる瓦礫でアウトでしょう。作中では実際アウトだったわけですが。

そのうち他の写真もまとめるかも。

2011-09-11

Google App Engine memcache cas (compare and set)

Google App Engine SDK 1.5.3 で memcache cas (Compare and Set) 操作ができるようにななりました。今更ですが。 GvR のブログでも丁寧に解説されています。

で、どんなときに使おうっかなぁと思うわけです。データが揮発してよい、クライアント間のデータ共有の遅延を遅らせたい、整合性は保つ、みたいなときに意味があるのでしょうか。クライアントが自身のID を渡してサービスを呼び出し、最後に呼び出して一定時間以内のクライアント数を数える、みたいな機能とか。


と、思ったんですが、cas のリトライに失敗したときにどうしましょうっかねぇ。どっか、別のエラーの回数を incr() しといて、エラー率とアクセス数を関連付けるとかでしょうか。むずい。


2011-09-11 22:43 追記: シャーディングで、ひとつのキーに集中しないようにする、というのは大前提で。

2011-09-04

Eclipse と virtualenv で App Engine アプリを開発する設定


Emacs で Google App Engine 触っているのですが、もういい加減に IDE 使おうと思いました。Wing IDE、Komodo IDE、PyCharm あたりを考えたのですが、(ここでビール3杯をはさんだのでロジックは忘れたが) Eclipse だろうとなりました。で、ちょっとはまったので、メモです。

前提

  • Mac OS X (Lion)
  • Google App Engine Launcher を使う。
  • プロジェクトごとに virtualenv 環境を用意する。
インストール手順

  1.  (したければ)Python をインストールする。(homebrew の 2.7)
  2. App Engine Launcher をインストールする。(1.5.3)
  3. Eclipse をインストールする。(3.7.0)
  4. PyDev をインストールする。(2.2.2.2011082312)

プロジェクト作成手順

  1. [File]-[New]-[Other…]- [PyDev の下の PyDev Google App engine Project] を選択して [Next]
  2. [User default] のチェックをはずす
  3. Directory にソースコードがあるディレクトリを指定 e.g. /Users/bucho/show
  4. Interpreter が空欄なので「Click here to configure an interpreter not listed」 をクリック
  5. [New] をクリックして適当な名前にする。e.g. python2.5@show
  6. PYTHONPATH に追加すべきディレクトリが出てきます。デフォルトでチェックがはいっている項目に加えて、virtualenv の Python がもともと参照している /lib ディレクトリにもチェックします。例えば homebrew でインストールした Python 2.7 なら、/user/local/Celler/python/2.7/lib/python2.7 とか。(2011-09-04 追記)
    いろいろと PYTHONPATH に追加すべきディレクトリが出てくるけどデフォルトでOK。
  7. ターミナルで、echo $PATH した値をコピー
  8. [Environment] タブをクリック、New ボタンをクリックして、Name に「PATH」、Value 欄にさっきのをペースト。(私の環境で必要だったのは /usr/local/bin です。めんどいので全部ペーストしました)。
  9. [Finish]ボタンをクリック
  10. App engine の場所を聞いてくるので「/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine」 を指定する。
  11.  [Django0.97] にチェック。
補完が...

これでいちおう動くんだけど、標準ライブラリの import 文なんかを、resolve できない。たとえば import unittest だと、下みたいになります。



コードは動くけど、補完してくれなくて、それじゃあ何のために IDE 使ってんだよって話です(Emacs でも補完とかできるんでしょうけど、.emacs とか無理っす)。

  1. [Eclipse]-[環境設定]-[PyDev]-[Interpreter]-[Python] を選択
  2. インタプリタ設定を選択してから、「Forced builtin」を選択。
  3. [New] ボタン → 「unittest」と入力する。

これが正しいやりかたなのか、まったく分からないわけですが、とりあえずこれで様子見です。

2011-09-01

バスキュール号に入社しました

@shin_no_suke
どういうことなの? (hg)changeset:   164:aedce6151bf9 user:        Shinya Okano<...> date:        Wed Aug 31 17:23:39 2011 summary:     buchoはオワコン
( http://twitter.com/#!/shin_no_suke/status/108831096287920128 より)


昨日で株式会社ビープラウドを退職し、本日より株式会社バスキュール号で働きます。

なんで辞めるの?と聞かれるのですが、因果関係としては、ビープラウドを辞めるからバスキュール号に行くのではなくて、バスキュール号に行くのでビープラウドを離れます。

以前、マーケティングをかじっていたことがあります。プロモーションのためのシステムを開発するという、バスキュール号のビジネスに関わりたいと思うようになりました。サーバサイドのエンジニアとしての採用ですが、企画にも口を出してよいということなので、そういう現場で働いてみたいという思いです。別に尻文字を間近で見たいからではありませんよ。見たいですけど。

業務としてのソフトウェア開発未経験の私を拾ってくれて、ここまで育ててくれたビープラウドには感謝しています。技術的なスキルも、その他のスキルもまだまだビープラウドで学ぶ余地はいっぱいあるので、そこはもったいないなぁと思います。また、ちゃんとバリューを出せていたのだろうか、と憂うことなど多々あります。が、気に病んでいても私の価値は変わりませんし、私にとってのベストをつくしたので、評価はビープラウドのメンバーとそのお客様に任せるということで。

仕事では当分 Python を使い続けますし、仕事におけるタスク自体は似ていますし、公私共にソーシャルグラフは大きく変わることはないので、これまでどおり、自分が出せるバリューを出していく所存でございます。

そうそう、ビープラウドバスキュール号も、Pythonista を探しています。

2011-07-31

バグの影響を伝播させないアプローチ - Michael T. Nygard / Release It!

なんとか It! シリーズのひとつ「Release It! 」を読みました。


航空券予約システムをダウンさせたバグの経験から始まります。
極論を言うと、この種のバグがひとつ残らず消滅するなんて夢物語にすぎない。 バグは発生する。消えてくれないなら共存するしかない。 [...] この例で最悪だったのは、ひとつのシステムのバグが全システムに伝播して影響したことだ。答えるべき問いは、「ひとつのシステムのバグが他に影響するのをどうやって防ぐか?」である。 (p.21)

バグの影響を伝播させないシステムの作り方、というのが、この本の「本番用システム」のあるべき姿です。その中からいくつかピックアップ。

本番と同じサイズのデータセットでないと、クエリで数百万行が返っ てきて、それをオブジェクトに変換するときに何か起きるか確かめることかできない。本番と同じサイズのテストデータを使えば、パフォーマンステストでより良い情報が得られるという副次的な効果もある。 (p.93)

ダミーでいいから量をある程度用意するのはよいことだと思う。いまやっているプロジェクトでは、想定ユーザ数と平均的なユーザの典型的な行動パターン(つまり各URLのアクセス頻度)のシナリオを用意してもらえた。まだ調整中なのだけれど、どこがボトルネックになっているのか分かるし、優先順位も明らかになっている。

待たせた結果がエラーメッセージであってはならないし、相手のタイムアウトまで待たせてもいけない。それでは、こちらの問題を呼び出し側に転嫁するだけだ。 (p.116)

フェイルファストっていうのは、なんか努力せずに諦めたみたいな感じがするんだけど、応答が遅い状態が長く続くくらいなら、さっさとフェイルして、アラートメールを出したほうがいい。どうせアラートメール出すハメになるのだから。

開発プロジェクトか大忙しのときに、こうした設計についてのトピックを意識するのは難しいかもしれない。[...] そんなあなたにいいニュースかある。これらの問題を開発中に取り上げるのは断念してもいい。ただし、これは悪いニュースでもある。開発中に収り上げないなら、 本番で取り組むことになるだろう。 (p.235)

開発開始段階ですべての機能/非機能要件が決定されていない(つまりほとんどの)場合、最適な設計も最初に決定できないということになる。少し設計して、作って、また設計して、の繰り返しになるので、設計コストは増加する。そのかわり不適切な設計のままつっぱしって、システムがダウンするリスクを避けられる。

今やっている案件でできていることは続けたいし、できていないことは今後やっていきたいものです。


2011-07-11

データではなくメソッドを学ぶ - 森博嗣「科学的とはどういう意味か」

「信仰は科学です( ー`дー´)キリッ」みたいなことを普段言っている割には、かなりな勢いで直感でものを考えているふるかわです、こんばんは。

駅の本屋さんでふと目に止まって、森博嗣の「科学的とはどういう意味か」を買いました。

人間にとって「気持ち」の影響力は大きい。けれど、いくら感動しても、いくら泣いても、飢えている人を救うことはできない。いくら一時の笑顔があっても、それは「解決」ではない。 (p.12)
当たり前のことですが、つい忘れてしまいますね。

こんなに大げさな話ではなくても、問題の解決にまったく寄与しないことが、自明であるにも関わらずやってしまうことってあります。目下の課題とまったく関係のない質問、誰にも聞かれていないし、役にもたたない発言など。そういうことに自覚的でありたいと思います。

学科で教わることには、以上のように2種類ある。きっちりと分かれてるものではないけれど、大別すると、「データ(情報)」と「メソッド(方法)」だ。[...] これに対して、後者は、それらの材料を用いて加工する「方法」を憶えることになる。算数や数学というのは、一言でいえば「方法」なのである。 (p.35)
 「大学の研究室でやった研究で仕事してる奴なんか、ほとんどおらんで。ここは方法論を教えるところやから」と言ったのは、私が所属した研究室の先生の言葉でした。私は物覚えが悪いので、テストは常にひどかったですし、いまでもひどいです。


仕方なく方法論を身につけようと思いました。なので、何か新しいことを知ったら、とくにそれが方法論であったり、解釈の仕方であったりすると、「この方法を違う問題に当てはめられないか」と考えます。いつも、ではないですが、そういうふうに考える傾向があります。


それがどのくらい有効なのかよく分かりませんが、少なくとも私には役に立つ考え方であり、アプローチです。


と、まあ、当たり前なことを、ドヤ顔で書いてみました。「こんなこと役に立つのかよ」って思い悩む、高校時代のときから余裕でプログラミングとかできている学生の役に立てば、と思いました。


2011-07-10

大阪国際トライアスロン 2011

大阪市此花区の埋立地であるところの舞洲(まいしま)で開催された、大阪国際トライアスロン舞洲大会に出ました。別にワールドカップとかではないです。

てめー、この仕事の立て込んでるときに、と、言われそうではあるのですが、トライアスロンしなければ、きっと飲んだくれてるだけでしたので、まだ、マシであったと思っていただければ。スイム750m、バイク23km、ラン5km の短い距離でもありますし。

スイムは大阪湾を泳ぎます。東京湾を泳いだこともあるし、汚いといっても知れているだろう、そう思っていた時期が私にもありました。ビニール袋が打ち上げられていると思ったら、鯛の屍骸でした。往復750mのコースを、岸と並行に泳ぐコースです。

バイクは4周回で、23km。大井埠頭みたいなもんだろうから、大したことないだろう、そう思っていた頃が(以下略)。夢舞大橋という橋を往復するのですが、これがまあ坂です。しかも可動橋なので道路のつなぎ目が大きい段差になっていて、バランスをくずしやすいのです。バイクが下手な私は、下りのスビードをあげられませんでした。橋以外はコーナーが続き、こちらもスピード出しにくいです。アウターギアつかってない。

埋立地のランなんてフラットだから楽勝だろう、そう思っていた頃(ry 。岸に沿った歩道を走るのですが、津波対策なのか、そういう景観狙いなのか、埋めたての土砂が余っていたのか、結構アップダウンがあります。ただ、変則片道2.5km のコースには、給水ポイントがわりとあります。500m ごとは言い過ぎかもですが、1kmよりは短い間隔で給水があります。ただし、水だけ。全体的を通して、日を遮る箇所がほとんどなく、かなり暑いです。

JTU 公認のエリート部門もあるので、スタッフはたくさんいます。大阪の都市部でのレースの手軽さを求めるならよいかも。ちなみに電車は通っていません。オリンピック誘致できてたら、通ってたかもですね。

そんなレースでした。

2011-06-12

節度をもって変化を抱擁する - アラン・デービス「成功する要求仕様 失敗する要求仕様」

今の職場に入ったとき、初めてアサインされた仕事が要件仕様の定義でした。そのときに何冊か買った本のうちの、ひとつが、アラン・デービスの「成功する要求仕様 失敗する要求仕様」です。原題が「Just Enough Requirements Management - Where Software Development Meets Marketing」であるということに、今、気づきました。

ケビン・フォースバーグとハロルド・ムーズによるNASAプロジェクト の研究によれば、NASA(米航空宇宙局)が要求活動を含む計画活動に開発予算の5パーセント以下しか充てなかったプロジェクトでは、総コストは40~170パーセント超過した(図1-11)。一方、NASAが全開発予算の10~20パーセントを計画活動に充てたプロジェクトでは、コスト超過は30パーセント未満だった。 (p.21)

計画活動に時間をかけると、コスト超過が起こりにくい。デービスは他のところでも、この例を挙げて、計画に時間をかけるように勧めています。その最初の段階での成果物が、要件仕様、というわけです。

私は、スケジュールが要求を決めるべきだと強く感じている。 (p.23)

最大の不足するリソースは時間だということを考えれば、必然的にこういう方針になりますね。時間は貯めたり、借りたり、増やしたりといったことができない、とドラッカーが指摘していたと思います。もちろん、時間を節約するために、ヒト、モノ、カネを調達することはできるでしょうけれど、時間自体を増やすことはできません。

どれだけ徹底的に導き出しを行っても、要求は変わっていくものだ。問題は変化していく。そして、問題に対する ステークホルダーの認識も変わっていく。要求を洗練させ、議論するうち、ステークホルダーは新しい要求をさらに思いつくはずだ。それでいいのである。ただ、変化に対する備えはしておこう。決してステークホルダーに「それで、これが最終的な要求なんですね?」などと言ってはならない。  (p.55)

耳が痛いです。最後の要求なのか、とつい聞いてしまいます。

一方で、要求を決めないと、設計や実装ができません。だから、多くのアジャイルなプロセスでは、タイムボックスを区切って、その間は仕様変更をしないというプラクティスがあるわけです。

しかし、約束してもいい。スピードと品質は両立できないものなのだ。  (p.67)

これは心しておかないと。そして、それを痛感するような経験もあります。

  • 要求への変更は、善であり、悪ではない。 
  • 要求変更の流れを阻止しようとしてはならない。 
  • 要求変更の流れを管理できるようにならなくてはいけない。 
  • どの要求を次のリリースに含め、どの要求を含めないかを決定するために、定期的にミーティングを開く。 
  • 10パーセント以上の要求変更を受け入れれば、プロジェクトは失敗するだろう。 
(p.209)

「変化を抱擁せよ。ただし、抱擁できる範囲で。」ということです。その手段として、さまざまなやり方があり、アジャイルな手法はこの前提をもっているように見えます。ですが、デービスはアジャイルがいい悪い、という見方ではなくて、要求のトリアージができるプロセスに、という立場をとっているようです。


2011-06-05

構成管理をちまちま始めるヒント集 - 「パターンによるソフトウェア構成管理」バーチャック、アップルトン

構成管理というは、開発プロセス全域に影響があるので、変化を起こすときに大変な思いをすることがあるんじゃなかな、と思います。プロジェクトの途中で、しかも、自分が下っ端だったり、いろんな事情で発言力が弱いときだってあると思います。どっかのこわい話とか、バージョン管理システムについて  の話題とか。

そんなときに、この本のプラクティスをちょっとずつ実践することで、ちょっとずつプロセスに変化ができて、その変化に慣性がついてくれば、大きな変化になるのかも知れません。幸か不幸か、私の職場は mercurial + trac 、最近は redmine ですし、多少ラディカルな変化も受け入れやすい体質なので、よく分かりませんが。

すべての変更(とその依存関係)が、集約化された統合ビルドプロセス によってビルドされるかどうかを確認して下さい。 p.114

Python のウェブアプリの場合は、本番環境へのデプロイも含まないといけませんね。実行時まで分からないことが多いというのは、こういうとき割と不便です。

新しいメジャーリリース用にコードラインを分岐する時が来たら、前回のリリースラインから新しいリリースラインを分岐する代わりに、まず前回のリ リースラインを本流にマージして下さい。そこから新しいリリースラインを分岐してドさい。 p.62

まず、現行最新版の変更を、メインの開発作業に取り込め、ということですね。

バージョン管理システムを使って、ベンダから受け取ったソフトウェアのバージョンと、顧客に配布したソフトウェアの両方を管理して下さい。 p.123

どっかから拾ってきたライブラリを使用するときには、(1) 本家から引っ張ってきたコードを保持するブランチ/リポジトリ、と (2) そのコードを自分で変更したコードを保持するブランチ/リポジトリで管理せよ、ということです。本家でバージョンアップがあったら(1) に取り込み、つづいて、(1) を (2) に取り込む、ということです。

フレームワークが大きくバージョンアップするような場合には、そうはいきませんが、それはまあアプリケーションからみたら、プラットフォームが変更されるということなので、ここでの議論の対象外です。

それぞれのビルドをすべてスモークテストの対象とし、アプリケーショ ンがあまりにも明らかな形で動かなくなることがないように検証しましょう。 p.145

スモークテストというのは、機械の箱を開けて修理したあとに、電源を入れたときに煙が出ないか確認する、というのが語源です。つまり、よっぽどひどいコード変更をしていないのか、というののテスト。

API を提供するサーバ開発と、JavaScript や Flash で書かれたクライアント開発が、別組織になっているときのデバッグ中に使いました。各 API 個別のテストはもちろん書いてあるのですが、それとは別に、ちょっとした変更を依頼されたときに、スモークテストを走らせていました。

回帰テストのテストケースは、以下の項日から構成するとよいでしょう。
- リリース前の品質保証プロセスで見つかった問題。
- 顧客およびユーザから報告された問題。
- 要求什様に雌づくシステムレベルテスト。 
p.158

(テストがないという意味での)レガシーコードを触るときに、これやりますね。まず fail するテスト書いて、修正して、pass させる。それを回帰テスト(ユニットテストに含めるにしろ、より上位レベルのテストになるにせよ)として残す、と。レガシーコードをいじるときには、いきなり全部のテストを書くわけにもいかないので、変更/修正する箇所から段階的に追加していくことになるかと。

リリース済みバージョンと次回リリースの開発を同じコードラインで対応しようとするのではなく、保守用と継続開発用でコードラインを分割しましょう。 p.171

よい子のみんなは、きっとやっているよね ♡