唯一ぬにの盾

今は亡き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改善日記シリーズ

イヤーパッドって言うんですね。僕はアメ耳なのでカナル型なんてありえないし、替えのイヤーパッドを必要とします。アメ耳派からするとカナル型なんて鼻に突っ込んでるのと同じですからね・・・。

先週の日曜日にデザイナーの為のgem作成勉強会をFJORDでやりました。

RubyGemsによるデザインの再利用 // Speaker Deck

元々は@machdaさんが

「jsとかsassのmixinとかがgemになってると楽でいい。作り方教えて欲しい」

と言ってたので1人に教えるんだったら勉強会にしようと思いつきで10分ぐらいでATND建てたのでした。

デザイナーといってもみんなgitやterminal使えてrubyの環境つくれること前提って感じだったのでいきなりgem作成は厳しいかもですが、複雑化する長期プロジェクトではcssのgemによる構造化はほぼ必須かなって気がしました。

追加:

@kyokutyoさんが手順を公開されていました。

デザイナーの為のgem作成勉強会に参加しました = 2012.10.14.Sun • FIXME

% defaults write com.apple.screencapture location ~/Pictures/screenshot
% killall SystemUIServer

あるべき初期データの姿が不明。今動いてる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改善日記シリーズ

こんなこと書いてたけど普通にできた。(simpletestは必要)

% cake testsuite app all

知らんかった・・・。

ついでに、phpunitを使ったseleniumのテストも一緒にできるshell作った。

app/vendors/shells/tasks/integrations.php:

<?php
class IntegrationsTask extends Shell
{
    public function startup() {}

    public function execute()
    {
        $this->out(`phpunit app/tests/integrations`);
    }
}

app/vendors/shells/test.php:

class TestShell extends Shell
{
    public $tasks = array('Cases', 'Integrations');

    public function startup() {}

    public function main()
    {
        $arg = isset($this->args[0]) ? $this->args[0] : '';
        switch ($arg) {
        case 'cases':
            $this->Cases->execute();
            break;
        case 'integrations':
            $this->Integrations->execute();
            break;
        default:
            $this->Cases->execute();
            $this->Integrations->execute();
        }
    }
}
% cake test

とりあえず本番でエラーが起きてたら開発MLへメールしたい。レガシーPHP案件にとりかかる時はいつもやるphp.iniでメール飛ばす設定をしようとしたら、CakePHPがエラーを捕まえているので飛ばない。

大抵のフレームワークではググるとMailを飛ばすLoggerが見つかるもんだが、CakePHP1.3用のが見つからなかったので書いときました。

<?php
/**
 * メールを送るロガー
 *
 * 使い方:
 * // app/config/bootstrup.php:
 * CakeLog::config('mailLog', array(
 *     'engine' => 'MailLog',
 *     'to'     => 'to@example.com',
 *     'from'   => 'from@example.com'
 * ));
 */
class MailLog
{
    private $to;
    private $from;

    /**
     * オプションを設定する
     *
     * @param  array $options メールのto、fromの設定
     * @return void
     */
    public function __construct($options = array())
    {
        if (isset($options['to'])) {
            $this->to = $options['to'];
        }

        if (isset($options['from'])) {
            $this->from = $options['from'];
        }
    }

    /**
     * ログをメールで送る(ログを書く)
     *
     * @param  string $type    エラーの種類
     * @param  string $message エラーの内容
     * @return void
     */
    public function write($type, $message)
    {
        $subject = "[{$type}] ".mb_substr($message, 0, 32);
        mb_send_mail($this->to, $subject, $message, "From: {$this->from}");
    }
}

Sending log by mail for CakePHP1.3 — Gist

PSR0, PSR1, PSR2にしたがってPHPのソースコードをフォーマットしてくれるphp-cs-fixerを使う。(全部のルールに対応してるわけじゃない)

% sudo curl http://cs.sensiolabs.org/get/php-cs-fixer.phar -o /usr/local/bin/php-cs-fixer
% sudo chmod a+x /usr/local/bin/php-cs-fixer
% sudo php-cs-fixer self-update
% php-cs-fixer fix app/models/foo.php

vimから使う

~/.vimrc:

Bundle 'stephpy/vim-php-cs-fixer'
let g:php_cs_fixer_path = "/usr/local/bin/php-cs-fixer"
nnoremap <silent><leader>pcd :call PhpCsFixerFixDirectory()<CR>
nnoremap <silent><leader>pcf :call PhpCsFixerFixFile()<CR>

これでテンション上げてレガシーPHPを直す。

ヘッドレスブラウザとか言ってらんない人用。

selenium

% sudo su
% mkdir /usr/lib/selenium/
% cd /usr/lib/selenium/
% wget http://selenium.googlecode.com/files/selenium-server-standalone-2.25.0.jar
% mkdir -p /var/log/selenium/
% chmod a+w /var/log/selenium/

seleniumのinit scriptを書く。

% sudo vi /etc/init.d/selenium
#!/bin/bash

case "${1:-''}" in
    'start')
        if test -f /tmp/selenium.pid
        then
            echo "Selenium is already running."
        else
            export DISPLAY=:99
            java -jar /usr/lib/selenium/selenium-server-standalone-2.25.0.jar > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
            echo "Starting Selenium..."

            error=$?
            if test $error -gt 0
            then
                echo "${bon}Error $error! Couldn't start Selenium!${boff}"
            fi
        fi
    ;;
    'stop')
        if test -f /tmp/selenium.pid
        then
            echo "Stopping Selenium..."
            PID=`cat /tmp/selenium.pid`
            kill -3 $PID
            if kill -9 $PID ;
                then
                    sleep 2
                    test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
                else
                    echo "Selenium could not be stopped..."
                fi
        else
            echo "Selenium is not running."
        fi
        ;;
    'restart')
        if test -f /tmp/selenium.pid
        then
            kill -HUP `cat /tmp/selenium.pid`
            test -f /tmp/selenium.pid && rm -f /tmp/selenium.pid
            sleep 1
            export DISPLAY=:99
            java -jar /usr/lib/selenium/selenium-server-standalone-2.25.0.jar > /var/log/selenium/selenium-output.log 2> /var/log/selenium/selenium-error.log & echo $! > /tmp/selenium.pid
            echo "Reload Selenium..."
        else
            echo "Selenium isn't running..."
        fi
        ;;
    *)      # no parameter specified
        echo "Usage: $SELF start|stop|restart|reload|force-reload|status"
        exit 1
    ;;
esac
% sudo chmod 755 /etc/init.d/selenium
% sudo update-rc.d selenium defaults

xvfbとfirefox(iceweasel)

% apt-get install xvfb iceweasel

xvfbのinit scriptを書く。

% sudo vi /etc/init.d/xvfb
#!/bin/bash

XVFB=/usr/bin/Xvfb
# XVFBARGS=":99 -fbdir /var/run -ac"
XVFBARGS=":99 -nolisten tcp -fbdir /var/run"
PIDFILE=/var/run/xvfb.pid
case "$1" in
  start)
    echo -n "Starting virtual X frame buffer: Xvfb"
    start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile --background --exec $XVFB -- $XVFBARGS
    echo "."
    ;;
  stop)
    echo -n "Stopping virtual X frame buffer: Xvfb"
    start-stop-daemon --stop --quiet --pidfile $PIDFILE
    echo "."
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  *)
        echo "Usage: /etc/init.d/xvfb {start|stop|restart}"
        exit 1
esac

exit 0
% sudo chmod a+x xvfb
% update-rc.d xvfb defaults