表題は「404 Blog Not Found」のまね。
まだ最初しか読んでいないけれど、ご紹介。
あと、前回のエントリでは大きいことを書きすぎて恥ずかしくなったので削除した。

今回はRuby ベストプラクティスという本のご紹介。

この本には、筆者 (Gregory Brown さん) 流の開発方法や考え方の
エッセンスが詰まっている。
(と、思う。)
対象者は、Ruby の中級者以上。
大体、1000ステップ以上の Ruby プログラムを 1から作成した事の
ある人ならば、簡単に読めるだろう。

ただ、誤解を与えないために一応書いておくと、
ここで言う「簡単に読める」とは「簡単に身につく」ではない。
読む事は簡単だけれども、自分の血肉にする為には実践が必要だと思う。

また、本書には小手先のテクニックはあまり書いていない。
「監訳者のあとがき」で高橋征義さんは、
「あなたはあなたのスタイルでコードをかくべきだ。
 そんなあなたの、あなたらしいスタイルを確立するために、
 本書が役立てば幸いである」
とおっしゃっている。
本書は、そういう本だ。

ある意味、「仕事のやり方」みたいな How to 本に近いかもしれない。
自分の能力を底上げしてくれる本だ。

本書には、筆者の過去ソフトウェア開発の経緯とその時の考えを
時系列で記載してある。
1. イントロ + 前提知識の簡単な紹介
2. こういうプログラムを作ろうと思った
3. とりあえず、こうしてみた + コード例
4. ここで、こう思った。だから、次にこうした

という流れの繰り返し。
これにより、筆者のソフトウェア開発を追体験する事ができる。

例えば、1章の TDD (テスト駆動開発) の前半は、

1. Unit Test や TDD の簡単な紹介
2. Prawn というソフトウェア開発で、パーサーを作ろうと思った
3. まず、パースの必要が無い文字列を入力した際の仕様とそのテストを作成
4. 作成したテストが通る簡単なメソッドを作成
5. 同様に、順次仕様とテストを追加し、それを通るようにメソッドを拡張
6. 途中でメーリングリストに投稿してみた
   テストも一緒に投稿すると、意図がちゃんと伝わる
7. 作業を続けていると、最初に作成したテストが通らなくなった
   でも、これは仕様を変更した事が原因
   テストを修正した
8. module にした方がいいと思ってリファクタリングした

という流れ。

ちなみに、実は原著(英語版)は Web 上に公開されていたりする。
http://blog.rubybestpractices.com/posts/gregory/022-rbp-now-open.html

でも、この本に限って個人的な見解を述べると、
日本人は日本語に訳された本書を読んだほうが良いと思う。

なぜなら、日本語の方が簡単に読めるから。
普段から英語のドキュメントを読みなれている人でも、日本語の方が
もっと楽に読めると思う。

最初に書いたけれど、本書を読む事は簡単。
むしろ実践する事に意義がある。
力の入れどころを間違えてはいけない。
私自身は、仕事中の休憩とか、息抜きの際に読んでいる。

私のような凡人には、上級者からのレクチャーによる経験地アップに、
上級者には、他人の考え方を知るになると思う。
ぜひ、皆さんも読んでみてください。おまけせっかく Ruby の TDD が書いてあったので、ネットワーク越しの通信を行うプログラムのテストにつかうモックやスタブをうまく(汎用的に)作る事ができないか考えてみた。Ruby で記載したプログラムも、内部的には OS の動的ライブラリを使用しているはずなので、「そのライブラリに手を加えればうまくいくかな」と。調べた事・Linux で環境変数 LD_PRELOAD で優先的に使用する動的ライブラリを指定できる・dlsym(RTLD_NEXT, "シンボル名") を実行すると、  次の優先順位のシンボル(関数)を実行できる例) read 実行時に、通常は read(2) を、標準入出力から読み込む場合は    "Hallo World!" と出力するライブラリの作成方法
$ cat my_read.c#define _GNU_SOURCE#include #include #include // オリジナルの read(2) を read_org というシンボルで呼び出せるようにするstatic ssize_t (*read_org)(int fd, void *buf, size_t count);void __attribute__((constructor))init_read_org(){    read_org = dlsym(RTLD_NEXT, "read");}// read を書き換えるssize_t read(int fd, void *buf, size_t count){    if( fd > 2 ){        // 標準入出力以外 (fd > 2) ならば通常の read(2) を実行        return (*read_org)(fd, buf, count);    }else{        // 標準入出力から読み込む場合は "Hallo World!" と出力        printf("Hallo World!\n");        return (ssize_t)0;    }}$ gcc -shared -fPIC -o my_read.so my_read.c -ldl && \  LD_PRELOAD=./my_read.so 
「これで read(2) や write(2) をフックすれば、ネットワーク越しの  ソケット通信時だけ別の挙動を示すプログラムが作れる」と思った。が、ファイルディスクリプタが指す物がソケットかどうか確認する方法がわからなかった。
とりあえず、週 1回は blog を書こうと思ったのもつかの間。
前回からあっという間に 1ヶ月がすぎてしまった。

とりあえず、gmail にログインしたら Buzz という物ができて、
Python についても少しずつ分かってきて、
書きたい事は盛りだくさん。

もうすぐ、東京 Ruby 会議 03 もあるし。

そんな事はさておき、本日は rsync の話。

rsync と言えば、linux の差分バックアップコマンド。
主な特徴は以下
  1. ssh を通して、リモートのマシンにもバックアップ (コピー) 可能
  2. 以前のバックアップ結果と比較して、新しいファイルのみコピーする事が可能
     当然、前回のバックアップ時から消されたファイルを削除する事も出来る
  3. Linux のハードリンクを使用することで、複数のバージョンの
     バックアップをとっても、更新されていないファイルは HDD 上に 1個だけに
     する事が可能
     (ディスク容量が少なくて済む)
  4. ファイルのバイナリ差分を取ることで、大きいファイルの更新された部分だけ
     差分バックアップを取ることが可能

端的に言うと、scp と cp コマンドに差分とハードリンク機能を加えたような物。
詳しい事は、rsync で検索してください。
この 2個の記事なんかが詳しい

  ▼ はじめてrsyncを使う方が知っておきたい6つのルール
     http://www.itmedia.co.jp/enterprise/articles/0804/21/news013.html
  ▼ rsyncで差分バックアップを行うための「--link-dest」オプション
     http://www.itmedia.co.jp/enterprise/articles/0804/25/news034.html

さて、上記の特徴だけ見ると良いことだらけに思えるかもしれない。
実際、悪い評判は聞いたことがない。
でも、私は今までこのコマンドを毛嫌いしていた。

そもそも、rsync を使うのはどんな時だろう。
このコマンドは shell に手入力するような物ではない。
そんな時は、scp と cp だけで十分だ。

では、運用中のサーバーで定期的にバックアップを取る時はどうだろう。
これには、信頼性が低い。
rsync は、しょせん shell のコマンドだ。
サーバーやネットワークに異常があれば、すぐにエラーとなる。

堅牢性のためにはストレージ等のハードウェアの機能や、DRBD のような
信頼性の高い機能を使うべき。

費用面で難しければ、詳細なログを書き出すバックアップスクリプトを
作成すればよい。
信頼性が足りない分、せめて詳細なログを残したい。
バイナリ差分についてはハードルが高いかもしれないが、それ以外は
誰でも十分に実装する事が可能だろう。

要は、rsync とは、バックアップという重要な場面において信頼性と利便性が
中途半端なコマンドと認識していた。


ところが、先日、自分の意思には反しつつも rsync を使用した
shell スクリプトを作成する事があった。

スクリプトを作成したら、その後はテスト。
異常発生時の挙動を調べるため、プロセスを kill したくて
ps コマンドにて rsync のプロセス ID を調べた。

すると、複数の rsync プロセスが存在するではないか。
もちろん、rsync は 1回しか実行していない。
おそらく、バックアップ速度を上げるために fork して複数プロセスが
立ち上がったのだろう。

これは便利。
子プロセスの数をどうやって決定しているのかは知らないが、
ほとんどの場面で、バックアップの速度が上がるだろう。

スクリプトで同様の実装をすることも可能だが、自前で fork をすると
テスト工数が極端に増える。
その点、rsync ならば十分にテストされているはずなので、rsync 自体の
テストは不要だろう。

「rsync の利便性が低い」というのは、実は私の勘違いだったのだ。
やっぱり、世間の評判が高い事にはそれなりの理由が有ったのだ。

あと、信頼性はやっぱり低い。
特定の子プロセスを kill したりすると、失敗した。
差分バックアップは 1 回の失敗がその後の全てのバックアップに
影響与えるので、十分に注意する必要はある。
また、Python の話。
Python の仕様を良く知らずに、手元にあった Singleton を参考にしたら
はまってしまった。
(良く考えると、当前の仕様だが。。)

作業開始時の私の Python の知識では、新スタイルを用いた Python のクラスと
インスタンスの関係は以下のようになっていた。

例えば、以下のように MyClass というクラスと、そのインスタンス ins を
作成する場合を考える。
1)  class MyClass(object):
2)      pass
3)
4)  ins = MyClass()

始めに、1、2 行目で MyClass というクラスを作成している。
しかし、このクラス自体が、実は別のクラスのインスタンスである。
(このような「クラスのクラス」を「メタクラス」と言う)

ここでは、__metaclass__ という属性(アトリビュート)を定義していない。
この場合、MyClass のメタクラスはは最初に継承しているクラスのメタクラスと
同じ物になる。
つまり、この場合は MyClass のメタクラスは object のメタクラスである
type となる。
(ちなみに、__metaclass__ が定義されていると、MyClass のメタクラスは
__metaclass__ にバインドされているクラスになる)

次に、4 行目で MyClass のインスタンス ins を作成している。

別の書方をすると、、MyClass() を実行した結果を ins にバインドしている。
MyClass にはメタクラスで実装された __call__ というメソッドがあり、
MyClass()を実行するとは、この __call__ を実行することだ。

__call__ の内部は、始めに MyClass の __new__ を実行する。
(今回は object で実装された __new__ を実行する)
__new__ は Python の object を作成し、返す。(return する)

次に、__call__ は __new__ で返された object の __init__ を実行する。
(今回は object で実装された __init__ を実行する。
実質的に何もしない)

上記のクラス、インスタンスの関係は以下のようになる。
new_style
で、やっと肝心の Singleton の話題に入る。Singleton を実装したければ、以下の事を気を付ければよい。  1. 初めてインスタンスを作成する時は、作成と同時に、作成したインスタンスを保存する  2. 2回目以降は、インスタンスを作成せずに、保存されている物を返答する。これを実装するために、私の参考にしたプログラムは以下のようにしていた。
1)  class MyClass(object):2)      _singleton = None3)4)      def __new__(cls, *argc, **argv):5)        if cls._singleton == None:6)          cls._singleton = object.__new__(cls)7)        return cls._singleton8)9)  ins_a = MyClass()10)  ins_b = MyClass()
2行目は、MyClass 定義の際(ファイル読み込みの際)に 1 回だけ実行され、type のインスタンス MyClass の属性(アトリビュート)となる。9、10行目で MyClass() が実行されると、MyClass.__call__() が実行され、その中で MyClass.__new__(cls, *argc, **argv) が実行される。'cls' という変数は第一引数なので、メッセージを送信されたオブジェクト自身を指す。通常のメソッドでは 'self' と記載することが多いが、「オブジェクトが class である事を想定している」という事を示すために'cls' を用いている。つまり、5行目の cls._singleton とは、MyClass の属性 _singleton を想定している。9行目で実行された時は、cls._singleton は 2行目で初期化されたままなので、None だ。そのため、__new__ の if ブロックの中身が実行される。通常と同じ object.__new__ が実行され、その戻り値を cls._singleton にバインドした後、cls._singleton を返す。つまり、インスタンスの作成と保存を行う。10行目で実行された時は、cls._singleton は None ではない。そのため、__new__ の if ブロックの中身は実行されない。そして、cls._singleton に保存してある、前回と同じ object を返す。こうして、ins_a と ins_b はめでたく同じ object となる。GOF の Singleton が完成だ。しかし、9、10 行目で実行されているのは __new__ だけではない。実は、__init__ も 2回実行されている。長くなったので結論だけ畫くと、Python の入門書には、よく「object の初期化は __init__ で行いましょう」と書かれている。これを鵜呑みにすると、上記のサンプルでは初期化を 2 回行ってしまうのだ。対応方法としては、__new__ の中で object の初期化も行うか、Singleton の実装に別の方法をとるかだろう。(例えば、メタクラスの __call__ を変更する方法もある。)良く考えてみると当然の挙動だが、はまってしまった。普段、Python を書く時は print を用いた行デバッグしか使っていないが、どうしても原因がわからずにとうとうデバッガ(pdb)まで用いてしまった。