リファクタリングの必要性を数値で訴えるには

コーディングを進める上で、以下のような指標を考える。

  • コードを書き足す時、ぎりぎりの時間で実装すると増えるが、充分に時間が与えられれば増えない。
  • 仕様の変更があると増える。
  • 仕様が確定しない状態で開発を進めると増える。
  • リファクタリングの工数をとると減る。
  • 開発を始める段階ではゼロで、ゼロより小さくはならない。

直接的なイメージとしては、「削減可能なコード量」である。便宜的に、「冗長量」と呼ぶ事にする。

ビジネス的観点から見た冗長量の意味は以下のようになる。

  • 機能を追加する際のコストは、冗長量に比例する。
  • プロダクトの納品時に、冗長量を低く抑える事は必ずしも求められない。
  • 冗長量が多いと、バグの発生率が高まる場合がある。

開発担当者が冗長量を日々の報告に付け加える事で、ビジネス担当者は仕様変更や緊急開発のリスク、リファクタリング工数のベネフィットを定量的に考える事が出来る。

緊急開発と呼んだのは、たとえばこんなシーンだ。

上司「ねえ君、大至急この機能Aを実装してくれないか」
開発「とりあえず動くだけなら1日で出来ますが、ちゃんと作ろうと思ったら3日は欲しいですね」

こう答えれば、上司は間違い無く、「動けばいいよ、今日中でよろしく!」と言うだろう。(あなたの上司がここで「じゃあ3日で」と言うのであれば、このコラムを読む必要は無い)

冗長量を考慮して、このように答えたらどうだろう。

開発「動くだけのものは1日で作れますが、冗長量は200上がります。冗長量を100下げるのには1日のリファクタリング時間が必要です」

まあ、こう言っても、上司は「1日で」というかもしれない。
開発担当者は、冗長量が200上がった事を報告書にあげておく。

後日、別の緊急依頼が来た。

上司「ねえ君、この前保留にした機能Bね、必要になったから、よろしく頼むよ。」
開発「3日かかります」
上司「以前、2日で作れると言ったじゃないか」
開発「先日冗長量が上がったので。早めのリファクタリングをしないと非効率ですよ?」
上司「ぐぬぬ。。」

数値は必ずしも客観的である必要はなく、開発担当者の主観で適当な値を報告してもらえば良いだろう。
(ただし、減らすのに必要なリファクタリング時間に比例した量であった方が分かりやすいと思う)

もちろん、冗長量に関するデータは給料の査定に響くようにすべきだ。
どのように評価すべきかは、別途議論の余地があるテーマだが。

Model.find(array) で順序が狂う

rails 覚えたての頃に書いた以下のようなコード

def items(array)
array.map{ |i| Item.find(i) }
end

を、以下のように書き換えてハマる。

def items(array)
Item.find(array)
end

タイトルに答えを書いちゃったけど、find(array) は array の順序を保存しない。
array が sort されてれば大丈夫かと思ったが、 mysql では OK だが postgresql ではダメだった。
幸い僕のケースでは array が sort 済みのものだったので、 find(array, :order=>:id) で丸く収まったが、array が sort されてない場合には厄介そうだ。

list_tag で Array をリストに展開

あったらいいのに、ということで作った。ApplicationHelper とかに入れればOK.
array がnilや空のときには ul そのものが出ないのがポイント。

  # ex:
  #  < %= list_tag(:ul, ["menu1", "menu2"], :class=>"menu") %>
  # => 
  def list_tag(wrapper, array, *opt)
    return if array.to_a == []
    content_tag(wrapper, *opt) do
      array.inject(""){|s,i|
        s < < content_tag(:li, i)
      }.html_safe
    end
  end

redirect_toの時はflashでrenderの時はflash.now・・だと??

Railsにはflashという便利な仕組みがあって、redirect_toの先でエラーメッセージを出すのがとても簡単に書ける。便利!
しかし、うっかりそのままrenderしちゃうこともあって、そうするとメッセージが2回表示されるという現象が発生してしまうので、そんなときのために、flash.nowという仕組みも用意されている。
[Rails] flash.now[:notice]とflash[:notice]の違い - 拝啓、シーシュポス
なるほど、よく考えられているなあ、さすがRails.

ちょっとまったー!

僕はnoticeを出したいだけなんですよ。それも1度です。0でも2でもなく1度。そんなん、あたりまえじゃないですか。
なんで、ただ1回noticeを出したいだけの僕が、そのあとrenderになるかredirect_toになるかなんて気にしなきゃいけないんだ?

というわけで、以下のようにした。

ApplicationController に追加

def notice_push(msg)
(session[:notice] ||= []).push msg
end

ApplicationHelper に追加

def notice_pop_all
ret = session[:notice]
session[:notice] = nil
ret || []
end

以上!
使い方?説明しなくてもわかるよね!

Multi-siteをWordPress 3.1-RC2にアップデートしてはまって解決

coreserverでWordpressをMulti-site運用している。
もともとWPMUを入れていて、WP3にアップデート、現在に至る。

/wp-admin/ に入ろうとすると &reauth=1 に飛ばされて永遠にログインできない事態に巻き込まれ、泥臭く調査してどうにか解決した。

1. ResponseのSet-Cookieヘッダを確認
まずFirebugの用意。ChromeとかIEのツールはリダイレクトを追えないようだ(できるの?誰か教えて!)
まあ、HTTPを見られればなんでもいいです。
で、いっちゃん最初のPOSTからのResponseを確認する。Set-Cookieヘッダ内に domain=とpath=があるはず。僕の場合はこの2つがおかしくてログインに失敗していた。

2. wp-config.phpから、define(“〜〜_CURRENT_SITE”, 〜〜) を削る
これは確か、WPMUあたりでマルチサイト化を始めるにあたり、「wp-config.phpにこれを書いとけよ!」と指示されていたものなのだが、こいつがあるとSet-Cookieのdomainとpathがここに書いた設定に強制的になってしまう。詳しくは wp-includes/ms-load.php の wpmu_current_site() を見るといい。

3. wp_siteテーブルに子サイトの情報を書き込む
これ、なぜか手作業。RC2のバグかもしれん。報告した方がいいのかな。。。そわそわ。

4. wp-include/ms-load.phpを修正
なおらん!どう考えてもおかしい!ということでけっこう強引に直してしまった。
この修正はあんま自信無いです。(動くけど、方針に合ってないかも)
function wpmu_current_site() の真ん中の辺り。

if ( $current_site ) {
	$path = $current_site->path;
	$current_site->cookie_domain = $cookie_domain;
	define('COOKIE_DOMAIN', '.' . $current_site->cookie_domain);
	define('COOKIEPATH', $path);
	return $current_site;
}

2つのdefine分が追加箇所。

とりあえずこれで動いてます。たぶん。

ControllerとViewは対応しなくてもいいってさ!

Railsのいくつかのチュートリアルをこなし、いくつかのオンラインドキュメントを読んで、MVCの使い分けはだんだんわかってきた。しかし、はたと立ち止まってしまったことがあったので記録。

ごく簡単な名簿アプリケーションを作る。2つのmodel、 Group と Person があるとする。
Person はいずれか1つの Group に関連づけられるものとする。

/:id を GroupController へ繋ぎ、所属する Person のリストが表示されるものとする。
/:id/:person_id をPersonControllerへ繋ぎ、Person の各種属性を表示編集できるようにする。
Groupを新規作成するための form を / とか /new とかに置く。
ここまではまあ、普通。

Person を新規作成するための form は、どうしようか。
教科書的に考えれば、 /:id で表示されるリストの末尾とかに「人を追加」ってなリンクを用意し、これが /:id/new かなんかに飛んで PersonController#new で承る、と、こうなる。

しかしもし、Person の作成に必要な情報が3つとか4つだったら、「人を追加」なんて悠長なことを言うより、とっととそこにFormを埋めてしまえという気分になる。

ハンドル 生年
大久保 kuboon 1980
猫村 catchocolate 1989
追加

こんなかんじぃ?

RailsのMVC哲学からちょっと外れたような気もするが、UIの最適化を考えれば自然の成り行きである。
さてさて、次が問題だ。

このformのPOST先をどうしよう?

formをGroupControllerで生成したとはいえ、これから生成しようとしているのはPersonであるから、PersonControllerで処理してやりたい、というのが人情というものである。
実際そのような記述は可能であるし、一見何の問題も無いような気が、少なくとも僕はしたのだが。。。
Validationめんどい。。。
中止!中止!

明らかにRailsは、formとそのPOST先が同じControllerであることを要求している。
POST先を別のControllerにしちゃあまずいのである。

と、いうことがわかった。

さーてどうするんだ。「この人でなし!」と言われるのを覚悟でGroupControllerでPersonを生成しちゃうのか、それとも、やはりイニシエの掟に従って、GroupController 側は「人を追加」リンクのみに留めておけということなのか、はたまたajaxでformを表示しちゃうようなウルトラCが既に常套となっているのか。

と、一人ぶつぶつと悩んでいたところで頂いた神の助言が表題である。

ControllerとViewは対応しなくてもいいんだよ!

まーじーかー!
ということで、GroupControllerへPOST、そこでPerson.newし、saveに失敗したらそのままformを再表示、成功したら取得したての:person_idを使って /:id/:person_id へとリダイレクトという流れに落ち着いた。

おしまい。

_focusrect(as2)やstageFocusRect(as3)だけではTabキーは無効になっていない

黄色い矩形が消えているだけで、フォーカス移動は発生している。

http://wonderfl.net/c/8m4M

フォーカス移動を消すにはTabキーへのイベントハンドラを書く。

IE6と7以降でIEキャッシュの命名規則が違う

http://127.0.0.1/ のようなURLにキャッシュ可能な文書を置いてアクセスし、Temporary Internet Files に生成されるファイル名を確認すると、

  • IE6 では、 127.0.0.1[1].txt となる
  • IE7/8 では、 127_0_0_1[1].txt となる

なんて微妙な仕様変更。

SDカードから失われた動画を復旧する

ffmpeg で -i オプションにワイルドカードを使ったらSDカード内のAVIがごっそり0バイトになって焦った。

ここは本気を出さねばならない。

selfImageで、メディアまるごとのバイナリイメージを取って、以下のコードを実行し、「”RIFF”から始まるファイル」に分割。なんせ元のファイルサイズが失われてるのでほんとはきちんとRIFFフォーマットを解析しなきゃならないのだが、面倒なので。

あとは、出来上がった「後ろにおまけのついた動画ファイル」を無理やり動画コンバータに放り込んで変換成功。

ふぅ、冷汗かいた。

int _tmain(int argc, _TCHAR* argv[])
{
char buf[BUF_SIZE];
FILE* fp;
::fopen_s(&fp, "sd_card.img", "rb");
FILE* fOut=NULL;
int count=0;
while(!::feof(fp)){
::fread(buf, 1, BUF_SIZE, fp);
if(fOut)::fwrite(buf, 1, BUF_SIZE,fOut);
long cur=0;
while(cur<BUF_SIZE){
const char* pos=(const char*)::memchr(buf+cur, 'R', BUF_SIZE-cur);
if(!pos){
break;
}
if(::memcmp(pos, "RIFF", 4)==0){
if(fOut)::fclose(fOut);
char fname[20];
::sprintf(fname, "%d.avi", count);
fOut=::fopen(fname, "wb");
::fwrite(pos, 1, BUF_SIZE-(pos-buf), fOut);
count++;
}
cur=pos-buf+1;
}
}
if(fOut)::fclose(fOut);
::fclose(fp);
return 0;
}

substとショートカットの怪しい関係

  1. substでドライブ作る
  2. 出来たドライブへのショートカットを作る
  3. subst /dでドライブを消す
  4. さっき作ったショートカットのリンク先がいつの間にか変わる。

きもーい。