唯一ぬにの盾

今は亡きWiseknot社の同僚であった「三度の飯より四度の飯、Javascriptはもっと好きです。」でお馴染みの@kjirouが一ヶ月来てくれる事になった。

また、アリウープ様にお願いしていた件もCakePHP2実践入門の著者の一人でもある@kaz_29さんが参戦してくださることになった。

「もうついたのか!」「はやい!」「きた!盾きた!」「これで勝つる!」と大歓迎状態だった。哀れにも@komagataは盾の役目を果たせず死んでいた近くで素早くフラッシュ(略

軽く発生ポイント

俺「あのポイント関連の処理ってどこでやってるの?」

@kjirou「あー、あれは共通化されてないからどっちかは処理されてないんだけど、片方は"軽く発生ポイント"でやっ(略」

俺「軽く?・・・何?」

@kjirou「だからカルクハッセイポイントクラスで」

俺「あー、Calculation?ちょwwwww CalcHasseiPointかよwwww。英語日本語英語のクラス名をさも一般名詞みたいに日常会話に混ぜないでよwwww」

@kjirou「いやそんなんこのコードの中じゃ普通でしょ、それより酷いのがこっちの処(略」

Testing Testing Testing!

大量メール配信の部分もヤバイということで@kaz_29さんがSimpleTest(CakePHP1.3なのでPHPUnitじゃないところがまたダルいが)でテストを書き始めてくださった。

@kjirouも複雑怪奇な仕様と実装をGithub Wikiにまとめながらどんどんコードを書いてる。

@hrysdは先日仕込んだMailLoggerから容赦なくNotice, Warnningが飛んでくるのでそれを潰すのを担当。

盛り上がって来やがった。

強引なEnd To Endテスト

app/tests/helper.php:

<?php
require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class CakePHPSeleniumTestCase extends PHPUnit_Extensions_SeleniumTestCase
{
    protected function assertNoError()
    {
        $html = $this->getHtmlSource();
        $patterns = array(
            '/<b>Fatal Error<\/b> \(256\)/',
            '/<b>Warning<\/b> \(512\)/',
            '/<b>Notice<\/b> \(1024\)/'
        );

        $result = 0;
        foreach ($patterns as $pattern) {
            $result += preg_match($pattern, $html);
        }

        $this->assertEquals($result, 0);
    }
}

「とにかく普通に画面にエラーが出てる」

という問題を直すためにCakePHP1.3が出力するエラーを確かめるための強引なAssertionを作成。

app/tests/integrations/RegularOrdersTest.php:

<?php
require_once 'helper.php';

class RegularOrdersTest extends CakePHPSeleniumTestCase
{
    protected function setUp()
    {
        $this->setBrowser('*firefox');
        $this->setBrowserUrl('http://xxxxxxxxxx/');
    }


    public function testIndexTitle()
    {
        $this->open('/regular_orders');
        $this->assertTitle('管理画面');
        $this->assertNoError();
    }
}

こんな感じで使う。

PHPUnitのソース追ってましたが、開発がかなり活発。APIとマイナーバージョンがどんどん変わってく。そんなに頑張るとハゲるぞセバスチャン。

まだJenkinsに怒られるけど来週には環境が整うはず。強引にCIに乗せようと思えば乗るもんだなあという感じ。

PHPMatsuri

PHPMatsuri2012行きます。基本、Hackathonだそうなのでレガシーに役立つ何かが作れればなーと思います。

関連:レガシーPHP改善日記シリーズ

あるべき初期データの姿が不明。今動いてるDBが全て。という場合にmigrationをはじめるのが難しい。また、複合主キーを使ってる場合はそもそもmigration出来ない。なので無理矢理afterでdumpファイルを読み込んで対応。

<?php
class xxxxxxxxxxxxxxxxx extends CakeMigration
{
    public $description = '';

    public $migration = array(
        'up'   => array(),
        'down' => array(),
    );

    public function before($direction)
    {
        return true;
    }

    public function after($direction)
    {
        if ($direction == 'up') {
            $config = $this->db->config;
            `mysql -u{$config['login']} -p{$config['password']} {$config['database']} < db/20121010154033_create_initialize.sql`;
        }
   
        return true;
    }
}

レガシーコード改善ガイドが届いたので読みました。とても勇気づけられたし、レガシー改善のコーディングテクニックがいろいろと参考になりました。

個人的にはコーディング以外の開発環境やツール、スクリプト言語ならではな部分も知りたかったのでレガシーコード改善ガイド風に思いついたのを書いていこうと思います。

このPHPのコードを追える気がしません

まずは深呼吸して、コードベースをeval, create_function, extractでgrepしましょう。意外と少ないでしょう?だったら大丈夫。findとgrepとviがあればいつかは何とかなります。

バージョン管理されてません

テスト環境みたいなのがあるはず(もしくは本番環境)なのでFTPとかSCPでそれを全部もってきてgithubに突っ込む。今後、開発はそこにコミットして、アップするときもそこからアップしてねと言う。

githubを借りる許可が出なかったらその辺のマシンにgitをreposを立てる。みんなでreposを使う合意が取れなかったら自分だけでもgitで管理する。githubの利点はpull requestとコードレビューがしやすい点。

ソフトウェア資産を失わないようにという話をすれば大抵聞き入れてくれるはず。

ステージング環境がありません

その辺のマシン、もしくはさくらVPS1Gを借りてreposにpushされたらそこにデプロイされるようにしよう。(capistranoとかで)

本番環境しかないのでテストにつかうまともな初期データがありません

おそらく本番環境のDBの状態=仕様になっているのでしょう。3桁に迫るテーブルがある場合、まっさらなスキーマから手で初期データを作っていると日が暮れてしまいます。本番のDBをdumpしてきてそれをベースにテストを実行しましょう。ボトムアップではなく、トップダウンで初期データを少しずつあるべき状態にしてきましょう。

中長期的な作業をする時間がありません

本業(デスマ)も「このくらいやってれば普通以上だろう」ぐらいやりつつ開いた時間でやろう。もしくは業務時間外でやりましょう。

頑張る気が起きません

ひどい状態なら怒られない範囲でブログに書けばネタになるし同僚や誰かが助けてくれるかもしれません。着実に改善していくところをRPGみたいに楽しもう。ナイトメアモードのゲームだと思えばやりがいがあります。

関連:レガシーPHP改善日記シリーズ

祝Github導入

お陰様でsvnからGithubに移行。気軽にブランチ作れてとっても快適。lingrやjenkinsとの連携もやっぱりスムーズ。@hrysdとはしゃぎました。

cakeplusプラグイン

i18n関連が面倒だと書いたらコメント欄でk1LoWさんが教えて下さったcakeplusプラグインを入れてみた。超改善された。あざーす!

レガシー認証

以前も書いたが、全コントローラー、全アクションに認証処理がベタ書きされてる。そこでその処理をLegacyAuthComponetという名前で分けた。

<?php
/**
 * 旧認証
 *
 * 古い認証機能をラップしたもの。
 * 管理者ログインしていない場合はログイン画面に
 * リダイレクトする
 *
 * 使い方:
 * class FooController extends AppController
 * {
 *     public $compornents = array('LegacyAuth');
 * }
 */
class LegacyAuthComponent
{
    public $name = 'LegacyAuth';
    public $controller;

    public function startup(&$controller)
    {
        $this->controller =& $controller;
        $this->adminLoginRequired();
    }

    public function adminLoginRequired()
    {
        if (isset($_SESSION['ses_login']['id'])) {
            $id = $_SESSION['ses_login']['id'];
        } else {
            $id = null;
        }

        if (!($id > 0)) {
            $this->controller->Session->setFlash(__('admin_login_required', true));
            $this->controller->redirect('/login/');
        }
    }
}
?>

プログラマーを求めて

一人だけでいいんだけどとにかくPHPプログラマーが見つからない。PHPの老舗、アシアルさんに相談したが、直近では空いている人がいないとのこと。しかし同じくPHP老舗であり、PHPCon2012でもニアミスしていたアリウープの柏岡様を紹介していただいた。

なんとこのブログの記事を読んでいたとのことで、ぶっちゃけたところからお話することができてとても話が速かった。しかしアリウープさんもお忙しいとのことでまだ決まるかどうかわからない状態。

それとは別で以前、ちょっと一緒にやっていくのは難しそうと書いた会社様より、「具体的に何が悪いのか、どういう人材を探しているのか説明して欲しい」との電話があり、上司の方とお話した。

どうも、こちらの話しているユニットテスト/自動テストと先方の話しているテストが違っているような気がする。

「よくわからないけど要はそういうツールがあるんですよね?だったらうちもできますよ。普通やりますよね、自動テスト」という感じ。

「でも先日見せてもらったコードにはユニットテスト無かったですよね?」(ついでにいうと使ってるライブラリはPEAR DBとSmartyだけでしたよね?)というようにちょっと話が噛み合わなかった。

僕らはそんなにすごいプログラマーを募集してるわけじゃないんです。ただ、プライベート(仕事外)でコードを書いてるPHPプログラマーだったら誰でも歓迎なんです。

しかしSIerから離れて随分経つので忘れていました。世の中にはプライベートでコードを書かないプログラマーが沢山いるという事実を・・・。

レガシーPHP改善日記

この日記が終わるかもだと・・・?

ハハハ、あんなメール、俺にとってはご褒美だ。

関連:レガシーPHP改善日記シリーズ

お世話になっております。フィヨルドの駒形です。

下記大変申し訳ありませんでした。NDAも理解しております。 該当文章をブログから削除しました。

私がどのような意図で様に関するブログを書いたのか 簡単に説明しておきたいと思います。 (そもそもブログを書くのはとても工数のかかる作業なので 理由無く書きません)

  • プログラマーのリクルーティングのため
  • 関係者間の情報共有(を強制的に行う)ため
  • 改善案や情報を外部から募るため
  • 改善したあかつきに、非公式販促コンテンツとするため

私はAAAAやBBBBに限らず、殆どのシステムのアイデアや実装は 簡単に真似できるので殆ど価値が無いと思っています。 (2日で概要が出来るぐらいなので)

ASP等のシステムの場合、そこに集まるユーザーやデータ、そしてその システムを維持し、改善していける組織は今体験しているように 簡単に真似することができません。 (なので弊社のシステムもオープンソースにしています)

「FJORDのWebサービスはオープンソースです ≪ FJORD, LLC」
http://fjord.jp/love/534.html

最初の方のブログにも書きましたが、「ヒドイ状態」などと笑い話になりますが、 業界的には非常にありふれた状態で、誰しも大っぴらに言えないだけで 周りにそういうシステムが沢山あります。

表向きにはいい言葉だけを並べますが、実情は大抵のシステムがヒドイ 状態です。そこを隠さず、真正面から改善し、内情すら公開して自信を持って 提供できるようになった時、ものすごい信頼を得られるサービスになり、 売上が大幅に上がるのではないか、そしてそういう態度がそれを是とする 開発チームの構築につながると考えたためです。

とはいえ、そんなことは私が勝手に考えて勝手にやったことなので ご迷惑をお掛けしてしまって申し訳ございませんでした。

他にも問題ある点があればご指摘いただければと思います。

以上、宜しくお願い申しあげます。

--
----------------------------------------------------------------------------------
 Masaki Komagata

 ブログ: http://docs.komagata.org/
 FJORD, LLC: http://fjord.jp/
 CMS for Cloud: http://lokka.org/
 スマホで怖い話: http://kowabana.jp/
----------------------------------------------------------------------------------

関連:レガシーPHP改善日記シリーズ

(レガシー改善に関しては文章が長くなるのでいつもと違って、〜だ、〜である口調で書きます。)

bake

CakePHPははじめてなのでところどころハマったが基本的なCRUDは出来た。というかbakeで殆ど生成されるのだが。CakePHP1.3のbakeはデフォルトでページングとカラム毎のソート機能が入っている。

しかし、「データがたくさんある時ページ分けして欲しい」というタスクが幾つかRedmineにあったし、ソースをみても既存の部分はbakeを使った形跡は無い。俺が初bakeか。

i18n

bakeにともなってi18nも使った(bakeで生成されるソースにはi18n前提だし)。現状、poファイルが無い。俺が初i18nか。

% cake bake i18n extract

こんな感じでソースからpotファイルを生成する。app/vendorsにPHPExcelというプラグインがあって、それの処理に死ぬほどメモリと時間がかかる。app/vendorsを除外する方法は無いものか。

ソースを見ると上記にpotを既存poにマージする機能があるっぽいが、やり方がわからなかったのでとりあえず初回はmsginitした。

macではデフォルトでgettextが入っているが、% brew link gettextしないとパスが通らないところが若干罠だった。

既存ソースに何故か

__('')
msgid ""
msgstr ""

みたいな空文字を翻訳している箇所が5箇所ぐらいあってmsginitが出来ない。どういう理由でこういうコードがあるのか不明だが、とりあえず消した。

validateエラーメッセージのi18n

modelでvalidateエラーメッセージをi18nしようとしたがsyntax errorが出る。何故?と思ったがphpではrubyと違ってインスタンス変数の定義は定義であって評価じゃない。初期値は書けるが関数の実行はできない。

要は下記はOKだが、

class User {
    public $name = 'komagata';
}

下記は駄目ということだ。

class User {
    public $length = strlen('komagata');
}

rubyを始めた頃はattr_accessorを見て「クラス定義内も普通に式が評価されるのか、凄いなー!」なんて思ったはずだが、それが当たり前になりすぎてそうじゃない言語が沢山あることを忘れていた…。(array()はOKなのでarrayは関数じゃなくてリテラルなのかな?)

class RegularOrder extends AppModel {
    public $validate = array(
        'interval' => array(
            array(
                'rule'    => 'numeric',
                'message' => __('must be number', true)
            )
        )
    );
}

何故これがsyntax errorなんだー!・・・みたいな。

FormHelperへの疑問

CakePHP1.3のbakeが生成するコードはFormHelperを使っている。railsのscaffoldと比較して疑問点がいくつかあった。

bakeが生成するviewのコードは下記のようになっている。(分かりやすく色々端折ってます)

<?= $form->input('interval') ?>

でも実際は下記のようにモデル名も書かないと動かない。

<?= $form->input('RegularOrder.interval') ?>

formはmodelを知ってるんだから自動で付けてくれてもいい気がするが、PHP4もサポートするせいでモデル名がわからないのかもしれない。それだったらPHP4サポートを切ったCakePHP2系だったら改善されてるのかも。

レガシー改善パターン "Partial Proxy Pattern"

既存のレガシーの中にリーダブルコードを書いていく為のショーモナイテクニックに勝手に名前を付けていきたいと思います。

既存のテンプレートの中の共通化したい部分はこんな感じで書かれてる。

<body>                      
  <?php include('../views/head-menu.ctp');?>
    <h2 class="title_1">注文入力</h2>

ファイル名と置き場所が分かり辛い。include(!)ではなく、CakePHPのElementを使って欲しい。かといって既存部分をいじったら他で何が起こるか分かったもんじゃない。怖い。

app/views/elements/admin/menu.ctp:

<?= $this->element('../head-menu') ?>

そこでこんな感じで本来置きたい場所に一旦既存のテンプレを読み込むだけのProxy的なElementを作る。そうすると、新しく実装するviewはリーダブルでいられる。

app/views/layouts/admin.ctp:

<?= $html->doctype('xhtml-trans') ?> 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <?= $html->charset() ?>
    <title><?= $title_for_layout ?></title>
    <?= $html->css('admin') ?>
    <?= $scripts_for_layout ?>
  </head>
  <body>
    <?= $this->element('admin/menu') ?>
    <div id="content">
      <?= $content_for_layout ?>
    </div>
  </body>
</html>

レガシー改善パターン "CSS Proxy Pattern"

同じく、既存のCSS(style.css)はゴチャゴチャなので新しく作るview用のCSSはリーダブルにしたい。そこでこう!

app/webroot/css/admin.css:

@import url('style.css');

/* ここに新しいviewで必要なcssを書いてく */

レガシーCSSとリーダブルCSSを切り分けることが出来る。

絶賛募集中

僕と一緒にレガシー改善をしてくださるプログラマーを募集しています。常駐・受託、開発会社・フリーランス、派遣・バイト問いませんので是非お願いします。というか助けて欲しい・・・。詳しくはこちら

関連:レガシーPHP改善日記シリーズ

Migrations Pluginの導入

cakeディレクトリがcake_core/cakeという感じで置かれていて色々設定しないとcakeコマンドが動かないのでデフォルトの位置に戻した。おそらくcakeコマンドによるgenerateは使ってなかったんだろう。generateを使わないとCakePHPのレールから大幅に外れることになりがち。cakeコマンドが動いたことによってようやくmigrations pluginを導入できた。現状のDBの状態をmigrationファイルに変換できたが、そもそも初期データはどうあるべきなのかがわからないので困った。テーブルが50ぐらいあるDBの初期データを手で作っていたら日が暮れてしまう。相談したところ、初期化に使っているSQLがあるとのことでそれを頂いた。とても助かった。

一番手っ取り早いテスト

seleniumでテストを書いている。とにかく「このページ開いたらエラーが出てるんですけど」という状態を無くしたい。capybaraとかであれば「GETしてステータス200かどうかだけを調べる」というのを量産すれば可能だけどseleniumでステータスが取れるかどうかがわからない。そもそもPHPでWARNINGとかが発生した時ってHTTP的には普通に200をかえしちゃったりしてないだろうか?試してみよう…。(どなたか知ってる方がいたら教えていただけるとありがたいです)

hrysd苦戦

@hrysdは引き続きバグ修正。数日前にPHPはじめたのに頑張っている。しかし元々ソースがカオスなので一箇所修正したと思ったら、その影響で他のどこかが壊れるということが多発する。一刻も早く自動テストを増やさねば。

コメントを入れる

バグ修正をしているとき、下記の用な感じで変数名やカラム名が全然意味がわからないのでわかったところにコメントを入れてた。変数名や関数名を分かりやすくしてあればこんな作業は必要無いのに…。カラム名のD1, SD, MT, YDとかまったくわからない。なぜカラム名の長さの数バイトをケチるのにsessionにバカでかいデータを入れまくるのか。

$dd = $tk[0]['NcrmDTeiki']['D1']; // 発生日付(YYYYMMDD)
$dd = $tk[0]['NcrmDTeiki']['SD']; // 配送予定期間(開始日付)(YYYYMMDD)
if ( $tk[0]['NcrmDTeiki']['MT'] == 3 ) {
    if ( $tk[0]['NcrmDTeiki']['SD'] > 0 ) {
        $dd = $tk[0]['NcrmDTeiki']['SD'];
    }
}
$kyou = date('Ymd');
$kyou = $chd[0]['NcrmDCyuumon']['D1'];
if ( $lst != 0 ) { $kyou = $lst; }
// 次の日付を求める
$yyy = substr($dd,0,4);
$mmm = substr($dd,4,2);
$ddd = substr($dd,6,2);
if ( $mmm == 4 or $mmm == 6 or $mmm == 9 or $mmm == 11 ) {
    if ( $ddd == 31 ) {
        $ddd = 30;
    }
}
if ( $mmm == 2 ) {
    if ( $ddd >= 29 ) {
        $ddd = 28;
    }
}
if ( $tk[0]['NcrmDTeiki']['MT'] == 1 ) {
    $mmm = $mmm + 0; if ( $mmm == 13 ) { $yyy = $yyy + 1; $mmm = 1; }
    $ddd =$tk[0]['NcrmDTeiki']['YD'];
}
(この1行目と2行目は同じ変数に連続して代入してるので明らかにおかしいと思うんだが、怖いので触れない)

プログラマーを探す

このブログでも募集文を書いているが、社長も開発会社を探してくれている。目安として、"作業担当者のGithubアカウントもしくは見せられるコードがあること"を条件に探しているんだが、今までの5〜6社で該当するところが一つもない。普通そういうもんなんだろうか。下記のようにGithubでPHPのrepos持ってて、Tokyo在住の人のブログとかをみて、開発を受け付けているか探したほうが早いんじゃないかと思った。

Search · location:tokyo

定期購入機能が間に合わない問題

ECのシステムで定期購入という機能がある。一回買うと定期的にものが送られてくる機能だ。この機能の実装がバグが多発(というかまだできてない)していて困っている。

実装的にちょっとバグが出ているなら直せばいいんだけど、仕様の段階で破綻しているっぽいので悩む。

定期購入を実現するために今の仕様では、購入した時点で普通の商品と同じように1回目の伝票を1個作成するのだが、定期購入は期間をしていして、2回目、3回目、4回目などの未来の伝票を画面から手動で生成するようになっている。

伝票は編集したり削除したりできるので2, 3, 4回目が出来た後で1回目(元伝票と呼んでいる)を変更するとおかしな自体が多発する。2回目だけを消したら3回目は本当に3回目なのか?とか、2回目を消した後で次の伝票を作成(5回目を作成したいつもり)するとまた2回目が出来たり3回目が2個出来たり…。

そもそも手動で伝票を生成するのなら定期購入って意味あるのか?というのはあるにしろ、定期購入というのは実物の伝票ではなく、購入に関するルールという抽象的な概念なので、実物の伝票として未来まで作成してしまう(DBにデータを作成してしまう)とルールの変更があった時に不整合がいかにも起こりやすいことはさらっと聞いただけでわかる。

今のシステム(新CRM)の前身である旧CRMはそういった不安な仕様はなく、定期購入は先に未来の伝票を何回目まで先にデータを作るといったことは無い。

開発チームの文化

締め切りが迫っている中、現状をなんとか改善するのか、仕様を見直すのか、リスケするのか、答えが出ていない。現状を改修しようとしても何らかのトラブルが起きそうなことは目に見えている。それを是とするのか非とするのか、当然非なんですが、答えのでない長時間の会議に僕も疲れてしまってとりあえず現状維持みたいな感じになってしまいました。

そういうのを是とすると、中長期的には、開発チームとして論理的矛盾やバグがあってもいい・仕方無いみたいな雰囲気が根付いてしまう。根付いた文化を変えるのはとても大変なので急に「次のバージョンからは安定したシステムを作ろう!」といってもそういった下地ができていなければ無理。まずその辺の認識を変えていかなければ…。

関連:レガシーPHP改善日記シリーズ

hrysd心を折られる

チンカスプログラマーことhrysdがバイトで来てくれることになったので一緒に大門に出社。

初出社前に既にバグを一個潰してコミットしているという荒業を見せたhrysdだが、3000行を超えるcontrollerに早くも心を折られる。

俺「actionのメソッドが5行を超えたら危険印、なんていうrailsのぬるま湯に使ってたんだよ!これがサバンナだ。」

Github Organization契約

出社後早速、社長にGithub Organization Bronzeプランを契約してもらう。技術的なことはわからないというが、リスクを背負って立てなおそうという気持ちが伝わって来ました。

9月30日の直近の締め切りに間に合わないのでsvn + redmineからの移行は10月にお預けだ。

svnがよくわかってない

まずはsvnでもトップにぶち撒けられてるというのは辛いのでtrunkディレクトリを作ってそこにsvn mv。しかし、svn updateの使い方がわかってない外部パートナー様からヘルプの問い合わせ。svn updateしてからsvn ciすればいいだけだと思うんだが・・・。

新たな外部パートナー様を求めて打ち合わせ2件

社長が検索で見つけた2社様と打ち合わせ。技術的に良い会社かどうか判断して欲しいとのこと。githubアカウントの提出をお願いしたら(担当者がプライベートで作ってるコードでも構わないので)2社とも無いとのことで会う前からちょっとガックシ。

1社様はunittestを書いたことがあるということで無くは無いといった感じ。(パートナー様を選ぶ最低限のポイントとしてはgitが使えること、自動テストが書けることの2点と伝えてあります。できればプライベートでコード書いてる人が良い)もう一社様はテストが書ける人を探してると言ったら、エクセルのテスト仕様書を見せられた。phpunitやselenium, jenkinsなんて名前も聞いたこと無い様子だったのでちょっと一緒にやってくのは厳しそうでした。(社長には良い感じの人に見えたと言っていたのが印象的でした。やはりプログラマーが見るべきだと改めて感じた。)

レガシー改善の王道、テストの整備

ステージング環境をさくらVPSで用意。jenkinsでのsimpletest, phpunit + selenium rcでのCI、capistranoでの継続的デリバリーの環境が整った。後はテストを増やすだけだ。

レガシー改善の方針

何はなくとも自動テストだ。テストが無いと、バグもデグレも治った保証が無いし、怖くてリファクタリングできない。

最低限のテストを完備し、その庇護の元、デグレに怯えないバグ修正やリファクタリングを行なっていく。それにはバグを再現するためのテストデータの整備が欠かせない。要はmigrationの仕組みが無いと、多くのバグがそのタイミングでたまたま発生しただけで再現しないため放置されるのだ。(CakePHP1.3でのmigration pluginの導入はまだうまく行ってない)

CakePHP1.3からCakePHP2.xへの移行はもうファイル名やフォルダの命名規則、コア関数などが変わりすぎていてほぼ無理だと判断した。

かと言って中長期的な施策ばかりで直近のデッドラインを無視するわけには行かないので、同時にhrysdにお願いしてヤバイ順に泥臭いバグ修正を並行して行なってもらった。午後は僕も参戦した。

バグと格闘してわかってきたマズイ部分

CakePHPのユーザー認証コンポーネントを使ってない。before_filter的な機能を使ってないので全Controllerの全メソッドの先頭にユーザー認証処理が書いてある。

ユーザーの権限管理にCakePHPのACLを使っていない独自実装の為に膨大な行数が直書き+コピペされている。

検索条件、ページ間のデータ受け渡し(本来POSTされるべきもの)、様々な構造化されたデータのやり取りは全てSessionを通して行われているので各controller、method間の入出力が非常に分かり辛い。完全にGUIアプリのようなステートフルな実装になっているので単体でのテストが非常にやり辛い。バグの再現も難しい。

foreachで処理できるところを(ループをインライン展開しているのか?)全て手で書下しているのでただ代入するだけのような同じ処理が延々と何十行と続く。

$pg, $md, $mk, $sw, $sidなど何を表しているのかわからない変数が大量にあり、推理に時間がかかる。($mdがmodeの略だとわかった時にはガッツポーズをした)

validationはサーバーサイド(model)には一切無く、javascriptだけで行われている。これではデータの整合性を保つのは難しい。

技術的負債が増える理由1

バグを減らす一番の方法はコードの行数を減らすことだ。コード行数を減らすというと、共通処理を関数にくくり出すなどをまずイメージしがちだが、真っ先に考えるべきは、既存のライブラリやフレームワークが備える機能をなるべく使うということだ。

十分テストされたライブラリはその場で作ったスクラッチコードより品質が高い。多くの目に触れているものは不具合の解消も進んでいるだろう。

作ろうとしている機能が、既にあるライブラリで解決可能かどうか、フレームワーク標準の機能にそういったものが無いか、吟味するのはとても重要だ。(フィットするものが無ければその時初めて作ればいい)

そういった調査をせず、闇雲にオレオレコードを量産するとバグ発生率が増加する。

技術的負債が増える理由2

技術的負債が非エンジニアにとって見えづらいのは、組み合わせ爆発の恐ろしさを理解していないことにあると感じました。

日常生活では仕事が増えるといっても精々2倍になったとか3倍になったで大騒ぎ。ソフトウェアの分野では浅慮によって手間が2乗、3乗という風にグラフにしたら垂直に上昇していくようなことが簡単に起きる。

階乗とは違うけどわかり易い例でいうと、何かのmodelに対してcommentを付けたいという要求があったとする、railsノリで言えば、acts_as_commentableみたいな作りにしてcommentテーブルは共通、commentablesみたいなテーブルを作って、コメントを付けたい対象が増えても組み合わせは1×Nのまま爆発的な増大はしない。

ところが、ベタに作ると、post_comments、page_comments、picture_commentsといった風にドンドン増えていく。

色んなモノにtagを付けたいという要求があったらまたpost_tags、page_tags、picture_tagsとその分だけテーブルが増える。

comment, tagの部分もcategory, review, ratingと増えていくとN×Mで爆発的にテーブルは増えていく。

そういったちゃんと抽象化せず、場当たり的実装を重ねた結果、DB定義書のエクセルシート数が3桁に迫り、そんなドキュメントを人間の手でメンテナンスするのは事実上不可能になり破綻する。破綻したドキュメントは軽んじられ、作成者は疲労し、メンバー全員のモチベーションは地に落ちる。

いくら気合で頑張るといっても1日は定時8時間の3倍の24時間しかないのだ。物理的に3倍以上の作業量をこなすことは出来ない。組み合わせ爆発で3倍なんて簡単に超えてしまう。

いかに組み合わせ爆発を起こさないような作りにするか。それが肝心なのだ。

非エンジニアの方(経営者など)はこの動画をみて是非とも組み合わせ爆発の恐ろしさを感じて欲しい。

レガシー改善仲間募集

僕が参加しているプロジェクトでは短期でも一緒にレガシー改善に正面から取り組んでくださるプログラマーさんを募集しています。フリーランス・開発会社様、アルバイト、どんな形でも結構ですのでご連絡いただけるとありがたいです。詳細はこちら

phpプログラマーの募集 - komagata

関連:レガシーPHP改善日記シリーズ

テスト環境(ステージング環境)が本番環境と同居している問題

さくらVPS 1GBを借りてそこをステージング環境にした。

テストが無い問題

simpletest + Stagehand_TestRunnerでテストを書き始めた

デプロイが手動問題

capistranoでcakephp1.3用のtaskを書いて上記のさくらVPSにデプロイできるようにした。

CIされてない問題

さくらVPSにJenkinsを入れて、上記のテストとステージングへのデプロイをする設定をした。


ここからはわからない点、課題。

cakephp1.3をselenium rcでテストする時、simpletestかunittestどっちかに統一したい

今は混在しているので。

cakephpが古い(1.3)問題

既存コードに大きく手を入れず2.x系に移行できるのかな?

githubへ移行

phpconの資料を元に社長を説得中。($25/mかかるから)

テーブル名問題

まずは、これから新たに作成するテーブルは普通の名前にするという同意を得たが、既存の部分はどうもならない。

数万件のmail送信をテストしたいがsendmail or SMTPサーバーのモックが欲しい

そういうライブラリとかあるのかな?(webmockのsmtp版みたいな。PHP以外でも構わない)

関連:レガシーPHP改善日記シリーズ