http://anond.hatelabo.jp/20130325165709

去年一年振り回されたプロジェクト(まだ振り回されている)がこんなかんじだな。

  • 工期が決まっていない→なんとなく決まってはいるが、しかし長くなることはなく短くなる一方である
  • 企画書の完成日が決まっていない→うちが企画元ではないのであれだが、なんとリリースされても企画書が出て来なかった
  • 競合調査より先に自分たちだけで企画を考える→一応やってはいたぽい
  • ターゲット層の調査より先に自分たちだけで企画を考える→ターゲット層も使う機器についても調査せずに企画が先行
  • 議事録を取らない→まじでなかった
  • 議事録をとり始めても訂正や意見がない→誰も議事録を見ていない
  • 企画書のひな形を作っても曖昧な表現で駄目出しだけする→ひな形すら出て来なかったので、うちで勝手に書いた
  • 思いつくままに会議を進める→これだ
  • 業務進行の定石を無視して効率の悪い方法を採用する→これだ
  • リーダーがウェブに疎い→まさにこれである
  • メンバーに技術者がいない→技術者は一応いたが展開中だった
  • キラーコンテンツの準備より先に課金方法を取り上げる→これだwww
  • ブレスト中にダメ出しする→企画が上がってこないので勝手に書いたストーリーで(それもどうなんだ)要求分析したが確かに駄目出しが多かった(かと言って代案もない)
  • 事あるごとにその分野に疎いことをほのめかす→一部の人々が。いまだに言ってる
  • 素人だからできると豪語する→それはなかったな
  • 素人をターゲットにする→まぁこの分野玄人が少ないので…
  • 素人であるの指摘に対して口論で勝利しようとする→そういう人がいました
  • ユーザとしての疑似体験を却下→却下したわけではないが取り入れる時間がなかった
  • 問題の切り分けをしない→しないひとがいました。それより優先順位が付けられないことが一番の問題だと思う
  • 先人が利用してきたツールや手法を否定する→ウォーターフォール全否定、とかね 否定するなら新たにきっちり決めるかとおもいきやなし崩しで始まったりとか。それを指摘すると逆ギレするとか。別にウォーターフォール反対派ではないしかと言ってアジャイル慎重派でもないんだが、こう行こうという指針がないまま始めるのはどうなのかと思う。だが、だいたいアンチ・ウォーターフォール派はそれをきかないんだよなぁ。なにか決めたらアジャイルじゃなくなると恐れてるみたいで。
  • 一度選定したテーマを途中で覆す→企画が途中でひっくり返ったりとかね
  • 高すぎる目標を設定する→無理だと言っているのに即販ツール作ってとかね
  • 高すぎる目標に対して全部企画に盛り込もうとする→無理だと言ってるのにね。でも企画が一番強いので逆らえない→なし崩し→現場が阿鼻叫喚というのが何度も繰り返された
  • 小さくリリースするの発想を理解しない→最初に理解してもらえなかったが、最近は理解してくれている
  • 一ヶ月以内に市場調査及びユーザの調査をしない→それは一応してたかな
  • 一ヶ月経過してもサービスの制作に入る見込みが立たない→要求は決まってなかったけど最低限絶対に必要なところは開始するなどした
  • 一ヶ月経過してもチームは素人のまま→一部はちゃんと玄人化したがいつまでも素人、もいる
  • 素人の作るサービルは成功の確率が低いことを理解しない→それはなかったな

ギリギリ許容される範囲内での失敗+すこしずつ啓蒙して良い方向へ向かおうとしている+どんな時でも最低限テストは書き、できるテストは自動化し、ツールを導入して繰り返しの作業は消していくという地道な作業で現在も何とか動いているし、これからは改善するだろうと思っている。ぼくはただ淡々と自動化して悪いところはやめて煩雑にならないようにきをつけて(おれがめんどい)、だれにでも使えるようにしてる(呼び出されるのめんどい)けれども、やはりアンチ・ウォーターフォール派の抵抗が根強くてそれをひっくり返される。アンチ・ウォータフォールだからといってアジャイルなのかというとそういうこともないのが困る。ただとにかく書類を書きたくない、責任を取りたくないという信念のもとに行動する人とどうやってチームメンバとして付き合っていけばよいのか、これからも考えなければならない。

今春からようやく会社でレガシーコード改善ガイドを使って勉強会をするようになった(やっと…!)のでちょっと古いエントリを再掲しておく。

「テストを書けばよいのでは?」の一言がためらわれる時もある

整然とし、秩序だった美しい世界を構築する人がいた。設計もコードも、リファクタリングする指針でさえも一貫したポリシーを持って行われていることが、経験のない僕にもよくわかった。たしかに彼が手を入れた部分は美しかった。でもその周りには広大な廃墟とスラムが存在していたのだ。古い、改修しながら使っているライブラリはすでに原型をとどめていない。彼はすこしずつ手直しをしてすこしずつ改善していたけれども、ライブラリには手をつける暇もなく去っていった。広大なスラムだけが残った*1

しばらくそこを離れていた僕がもどってきたとき、最初の仕事はコードレビューだった。かの整然とした世界を構築できる人の代わりにしては僕はあまりにも物事を知らなさすぎるが、それでも何百分の一かの貢献は期待されている。
似たようなコードをそのままコピーして新しいメソッドにしている部分を目にして僕は、同じ処理を抽出してメソッド化したらよいのではないかというレビューをしたが、担当者はスラム化したライブラリがうまく動かなくなるのを怖がってその指摘を受け入れなかった。ライブラリにはもちろんテストがない。クラス関係も複雑で、設計書も仕様書もない。そのコードは関連会社から譲り受けたものだから構築した人もいない。そして、設計ができずスキルの低い僕は自信がなかった。それでもやるべきだとは言えなかった。

レガシーコード改善ガイド (Object Oriented SELECTION)

レガシーコード改善ガイド (Object Oriented SELECTION)

「レガシーコード改善ガイド」を読み始めた。

序文からしてすでに涙目である。
自分のコードがひどいのは前から分かっているし、なにもひどさに涙しているわけではない。

僕はコードが書けない。知識はあっても、簡単なプログラミングができても、僕はコードを書けないと思っている。自信がないのだ。自分が作るものが本当に意図したとおりに動くのか、わからない。作れば作るほど発散し、自分でも構造がつかめなくなっていくことを知っているから怖いのだ。誰かが作ったコードを正確に読み取り、その動作がなんの機能に当たるのか、わかっているつもりになっている気がする。そして僕は変更しない方向を選んでしまう。「できるだけ変更せずにとっておいて新たに書き加える」。その繰り返しが膨大な二度と使わないメソッドとクラスのゴミの山を積み上げることになると知っているのに。

きれいなコードは有益ですが、それだけでは不十分です。テストなしに大規模な変更をしようとすると、チームは危険な賭けに出ることになります。転落防止の網を張らずに空中ブランコをするようなものです。とてつもないスキルが必要であり、ステップごとに何が起きる可能性があるかをきちんと把握しておかなければなりません。
(中略)
ここまで、テストについてかなりたくさん書いてきましたが、本書はテストについて解説している本ではありません。どんなコードでも自信を持って変更できるようにするための本です。本分の各章では、コードを理解し、テストで保護し、リファクタリングし、機能を追加するための手法について説明しています。

はじめ、単体テストを書くのは嫌いだった。面倒でしょうがなかった。でもモックオブジェクトに出会って以来、僕は単体テスト職人になりたいとすら思うようになっている。モックオブジェクトで依存関係を切り、仕様書をにらみ、設計書と照らし合わせながら一つずつテストケースを組み立て、そのテストはどうやってすべきか頭をひねる作業は単純に楽しいというのもある。でもそれよりも効率よくそのメソッドが、クラスが、何を意図しているのか、そしてどういう振る舞いをするのか頭の中に自信を持って組み立てていくことができるということの方が大きい。自分の行った変更が問題なかった、ほかの部分を壊していないと自信を持って言えることの方が大きい。
向かう対象が大きく複雑で汚いほどその作業は楽しいことを僕は知っている。そうやって少しずつ設計とは何かを理解できるようになる。仕様として出てくる機能の背景が気になるようになる。

きっとこれから10年、幸運にも開発者でいられたとしてもきっと僕は整然として矛盾も無駄もない世界を構築することはできないだろう。でも、その世界を理解するための道具はある。始めから終りまで迷うことなく出力することはできなかったとしても、地道にゆっくりと這い寄ることはできる。大きな手術を施すことになったとしても、正してゆくことはできる。たとえスキルが高くなくても、美しい世界を保守することはできるのだ。

*1:廃墟は破棄された。

Seleniumでテストしたページのキャプチャを撮る+画像を全部保存する

http://d.hatena.ne.jp/wonodas+dev/20121129/1354163496 の続き

前回のでもできるはできるのだが、今回の場合

  1. セッションが変わるとログインページに戻ってしまう
  2. imgのsrcに入っているタグが*.jpgなどの形式ではなくURL

file_get_contentsは新しくセッション作ってページの内容を取得するらしく、やれどもやれどもログイン画面のソースコードしか手に入らなかった。んで、しょうがないのでjavascriptを駆使することに…
あとグラフを描画させているんだが、サーバでグラフ画像作って貼り付ける形式ではなくクライアントサイドで描画させる方式のため、img src="URL"のURLで(jpg|png|gif)が含まれないことが判明(先にわかっとけ)。これを取得する方法はなさげなのでとりあえずURL表示させてキャプチャという強引な方法を取ることにしました。

SeleniumではgetEvalまたはrunScriptでjavascriptコードを走らせることができます。

VerifyTestCase.phpに追加

<?php
class PHPUnit_Extensions_VerifyTestCase extends PHPUnit_Extensions_SeleniumTestCase
{
     //中略
    //指定したURLを開いてスクリーンショットを取る(クライアントサイドで生成した画像)
    public function captureImageFromUrl($url, $dir = null, $fileName = null)
    {
        if($dir == null ){
            $dir = __DIR__;
        }
        
        if($fileName == null){
            $fileName = date("Ymd_His");
        }
        
        $this->open($url);
   //余計なのをうつさないために(twitterとかね)最大化しておく
        $this->windowMaximize();
        $this->captureScreenshotAndWait($dir . "/image" . $fileName . ".png");
    }
    
    //現在のページのスクリーンショットを撮る
    public function captureCurrentPage($dir = null , $fileName = null){
        if($dir == null ){
            $dir = __DIR__;
        }
        
        if($fileName == null){
            $fileName = date("Ymd_His");
        }
   //余計なのをうつさないために最大化しておく
        $this->windowMaximize();
        //file名を指定していない場合はimage-Ymd_Hisというファイル名で保存される
        $this->captureScreenshotAndWait($dir. "/image-". $fileName. ".png");
    }

    //URL指定したページのイメージを取得する(jpg, gif, pngだけ)
    public function saveImageFromUrl($url, $dir = null)
    {
        if($dir == null) {
            $dir = __DIR__;
        }
        
        if (preg_match("/(jpg|gif|png)$/i", $url)) {
            //画像のファイル名で保存する
            file_put_contents($dir . "/" . basename($url), file_get_contents($url));
        }
    }

    //ページ内の画像をすべて保存する
    public function saveImagesOnPage($url, $dir = null)
    {
        $this->open($url);
        //今表示しているページのdocumentを取得
        $this->getEval("doc=this.page().getCurrentWindow().document;");
       //imgタグを全部取得
        $this->getEval("imageArr = Array.slice(doc.getElementsByTagName('img'));");
        //imgタグがいくつあるかをimgCountに格納
        $imgCount = $this->getEval("imageArr.length;");

        //なかったら終わる
        if ($imgCount == 0) {
            return;
        }

        $dir = $dir === null ? __DIR__ : $dir;
        $dirPath = $dir . "/" . date("Ymd_His");
        mkdir($dirPath);

        $imgUrlArr = array();
        for ($i = 0; $i < $imgCount; $i++) {
            //imgのsrcを取得して配列に詰める
     //なぜかgetEval("i=0");getEval("iamgeArr[i].src");getEval("i = i+1");だと最初しか取れないのでこうした
            $imgUrlArr[] = $this->getEval("imageArr[" . $i . "].src;");
        }

        //重複はのぞく
        $imgUrlArr = array_unique($imgUrlArr);

        foreach ($imgUrlArr as $index => $url) {
            //jpg,gif,pngだったら画像を保存、そうじゃなかったらキャプチャ(二重チェックになってるなこれ…)
            if (preg_match("/(jpg|gif|png)$/i", $url)) {
                $this->saveImageFromUrl($url, $dirPath);
            } else {
                $this->captureImageFromUrl($url, $dirPath, $index);
            }
        }
    }
}

Seleniumで画像を取得する(キャプチャを使わない)

captureEntirePageScreenshotがfirefox以外だとうまく動かないが、firefoxnetbeansからだとなぜか実行できないので自前でHTML内にある全ての画像を落とすようにした。どうせエビデンス取らなきゃいけないしページが長い場合はスクロールしなきゃいけないけどようわからんので画像ファイルで落とせればいいや、というかんじ。

HTMLファイル

<html>
<body>
    <img id="smp" src="./sample.png">
</body>
</html>

ひとまず画像だけが置いてあるHTML

ストファイ

なお、phpunit-seleniumを使用しております。本心としてはPHPUnit_Extensions_SeleniumTestCaseクラスを継承したPHPUnit_Extensions_VerifyTestCaseを作成して、そこにsaveImageメソッドを作成してどこでも使えるようにする。
テストケースはやっぱりテストケースで分類すべきだよなとちょっと思ったので。ホントはちゃんと名前空間柄使ったほうがいいんだろうけどめんどいので省略。

SaveImageTest.php
<?php
require_once __DIR__. '/PHPUnit_Extensions_VerifyTestCase';

class indexTest extends PHPUnit_Extensions_VerifyTestCase {
    function setUp() {
        $this->setBrowser("*googlechrome C:\Program Files\Google\Chrome\Application\chrome.exe");
        $this->setBrowserUrl("http://localhost/index.php");
    }

    function testGetImage() {
        $this->open("http://localhost/index.php");
        //assertは一個くらいいれておく
        $this->assertElementPresent("id=smp");
        //指定したページの画像ファイルだけを保存する。第一引数がURL, 第二引数は保存するディレクトリパス
        $this->saveImage("http://localhost/index.php", __DIR__);
        $this->close();
    }
}
PHPUnit_Extensions_VerifyTestCase.php

ついでなのでphpUnit-Seleniumに定義されていないverifyなんとかメソッドとかもここに定義してしまおうと思っている。verifyじゃなくてcustomとかにしたほうがいいのかね。まあいいか。

<?php

require_once 'PHPUnit/Extensions/SeleniumTestCase.php';

class PHPUnit_Extensions_VerifyTestCase extends PHPUnit_Extensions_SeleniumTestCase
{

    public function saveImage($url, $dir = __DIR__){
        //時間がかかる場合にタイムアウトしないように念のため0にセット
        set_time_limit(0);
        //<img ~>タグを検索
        preg_match_all('/(<img+(.*?)+>)/i',file_get_contents($url), $matches);
        //重複を削除する
        $getArr = array_unique($matches[0]);

        foreach($getArr as $tag) {
            //<img ~>タグの中身をとりあえず空白で分割する
            $imgTagArr = explode(" ", $tag);
           //空白で分割したものの中からsrc="~"というのを探す
            foreach($imgTagArr as $item) {
                if(strstr($item, "src") != false){
                    //srcが見つかったらファイル名を取得し抜ける
                    $path = explode("\"", $item);
                    break;
                }
            }
            //画像ファイル拡張子がついているかどうかを調べる
            if(preg_match("/(jpg|gif|png)$/i", $path[1])) {
                //ファイルを取得するURLを作成し直す
                $urlReplacePos = strrpos($url, "/");
                $urlFile = substr($url, 0, $urlReplacePos+1) . $path[1];
                //保存ディレクトリ名を生成し、作る
      $dirPath = $dir. "/". date("Ymd_His");
                mkdir($dirPath);
                //ファイルに書き込み
                file_put_contents($dirPath. "/".  basename($path[1]), file_get_contents($urlFile));

            }
        }
    }
}

こんな感じ。

参考:http://qri.seesaa.net/article/120453031.html

Developers summit 2012 行ってきた

devsumiねデブサミ。今年も行って来ました。
17日午前中だけなんですけどね…色々と厳しい状況だったけどJenkinsだけはききたかったので!

「Continuous DeliveryとJenkins」

概要(記憶に残ったのだけ書いとく)
  • マシンの性能は上がっている。一方人の性能は変わらない→それなら自動でできることはマシンにやらせるべき
    • 高性能マシンを並列化しまくったら時間もかからないしコスト削減になる
    • というか時間=コストってことだもんなぁ、人月計算してる以上はと思ったりした
  • 人の性能は変わらんのでコード書く速度はそんなに早くもならない(むしろ大規模になると低下したりもする?)
  • 削れる時間はビルド時間、テスト実行時間→そこでCIツール
  • ただCIツールを入れるだけでなにもかも解決するわけではない
    • たいていはバージョン管理をしているが、サブミット(コミットとかチェックインとかツールによっていい方は様々だが要するにファイルを共有サーバに入れることだ)する前にビルドが通ること、などというルールがあったりすると、結局ローカルでビルドして直して…となってなかなかサブミットできないし、CIツールの意味がないし、ローカルの環境よりサーバのほうがスペック言いに決まっとる
    • 現状でうちでもビルドと単体テストと動的メモリテストなんかはJenkinsを使っているが、基本ローカルで単体テストに通ることを確認してから入れる決まりになっている。んで、うちはC++つかってるんだが、でかいやつになるとビルドに5,6分はかかる…ちょっと直してデバッグしてビルドして…で一日かかるとかザラ
  • なので、ビルドはもう完全にCIツールに任せてしまいましょう、という話だった。
  • あとこれとパイプライン方式を混ぜればなんかうまいこと運用を考えられそうだ
  • とりあえず思ったのは、
    • コードサブミット用ブランチをひとつ作る。開発者はここにビルド成功するかどうか分かんないけどとりあえずコードをサブミットする
    • Jenkinsはサブミットされたらビルドを開始する。ビルド中開発者は別の所を修正したりテストを書いたりとかしておく
    • ビルドが失敗したらメールが来る。開発者は直してサブミットしなおす
    • ビルドが成功したらJenkinsがテスト用ブランチにサブミットする。サブミットがあるとテスト用ブランチで自動テストが走る。失敗したら(ry)。成功したら次のブランチもしくはマスターブランチにサブミット
      • 問題になりそうなのは衝突が起こった場合だな。これはやっぱり手動でしないといけないかもしれない。
      • あといま、自動メモリテストのジョブが成功したら自動的にリリースビルドのジョブが走るようにしてるので、*用ブランチというのをいくつも作らんとサブミット用ブランチ一つとマスター用ブランチでよいかもしれない
      • Perforce連携のプラグインてないんですかね…

「仕事のバトン、渡っていますか? - プロジェクト管理におけるコミュニケーション基盤作り」

どうしようかなーと思ったんだけど一応行ってみた。ちょうど課題管理のことは問題になってるので。ちなみにうちはRedmineをつこうております。
記憶に残ったとこだけ

  • 課題管理は特に理由がないのならExcel最強
    • まぁたしかにそうだけど使いたくないよ
    • 人数多くなるとバージョン管理ツールで管理するのが辛くなる。マージできないし
    • ただ時間軸での管理はExcel最強
  • 曖昧なトリガはチケットとしては不適当。結局グレーのまま放置される
  • はっきりとしたバグとかそういうのだとチケット駆動にはしやすい
  • チケットの内容は丁寧に書きましょう
  • チケットは共有しているものだという意識を持ちましょう
  • 計画段階切っていくチケットはよっぽど慣れていない限りあんまり有効でない。計画の段階では大抵のことは曖昧なため(もしくは曖昧なこともわからない(まぁあるあるですな
  • あとツールはいくつも使うな(あるあるすぎますな
    • メール、電話の内容も全部課題管理表に書きこむこと(うちはこれをバージョン管理ツールでやってしまっている

いやあ本当にまっとうなことばかりだったのですみませんという感じでした。

Wordpressではてな記法を使えるようにしてみた。

http://rewish.org/wp/hatena_notation_plugin
こういうありがたいプラグインが!

今のところ記事ごとに切り替えというのができなくて、会社ローカルで導入する際にpukiwiki記法との併用が出来なかった(pukiwikiでかくとhtmlで吐き出されてしまう)のでちょっとだけカスタマイズした。突貫工事なのであってるかどうかは不明。

wp-hatena-notation.phpにあるrender関数を

public function render($content)
	{
		$tag = '/\[hatena\](.*?)\[\/hatena\]/s';   //追加
		preg_match($tag, $content, $matches);    //追加

		if (!isset($this)) {
			return self::getInstance()->render($content);
		}
		if (empty($this->option['after_enable_date']) && strstr($content, '[hatena]')) {    //条件追加
			return $this->_render($matches[1]);    //代入文字列変更
		}
		global $post;
		if ((self::df($post->post_date) > self::df($this->option['after_enable_date'])) && (strstr($content, '[hatena]'))  ) {    //条件追加
			return $this->_render($matches[1]);    //代入文字列変更
		}
		return $content;
	}

とかきかえた。これで[hatena]〜[/hatena]でくくったところははてな記法になるはず――なんだけどなぜか1ポストまるまるはてな記法になるのであれ?と思っているところ。うーむ。一応記事ごとの切り替えは出来ます。

あとはxoops上のwikiWordpressに吐き出さればいいんだが、エクスポートできないのか?プラグインどれ入れりゃいいのか全然わからん。。。