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

PEARのインストール

既にシステムにPEARが入ってる場合。

% sudo pear uninstall pear

PEAR公式ドキュメントの通りにやる。

% curl -O http://pear.php.net/go-pear.phar
% php -d detect_unicode=0 go-pear.phar

(debianの場合)

% wget http://pear.php.net/go-pear.phar
% php -d suhosin.executor.include.whitelist="phar" go-pear.phar

PHPUnitのインストール

公式ドキュメントの通りにやる。

% pear config-set auto_discover 1
% pear install pear.phpunit.de/PHPUnit

include_pathは自動で追加してくれるけどPATHは通してくれないので自分で通す。

export PATH=~/pear/bin:$PATH
% phpunit --version
PHPUnit 3.6.12 by Sebastian Bergmann.

せばすちゃああああん

$ brew install selenium-server-standalone
$ cp "/usr/local/Cellar/selenium-server-standalone/2.25.0/homebrew.mxcl.selenium-server-standalone.plist" ~/Library/LaunchAgents/
launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.selenium-server-standalone.plist

便利な時代になったもんですね。(結構メモリ食うのでウザくなったら起動しない設定に変えといたほうがよさそう)

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

さくら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改善日記シリーズ

プロジェクト名に愛が無い

そしてリポジトリ名がncrm(多分New CRMの略)。だったら更に新しいの出たら何になるのか。nncrmか?nnncrm、n5crmとかschemeの仕様みたいになっていくのかと小一時間(略

テストが無い

テストぉ?そんなお上品なもんなんざぁ、とんとお目にかかったことねーなぁ?

バリデーションが無い

バリデーション?そんなお上品なもんなんざぁ(略

サーバーがrootログインの許可+IP制限している

セキュリティを高めたいのか低めたいのかどっちなのか。使い辛いわ。

バージョン管理システムがよくわかってない

なぜトップにぶち撒けられてる?trunkはどこ?branchesとtagsはなぜ空?

メソッドが大文字から始まる

あんた絶対Windows畑から来たね?同じ調子でPHP書かれても困るんだヨォ。

全テーブルに共通のプレフィックスが付いている

いや、データベース名があるからわかるよ。

テーブル名に型が付いている

はいはい、mはマスターでtはトランザクションね。データベース版ハンガリアン記法。

テーブル名が日本語

逆に分かり辛い。

テーブル名が日本語の複数形

「フレームワークの規約で複・・・」シャラップ!!みなまで言うな!!

テーブル名がその全部だ

もう何が何やら・・・

レガシー改善仲間募集

「これはひどい」といっても実際にはそこら中にいっぱいありますよね、こういうプロジェクト。ひとしきり毒づいたあとは一つづつ地道に改善していけばいいんです!

僕が現在関わっているプロジェクトではこういったレガシーPHPを改善してださるPHPプログラマー仲間を募集しております。くわしくはコチラ

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

ほんとたすけてくだしあ・・・

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

PHP Conference 2012に来ています。

PHPプログラマー・・・募集っっっ!!圧倒的・・・募集っっっ!!徹頭徹尾、募集っっっ!!

最近、大門からほど近い会社さんに週2日いってPHPプロジェクトのお手伝いをしています。所謂一つのレガシー改善というやつです。

PHP5.3.3 + CakePHP1.3って感じなんですが、圧倒的にPHPプログラマーが足りないっ!

なので、PHPプログラマーのかた、フリーランス・開発会社様、問いませんので、PHPプログラマーのかた、またはPHPプログラマーをご存知のかた、ご紹介いただけると嬉しいです・・・。(出来れば僕といっしょに常駐していただければなお・・・)

もちろんtestディレクトリなんて空っぽだったのでテストを追加したり、jenkinsでCI環境作ったりしています。一緒にPHPをガリガリ書いてくださるかたを募集しています。というか助けてください・・・。

ご連絡はTwitterで@komagataもしくはkomagata@gmail.comまで「興味あるよ!」って感じでいただければありがたいです。

※追記:

「来週までにPHPとCakePHP覚えといて」

と無茶振りしてるので、チンカスーくん(チンカス + わぷーくん)もいます。

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

cakephp1.3はwebアプリとしてのtestrunnerしか無い。terminalからやりたいとかjenkinsでshell exexuteしたいときとかに困る。

神の子、@itemanさん達が作られてるStagehand_TestRunnerがCakePHP x simpletestの実行に対応してるのでそれを使えばOK。

CLI のための継続的テストランナー v3 - Stagehand_TestRunner - Piece Framework

Stagehand_TestRunnerをComposerでインストール

% vi composer.json 
{
  "require": {
    "piece/stagehand-testrunner": ">=3.3.1"
  }
}
% php composer.phar install
% ./vendor/bin/testrunner --preload-script=vendor/autoload.php --cakephp-app-path=app --cakephp-core-path=cake cakephp app/tests/cases/models/post.test.php

(ハマっていた時、Twitterで@itemanさん、@rskyさんに助けていただきました。ありがとうございます。)

% curl -s http://getcomposer.org/installer | php

http://packagist.org/ を見て欲しいライブラリをcomposer.jsonを書く。

% php composer.phar install

bundle installと同じ。vendor以下に色々入る。あとはアプリの方でvendor/autoload.phpをrequireすればいい。(Bundler.requireみたいなもん)

xxx.pharはxxx.jarみたいなもん。

CakePHP1.3でtestを書くにはsimpletestが必要。(デフォルトで付いてない)CakePHP1.3はちょっと古いのでsimpletestの最新では動かないので1.0系を使う。

まずFixtureを書く。Fixtureはクラスで書く。(これyamlとかでできないかなあ)

<?php
class PostFixture extends CakeTestFixture {
    var $name = 'Post';
    var $table = 'posts';

    var $fields = array(
        'id' => array(
            'type'    => 'integer',
            'null'    => false,
            'key'     => 'primary'
        ),
        'name' => array(
            'type'    => 'integer',
            'null'    => false
        )
    );

    var $records = array(
        array('id' => 1, 'name' => 'komagata'),
        array('id' => 2, 'name' => 'machida'),
        array('id' => 3, 'name' => 'yoshida')
    );
}
?>

modelのテストケースを書く。

<?php
class TestPost extends Post {
    var $cacheSources = false;
    var $useDbConfig = 'test';
}

class PostTestCase extends CakeTestCase {
    var $Post = null;
    var $fixtures = array('app.post');

    function startTest() {
        $this->Post =& ClassRegistry::init('Post');
    }

    function endTest() {
        unset($this->Post);
        ClassRegistry::flush();
    }

    function testPostInstance() {
        $this->assertTrue(is_a($this->Post, 'Post'));
    }

    function testFetchKomagata() {
        $this->Post->recursive = -1;
        $result = $this->Post->fetchKomagata();
        $expected =  array(
            'Post' => array('id' => 1, 'name' => 'komagata')
        );
        $this->assertEqual($result[0], $expected);
    }
}
?>

こんな感じ。/test.phpから結果が見れる。

phpの読み込みはコメントアウトされてるので外す。

# /etc/apache2/httpd.conf:
LoadModule php5_module libexec/apache2/libphp5.so

VirtualHostの設定の読み込みがデフォルトでコメントアウトされてるので外す。(罠)

# /etc/apache2/httpd.conf:
Include /private/etc/apache2/extra/httpd-vhosts.conf

Web共有は無くなったのでapachectlを使う。

% sudo apachectl restart