『世界は分けてもわからない』 福岡伸一

今、福岡伸一の『世界は分けてもわからない』を読んでいる。
福岡伸一は、何年か前に爆笑問題がやってるNHKの『爆問学問』のゲストで初めて見た。
爆問学問はずっと見てるけど、凄い博識で話しが面白くて、この回は強く印象に残ってる。
この放送を見た後、この本の前著である『生物の無生物のあいだ』を買って読んだ。
非常に面白かった。
この本も前著も、生物学の解説なのにストーリーテリングになってるのが凄い。複線があったりして話が展開してる。

世界は分けてもわからない

PHPカンファレンス2010

PHPカンファレンス2010に行ってきた。
朝から晩までパイプイスに座ってたんで、家に帰ってからも疲れてた。
PerlやPHPのセミナー・勉強会にたまに行くけど、インフラの話が多い。
うちのサイトにはこんだけたくさんのアクセスがあるから、MySQLをレプリケーションさせて、キャッシュサーバを導入して、アプリケーションサーバをスケール展開させて、みたいな話。
今回のPHPカンファレンスもそうだった。
インフラ周りのノウハウ紹介みたいなのもタメになるなとは思うけど、もう少しアーキテクト、システム開発手法についての発表があってもいいのでは?
エンタープライジーな業務アプリを作ってる人、JavaやASP.NETではそういう話題も多いと思う。
例えば、この2ちゃんねるのスレッド「ドメインモデルVSトランザクションスクリプト」なんてエキサイティングで面白い。
こういうのを公開の場で、朝まで生テレビみたいな感じでやれば、かなり盛り上がりそう。
自分は昔・プロレス、今・総合格闘技を見るのが好きなんだけれど、実戦で最強は誰か?というのは定番の話題。
候補に挙がるのは、ブロック・レスナー(現UFCヘビー級王者)、マイク・タイソンアレクサンドル・カレリン(総合格闘技以外のメジャー格闘技の近年最強チャンピオン)、アフリカゾウグリズリー(陸上生物最大級)、ダイオウイカマッコウクジラ(水上生物最大級)、ムツゴロウさん(対野生生物最強)、バラク・オバマ(アメリカ合衆国大統領)あたり。
いろんな観点から語る事が出来、評価する事が出来、議論が尽きないというのが、システム開発手法に通じると思う。

既存ウェブサイトへのアクセス制御機能の追加開発

内容 既存ウェブサイトへのアクセス制御機能の追加開発
期間 2008年6月
OS Linux
言語 PHP
アプリ Apache

あるウェブサイトを運営している制作会社から、サイトに会員制を導入するので特定のディレクトリ下のページを会員のみアクセス出来るようにして欲しいという依頼を受け、そのような機能の追加開発を行いました。
クライアントの都合でコストをかけられないため、簡易ですが十分な機能性のシステムを短期間で開発しました。

通常、ウェブサイトでアクセス制御を行うには、

  • ウェブフレームワークのアクセス制御機能を利用する
  • (ウェブフレームワークを利用していない場合は)ページの初期化ルーチンにアクセス制御の実装を追加する

と思います。
これはサイトがPHP/Perlなど動的ページで構成されている場合で、このサイトの場合、静的なHTMLだけで構成されていました。
本案件では工期・予算的にサイトの全面的な修正は無理なため、Apacheのベーシック認証を利用してはどうか?と提案しましたが、ベーシック認証のダイアログによるUIは嫌で、デザインされたページでログインフォームにIDとパスワードを入力するUIにしたいと言われました。
当該サイトのウェブサーバのシステム環境を調べると、共用レンタルサーバでしたが、PHPの利用及び.htaccessによる設定のオーバーライドは可能でした。
そこで、

  • ApacheのAddTypeで拡張子.htmlをPHPに関連付け、全てのHTMLページへのリクエストをPHPで処理させる
  • PHPのauto_prepend_file機能を使ってPHPによるアクセス制御処理を呼び出す
  • 未ログインのユーザはログインフォームページにリダイレクトし、ログインフォームからIDとパスワードによってログインする

というシステムを提案しました。
会員種別に応じたページだけ参照出来るようにするなどいくつかの要件を盛り込んだ上で、納品したシステムはPHPファイルが2つ、.htaccessファイルが2つ、ログインフォームページのサンプルHTMLファイルが1つでした。
既存のHTMLファイルは一切修正する必要がないようにしました。
新たにアクセス制御したいページが出来た場合でも、HTMLファイルは普通に作成し、そのファイルを置くディレクトリに.htaccessファイルをコピーするだけでよいので、追加開発を依頼する必要はありません。
手離れの良いシステムになったと思います。

この案件のクライアントは以前から知ってる会社で、最初にメールで連絡があり、何度かメール・電話のやり取りで要件定義した後、その日のうちにメールでPDFの発注書を送ってもらい、受注しました。
翌日、数時間かけてプログラムを作り、サーバ設定をし、プログラムはメールで納品しました。
予算の少ない小さなシステムだからどう作っても一緒という事はなく、大きなシステムとは違った工夫や手際の良さを発揮する余地があると思います。
大きなアクセスが見込めないサイト、運営にスキルのあるオペレーターがいるサイト、そういったサイトに過度の機能性を盛り込んでもコストが膨らむだけなわけでしで、必要にして十分なシステムを提案する事を心がけています。

道に片方だけブーツが落ちていた

国道246号線沿いを歩いていると、道に片方だけ女性物のベージュのブーツが落ちていた。
片方だけブーツを置いていくって、どういうシチュエーションだろうか?
しばし、黙考。
片足が裸足、片足がブーツでこの場を去って行ったとしたら、さぞ歩きづらかった事でしょう。

boot

海外のショッピングサイト

結構、海外のショッピングサイトで買い物する。
インポートブランドの服は海外のサイトだと日本より安く売ってるので、狙い目。
今年の夏のセールでJIL SANDERのアンコンジャケットをBrownsで、LUKE SIMONのチノパンをoki-niで買った。
 どちらもイギリスのサイトだが、3-4日であっという間に届いた。
田舎に住んでた子供の頃、月刊のコロコロコミックが3日、週刊の少年ジャンプが1日、東京より発売が遅くて悔しかった。
地球が狭くなったと実感する。
今はTanner Goodsのベルトを買おうか思案中。

JIL SANDERluke simon


大手派遣会社仕事情報モバイルサイト開発

内容 大手派遣会社仕事情報モバイルサイト開発
期間 2008年1月 – 2008年3月
OS Linux
言語 PHP
アプリ Apache, Oracle
フレームワーク 独自フレームワーク

大手人材派遣会社のモバイルサイトがリニューアルするに当たって、システム開発を行いました。

この案件の主な特徴は以下です。

  • XHTML採用により、表現力・機能性の高いデザインを実現
  • 所定フォーマットのテキストファイルを設置するだけで数画面からなる派遣登録フォームを動的に作成し、プログラミング技術のない担当者でもサイト更新が可能
  • 独自ウェブフレームワークを採用

この仕事で苦労したのは、エンドのクライアントとのやり取りでした。
この案件は、派遣会社であるエンドのクライアントが某ウェブ制作会社に制作を委託し、その制作会社が自分にシステム開発を再委託した、という構図でした。
クライアントとの打ち合わせをするのがウェブ制作会社のディレクターだけなので要件が自分まで上手く伝わらないという問題もありましたが、より困ったのがシステム環境の違いでした。
自分にはクライアントが管理する本番環境へのアクセス権がありませんでした。
開発はウェブ制作会社の提供する開発環境がで行ったのですが、OSはLinuxで本番環境のSolarisと違いました。
DBはウェブ制作会社にOracleのライセンスがない為、開発向けエディションであるOracle XEを自分でインストールしました。

そうした開発環境の制限の為、DBの文字コードが開発DBはUTF8、本番DBはSJISになったのですが、文字コードの違いの吸収はウェブサーバやDBサーバの機能を利用すれば良いのでそのような設定方法をクライアントのエンジニアに示すのですが、コンプライアントが厳しいというか、率直に言ってこちらの説明が理解出来ないようで、そうしてもらえない。
仕方ないので、文字コードの違いの吸収はPHPレベルで解消しました(毎回文字コード変換処理が走るので無駄な負荷が発生します)。
それ以外にもウェブサーバ・DBサーバの設定を変える(現在どのような設定になってるか教えてもらう)依頼は基本的にNGなため、そうしなくても済むように工夫する必要がありました。

また、この案件はPHP4だったのですが、PHP4の標準Oracleモジュールは非常に不安定で頻繁にシステムエラーを起こす為(PCサイトではエラー発生してもブラウザはスルーするので見過ごされてきたようですが、携帯の場合、携帯ブラウザがエラー画面を表示するので標準モジュールは使い物にならなかったです)、oci8モジュールが含まれるようにPHPをリコンパイルせねばならず、その指示書を書いて、クライアントの作業をサポートをしました。

以上のような理由で、開発自体は順調に進捗したのですが、それ以外の部分でひどく工期を取られました。
この案件で学んだ事は、開発環境の構築・システム要件の保証はよく検討して工数にプラスする事、でした。

ショッピングサイトのパフォーマンス改善開発

内容 ショッピングサイトのパフォーマンス改善開発
期間 2007年2月 − 2007年8月
OS Solaris
言語 PHP, JavaScript
アプリ Apache, Oracle

PC向けのショッピングサイトの開発に、プロジェクトマネージャ1名、プログラマ4名からなるチームにプログラマとして参加しました。

この案件は出自(?)が特殊でした。
クライアント(サイト運営者)が某大手独立系SIerに開発を依頼したものの、納期を過ぎても完成せず、その某SIerは自社開発を諦め、当時自分が参加していたウェブ制作会社に開発を再委託したというものでした。
自分達が委託を受けた時点で画面はだいたい出来ていて各画面の基本的な機能も実装されており、進捗率は高かったのですが、独自フレームワークを採用しており、そのシステム構造が驚く程複雑で設計が悪かったです。
新たな機能を実装しようとすると既存の処理に大量な修正が発生し、高い確率でデグレが発生しました。
すでに動いてる機能もパフォーマンスが低く、クライアントの要求性能を満たそうとすると、当該処理を大幅にリライトしなければなりませんした。
納期はすでに半年以上遅れてどうにもならない状態で、某SIerはギブアップし自分達が開発を引き継ぎました。

どのように設計が悪かったか説明すると、オブジェクト指向が過度だったと言えると思います。
リクエストの度にあらゆる(ビジネスモデリング的な意味での)オブジェクトが生成され、各オブジェクトが複雑に関連していました。
ウェブアプリケーションは、特にこの案件のようなショッピングサイトの場合、基本的に業務アプリケーション(DBアプリケーション)と見なせ、その為オブジェクト中心よりデータ中心でシステム設計をした方が良いと思います。(この辺の話はSE・PG入門「データ中心指向とオブジェクト指向」に詳しいです。)
ウェブでは複雑にオブジェクトを作っても保存する先はDBだからです。
もしオブジェクト指向ならORマッパーを使えばDAOが整理されると思うのですが、この案件のシステム設計ではSQLはDAO内で独自の生成処理を経ており、ちょっと検索項目を変更しようにも、生成処理内にあるSQLの断片(復元すると数十行になる)を追う必要がありました。
また、同じ目的と思われるが微妙に異なるSQLが別のメソッドに散在していて、SQLの記述自体もコピペの繰り返しで冗長化していて、という。
開発の長期化で担当者が変遷し、担当者毎に実装の癖、コーディングスタイルが違うというお決まりの問題もありました。

結局、半年ほどかけて、なんとかもう少しで完成という所まで持って行ったのですが、ある不可抗力な事情からリリース直前に開発が中止になってしまい、案件として強制終了しました。
自分達の会社には損害はなかったのですが、後味の悪い終わりで非常に残念な気持ちがしました。
このような案件は自分の経験でも今の所唯一で、こうなる前にもっとすべき判断があったなと思います。
個人としてはアンチパターン的に勉強になってのですが。

3キャリア携帯公式の占いサイト開発

内容 3キャリア携帯公式の占いサイト開発
期間 2006年1月 − 2006年4月
OS Linux
言語 PHP
アプリ Apache, MySQL
フレームワーク Maple

某ウェブ系受託開発会社にフルタイムで常駐し、携帯の公式サイトを開発しました。
PM1人PG4人くらいのチームにPGとして参加しました。
最近(現在2010年)では3G携帯のみ対象としてXHTMLを採用するケースが多いと思いますが、当時はまだ2G携帯のシェアも高く、クライアントの意向もあり、TABLEタグ非対応端末、白黒画像のみ表示可能な端末等の各種端末に対応させるため、非常に細かくコントローラ、テンプレートが分かれているサイトでした。
旧型端末が多数揃っており、別部署のテスト部隊によって細かく動作チェックを行っていましたが、そのチェックが厳しく、端末対応が大変な開発でした。

この会社は社員50人くらいのうちプログラマー(SE)が8割を占めてる感じでしたが、非常に質が高い人が集まっていたと思います。
自分はいろんな開発現場に入っており、その経験から言うと、会社の初期段階で起業メンバー数名だけの内は全員の質が高くても、会社の規模が大きくなり、10名、20名、30名と社員が増えてくるとその質は低下する事が多いように思います。
優秀なプログラマーはそう簡単に集まらないし、会社に以前からいて経験を積んだ優秀なプログラマーは辞めていなくなるからです。
受託開発をしている会社の場合、そういう循環になるような気がします。
と、思ってるんですが、この会社はそんな中、結構な数、高い質のプログラマーさんがいました。
正社員だけでなく、派遣や業務委託のスタッフもいて、いろんな所から上手く人材を集めていたようです。

ちなみに、この案件の占いサイト、某有名(らしい)占い師さんが監修していました(している事になってました)。
サイトオープン前後はその占い師さんは打ち合わせ等に参加していたようなのですが、新しい企画は開発チーム任せになっており、チームの中にその占い師さんの本を何冊も読破して占い理論を身につけたPGがおり、占いアルゴリズムが要求される処理はそのPGが担当していました。
業務システム系で簿記等の会計知識に特化した会計プログラマーっていると思いますが、そのPG(●●さん)は占いプログラマーといった感で、開発チームでは「●●さん、占い師になれるよね。というか、すでに占い師だよね。自分でサイト作ればいいのに。」とよく話してました。

PHPでページャークラスを作ってみた

ページャークラスを作ってみた。
特徴は、
・「前へ」「次へ」「最初へ」「最後へ」の相対表現のリンク、全ページ番号への直接リンク、全ページ数、現在表示中の項目の番号に対応。
・絞り込み検索等でもページング出来るようにGETのクエリパラメータを保持出来る。
・メソッドチェーンで直感的(?)にページ情報を取り出せる。
です。

使い方は下のようにPagerクラスのインスタンス$pagerを作り、
[php]
<?php
$pager = new Pager(array(
‘cur_no’ => 2, // (1)
‘total’ => 45, // (2)
‘num_per_page’ => 10, // (3)
‘path’ => ‘http://abc.com/index.php?flg=1’, // (4)
‘query’ => array(‘year’ => 2010, ‘month’ => 9) // (5)
));
[/php]

コンストラクタの引数
(1) 現在のページ番号
(2) 全項目数
(3) 1ページに表示する項目数
(4) ページのURL
(5) クエリーパラメータに加えたいハッシュ情報

↓のようにHTML内で$pagerからページ情報を取り出す。例えば「次のページ」のURLは$pager->next()->url()で、ページ番号は$pager->next()->no()のようにして。
[php]
<ul>
<li><a href="<?= $pager->first()->url() ?>">最初へ</a></li>
<li><a href="<?= $pager->prev()->url() ?>">前へ</a></li>
<li>
<ul>
<?php foreach ($pager->all() as $p) { ?>
<?php if ($pager->no() == $p->no()) { ?>
<li><?= $p ?>ページ</li>
<?php } else { ?>
<li><a href="<?= $p->url() ?>"><?= $p ?>ページへ</a></li>
<?php } ?>
<?php } ?>
</ul>
</li>
<li><a href="<?= $pager->next()->url() ?>">次へ</a></li>
<li><a href="<?= $pager->last()->url() ?>">最後へ</a></li>
<li>全<?= $pager->last() ?>ページ中 <?= $pager->cur() ?>ページ 表示</li>
<li>全<?= $pager->total() ?>項目中 <?= $pager->cur()->firstIndex() ?>から<?= $pager->cur()->lastIndex() ?>番目の項目 表示</li>
</ul>
[/php]

これで下のようなHTMLになる。
[html]
<ul>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">最初へ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">前へ</a></li&gt;
<li>
<ul>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=1">1ページへ</a></li&gt;
<li>2ページ</li>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=3">3ページへ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=4">4ページへ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=5">5ページへ</a></li&gt;
</ul>
</li>
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=3">次へ</a></li&gt;
<li><a href="abc.com/index.php?flg=1&year=2010&month=9&no=5">最後へ</a></li&gt;
<li>全5ページ中 2ページ 表示</li>
<li>全45項目中 11から20番目の項目 表示</li>
</ul>
[/html]

ページャークラスのソースコードはこれ↓。
[php]
class Pager {
private $total;
private $num_per_page;
private $cur_no;
private $first_no;
private $last_no;
private $next_no;
private $prev_no;
private $query = array();
private $no;

function Pager($params) {
$total = $params[‘total’];
$num_per_page = $params[‘num_per_page’];
$cur_no = $params[‘cur_no’];
$path = isset($params[‘path’]) ? $params[‘path’] : ”;
$query = isset($params[‘query’]) ? $params[‘query’] : array();

$first_no = 1;
$last_no = ceil($total / $num_per_page);
$next_no = $cur_no < $last_no ? $cur_no + 1 : null;
$prev_no = $cur_no > $first_no ? $cur_no – 1 : null;
$all_nos = array();
for ($i = $first_no; $i <= $last_no; $i++) {
$all_nos[] = $i;
}
$this->total = $total;
$this->num_per_page = $num_per_page;
$this->cur_no = $cur_no;
$this->first_no = $first_no;
$this->last_no = $last_no;
$this->next_no = $next_no;
$this->prev_no = $prev_no;
$this->all_nos = $all_nos;
$this->path = $path;
$this->query = $query;
$this->no = $cur_no;
}

function all() {
$buf = array();
$all_nos = $this->all_nos;
foreach ($all_nos as $no) {
$buf[] = new Pager(array(
‘cur_no’ => $no,
‘total’ => $this->total,
‘num_per_page’ => $this->num_per_page,
‘path’ => $this->path,
‘query’ => $this->query
));
}
return $buf;
}

function cur() {
$this->no = $this->cur_no;
return $this;
}

function total() { return $this->total; }

function first() {
$this->no = $this->first_no;
return $this;
}

function last() {
$this->no = $this->last_no;
return $this;
}

function next() {
$this->no = $this->next_no;
return $this;
}

function prev() {
$this->no = $this->prev_no;
return $this;
}

function no() {
return $this->cur_no;
}

function firstIndex() {
$idx = $this->num_per_page * ($this->no – 1) + 1;
if ($idx > $this->total) $idx = $this->total;
return $idx;
}

function lastIndex() {
$idx = $this->num_per_page * $this->no;
if ($idx > $this->total) $idx = $this->total;
return $idx;
}

function url($ssl = false) {
if (!$this->no) return ”;
$url_info = parse_url($this->path);
if ($ssl) $url_info[‘scheme’] = ‘https’;
$query = array();
if (isset($url_info[‘query’]) && $url_info[‘query’] != ”) {
parse_str($url_info[‘query’], $query);
}
$query = array_merge($query, $this->query);
$query[‘no’] = $this->no;
$buf = array();
foreach ($query as $k => $v) {
$buf[] = sprintf(‘%s=%s’, urlencode($k), urlencode($v));
}
$url_info[‘query’] = join(‘&’, $buf);
if (isset($url_info[‘scheme’]) &&isset($url_info[‘host’]) ) {
$url = sprintf(‘%s://%s%s%s%s’,
$url_info[‘scheme’],
$url_info[‘host’],
$url_info[‘path’],
$url_info[‘query’] ? ‘?’ . $url_info[‘query’] : ”,
isset($url_info[‘fragment’]) && $url_info[‘fragment’] != ” ? ‘#’ . $url_info[‘fragment’] : ”
);
} else {
$url = sprintf(‘%s%s%s’,
$url_info[‘path’],
$url_info[‘query’] ? ‘?’ . $url_info[‘query’] : ”,
isset($url_info[‘fragment’]) && $url_info[‘fragment’] != ” ? ‘#’ . $url_info[‘fragment’] : ”
);
}
return $url;
}

function __toString() {
return (string) $this->no;
}
}
[/php]