なんかばんざい 2009-06-16T00:21:12+09:00 tt25 http://tt25.org/blog/rss MySQLの変な癖、あるいはPostgreSQL使いがMySQLと接するときの心構え http://tt25.org/blog/20090615/mysql-postgresql 2009-06-16T00:21:12+09:00 2009-06-16T00:21:12+09:00 はじめに

PostgreSQLのクエリプランナ/クエリオプティマイザは非常に優秀です。ユーザーはただ自分が欲しいデータをSQLで記述し、酷使するカラムに対してインデックスを張ってやればいいだけです(DB自体が酷使されてるなら細かいチューニングは必須です)。MySQLはそれら一連の最適化を人間が担当することになります。

後で詳しく書きますが、たとえば、"SELECT * FROM foo WHERE id IN (1,2)"と"SELECT * FROM foo WHERE id=1 OR id=2"の2つのクエリを、PostgreSQLはまったく同じように処理します。意味が同じなので当然といえば当然です。MySQLもたぶんここまでは同じです。問題は次。

  • "SELECT * FROM foo WHERE aid=1 OR bid=2 ORDER BY cid DESC LIMIT 10"
  • "SELECT * FROM (SELECT * FROM foo WHERE aid=1 UNION SELECT * FROM foo WHERE bid=2) as tmp ORDER BY cid DESC LIMIT 10"

aidとbidとcidの3カラムが登場しました。どちらのクエリも意味は同じです。さて、aid,bid,cidともにインデックスが張られているとして、MySQLでは後者のほうが圧倒的に高速です(コメント欄1つめ)。PostgreSQLではどちらも同じか、2つ目のごちゃごちゃしたやつのほうが遅いかだと思います。ちなみに、2つ目のSQLにおいてcidのインデックスは使われません。

本稿では、このようなMySQLの癖についてとりあえずまとめたいと思います。PostgreSQLは8.2以降、MySQLは5.1を想定しています。

MySQLはSELECT文の中で1つのインデックスしか使わない

たとえaid,bid,cidにそれぞれインデックスが張られていようとも、SELECT (略) WHERE aid=1 OR bid=2 ORDER BY cid DESCという一文において使われるのはひとつだけです。aid=1を探すのにインデックスを使うと、bidの絞り込みは全スキャン(MySQLのEXPLAINでいうとALL)、cidでのソートもまた全スキャン(同Filesort)になります。こういうクエリをEXPLAINすると、負け惜しみのようにpossible_keysとかいって候補一覧を出してくるところが苛立たしいです。わかってんなら使えよ。

この欠点というか怠惰なところに対処したのが、さっきのUNIONとか出てきたほうのクエリです。UNIONの前後でそれぞれ独立した文なのでインデックスを計2回使えます。UNIONで動的に作ったテーブルなのでfooに張られたcidのインデックスは出番がありません(Filesortになります)。いちおう次のようにすることで、すべてインデックスを使うクエリにできます。

  • CREATE TEMPORARY TABLE tmp (KEY(cid)) (SELECT * FROM foo WHERE aid=1 UNION SELECT * FROM foo WHERE bid=2)
  • SELECT * FROM tmp ORDER BY cid DESC LIMIT 10

UNIONの結果集合(WHEREで絞り込んだ部分集合)をテンポラリテーブルにして、cidにインデックスを張っていったん終わり、次のクエリで改めてORDER BYして取り出す形です。tmpの集合をこれ以外の用途(たとえばSELECT count(*)するとか)にも使う予定があるなら、テンポラリテーブルにして使いまわしたほうがいいと思います。

さて。ORがUNION(和)ならANDはINTERSECT(積)だな、と思うかもしれませんが、MySQLはINTERSECTに対応してません。このようにサブクエリをJOINしまくることで一応実現はできますが、インデックスは不完全燃焼です。ORのときのようにテンポラリテーブルを作ってもいいですが、一般的にはインデックスの張り方を工夫して対処します。

たとえばWHERE did=5 AND eid=6という条件式であれば、didとeidの複合キーを作ります。ここでWHERE eid=6 AND did=5と書くとdid_eidなインデックスは使われないので注意です。意味は同じですが解釈が変わり、MySQLは黙々と全スキャンし始めます。

O/Rマッパのようなものを使っているのならWHERE句になるべきものは配列か何かで保持しているはずなので、SQLへ変換する前に辞書順でソートするようにしておけば順番については気にしなくてもいいかもしれません。

しかし調子に乗ってaid,bid,aid_bid,cid,did_eidなどなど片っ端からインデックスを張るのは美学的にも実用的にもおすすめできません。実テーブルの数倍の容量を持つインデックスがいい仕事してくれるときもありますが、なんというか、効果も感想も微妙です。

DATE型、DATETIME型のインデックスは信用しない。TIMESTAMP型は忘れる。

言いたいことはここで全部言われてますが、補足すると、DATETIME型のカラムをBETWEENしたいときは+ INTERVAL 1 DAYとかするといいらしいです(未検証)。たぶんdtが文字列系の何かにキャストされて張られたインデックスがはがれ落ちるのを防ぐ意味があるんだと思う。ていうかそもそもDATE型の範囲が1000-01-01から9999-12-31ってとこからして、内部的には中途半端に文字列として扱われてるような気もする。

あ、PostgreSQL使いに向けての説明だってことを思い出したので書いておきます。CREATE TABLE foo ( dt DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP)は使えません。CREATE TABLE時にCURRENT_TIMESTAMPが解釈され、テーブルを作った時刻がデフォルト値になります。INSERT時に毎回CURRENT_TIMESTAMPを指定してください。それを自動でやってくれるのがTIMESTAMP型とのことですが、まったく関係ないUPDATEが走っても勝手にカラムの値が更新されます。つまり、updated_atには使えるけどcreated_atには使えないということです。

この微妙な状況を打開するため、日付情報を整数型として使う手が出てきます。20090615みたいなやつですね。秒まで記録するにはINTじゃ桁が足りないのでBIGINTを使って20090615232425のようにします。

こいつにインデックスを張り、仮にカラム名をtsとすると、ts >= 20090615000000 AND ts < 20090616000000とすることでインデックスを使いつつ特定日時・時刻の結果が得られます。ts LIKE '20090615%'のように手抜きしないこと。tsが文字列になってインデックスが使われなくなりますので。FLOOR(ts / 1000000) = 20090615も同様にダメです。


JOINはなるべくしない。サブクエリは丁寧に。

「RDBMSはJOINが遅いから〜」という言説を聞いたことがあるかと思います。これも上で述べた「1 SELECTで使われるインデックスは1つ」がその理由です。ていうかRDBMSじゃなくてMySQL特有の現象だと思います。

JOINをなるべく控えるのは、もちろん「1 SELECTで使われるインデックスは1つ」の法則により、貴重なインデックス使用枠がひとつ減るからです。逆にサブクエリは、インデックスの使用枠をひとつ増やせるので積極的に使っていきましょう。

PostgreSQLのような賢いクエリプランナが居ないことを思い出してください。WHERE posts.writer_id IN (SELECT id FROM writer WHERE name LIKE 'john')と書けば勝手にWHERE EXISTS(SELECT 1 FROM writer WHERE name LIKE 'john' AND id=posts.writer_id)と同じ意味だと理解してよしなにやってくれますが、MySQLでは後者のほうが速いです。サブクエリの中のSELECTも*やidなどではなく1などの定数を使ったほうが若干速いようです。

LIMITは処理時間をほとんど低減しない

ORDER BY foo DESC LIMIT 10と書けば上から10件見つかった時点で処理を停止するので、LIMIT 100やLIMITなしよりも速いはずだと思いがちですが、別にそういうわけでもありません(細かいことをいえばLIMIT 1よりLIMIT 1000000のほうが遅いに決まってますが、フェッチの時間を無視すれば誤差です)。例外はLIMIT 0で、これはクエリの解析を終えた時点で終了するので(実データを見にいかないので)高速です。文法チェッカーとしてLIMIT 0を使いましょう、みたいなことをMySQLのサイトで見た気がします。

PostgreSQLはたしか8.2か8.3あたりでORDER BY 〜 LIMIT構文の最適化がなされて、LIMIT 10ならまず上位10件を特定し、その10件だけをソートすることで高速化してる(つまりLIMITしたあとORDER BYしてる)のでLIMITの数はけっこう影響しますが、MySQLは全部ORDER BYしたあと改めてLIMITなので、1でも1000でもそんなに差はないです。

集約関数はそれほど遅くない

MySQLはGROUP BYやcount(),sum()などの集約関数が遅いと言われていたので身構えてましたが、いろいろ触るうちに言うほど遅くないなと思うようになりました。集約関数よりも、インデックスがひとつしか使われない影響の方が大です。JOINがインデックス枠をひとつ消費するようにGROUP BYもひとつ消費するので、それに気をつけていれば大丈夫かと。

MySQLのGROUP BYはPostgreSQLのような厳格さがなくて個人的には使いやすいです。

他にも何かあった気がするけど思い出せない

とりあえず第一部はこれで終了ということで。

まとめ:そりゃみんなRDBMSよりDHTに興味持つわ

]]>
SinatraとTokyo Cabinet使って25行でチャットスクリプト書いた http://tt25.org/blog/20090610/tinychat 2009-06-10T00:19:34+09:00 2009-06-10T00:19:34+09:00 http://github.com/tt25/tinychat/tree/master

http://github.com/tt25/tinychat/blob/a0aa1b01105a66f5c117278c0b1ed1aa8f312d32/index.rb

元ネタの1/4くらい。思ったより短くならなかった。

データベースがTDBなのでやろうと思えば簡単なログ検索もつけれるけどパス。

元ネタ:http://alpha.mixi.co.jp/blog/?p=1029

]]>
SinatraでErubisを使う(Sinatra/0.9.1.1) http://tt25.org/blog/20090502/sinatra-with-erubis 2009-05-02T00:44:07+09:00 2009-05-02T00:44:07+09:00 SinatraはErubisじゃなくERBを使ってるので<%== @var %>とかの構文が使えない。

githubのソースを参考に作ったのに引数の数がおかしいとか言われたので*argsで逃げた。

# start.rb

require "rubygems"
require "sinatra"
require "erubis"

module Sinatra::Templates
  def erubis(*args)
    render :erubis,*args
  end

  def render_erubis(*args)
    erubis=Erubis::Eruby.new(File.read("views/#{args.first}.erubis"))
    erubis.result(binding())
  end
end

get "/" do
  @test="a<h1>h1</h1>a"
  erubis :index,:layout => "wrap"
end

views/index.erubis

<%= @test %>
<%== @test %>

views/wrap.erubis

layout start
<hr />
<%= yield %>
<hr />
layout end

なんか細かいところが気になるけど上手く動いたからまあいいや。

Sinatraのバージョンは0.9.1.1で試したけど将来APIとかが変わる可能性は高いと思うのでご注意。

それにしてもMerbのルーターとRailsのモテっぷり(webratやcucumberが普通に使える)とSinatraの潔さとRamazeの貪欲さを備えたフレームワークはないんでしょうか。

]]>
TwitterFoxの入力欄が伸びてうっとうしいので改造した手順メモ http://tt25.org/blog/20090419/twitterfox-patch 2009-04-19T01:41:46+09:00 2009-04-19T01:41:46+09:00 TwitterFoxを開いて日本語を入力すると、ちょうど変換を確定した瞬間に入力欄が変身しメッセージが途中で送信されたりしてうっとうしいので、常に伸びている状態に改造します。

TwitterFoxの話ですが普段はP3:PeraPeraPrvを常用してます。いろいろあってTwitterFoxも使うことになったので作業したメモです。

ここでは現時点で最新のTwitterFox 1.7.7.1をベースに話を進めますが、たぶん近いバージョンなら同じだと思います。もちろん本家が更新されれば同じ手順の繰り返しなので微妙な解決手段ですけども、僕の用途だとそれでもまあ大丈夫なのでいちおう書きます。バージョン追従が面倒になれば他の手を探せばいいし、みたいな。

当然ながら無保証ですし、Firefoxアドオンの自動更新の仕組みすらよくわかってないまま書いてます。

1. xpiファイルをダウンロードして解凍する

twitterfox-1.7.7.1-fx.xpiはただのzipファイルなので、拡張子を変えていつもの方法で解凍します。いくつかのファイルとフォルダが出てくるかと思います。

2. オリジナルと区別するためにinstall.rdfにちょっとした変更

上のほう(7行目)に、

    <em:name>TwitterFox</em:name>

というのがあるので

    <em:name>TwitterFox Patched</em:name>

とでもしておきましょう。これがアドオンリストに出てくる名前になります。

3. chrome/TwitterFox.jarファイルを解凍

ただのzipファイルなので、拡張子を変えていつもの方法で解凍します。contentとlocaleができるはず。

4. 解凍して出てきたcontent/twitterfoxnotifier.xmlを変更

http://gist.github.com/97675にパッチを置いてますが、変更点はちょっとなので手でやってもいいです。

大雑把に説明すると、赤いマークの行を消して緑のマークの行をコピペで同じ場所に追加すればOKです。ファイル名っぽいものとかグレーのところは無視してください。

5. contentとlocaleをTwitterFox.jarへと戻す

普通にZIPにすればOKですが、ディレクトリ構造に注意してください。古いほうのTwitterFox.jarファイルは消すなりリネームするなりして退避しておきます。

6. 全体をZIPにして適当な名前.xpiにする

手順5と同じような感じです。ディレクトリ構造に注意。

ここでは仮にtw.xpiとします。

7. tw.xpiをFirefoxにドラッグ&ドロップしてインストール

オリジナルのTwitterFoxを削除して、新しくTwitterFox Patchedをインストール。Firefoxを再起動すれば終わりです。アカウント情報とかも引き継げているはず。

]]>
Ubuntu 8.04上でRuby 1.8.7をソースからコンパイルしてRedmineを動かすまで http://tt25.org/blog/20090308/redmine-build-from-source-ruby 2009-03-08T02:33:00+09:00 2009-03-08T02:33:00+09:00 Rubyのインストール

とりあえず/home/user/rubyにRubyをインストールする前提で。

# wget http://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p72.tar.gz
# tar xvf ruby-1.8.7-p72.tar.gz
# sudo apt-get build-dep ruby
-- Rubyのビルドに必要なパッケージがわらわらとインストールされる --
# cd ruby-1.8.7
ruby-1.8.7# ./configure --prefix=/home/user/ruby
ruby-1.8.7# make
ruby-1.8.7# make test
ruby-1.8.7# make install
# ~/ruby/bin/ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux]

これでインストール完了。続いてPATHを通す。

# vim ~/.bashrc
export PATH=/home/user/ruby/bin:$PATH
alias sudo="sudo env PATH=$PATH" # セキュリティの都合でsudoがPATHを引き継がないのでこれで対応
# source ~/.bashrc
# ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux]

Rubygemsのインストール

sudo ruby setup.rbのsudoは要らないかもしれない。

# wget http://rubyforge.org/frs/download.php/45905/rubygems-1.3.1.tgz
# tar xvf rubygems-1.3.1.tgz
# cd rubygems-1.3.1
rubygems-1.3.1# sudo ruby setup.rb
rubygems-1.3.1# gem -v
1.3.1
# sudo gem i rack
no such file to load -- zlib (LoadError)

zlibがないと言われるので以下のようにする。ここもやっぱりsudoじゃなくていい気がする。

# sudo apt-get install zlib1g-dev
# cd ~/ruby-1.8.7-p72/ext/zlib
~/ruby-1.8.7-p72/ext/zlib# ruby extconf.rb
~/ruby-1.8.7-p72/ext/zlib# make
~/ruby-1.8.7-p72/ext/zlib# sudo make install
# sudo gem i rack
Successfully installed rack-0.9.1
1 gem installed

ちゃんと入った。

subversionのインストール

普通にapt-getから。

# sudo apt-get install subversion

SQLite3のインストール

これも普通にapt-getから。

#  sudo apt-get install libsqlite3-dev 

gemも忘れずに。

# sudo gem i sqlite3-ruby

Rails(version 2.1.2)のインストール

Redmine 0.8がRails 2.1.2を要求してくるので入れる。

# sudo gem i rails -v=2.1.2

Rakeのインストール

普通にgemで。

# sudo gem i rake

opensslのインストール

Redmineがopensslないぞって怒るので入れる。

# sudo apt-get install libssl-dev
# cd ~/ruby-1.8.7-p72/ext/openssl
~/ruby-1.8.7-p72/ext/openssl# ruby extconf.rb
~/ruby-1.8.7-p72/ext/openssl# make
~/ruby-1.8.7-p72/ext/openssl# make install

Redmineのインストールと初期設定

Redmineのダウンロードページを参考に。ここでは0.8ブランチからチェックアウト。

# svn co http://redmine.rubyforge.org/svn/branches/0.8-stable redmine-0.8
# cd redmine-0.8
redmine-0.8# mv config/database.yml{.sample,}
redmine-0.8# vim config/database.yml
productionのadapterをsqlite3へ変更
redmine-0.8# rake db:migrate RAILS_ENV=production
-- データベースつくる --
redmine-0.8# rake redmine:load_default_data RAILS_ENV=production
redmine-0.8# ruby ./script/server RAILS_ENV=production
=> Booting WEBrick...
=> Rails 2.1.2 application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2009-03-07 17:26:29] INFO  WEBrick 1.3.1
[2009-03-07 17:26:29] INFO  ruby 1.8.7 (2008-08-11) [x86_64-linux]
[2009-03-07 17:26:29] INFO  WEBrick::HTTPServer#start: pid=14527 port=3000

これで終わり。あとはApache+Passengerするなりthinやebbでサーバ立てるなりお好みで。

]]>
GoogleサジェストAPI使ってGoogle's eyeつくった http://tt25.org/blog/20090307/googles-eye 2009-03-07T22:18:53+09:00 2009-03-07T22:18:53+09:00 http://raurublock.tumblr.com/post/84056744/id6nhh-jpg

が面白い使い方だなあと思ったのでつくった。pure javascriptなのでソースを一式ダウンロードしたあとindex.htmlを開けば普通に動きます。

http://gist.github.com/75318

http://tt25.org/static/googles_eye/

addEventListenerの分岐が面倒だったのでIEでは動きません。FirefoxとOperaで動いてるのを確認しましたが、Firefoxのほうが軽快に動くっぽいです。

それにしてもイギリスオワタとかアメリカオワタとかいろんな国が終了してますね。

]]>
Ubuntu 8.04でFirefox 3.0.6を独自ビルドしたときのメモ http://tt25.org/blog/20090302/build-firefox3 2009-03-02T23:59:15+09:00 2009-03-02T23:59:15+09:00 ブラウザスピードを気にするわりにソースからビルドしたことなかったので経験しておいた。Ruby 1.8の話だけども、apt-getで入れたRubyと、ソースからコンパイルしたRubyで速度が倍違うてのを見たのでFirefoxでも試してみる価値はありそうだと思いまして。

メインブラウザはOperaで、Firefoxはニコニコ動画とか開発とかにしか使わないので普通のバイナリとの速度差はよくわからないけど、レンダリングはやたら速くなったような気がする。

あとgcc -march=nativeで手元のPC(CPU)に最適化されるということを知った。

ソースコードのダウンロード

  1. ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/3.0.6/source/からtarballを落として解凍。mozillaディレクトリが出来るのでそこへcd。
  2. mozconfigを書く。
  3. make -f client.mk build
  4. 2時間以上待つ。
  5. mozilla/foo22/dist/bin/firefoxを起動

foo22てのはmozconfigで指定してるディレクトリ。普通はもっとまともな名前を付ける。

mozconfig

ccache付けたら速いとどっかに書いてたので付けたけど、速くなるのは2回目以降のビルド時間であってバイナリにはまったく関係ないということがあとでわかった。

http://gist.github.com/raw/72769/36058bd2c618d9662394ab74e32fd0dfb6c639ec/mozconfig

その他

about:buildconfigとかのメモ。

http://gist.github.com/72769

参考にした

]]>
LaTeXの走り書き http://tt25.org/blog/20090221/latex-memo 2009-02-21T01:55:18+09:00 2009-02-21T01:55:18+09:00 ちょっとTeXを速習する必要があったのでつまみ食いし続けた結果を自分用にまとめておく。

Ubuntu 8.04でのインストール

sudo apt-get install latex-extra-jaでたぶん必要パッケージが全部取ってこれるはず。Windowsはぐぐれ。

関係ないけど未知のものをaptでインストールする場合、なるべく瑣末そうなパッケージをインストールすると依存関係の都合で必要なものがほぼすべて揃うのでオススメ。必要ないものが入る可能性も高いけど、変なところでつまづくリスクとトレードオフ。

TeXとLaTeXの違い

よくわからん。shとbashのようなもの?

簡単な書式

% ここはコメント

\foo % fooマクロだかfooコマンドだかを呼び出し

\bar{ あああ } % barマクロだかコマンドだかに「あああ」を渡して呼び出し

\baz[opt]{ いいい } % bazマクロだかコマンドだかにオプション「opt」、引数?「いいい」を渡して呼び出し

\def\hoge{} %hogeマクロだかコマンドだかを定義

% TeX組み込みの\titleを使って文書のタイトルを設定
% この定義はPDFにも持ち越される。
% \authorとか\dateも同様。

\title{この文書のタイトル}
\author{tt25}
\date{\today} % \todayでその日の日付


% documentclassマクロだかコマンドだかを、オプション「a4paper,notitlepage」、引数?「book」で呼び出す。
\documentclass[a4paper,notitlepage]{book}


\begin{document}

ここに本文が入る。

\end{document}

.tex→.pdf

foo.texをfoo.pdfにする場合。文字コードはEUCが無難ぽい。Windows環境ならSJISが無難ぽい。

platex foo && dvipdfmx foo

dvipdfmxのコマンドライン引数で用紙サイズやらを指定する。

% font.map
% 使用フォントは.texファイルと同じディレクトリに置いとけばいいっぽい。
rml  H :0:ipam.ttf
gbm  H :0:ipag.ttf
rmlv V :0:ipam.ttf
gbmv V :0:ipag.ttf


# font.mapの定義を参考にA5サイズのbar.pdfが生成され、フォントが埋め込まれる
platex foo && dvipdfmx -p a5 -f font.map -o bar.pdf foo

文書フォーマットの定義はdocumentclassで

\documentclass{tbook}

で縦書き書籍用の書式が使える。bookだと横書き。treportで縦書きのレポートなど。

既存クラス(tbookとか)を下地に自分でクラスも作れる。

% よくわからん
\NeedsTeXFormat{pLaTeX2e}

% 名前。\documentclass{mytbook}で呼び出す。
\ProvidesClass{mytbook}

% ベースクラスとオプション
\LoadClass[a5paper]{tbook}

% 使うパッケージ(requireみたいなもん)
\usepackage{wallpaper}
\usepackage{color}

マクロとコマンドの違い

よくわからん。使うぶんにはあんま区別する必要なさそう。

段落

改行2回で段落区切り(HTMLでいう<p>)。「\\」で強制改行(HTMLでいう<br />)。

あああ

いいい\\ううう
えええ

おおお

% HTMLでいうと
% <p>あああ</p><p>いいい<br />ううう
% えええ</p><p>おおお</p>

\parはよくわからんけど、\par{いえーい}で<p>いえーい</p>相当だと思う。

\parboxはさらによくわからん。\parboxのオプションはTeXとLaTeXでがらっと違う。

\parbox

Ruby文法の擬似コードでいうとたぶんだいたいこういうマクロ。heightが必須。

def parbox align=t,width=0pt,valign=nil,height,text
  # t = top
  # c = center
  # b = bottom
  valign ||= align
  box=Box.new({:width => width,:height => height})
  box.insert(text)
end

こういう感じで使う。使いどころがよくわからない。

\parbox[t][10mm][c]{50mm}{ ここは幅10mm高さ50mmのparbox。 }

ボックスに枠をつける

\fboxをHTMLでいうdivのように使う。divなので中には何でも入れれる(っぽい)。

\fbox{\hbox{hboxが枠つきで表示される}}

センタリング

\hfilとか\vfilを使う。無限長のパディングらしい。無限長のパディングで文字列をはさむと中央になるらしい。

\hfil中央寄せしたい文字列\hfil

\hfil右寄せ

左寄せ\hfil
\vfil
\hfil
これが天地中央に表示されると思いきや、たぶんhboxとかを併用しないとちゃんと天地中央に寄ってくれない。
\hfil
\vfil

\newcommandでコマンドを定義

% 以下の定義をRubyでいうとこう。Rubyだと#はコメントだけど、ここでは気にしないこと。
% def mytitlepage #1=nil,#2
%   .....
% end


% Usage:
% \mytitlepage{タイトル}
% \mytitlepage[image.eps]{扉絵とかのページ}

\newcommand{\mytitlepage}[2][\null]{% 引数2個、1個目のデフォルト値は"\null"
     \ifx#1\null % #1と\nullが一致するか。Rubyでいうif #1 == null
      {\LARGE #2}
     \else
       \includegraphics[height=\paperwidth]{#1}
     \fi
}

あと\renewcommandでコマンドを上書き定義できる。

マクロ定義

\def\foo{ do something.. }って感じみたい。呼び出すときは\foo。

\def\foo{%
  \hbox{bar}
}

\foo % 「\hbox{bar}」と同義

変数

よくわからない。マクロ定義で代用してる。

\def\danger{赤色}
\def\safe{緑色}

自爆装置のランプを確認してください。
色が「\safe」に点灯していれば待機中ですが、「\danger」である場合はもうすぐ自爆します。

単位

mmミリメートル
ptポイント
inインチ
zw全角1文字の幅
zh全角1文字の高さ

だと思う。

文書全体の定義

CSSでいうbody{margin:0;padding:0;}みたいなの。出版用語でいう小口とかのどとかいうやつ。

\textheight=116mm
\textwidth=130mm

\topmargin=10mm % 上、あるいは天のマージン
\headsep=20mm % ヘッダと本文の隙間
\footskip=30mm % 本文とフッタの隙間

\oddsidemargin =-1in % 文字通り偶数ページの横のマージン。
\evensidemargin=-1in % 同じく奇数ページ。

\parindent=0pt % 本文のインデント幅。CSSでいうtext-indent

\renewcommand{\baselinestretch}{1.6} % 行間。CSSでいうline-heightで単位はemかな。

\includeと\input

どっちも別ファイル(.tex)をロードする。\input{ other.tex }みたいにして使う。

\includeは改ページする(条件不明ながら空白のページができたりもする)。\inputは改ページしない(前のページとつながることもある)。

\hboxと\vbox

よくわからん。名前からしてボックスが水平か垂直かだと思う。何がだ。

たぶん\hbox to 10mm{}で10mmの「幅」を持つボックス、\vbox to 10mm{}で10mmの「高さ」を持つボックスがそれぞれできるはず。たぶん。

\hspaceと\vspace

\[hv]boxと同様、水平か垂直の方向にスペースを挿入する。

行頭にあると無視されるので、そういうときは\vspace*{10mm}とか「*」を付けて呼び出す。


\small{\input{huge.tex}} みたいにするとなんかエラー出る

よくわからないけどバッファがどうとかっぽい。

\small{ \input{huge.tex} } % huge.texが大きすぎるとエラー出る

\small \input{huge.tex} % これならOK。でも後続のテキスト全部にsmallが適用されるので注意。

\small{}はブロック内だけの定義、\smallはグローバルなフォントサイズがsmallに設定される、みたいな感じかな。

\documentclass[draft]{anycls}なときは\includegraphics{foo.eps}が表示されない

Firefoxで<img src="notfound.png" alt="awesome image" />を表示したときみたいになる(枠付きのボックスで挿入予定のファイル名が表示される)。

draftじゃなくfinalにするか、単にdraftをけずればOK。

pagestyleの定義

\def\ps@PlainPage{
	 \def\@oddhead{}
	 \def\@oddfoot{%
		\hfil\thepage\hfil
	 }%
	 \def\@evenhead{}
	 \def\@evenfoot{%
		\hfil\thepage\hfil
	 }%
}%

\pagestyle{PlainPage}

って感じ。\ps@が何かは知らない。

\cmdと\thiscmd

\thiscmdは改ページされると消滅する揮発性の定義。\cmdはもっとグローバル。

thisっていうくらいなので、たぶんページの中から呼び出さないとダメな気がする。

行頭かぎかっこがだいぶ下に落ちるので寄せて上げる

\def\tume{\leavevmode\kern-.5zw}

\tume 「今ならなんと2000円」と社長は言った。

\leavevmodeとか\kernが何かは知らない。-.5zwは「0.5zw分マイナスせよ」の意味だと思う。処理内容をHTML+CSSでいうと、

<style type="text/css">
p.tume { text-indent:-0.5em}
</style>

<p class="tume">「今ならなんと2000円」と社長は言った。</p>

くらいの意味合い。

色つき線

colorパッケージが必要。\hrulefillで普通の線(HTMLでいうと<hr />)。

\usepackage{color}

\color{red}{\hrulefill} % 赤い水平線が表示されるはず。
]]>
好きなプログラミング引用句とやらを訳した http://tt25.org/blog/20090213/favorite-programming-quote 2009-02-13T23:32:46+09:00 2009-02-13T23:32:46+09:00 http://www.juixe.com/techknow/index.php/2008/08/17/favorite-programming-quotes/

を見つけたので気分転換に訳した。

The first 90 percent of the code accounts for the first 90 percent of the development time…The remaining 10 percent of the code accounts for the other 90 percent of the development time.

コードの最初の90%は、開発期間の最初の90%のためにある。コードの残りの10%は、他の開発期間の90%のためにある。

Tom Cargill

Most of you are familiar with the virtues of a programmer. There are three, of course: laziness, impatience, and hubris.

プログラマにとって馴染み深い美徳が3つある。言うまでもなく、怠惰、短気、傲慢のことだ。

(有名なあれ。元ネタは七つの大罪でLarryはクリスチャンです。お見知りおきを)

Larry Wall

Measuring programming progress by lines of code is like measuring aircraft building progress by weight.

プログラミングの進捗をコードの行数で測るのは、飛行機製造の進捗を重さで測るようなものだ。

ビルゲイツ

Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.

デバッグはコードを書き始めるよりも2倍難しい。ゆえに、これ以上ないほど巧妙にコードを書いたとしても、定義により、デバッグするには充分ではない。

ブライアン・カーニハン

Once a new technology starts rolling, if you’re not part of the steamroller, you’re part of the road.

新しいテクノロジが回転し始めるとき、もし君がロードローラーの側に居ないなら道路の一部になる。(あんまり自信なし。steamrollerには「圧制者」の意味もあるらしいので道路の比喩と掛けてるんだと思う)

スチュアート・ブランド

In theory, there is no difference between theory and practice. But, in practice, there is.

理論上は理論と実際に違いはない。しかし、実際には違いがあるのだ。

Jan L. A. van de Snepscheut

The hardest part of design … is keeping features out.

デザインでもっとも難しいのは……特色を出しつづけることだな。

ドン・ノーマン

Before software can be reusable it first has to be usable.

ソフトウェアを再利用可能にする前に、まずは利用可能にしよう。

Ralph Johnson(デザインパターンのGoFの一人らしい)

If debugging is the process of removing bugs, then programming must be the process of putting them in.

デバッグがバグを取り除く行為であるなら、プログラミングはバグを注入する行為のはずだ。

エドガー・ダイクストラ

Software and cathedrals are much the same - first we build them, then we pray.

Anonymous Preacher

ソフトウェアと大聖堂はとてもよく似ているーー我々はまずそれらを作り、そして祈る。

無名の伝道師

The goal of Computer Science is to build something that will last at least until we’ve finished building it.

Anonymous Consultant

コンピュータ科学のゴールとは「何か」を構築することだ。少なくとも我々がそれを構築し終えるまで使い続けられる「何か」を。

無名のコンサルタント

The software isn’t finished until the last user is dead.

Anonymous Support Group Member

最後のユーザーが死ぬまでソフトウェアに終わりはない。

無名のサポートグループメンバー

Better train people and risk they leave - than do nothing and risk they stay.

Anonymous Technical Trainer

彼らを鍛えてリスクから遠ざけるのはいいことだ。何もせず彼らをリスクにさらしつづけるよりは。

無名のテクニカルトレーナー

Programming is 10% science, 20% ingenuity, and 70% getting the ingenuity to work with the science.

Anonymous Scientist

プログラミングは科学が10%、創意が20%、そして創意を得るために科学するのが70%だ。

無名の科学者

All programmers are playwrights and all computers are lousy actors.

Anonymous Hack Actor

すべてのプログラマは脚本家であり、すべてのコンピュータはどうしようもない役者だ。

無名の大根役者

Bad code isn’t bad, its just misunderstood.

Anonymous Code Behaviorist

悪いコードは悪くない。ただ誤解されてるだけだ。

無名のコード行動主義者

It is easier to measure something than to understand what you have measured.

Anonymous Analyst

君が値踏みされたのは、「分かる」より「測る」ほうが簡単だからさ。

無名のアナリスト

The sooner you get behind in your work, the more time you have to catch up.

Anonymous Scheduler

仕事の遅れを取り戻そうとする時間よりも、仕事が遅れる時間のほうが先に来る。

無名のスケジュール管理者

When a programming language is created that allows programmers to program in simple English, it will be discovered that programmers cannot speak English.

Anonymous Linguist

プログラミング言語がつくられたときは簡単な英語でプログラムできるようにと考えられているが、そのうちにプログラマは英語が喋れないことを発見するだろう。

無名の言語学者

Benchmarks don’t lie, but liars do benchmarks.

Anonymous Tester

ベンチマークは嘘をつかないが、嘘つきはベンチマークする。

無名のテスター

Why do we never have time to do it right, but always have time to do it over?

Anonymous Code Monkey

僕らがそれをする適正な時間なんてものはないのに、どうしていつも僕らは時間を費やしすぎてしまうんだ?

無名のコードモンキー

]]>
TDDはYAGNIと矛盾しない。必要だからテストを書く。 http://tt25.org/blog/20090207/tdd-vs-yagni 2009-02-07T17:15:37+09:00 2009-02-07T17:15:37+09:00 TDDはYAGNIに矛盾する? - u_1rohのカタチ

Joel Spolskyの意見とコメント欄でのやりとりがどっちも面白かった。

僕のテストの書き方としては、テストファーストではなく、思いつきで処理を実装していくうちに仕様のようなものが見えてきたらテストを書いて保証する、というスタイルが多い。コードカバレッジには今のとこあまり興味がない。

「仕様のようなもの」というのは、「このメソッドはこういう値を返すべきだ」というような挙動の定義を指す。RSpecが「obj.foobar.should == "barbaz"」のように「should(〜すべき)」といった形式でテストを書かせ、テストをまとめたファイルを「spec(仕様)」と呼ぶのはたぶん偶然ではない。

これらは厳密な意味でのTDDとは違う(ただのユニットテスト論?)かもしれないけれど、とりあえずはこういうスタイルでやってて上手くいってる。

概念や私見はこれくらいにして、実際にどういう使い方をしてるのか書いてみようと思う。

ユニットテストが必要になるとき

PHPでデータベースにアクセスするちょっとしたDAOクラスを書く機会があった。僕に管理権限のないサーバなのでDoctrinePEAR::MDB2といった既成ライブラリを使うには丸ごとアップロードするしかなく、それはちょっと大仰だったのでスクラッチで書くことにした。

それでまあいろいろあって、OFFSETやLIMITをどう実現するかといったところに行き着く。もちろんOFFSETやLIMITの値を引数で指定するわけですが、"OFFSET -7.77"とか"LIMIT a"みたいになったら困るので、引数が整数かどうかをちゃんとチェックする必要がある。

PHPは型の扱いがでたらめ(参考:2a問題)な上、それが整数かどうかを判定する便利関数もないので(is_intはstring型だとfalseを返す、is_numericは小数や8進や16進でもtrueを返す、ctype_digitは負数だとfalse)、自分でis_intメソッドを書くことにした。

<?php

// 実際には他のメソッドもありますが省略してます。

class Model{
  function is_int($var){
    // 整数ならtrue。intやstringじゃなければfalse
    return is_int($var)
      || (is_string($var) && preg_match("#^-?[0-9]+$#",$var))
    ;
  }
}

なんか複雑な感じになってしまった。最後のpreg_matchだけでいいような気もするけど、$varが何かのオブジェクトで__toStringの実装次第ではtrueを返すかもしれないとか考え出すとなんとなくこうなった。

さて。

実装しては見たものの、これがちゃんと動くかどうかあまり自信がない。試してみる必要がある。どうやって試す?

PHPUnit

軽く試すだけなら実行用にtest.phpなんかを書いてそこで動作確認するだけでもいい。もちろんphp -r ""でコマンドラインから試してもいい。でもテストすべき境界が多いし、0や"0"はどう? -1はちゃんとtrueになる? このメソッドちゃんと動いてる? などなど疑心暗鬼になるたびにいちいち書いて試すのは効率的じゃない。

PHPUnitを使うことで、このメソッドが仕様どおりに動作してると保証してやることにする。公式サイトのとおりにインストールしたら準備完了。いよいよテストを書く。

ちなみに各ファイルの構成はこういう感じ。

  Model.class.php
  tests/
    Model.php
<?php
require_once 'PHPUnit/Framework.php';
require_once dirname(__FILE__)."/../Model.class.php";

class ModelTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    $this->model=new Model();
  }
  function testIsInt(){
    // こいつらはtrueを返すべき
    $this->assertTrue($this->model->is_int(1));
    $this->assertTrue($this->model->is_int(0));
    $this->assertTrue($this->model->is_int("0"));
    $this->assertTrue($this->model->is_int(-99));
    $this->assertTrue($this->model->is_int("23"));
    $this->assertTrue($this->model->is_int("-1"));

    // ここからはfalseを返すべき
    $this->assertFalse($this->model->is_int(null));
    $this->assertFalse($this->model->is_int("string"));
    $this->assertFalse($this->model->is_int("23.2"));
    $this->assertFalse($this->model->is_int(array()));
    $this->assertFalse($this->model->is_int(array(0=>0)));
  }
}

コマンドラインから実行してみるとだいたいこういう感じの結果が出てくるはず。

# phpunit tests/Model.php
PHPUnit 3.3.4 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test, 11 assertions)

テストメソッドを1個(testIsInt)、一致するかどうかのチェックを11個やったよ、全部期待通りだったよ、とのこと。これ以後、何か気になる条件を思いついたらtestIsIntの中に適宜追加していけばいい。たとえば、"-1.0"を渡すとどうなる? あるいはどうなって欲しい?

Model#is_intを使ってis_positive_intを定義する

Model#is_intを作った当初の目的は、OFFSETやLIMITのためだった。"OFFSET -1"や"LIMIT 0"は要らないと気づく。つまり、整数かどうかのチェックではなく、正の整数かどうかのチェックが欲しくなる。

Model#is_intを変更してもいいけど、ここではModel#is_positive_intを書くことにする。どうせならModel#is_intを使って手っ取り早くやってしまおう。

<?php

class Model{
  function is_int($var){
    // 整数ならtrue。intやstringじゃなければfalse
    return is_int($var)
      || (is_string($var) && preg_match("#^-?[0-9]+$#",$var))
    ;
  }

  function is_positive_int($var){
    // 正の整数ならtrue。0はfalse
    return $this->is_int($var) && $var > 0;
  }
}

テストするまでもないように思えるけど、これもちゃんとPHPUnitでチェックするようにしておこう。疑心暗鬼はこういうところから忍び寄ってくる。

<?php
require_once 'PHPUnit/Framework.php';
require_once dirname(__FILE__)."/../Model.class.php";

class ModelTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    $this->model=new Model();
  }
  function testIsInt(){
    // こいつらはtrueを返すべき
    $this->assertTrue($this->model->is_int(1));
    $this->assertTrue($this->model->is_int(0));
    $this->assertTrue($this->model->is_int("0"));
    $this->assertTrue($this->model->is_int(-99));
    $this->assertTrue($this->model->is_int("23"));
    $this->assertTrue($this->model->is_int("-1"));

    // ここからはfalseを返すべき
    $this->assertFalse($this->model->is_int(null));
    $this->assertFalse($this->model->is_int("string"));
    $this->assertFalse($this->model->is_int("23.2"));
    $this->assertFalse($this->model->is_int(array()));
    $this->assertFalse($this->model->is_int(array(0=>0)));
  }

  function testIsPositiveint(){
    $this->assertTrue($this->model->is_positive_int(1));
    $this->assertTrue($this->model->is_positive_int("23"));
    $this->assertFalse($this->model->is_positive_int(0));
    $this->assertFalse($this->model->is_positive_int(null));
    $this->assertFalse($this->model->is_positive_int("0"));
    $this->assertFalse($this->model->is_positive_int(-99));
    $this->assertFalse($this->model->is_positive_int("-1"));
  }
}

テストを実行してみる。

# phpunit tests/Model.php
PHPUnit 3.3.4 by Sebastian Bergmann.

..

Time: 0 seconds

OK (2 tests, 18 assertions)

ちゃんと期待通りの挙動になってるようだ。

テストは何をもたらしたか

ここでは「整数かどうかのチェック」というあまり現実的じゃない例になってしまいましたが、実際には他に「Model#to_sqlが期待通りかどうか」「Model#escapeはちゃんと文字列をSQLエスケープしてくれてるか」「WHERE句の指定はちゃんと動いてるか」などもテストしました。

テストは開発中の疑心暗鬼を取り除いてくれた。疑心暗鬼にかられたときに何をすべきかを明確にしてくれた。バグ修正やチューニングの際にis_intメソッドを変更することがあっても、テストを通すことで安心できる。Model#is_intがきちんと動いてるから、Model#is_positive_intは安心してModel#is_intを利用することができた。また、OFFSETやLIMITのあたりで変な挙動をしたら、それは少なくともModel#is_intのせいではないはずだし、Model#is_intのせいであるならテストが漏れてたってことで、変な挙動をする引数をテストに追加して淡々と修正すればいい。

これらは"OFFSET -7.77"や"LIMIT a"が発生しないことを保証するために必要なことだ。YAGNIの思想とはまったく矛盾しないと思う。

ていうか、Joelが挙げてる「上手くいくケース」をより詳細に書いてみただけになりましたね。あちゃー

]]>
Remedieのインストールが上手くいかなかったのでRubyでHaremie作った http://tt25.org/blog/20090119/haremie 2009-01-19T03:18:14+09:00 2009-01-19T03:18:14+09:00 Remedieに必要なcpanのインストールをちらちら見ながら適宜Enterキーを押して進めていって数十分後に完了して喜び勇んでサーバ立ち上げたら順調にブラウザから見れたのでさらに喜び勇んでチャンネル追加しようとしたらエラー出て途方に暮れたので静止画版としてHaremie作った。Plaggerにも今まで7回くらい挑戦して全敗してるのでたぶんPerlとは水が合わないんだと思う。

Haremie

http://github.com/tt25/haremie/tree/master

手順はgithubのREADMEにも書いてますが、

  1. まず必要なgemとか入れてgit cloneする。
  2. "ruby migration.rb latest"でSQLiteのDB作る
  3. setting.yml.sampleをsetting.ymlにコピーして作る
  4. "ruby run.rb"してデータ取得
  5. "ramaze"でサーバ起動

でとりあえず一通り動かせるはず。

YAML書き直す→ruby run.rb→ramazeって手順がイケてないなあと思いますが、そのへんはそのうちなんか考えます。

将来的にはImagefapとか4UとかGIGAZINEとかにも対応したい。あとなんか変な英語の看板を上げてるサイトとかlolcatまとめみたいなのとかもあったはずだけどURLがわからないので見つけたら対応したい。

自分でもなんか書きたい!という奇特な方がいらっしゃいましたらlib/scraper/*あたりを見ていただければだいたいAPIがわかるかと思います。

]]>
GNOMEによる分散SCM(git,Mercurial,Bazaar)導入調査の結果 http://tt25.org/blog/20090106/gnome-dvcs-survey 2009-01-06T22:52:05+09:00 2009-01-06T22:52:05+09:00 Elijah’s Blog Blog Archive GNOME DVCS Survey results

githubのブログで見つけた。面白そうだったので訳してみる。

原文では「DVCS」と書いてますが、ここでは「分散SCM」で統一してます。同様にVCSはSCMです。ググったらそっちのほうが数多かったので。分散SCMが和製英語とかではなくて、AtomPPとAtomPubみたいなものです。たぶん。

あとfamiliarの訳が「身近」だったり「馴染みのある」だったりといまいち安定しませんが、スペースアルクによると「造詣が深い」「親しい仲間」とかいう意味もあるらしいです。たぶんfamilyと似たようなニュアンスかと思います。

ちなみにbzrはBazaar、hgはMercurial、svnはSubversionです。それぞれの略称はコマンドに由来します。

GNOME DVCS Survey results

GNOMEの分散SCM調査が完了した。1週間半ほど前に、579人の異なったSVNアカウントを持つ人々から集計した(コミット権を持つ人は全部で1083人居る、つまり約53%の回答率だ)。この調査は、2009年にGNOMEプロジェクトがSVNから他の分散SCMへと移行することが可能かを把握するために実施された。従って、ここでの質問は分散SCMではないSVNを侮辱的に見るようなものになっている。結果は下に載せた。なお、データはここで、グラフを描くために使ったスクリプトはここでそれぞれ見つかるだろう。

バイアスについて

簡単なプレゼンを2度おこなっている。ひとつめは個々の質問に対する回答者の割合を、次に回答者が他のSCMに関する質問にどう答えたかをそれぞれ載せている。私が考えられるかぎり、もっとも中立なグラフだ。

目を引いたものには私のコメントや分析も併記している(集計前にいろいろと予測していた。ほとんどはその通りとなったが、意外な結果もあった)。先入観のないコメントはつけようがない。実際には、私は調査結果が指し示す明白な筆頭候補に注意を引かれ、それがもっとも有用なものだと考えるに至った。つまり、私のコメントは多数派にフォーカスしている。そういったバイアスが不要なら、私のコメントを無視し、データから自身の結論を見出してほしい。


調査に用いた質問

まずは質問内容を確認しておこう。

  • あなたのGNOME SVNのユーザID
  • あなたは現在、SVNリポジトリにあるGNOMEモジュールをメンテナンスしていますか?
    • はい。私は多数のモジュールをメンテナンスしています。
    • はい。私はひとつのモジュールをメンテナンスしています。
    • いいえ。私はメンテナではありません。
  • あなたは現在、SVNリポジトリにあるGNOMEモジュールを開発していますか?
    • はい。私は多数のモジュールを開発しています。
    • はい。私はひとつのモジュールを開発しています。
    • いいえ。私はモジュールを開発していません。
  • あなたはGNOMEのSVNリポジトリにコミットしていますか?
    • はい。私は頻繁にコミットしています。
    • はい。私は時々コミットしています。
    • いいえ。私は自分ではコミットしていません。
  • あなたの現在おこなっているGNOMEへの貢献をひとことでいうとどういったものですか?
    • 開発・コード
    • ドキュメント
    • テスト
    • 翻訳
    • その他

(注:私は択一式の回答を期待していた。結果を見ると実に面白いことに、「すべて」という答えがあった)

  • 以下のうち、あなたにとって身近な分散バージョンコントロールシステムはどれですか?(複数選択)
    • bzr
    • git
    • hg
  • あなたが「頻繁に」使う分散SCMはどれですか?(複数選択)
    • bzr
    • git
    • hg
  • 2009年、GNOMEのバージョン管理がbzr,git,hgのいずれかひとつに変更されることをどう思いますか?
    • もうやめて! 移行したのがつい昨日のことのようだよ(no)
    • 特に何も感じない。提供されるものを使うだけだ(soso)
    • SVNじゃダメなの?(why?)
    • どうでもいい(nocare)
    • ぜひやってくれ! 何にせよSVNよりは良くなる(もちろんCVSは除いて)(yes)
    • その他(other)
  • 以下の中からどれを使いたいですか? ランク付けしてください。
    • SVN以外(順位なし)(any)
    • bzr
    • git
    • hg
    • svn(変更なし)

貢献グラフ

gnome contributions

ドキュメンターの少なさには思わず引きつけられる。ドキュメンテーションは「その他」カテゴリーだと思ったんだろうか(多くのドキュメンターが「自分はここだろう」と判断したんだろうか?) ある種のボランティアはこのカテゴリに引きつけられた? 我々は何かどこかで根本的なミスを犯したのか?

馴染みの分散SCMと、どれへ移行すべきか

gnome familiarity

おお。すさまじい数の人たちが他のSCMよりもgitと答えている。60%以上の人にとってgitは身近で、半数近くが頻用してるって? あそこには多くの人がいることを知っているが、これほどとは思わなかった。bzrとhgもコミュニティ内で適度な強さを示している(31人が3つすべてに親しんでいて、そのうち1人はすべてをよく使うと言っている。いや、私じゃないよ)。

多数の人がgitを頻繁に使っていて、他の2つを引き離している。それら2つ(あるいは、少なくともbzr)は今さらに知られているだろうが、私が提案することはもうないだろう。



SCMランキング

「Average rank」は他の5つから算出した。もしコミュニティが二分されているなら、あるいはどのSCMが使われるのか気にしないのであれば、すべてのSCMはrank 3になるはずだ。「Average rank」においてそれぞれのSCMがrank 3からどれだけ離れているかが意味を持っている。


gnome favorites(原文と同じ画像)

値は正常なものだが、それぞれのグラフで縦軸の幅が異なってしまっている。申し訳ない。

このグラフには非常に驚いた。gitは最高(rank 1)と最低(rank 5)がもっとも多くなり、評価が分かれるだろうと考えていた。たしかにrank 1は断トツだったが、rank 2で収束し、そこからは他のSCMと目をみはる差をつけた。この結果に私は打ちのめされた。

別の母集団によるAverage ranking

どのSCMがどういった人に支持されているのかに興味を持った。例えば、「その他」と回答した無視できない数の人々が、別のバージョン管理システムへと変更すべき回答した。彼らは何を支持したのだろう? 翻訳者やテスターは、開発者に比べて違うシステムを気に入ってるんだろうか? 複数のモジュールを保守するメンテナは、メンテナではない人と違う視点を持っているのではないか? すなわち、このセクションではこれらの問いに挑戦してみる。なお、丸括弧()の中にはそのグラフに関係する人数が書かれている。

メンテナ/開発者別のAverage ranking

gnome average-by-maintainers-and-developers.png

SCMの好みにメンテナか開発者かは関係ないようだ。しかしながら、bzrは単一モジュールのメンテナ/開発者にもっとも強く支持され、gitは複数モジュールのメンテナ/開発者に支持されている(Mercurialは非メンテナ/非開発者に一番強く支持されている。考えるに、これは後者の人々は強い意見を持たないことの反映だろう)。これはbzrとgitの設計方針に対する私の直感と一致する。どちらもそういった用途に最適化されており、それがこの結果にも反映されているのだろう。しかし、それでもこの差は無視できるほどの規模でしかないというのは正しいだろう。

コミット頻度別のAverage ranking

gnome-average-by-commit-frequency

どれもそれほど分散していない。期待していたのは、頻繁なコミッターは非コミッターよりも強い意見を持っている(rank 3から遠くなる)ことだった。

貢献種類別のAverage ranking


gnome-average-by-contribution-type

このグラフは驚きだ。gitは専ら開発者にのみ支持されると思っていたが、一見したところではドキュメンター以外のすべてのグループから支持されている。ドキュメンターはgitに最低ランクをつけている。

微小な母集団(ドキュメンテーションを「メイン」にしているたった4人)による最後のグラフを破棄するよう勧める。この主張によるメリットは、もっとも興味深いグラフ(すなわち何人かのSCMジャンキー)は非SCM

翻訳者のグラフにも興味深い点を見つけた。この集団においてはgitが他のSCMより圧倒的優位というわけではない。正直なところ、翻訳者集団においてはgitとSVNが同列で、もちろんそれはわずかにリードしている。

分散SCMユーザー別のAverage ranking


gnome-average-by-dvcs-usage-and-familiarity

特に驚きはない。気に入っているとおりだ。使えるかよく使うかにかかわらず、馴染みのあるSCMを支持する傾向がある。しかしながら、gitはすべての場合においてポジティブな評価に恵まれているか、少なくとも2位につけているのでは? 

GNOMEの移行に対する意見別のAverage ranking

gnome-average-by-users-wanting-to-switch

彼らは、我々がgitへ移行すべきだと考えている。他に強く支持するものはないし、gitを使うことに賛成だ。「気にしない」「なぜ移行するのか?」「移行すべきじゃない」のグループはみんなSubversionを支持している。後者のグループ内でも、「気にしない」「どうして?」のグループではgitは2番手につけている。

最後に

コミュニティ内部では移行を強く支持しているように見える。また、コミュニティ内ではgitがsvn,bzr,mercurialよりも強力に支持されている。

SCMに関わらない人たち(テスターとドキュメンター)はgitは2番手であり、支配的といえるほどのリードではない。テスターのあいだではそれでもgitは支持されており、SVNより上位だ(bzrとhgはそれらに及ばない)(訳注:テスターと翻訳者を勘違いしてるよね?)。ドキュメンターのあいだではgitは大差をつけて最下位だ(bzrが圧倒的な首位だ)。これがどうしてだかはわからない。

SCM利用者の集団では、彼ら自身のお気に入りのシステムを推している。gitは常に2位につけている。

(以下、しばらく上に書いたことの繰り返しなので中略)

私の分析が偏っていたら申し訳ない。最初に言ったように、気軽に私の分析を無視して自分自身でこのデータから自身の結論を見出してほしい。

]]>
RamazeでRSpecを使う http://tt25.org/blog/20090101/ramaze-with-rspec 2009-01-01T18:24:08+09:00 2009-01-01T18:24:08+09:00 RamazeにはBaconていうRSpecみたいなのが同梱されていますが、RSpecを使ってみたかったのでこんな感じでやってみました。

0. 試したバージョン

# gem search ramaze

*** LOCAL GEMS ***

ramaze (2008.11)
# ramaze -v
Ramaze Version 2008.10, on ruby 1.8.6 (2007-09-24) [i486-linux]

なんでバージョンが違うんだ。まあ何にしろ現時点での最新版です。

1. start.rbを分割する

  1. コントローラなどの必要なファイルをrequire
  2. Ramaze.start(サーバ起動)

の一人二役をやっているので、require "start"した途端にサーバが起動して困ります。

このうち1.のrequire部分をinit.rbなどにカットアンドペーストして、跡地にrequire "init"を置きましょう。

# init.rb

require 'rubygems'
require 'ramaze'

# Initialize controllers and models
require 'controller/init'
require 'model/init'
# start.rb

require "init"
Ramaze.start :adapter => :webrick, :port => 7000

2. RSpec用にhelperをつくる

ramaze/spec/helperをrequireしてしまうと、(たぶん)Bacon定義とRSpec定義がオーバーラップするなどして上手くいかなくなります。かといって単に外してしまうとget(path)などのメソッドが使えません。

# spec/helper.rb

require File.join(File.dirname(__FILE__),'/../init')
require "ramaze/spec/helper/mock_http"

# clean STDOUT
Ramaze::Log.loggers=[]

Ramaze.start({
  :adapter => false,
  :run_loose => true,
})


describe "http",:shared => true do
  before do
    extend MockHTTP
  end
end

これでMockHTTPが持つget,put,post,deleteなどがitの中から使えるようになります。

3. 実際にspecを書く

# spec/main_controller_spec.rb

require "#{File.dirname(__FILE__)}/helper"

describe "specサンプル" do
  it_should_behave_like "http" # <= 必須

  it "トップページを正常に取得" do
    got=get("/")
    got.status.should == 200
  end

  it 'リダイレクトを追う' do
    got=get("/redirect_to_toppage") # Ramaze側に"/redirect_to_toppage"がマップされてないならここでエラー。
    got.status.should == 302
    got.header[:location].should == "/"
  end
end

4. specを走らせる

コンソールから"spec spec/main_controller_spec.rb"でいけます。

]]>
Sequel先生の凄み http://tt25.org/blog/20081230/sql-subquery-with-ruby-sequel 2008-12-30T06:58:29+09:00 2008-12-30T06:58:29+09:00 class User < Sequel::Model has_many :categories end class Category < Sequel::Model belongs_to :user has_many :items end class Item < Sequel::Model belongs_to :category end USER_ID=3 items=Item.filter(:id => ( Item.dataset.select(:items__id).inner_join(:items_categories, :item_id => :id, :category_id => Category.filter(:user_id => USER_ID).select(:id) ) )).order(:pubdate.desc).limit(100) # まだSQL発行されない。 puts items.sql # ここで発行される。 p items.all # => [<Item::0xb7a21874>,<Item::0xb7a21875> ... ]

↑この「puts items.sql」が、

SELECT * FROM "items" WHERE ("id" IN (
  SELECT "items"."id" FROM "items" INNER JOIN "items_categories" ON (
    ("items_categories"."item_id" = "items"."id")
    AND ("items_categories"."category_id" IN (SELECT "id" FROM "categories" WHERE ("user_id" = 3)))
  )
))
ORDER BY "pubdate" DESC LIMIT 100

↑こうなる(実際にはSQLに改行入りません)。

PgAdminIIIで普通にSQL書いたあと逐語的にRubyへ書き換えたのでどっか変かもしれないけど、一発で動いたのは感動した。

Sequel: The Database Toolkit for Ruby

追記

Categoryって名前のせいで無駄なことをしてるように見えますが、やりたいことを説明するにはTagのほうがより適切な名前です。

はてなブックマークを使って説明すると、User=自分、Category(以下Tag)=はてなブックマークのタグ、Item=そのタグが持ってる記事、って感じになります。

tag:{
  "これはすごい":
    - たった100個の冴えたやりかた
    - GIGAZINE: いまから安価でPR記事書く

  "これはひどい":
    - GIGAZINE: いまから安価でPR記事書く
    - 年越しそばおいしい

  "lifehack":
    - たった100個の冴えたやりかた
    - なぜさおだけ屋はB29を撃墜できるのか
}

というようなブックマーク状況のとき、自分(USER_ID=3)は「これはすごい」「これはひどい」のタグを監視してるとします。

ここで自分が監視してるタグがついた記事をアグリゲートすると、「たった100個」「GIGAZINE」「GIGAZINE」「年越しそば」となり、GIGAZINEが重複してしまいます。なのでいったんサブクエリ化してitem_idだけを抽出し、一番外側のクエリでINすることでItemの重複を回避しようとしています。

]]>
Google Chrome(ていうかGreasemetal)を2ヶ月ほど使ってみて感想 http://tt25.org/blog/20081027/chrome-2-month 2008-10-28T00:52:04+09:00 2008-10-28T00:52:04+09:00 会社でiGoogle用サブブラウザとして2ヶ月ほどChrome(ていうかGreasemetal)使ってみた。Chromeは最新ビルドでGreasemonkeyみたいなのに対応したとか言ってるけどいまいち出来がよくないのでGreasemetalを使ってる。

ちなみに会社ではメインがOperaで開発用がFirefox。家はLinuxなのでOperaとFirefoxとwine上のOpera(ニコニコ動画用)で計3つ。

ChromeはFlashの制御ができない

たとえばFlashをいっぱい埋め込んだページを開いたらリソースを大量に食う。これによってOperaのキャッシュがスワップアウトしたりexplorerがもっさりしてOSが全体的にすっとろくなる。まあ、これはブラウザがというよりFlashのせいだからどうしようもない。問題は別にある。

OperaならF12キー、FirefoxならPrefBarやNoScriptあたりでFlashのオンオフを制御できるけど、Chromeの場合はそれがない。というかFlashオンオフのオプションすらない。仕方ないのでウィンドウのどっかを右クリック→タスクマネージャ→Flashプロセスをkill、として間引きしてる。

せっかくのGreasemetalなんだからUser Script使って対処できなくもないけど、会社用サブブラウザのためにUser Script書くのも探すのもめんどいなあといった印象。

Chromeは広告ブロックがない

嫌儲とかではなくて、単にニュース記事の右端や上端でちらちらされると鬱陶しいから消したい。せめて動きを止めたい。Operaならコンテンツブロックか「GIFアニメーションを無効にする」、FirefoxならAdBlockあたりで落ち着いた環境になるんだけど、Chromeはそのへんが出来ない。前述の手順でFlashプロセスを殺しても代替としてアニメーションGIFが出てくるのであまり意味がない。もちろんGIFアニメを止める機能なんてない。

ウィンドウ幅を縮めて広告を領域外へ追いやるか、堪えるか、あまりに鬱陶しければCtrl+L Ctrl+C Alt+Tab Ctrl+T Ctrl+BしてOperaでページを開きなおしてる。

Chromeは#付きURLを開いたとき上手く飛べない

なんか全然違うところにスクロールする。レンダリングを終える前にジャンプしてしまってて、画像とかCSSによってアンカーの場所が変わっても追随しない感じ。

レンダリング完了後にCtrl+L Enterでだいたい正しい場所へ飛んでくれるけど、なんというか、挙動があわてんぼすぎる。落ち着け。

タブまわりのUIがしょぼすぎ

タブの使い勝手がIEなみ。これは使い始めたころから思ってたんだけどまったく慣れない。どんだけ軽いだの早いだの言ったって、あんなUIじゃタブ20個も開けない。

いっぱいタブがあるとひとつひとつのタブ幅が狭いので、タブを切り替えるつもりが閉じてしまうなんてことがしばしばある。Ctrl+Shift+Tを知ってから復旧は助かってるけど、せめてタブを閉じるボタンくらい非表示にさせて欲しい。

新規タブがどこに出てくるか予測困難

だいたいのタブブラウザは「開いてるタブの左or右or上or下に」「常に最後尾へ」のどっちかのスタイルを設定で選択するけど、Chromeの場合はどっちの挙動でもない。強いていえばFirefoxのツリー型タブと似てる。

たとえば|A|B|C|とタブを開いていて、Bにあるリンクを新しいタブで開いたとすると、フォーカスはBのタブにあたったままで、新しいタブがBとCのあいだに出てくる。この新しいタブをB'とすると全体は|A|B|B'|C|になる。ここでBからもう一度新しいタブを開くとB'とCのあいだに入る、つまり|A|B|B'|B''|C|となる。この状態で、B'から新規タブを開くとB'とB''のあいだに出現する。

長時間タブを開きっぱなしにしてると、どのタブをどのタブから開いたかなんて覚えてないので、手札が|1|2|3|4|5|6|7|のとき、3のタブから新規タブを開くとどこにタブが出てくるかまったく見当もつかない(いちおう「3|?|4」〜「7|?」のどっかだということはわかるけど範囲広すぎ)。

そしてさらに具合の悪いことに、Chromeは既読タブと未読タブの区別ができない。

ウィンドウにページタイトルが表示されない

これは自分でも意外だったけど、無意識のうちにウィンドウのタイトルバーを見てページタイトルを確認してたみたい。

Chromeでページタイトルを確認するには、タブにポインタを置いて0.7秒ほど待ったら出てくるツールチップを確認するしか手段がない。これは予想外にストレスフルだ。

ただでさえタブ10個くらい開いただけで見えるタイトルが最初の1文字か2文字になるのに。そんなにタイトル見せたくないんか。

リンクにポイントしても完全なURLを確認できない

なんか知らないけどURLが長いときは中抜きされたURLが左下に出てくる。ドメイン名とURL末尾らへんはちらっと見えるけど、なんでこんな省略処理をしてるのかわからない。そんなにURL見せたくないんか。

タブ同士が別プロセスでも重いときは全部が重い

そりゃそうだ。Chrome以外のアプリも重いんだからChromeだけ軽快なわけがない。

総括

タブのしょぼいUIが雄弁に物語ってるように、あるいはデフォルトでFirefoxでいうPrism機能やOperaでいうスピードダイアルを搭載していることからわかるように、特定少数のウェブページを使うためのブラウザ。ウェブページは量より質、そんな思想が感じられる。

使用感はSafariやIEと似てたものの、それぞれSafariは便利さよりエレガントさ、IEはユーザーよりカスタマーって感じだからどっちもChromeとは方向性が違うとは思う。

少なくとも僕とは相容れないことがわかった。会社でiGoogle見るぶんにはぼちぼちだけど、メインには永久にならなさそうだ。

]]>
各ブラウザユーザーのイメージ http://tt25.org/blog/20081014/browser-users 2008-10-14T03:56:23+09:00 2008-10-14T03:56:23+09:00 各ブラウザユーザーのイメージ

http://my.opera.com/chooseopera/blog/2008/10/01/typical-browser-usersが面白かったので訳してみた。

あんまり自信ないとこ

  • IE/Experiencedの"I've never found this browser unstable"の訳
  • Firefox/Experiencedの"absolute controll"の訳
  • Opera/Obssesedの"holds up nose and scoffs at foolish IE and FF fanboys"の訳

他はたぶん意訳。

]]>
ブログをリニューアルした http://tt25.org/blog/20080909/renewal 2008-09-09T22:53:09+09:00 2008-09-09T22:53:09+09:00 PHP+テキストファイルの構成を捨ててMerb+PostgreSQLにした。merbメモを使いまわそうと思ったけどやっぱりやめた。あとデザインも変えたりした。

あとsudo a2dismod php5してPHPにさよならした。これによってtopでいうと、

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
25785 nobody    18   0  146m 6256 1632 S  0.0  0.2   0:00.00 apache2

PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
25856 nobody    15   0 83592 3932 1700 S  0.0  0.1   0:00.00 apache2

になったけど、そのぶんmerbプロセス(デーモン)とpostgresプロセスが増えたのでプラマイでいうとマイナス。

]]>
Chrome雑感 http://tt25.org/blog/20080903/chrome 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 個人的には、いろいろ変なことやるならFirefox、ヘビーに使うならOpera、それ以外のライトユーザーにはChromeって印象です。

ChromeがWineで動くならニコニコ動画をPrism化して専用ブラウザとして使おうと思ってたけど、インストールすら出来なかったので断念。

Firefox3は「ツリー型タブ」とか「Vimperator」とか「Ctrl+Tab」を使えば100近くのタブもそこそこ取り扱えるので、ヘビーユース=Operaって図式は年々あやしくなってきてるかなあ。

  • とりあえず、動作速度の早さがシェアに直結しないのはOperaが身をもって証明してるので争点はそこじゃないと思います。
  • Chromeはせっかく爆速なのに、UIのせいでタブをせいぜい10個くらいしか開けないのはデザインセンスないなあと思いました。
  • ローカライズがわりとしっかりしてた。
  • 基本的に既知の機能ばかりで目新しいものは特にないけどそれは別に悪いことじゃない。
    • 地味にPaste and Goをパクってたのは好印象(アドレスバーで右クリックしたら出てくる。ショートカットキー不明)。
    • →機能紹介
  • YouTubeとかGooで検索したら勝手に検索エンジンとして登録された。便利か鬱陶しいかは好みが分かれるところ(個人的にはやや鬱陶しい)。
  • consoleとかアドレスバーからwindow.openしたらタブで開くくせに、ページ中のスクリプトが(ていうかLDRで)window.openするとウィンドウをオープンするのはバグなんでしょうか。LDR開いてconsoleからpin.open("http://127.0.0.1")とかしたらタブで開いたのでChromeの挙動自体が違うはず。
  • oAutoPagerizeのブックマークレット版がChrome対応したので、AutoPagerizeジャンキーはCtrl+Bしてブックマークバーを表示したあとD&Dしておくべき。
  • Gearsとかありましたね。GearsがChromeの都合で拡張されて.xpiとか.user.jsが動くVMみたいなものになれば面白いのに。
]]>
OperaとChrome/Firefoxの違い http://tt25.org/blog/20080903/opera 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 速いよ。すごく速いよ。

Opera(全バージョン):へー

Firefox(3.0、3.1(TraceMonkey)):すげー! これでシェアまた増えるんじゃね?

Chrome:すげー! これだいぶシェア取れるんじゃね?

アドレスバーはタブバーの下にあるよ。

Opera:なんか慣れない。気持ち悪い。

Chrome:当然といえば当然。

Win,Mac,LinuxはもちろんFreeBSDとかBeOSとかにも対応してるよ。

Opera:へー

Firefox:WindowsでもMacでもLinuxでも使えるなんて素晴らしい!

Chrome:WindowsでもMacでもLinuxでも使えるなんて素晴らしい! 早くMac/Linux版出ないかなあ。

別マシンでもブックマークとか設定(の一部)とか共有できるよ。

Opera(Opera Link):へー

Firefox:Google Browser Sync便利! 開発終わったけど似たような機能のアドオンがあるからいいよね!

Chrome:まだベータ版なのでノーコメント。

いろんなところがカスタマイズできるよ。

Opera:iniファイル触るのって敷居高いなあ。

Firefox:about:configを開いてxxxxというキーを作りxxxxxという値を設定して、次に……→すげー!

Chrome:ノーコメント。

デフォルトでも充分使えるよ。

Opera:でも拡張性がなあ……

Firefox:カスタマイズしてこそだ!

Chrome:マウスジェスチャもコンテンツブロックもユーザースクリプトもないし、使えるようにもできないけどとにかくすげえ!(マウ筋使えばいちおうマウスジェスチャは出来る)

**って機能がないよ。

Opera:ユーザー側で機能追加も出来ないしダメだなあ

Firefox:それ(アドオン|Greasemonkey|userChrome.js)で出来るよ。あるいはつくるよ。

Chrome:機能の絞りかたがすばらしい! あるいは将来に期待!


無限alertになっても「このページでは以後表示しない」みたいなことができるよ。

Opera:そーなんだ

Firefox:無限alert作り込んだやつが悪い

Chrome:すげー便利だ!

 

別にすねてないです。この温度差はやっぱブランドとかそのへんのあれなのかなあ。

]]>
Ubiquityでテキストエリア内の文字列をはてな記法で変換するやつ書いた http://tt25.org/blog/20080901/ubiquity-hatenalize-text 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 はてな記法ワープロのソースをまるまるコピペしちゃった。ライセンス的にアレなら削除しますのでぜひご連絡ください。

Ubiquityについてはhttp://www.itmedia.co.jp/enterprise/articles/0808/31/news003.htmlとかhttp://journal.mycom.co.jp/news/2008/08/28/035/index.htmlとかhttp://d.hatena.ne.jp/teramako/20080827/p1を参考のこと。

つかいかた

  1. textareaにはてな記法でテキストを書く
  2. はてな記法で変換したい部分をドラッグして選択
  3. Ubiquityを召喚(Ctrl+SpaceかShift+Space)
  4. "hatena"と入力、Enter
  5. 選択した部分がはてな記法として評価されて入れ替わる

インストール

http://ubiquity.s21g.com/8202

ソース

http://gist.github.com/8202

インストールせずに試す

  1. ubiquityを起動して"command-editor"
  2. 出てきた巨大なテキストエリアにソースを貼り付け
  3. ソースの上らへんにある、コメントアウトされたtest h3とか書いてるあたりを選択
  4. ubiquityを起動して"hatena"

でだいたいどんなもんか体験できると思います。

]]>
はてなブックマークのコメントを強制的に表示させるuser script書いた http://tt25.org/blog/20080831/hatena-bookmark-comment-junky 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 http://hatena.g.hatena.ne.jp/hatenabookmark/20080829/1219979941

らしいので書いた。/entry/json/まで空になってた。

確認したページ

http://b.hatena.ne.jp/entry/http://d.hatena.ne.jp/fk_2000/20080829/p1

hatena-bookmark-comment-junky

ソース

http://gist.github.com/8119

動作原理

ドメインが同じなのでhttp://b.hatena.ne.jp/bookmarklist?url=http%3A%2F%2Fd.hatena.ne.jp%2Ffk_2000%2F20080829%2Fp1&mode=rssをAjaxってDOMってる。

知ってる問題

  • なんか表示が変。詰まってる。
  • Operaでしか動かない。jQueryとかの外部ライブラリの読み込みがダーティだからだと思う。
  • Buzzurlとかlivedoor clipのデータも取ってこようと思ったけど疲れた。
  • gist@githubなのでガンガンforkしてください! あんまり自分で作り込む気がしないです!
]]>
『オブジェクト指向でなぜつくるのか』よりprototype.jsとかActiveRecordのrdoc http://tt25.org/blog/20080729/oop 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 http://d.hatena.ne.jp/takahashim/20080725#p1

会社にあったのでたまに読んでたけど、理論先行というかサンプルコードがなかったのでいまいちピンと来なかったなあ。というか、本を読んでプログラミングの何かが上達したと思えたことがない。手を動かしていろいろしないと頭に入らないタイプなんだと思う。

一方JavaScriptのPrototypeは触っててなるほどなあと思った。Array#mapとかそうあるべきだなとしみじみ思った。あと[1,2,3,4,5].find_all(..).map(..).each(..)みたいにつなげて書ける(メソッドチェイン)のはすごいと思った。これはurlencode(mb_string(trim($str),"SJIS","UTF-8"))みたいな書き方になれたPHP脳を活性化するのに充分な刺激だった。

ActiveRecordのrdocというか、解説を読んでさらに理解が深まった。DBのテーブルがクラスに、その中の1行が1インスタンスに対応してて、save,deleteその他いろんな操作がメソッドとして提供されてる。アソシエーションも直感的に扱える。これは毎回DBのラッパー(O/Rマッパーとはいえない)を漸進的に改良しつつ使ってたPHP脳を活性化するのに充分な刺激だった。

ActiveRecordもPrototypeもつくるのは大変だろうけどすさまじく使いやすい。と思ったけど作るのも思ってたほど難しくはない。

jQuery以前のころにPrototypeを真似て、自分でArray.prototypeを拡張してArray#maxとかArray#sumとかを書いてみたけど簡単なものなら1〜3行くらいで完了する。prototype拡張じゃなくどっかにfunction array_sum()みたいなものを書くより手っ取り早いしわかりやすいし保守も使用も楽だ。さすがにActiveRecordを自作は出来ないけど、それはオブジェクト指向だからではなくもっと別の要因が大きい。

あとあんまり関係ない気もするけど、Head Firstのデザインパターン本はけっこうおもしろかった。いま見たらオブジェクト指向の姉妹本?もあったので読んでないけどたぶんおもしろいはず。

Head Firstデザインパターン―頭とからだで覚えるデザインパターンの基本

Head Firstオブジェクト指向分析設計 ―頭とからだで覚えるオブジェクト指向の基本

もちろん理論から入ったほうが頭になじみやすい人も居ると思うけど、getter/setterだとかinterfaceがどうだとかよりおいしそうなとこからつまみ食いすればいいとおもう。

]]>
OSC2008 in kansai@京都にいってきた http://tt25.org/blog/20080719/osc2008-kyoto 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 初日だけ。2日目は夢の中で見てきました。

普段でたらめな環境で仕事してるので、しっかりした人たちが集まってわいわいやってるのはとても勉強というか刺激になった。物理的に2日目いきたかったなあ。すごく残念だ。次なんかあったらぜひ行こう。

以下は聞きながら取った雑感メモをちょっとは日本語らしくしたものです。文中のセリフなどはオリジナルを無視して書きやすい形に再構成しています。その過程で何かが抜け落ちたり誤解してたりするかもしれません。ご注意ください。

Mashup Caravan in KYOTO

リクルートの人がノリよかった。Sunのスーツの人が絵に描いたように完璧な人だったので感動した。これが大企業の実力かと思った。Sunすげえ。

Wiiリモコンのセンサーを使ってFlashを操作する(Win用のドライバ?はすでにある)。Wiiのセンサでなくとも赤外線さえ出してればいいので、ライターの火を使って画面を操作するなどが可能→Googleマップと合わせて懐中電灯みたいにする

マッシュアップはアイデア勝負。一人で開発すると会議しなくていい(誰かがネガティブなこといって推進力を奪うなどがない)。

NetBeans(IDE)を使ってJRuby on Railsのさわりをちょっと。JRubyはJavaのライブラリを呼び出せる。Railsのscaffoldにちょこちょこ(6行くらい)足して、リンクをクリックすると(Javaのライブラリを使って)文中の英単語を読み上げさせる→これだとサーバ側で再生されるのでデータセンターの怖い話でしかないけど、まあこういうことも出来ますよというデモ。

マッシュアップアワードの1等賞金は100万円。

ステッカーとかリクルートのペンとかなんかノベルティいっぱいもらった。

OSS DRBDの紹介

時間がなかったので質問できなかったのが残念。

DRBDは大雑把にいうとネットワークを使ったソフトウェアRAID1(ミラーリング)。HDDを全然信用してない僕は当然RAIDコントローラもあまり信じてない。分散してくれないと安心できない。DRBDのDは分散(distribute)のD。

OSのディスクとのやりとりをフックして、自前(ローカル/プライマリ)のHDDへデータを書き込むついでにネットワークごしに別マシンのHDD(セカンダリ)へも書き込み指示を出す。/dev/sda1とかじゃなくて、/dev/drbdみたいなものをマウントして使う。

信頼性レベルは3段階あり、それぞれ高い順に

  1. セカンダリへの書き込み完了通知を受け取ったら
  2. セカンダリへ指示を出したら
  3. バッファに乗ったら

ミラーリング完了とする。普通は1か2。1でもそれほど(直感的に思うほど?)パフォーマンスは落ちないらしい。あとメタデータと照合して、セカンダリが一時的に応答しなくてもあとで勝手に同期するらしい。

DRBD+Heartbeat

いわゆるクラスタリング。DRBDはただのミラーリングなので、死活監視とかフェイルオーバとかをするなら何かと連携する必要がある。

Heartbeatを使ってプライマリが死んだら自動的にセカンダリがプライマリになるようにいろいろ。

プライマリが死ぬ
↓
セカンダリがDRBDをマウントして新しくプライマリになる
↓
プライマリ復活
↓
旧プライマリがセカンダリになる

共有ディスク vs DRBD

HDDを全然信用してない僕は共有ディスクなんてものがこの世にあることを受け入れられない。まあディスクがHDDとは限らないし、わからんでもないんだけど、それでもなあ。

ということであんまり聞いてなかった。DRBDのほうが優れてるのは言われなくてもわかってる。

DRBD+LVM

/deb/drbdみたいなものをマウントできるのはプライマリだけ。セカンダリはマウントできないし無茶するとデータ壊れる。そこでLVMスナップショットを使う。テラバイト級のデータでもLVMはほぼ一瞬でイメージがつくれる(マウント出来るの聞き違えだったかもしれない。自信なし)。

LVMイメージをバックアップすることでデータのバックアップを実現する。

実例。メールサーバは1メール1ファイルなMailDirですけど、増えてくるとファイル数が数千〜億とかいう単位になってくる。rsyncがこれらのファイルの更新チェックだけで丸一日掛かったりしてまともに機能しない。DRBDならデータを書き込んだときには既にミラーリングが完了しているので安心。ここでペッシとプロシュートに意識が飛んだので詳細はちょっと聞き逃した。


疑問1:スピーカーが使ってたタスク切り替えアプリが気になった(OSはおそらくLinux)。空中に各ウィンドウが浮かんでて、マウスで泳いでたどり着く感じのものだった。Compiz関係かなあ。

疑問2:DRBDで3台以上のHDD(ていうかマシン)を管理できるのか否か。出来るのであればミラーリングに限らずRAID5とかRAID1+0みたいなことも可能か。

疑問3:MogileFSと比べてどうなのか。

疑問4:話を聞いたかぎりだとあんまりスケールしなさそうだけどそのへんはどうなのか。

MySQLで大量のデータを投入するには?

仕事でMySQL 3.23と4.0と4.1を同時に触らされて日本語とかSQLクエリとかで散々な目に遭って以来、MySQLには警戒心しかないんですがPostgreSQLにも役立つ部分はあるかと思って見てみた。Postgresとはあんまり関係なかったけどInnoDBの内部構造がちょっとわかっておもしろかった。

4GBのCSVファイル(だいたい800万行くらい)をLOAD DATAでデータベースにロードするのに、何も考えずにそのままやると13時間くらい掛かる。予め主キーでソートしたデータであれば14分くらい。桁違い。13時間掛かったほうは14分のほうに比べて、SELECTで参照するのも圧倒的に遅い。ロードするのもした後も何もかも遅い。

この現象はなぜ起きるのか、解決策は、などを検証データを用いて説明。

この現象はInnoDBのデータ構造によって起きる。順番ばらばらのデータを入れると、B-Tree構造が行き当たりばったりというかしっちゃかめっちゃかというか、とにかく乱雑で非効率なものになってしまう→OPTIMIZE TABLE構文はこれを整理する。

解決するだけならOPTIMIZE TABLEでなんとかなるけど、これ自体の実行時間も桁違い。OPTIMIZE TABLEした後はどっちも同等の速度が出るようになる。でも、どうせならソート済みデータを入れたほうがいいよね。sortコマンドも数分程度で終わるわけだし。

大きなデータはソートしてから投入しましょう。

質疑応答


Q. 検証結果の表をみると1GBの場合がやたら早くなってるように見えますが、何か理由やノウハウなどがあるのでしょうか?

A.

たぶんメモリにバッファが残ってるからディスク読みにいかなくてよかったことが大きいんだと思う。ノウハウというか、InnoDBなら64bit OSをつかってメモリバッファをできるだけ大きくしましょう。


Q. MyISAMでも同様の現象/効果が認められますか?

A.

InnoDBしか検証してないのではっきりしたことはいえない。でもインデックスが絡むのでだいたい似たような結果にはなると思う。

Q. パラメータチューニングでどうにかなりますか?

A.

多少の効果はあるでしょうが、13時間が数十分になるとか見たらどうでもいいレベルではないかと思います。やるとしたら、先ほどもいいましたがメモリキャッシュを大きく取るのが有効かと思います。

Q. パラメータをいくつか設定して普通にバルクインサート(INSERT INTO foo (id,name) VALUES (1,'foo'),(2,'bar'),(3,'baz')みたいなやつ)するだけでもなかなかいい感じの速度が出ていますが、LOAD DATAと比べてメリット/デメリットなどは?

A.

それは興味深いですが検証出来てないのでなんとも言えません。すいません。LOAD DATAとバルクインサートは用途が違うといえば違うので、あーどうだろう、えーと、まあ、検証してみたらhpのサイト(注:スピーカーはhpの人)にどんどん掲載していきますので、そちらもよろしくお願いします。

やっぱりすごいmemcached

Memcached全盛な昨今ですが、まだまだ日本語での情報が不足しがちとのこと。立ち見だったのでろくなメモが残ってない。

memcached
松信 memcached
pg_memcached

このメモでいったい何をどうすればいいのか。仕方ないので記憶をさぐって補足しつつ。

Facebookが15TB / 805台のMemcachedマシンを使ってるよって紹介。

memcache自体はインテリジェンスのかけらもない。APIもset、getに加えてreplace,append,prependなどなどごく基本的なものしかない。認証とかそんなもんはないし付け足す予定もさらさらない。

プロトコル(といえるほど洗練されてもいない)も単純。memcache set [key] [データ長](たとえば5)と打って、MEIJIとか入力すればmemcache get [key]でMEIJIが取り出せる。プロトコルは将来的に(速度上の理由で)バイナリベースになるかも、とのこと。

ストレージがメモリに限らなくなるだったか何か忘れたけどそういうmixiの提案が通ったことで、事情はもうちょい複雑になっていくかもしれない。

PerlやRubyやPHPやApacheやNginxやMySQLやPostgreSQLとのバインディングを紹介。mod_memcachedとpg_memcachedが気になった。でもmod_memcachedって知らないけどたぶん静的ページのキャッシュだろうから、それほどキャッシュする必要もないんじゃないかとちらっと思った。アプリがからむならアプリ側でキャッシュしてそうだし。mod_cache_disk(だっけ?)の選択肢が増えるみたいなことなのかな。どうなんだろう。

pg_memcachedとかMySQLとか、データベースのキャッシュをmemcacheでってのはなんか期待してしまうなあ。クエリキャッシュがスケールするなんて素敵すぎる。

あとウェブアプリのセッションをDBじゃなくてmemcacheで、って話をいまかいまかと待ってたけど出てこなかった。残念。

]]>
Ubuntu 8.04 on EeePC 901で有線LANを使うまで http://tt25.org/blog/20080716/eee-pc-901-ethernet 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Ubuntu 8.04て書いたけど、要するにドライバがないだけなので落としてインストールすれば他のディストリも動くはず。

eeexubuntuとか、EeePC完全対応を謳うMandrivaとかを試しても有線LANを認識しない。いろいろググったけど、結局見つけた情報はぜんぶEee PC 70x用のものばかりだったと判明。この外人のいうとおりにしたらあっさり解決した。build-essentialは特に要らなかった。

とは言っても外付け光学ドライブはあるもののUSBメモリが家にないので、プリインストールされてるXPでダウンロードとかをすることにした。

  1. (XP)ドライバを落とす(Driversタブの中にSupport Linux Drivers.とかいうやつがある)。解凍してC:/直下にでも置いておく。
  2. リブートしてUbuntu CDから起動。
  3. 8GBのほうのSSDにUbuntuをインストール。これは滞りなく完了。
  4. OSインストール完了後に再起動。NTFSを読めるので、場所→4GB SSDからXPのディレクトリを見に行き、さっき解凍したドライバをUbuntuが入ってる8GBのほうにコピー。
  5. gnome-terminalを起動してcd LinuxDrivers/L1e_Lan/l1e-l2e-linux-v1.0.0.4/src
  6. sudo KBUILD_NOPEDANTIC=1 make
  7. sudo KBUILD_NOPEDANTIC=1 make install
  8. sudo insmod ./atl1e.ko

とりあえずこれでタスクバーにあるネットワークアイコンがくるくる回り出してデバイス認識+接続完了。

sudo apt-get update && sudo apt-get upgradeするとカーネルがアップグレードされて、再起動するとまたEthernetなんとかがないって言われるけど、同じ手順で復帰可能。

]]>
ゆの in PHP http://tt25.org/blog/20080713/yuno-in-php 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 ゆの in language

<?php
ob_start();


?>
X / _ / X < :来週も見てくださいね!
<?php


echo str_replace("X / _ / X < :","ひだまりスケッチx365 ",ob_get_clean());

ごめんなさいごめんなさい。演算子オーバーロード以前に、行末に;がないのと定数にはスカラしか入らないのとで、何かを表示するにはこれしか思いつかなかったんです。

でもこれPHPらしいと思うんですよ。

]]>
Merbでblogつくった http://tt25.org/blog/20080712/merblog 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Merbでブログつくってみた。

http://merblog.tt25.org/

環境は特殊といえば特殊なかんじ。ミーハーなので仕方ないですね。

]]>
Googleでたまに使うテクニック7つ http://tt25.org/blog/20080710/google 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Googleでよく私が使う検索テクニックまとめ | 楽してプロっぽいデザイン | Forty-N-FiveBlog

大文字は使わない

これはたまにじゃなくて常に。

大文字小文字は無視されるのでどっちみち検索結果は同じ。Shiftキー押下が面倒なので基本的に大文字は入れない。

(綴りとか名前を)うろ覚えのまま入力して「もしかして」に正解を出させる

delisiosとか。

Deliciousに行きたいんじゃなくて、Deliciousの綴りを調べるのに使う。カタカナ化してる英語は英辞郎(これも「えいじろう」で検索してもしかしてをコピペした)で見つかりますけどね。

同義語がうざいときは""で囲む

例がニッチすぎて恐縮ですが、たとえばrubyのO/Rマッパ「Sequel」について調べたいのに、「sequel」だと「SQL」が同義語とみなされて関係ないものまでヒットして鬱陶しい。

「"sequel"」とすると同義語が展開されない。

http://www.google.com/search?hl=ja&client=opera&rls=en&q=sequel+left+join&btnG=%E6%A4%9C%E7%B4%A2&lr=

http://www.google.com/search?hl=ja&client=opera&rls=en&hs=O1N&q=%22sequel%22+left+join&btnG=%E6%A4%9C%E7%B4%A2&lr=

Google計算機は日本語が面倒なのでできれば英語で

変換とIMEオンオフが不要なので、「1ドルin円」より「$1 in yen」のほうが入力しやすい。

でも「65536を漢字」のように日本語でないとダメ(in japaneseとin kanjiの2つで調べただけだけど)なものもある。

ローカライズが強すぎて鬱陶しいときはhl=en

たとえば、mixiは海外でどう見られてるのか調べたいとき、普通に検索すると日本語情報ばっかり引っかかって邪魔。そういうときに使う。そういうときじゃなくても、日本語の記事1つとそれへのソーシャルブックマーク数十、みたいにまともな情報がないときにも使う。

操作としてはCtrl+L End &hl=en Enter。オプション検索をクリックするのは面倒なので最後の手段。

http://www.google.com/search?client=opera&rls=en&q=mixi&sourceid=opera&ie=utf-8&oe=utf-8

http://www.google.com/search?client=opera&rls=en&q=mixi&sourceid=opera&ie=utf-8&oe=utf-8&hl=en

テンプレ化したタイトルや言い回しを一網打尽にする

intitle:そろそろ*について

「そろそろ*一言」も考えましたが、「そろそろ*について」で充分絞り込めてるようなので広く取りました。

一生一緒に* -いて

Wikipediaとメガフレアの2強にエビドリアが追随してて新興勢力もちらほらいる、って状況が概観できます。関係ないけどこのまえカラオケに行ったとき「青山テルマ/そばにいるね」って文字列を見て「誤植?」「山にいるね、だよな、たぶん」とか4秒ほど混乱しました。


AutoPagerizeを入れて表示件数を10件にする

AutoPagerize以前は100件表示でしたが、AutoPagerize + 10件表示にしてからレイテンシが向上したように思います。ていうかテクニックではないですね。

]]>
Sequelでデータ取得とAssociationを使うメモ http://tt25.org/blog/20080702/ruby-sequel 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Migrationとかupdate/delete/saveあたりはとりあえずパス。

テーブル定義

User

id integer
name string
created_at timestamp

Post

id integer
user_id integer
title string
created_at timestamp

なテーブルで、Post.user_id=User.idな関係。

Sequel::Model


class User < Sequel::Model
	set_schema do
		primary_key :id
		string :name
		timestamp :created_at,:default => :now[] # ハマりどころ
	end

	has_many :posts # 複数形
end

class Post < Sequel::Model
	set_schema do
		primary_key :id
		integer :user_id
		string :title
		timestamp :created_at,:default => :now[] # ハマりどころ
	end

	belongs_to :user # 単数形
end

:now[]なんてのは初めてみる。これが:nowとか"now()"とかだとなんか上手くうごかない。

ModelとDatasetの違い

Modelはテーブルの1行に相当する感じ。


# --------------
# Model
# --------------

p User[{:id => 1}]
#<User @values={...}>

p User[1]
#<User @values={...}>

p User[1].name
# id=1なユーザーの名前。

p User[3] == User[:id => 3] # データ的にも意味的にもtrue

DatasetはModelをいっぱい持ったEnumableで、to_sするとSQLが見える。


# --------------
# Dataset
# --------------

p User.filter(:id => 1)
#<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"id\" = 1)">


p User.filter(:id < 5)
#<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"id\" < 5)">


p User.filter(:id < 5).order(:id)
#<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"id\" < 5) ORDER BY \"id\"">


p User.filter(:id < 5).first
# (おそらく)id=1なUser Model


User.filter(:id < 5).each{|u|
	# ここでの u は <User @values={...}> なModel
	puts u.name
}

p User.filter(:id < 5).map{|u| u.id}
# [1,2,3,4]



p User.filter(:name.like('foo%'))
#<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"name\" LIKE 'foo%')">


p User.reverse_order(:created_at).paginate(1,5) # ページ数,件数
# "SELECT * FROM \"users\" ORDER BY \"created_at\" DESC LIMIT 5 OFFSET 0"


# ActiveRecodeでいうwith_scopeみたいなもの

User.subset(:kiriban,:id => [100,1000,10000])
User.kiriban
# #<Sequel::Postgres::Dataset: "SELECT * FROM \"users\" WHERE (\"id\" IN (100, 1000, 10000))">



# eagerするとDatasetが持つ各Modelにメソッドが定義される感じ?

p User.filter(:id => 3).eager(:posts).first.posts
# [#<Post @values={...}>,#<Post @values={...}>, ..]
# ↑配列で返ってくる

# belongs_toも同じ。
p Post.filter(:id => 7).eager(:user).first.user
# #<User @values={...}>
# ↑配列じゃなくてハッシュ

# あわせわざ
User.kiriban.filter(:name.like('bar%')).reverse_order(:created_at).eager(:posts).all.each{|u|
	puts u.posts # [#<Post @values={...}>,<Post @values={...}>]
}

てな感じ。


参考資料

Sequel: The Database Toolkit for Ruby

File: README

ANN: Sequel 2.0.0 Released

]]>
Ramaze ver. 2008.06(とSequelとErubis)使ってみた http://tt25.org/blog/20080701/ramaze-with-sequel-erubis 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 チュートリアルといえば掲示板だと思うので掲示板をつくってみた。

インストール

gemでいろいろ入れる。aptでSQLite3とかもいれとく。

# sudo gem install ramaze sequel erubis thin
# ramaze -v
Ramaze Version 2008.06, on ruby 1.8.6 (2007-09-24) [i486-linux]

プロジェクト作成

# ramaze --create bbs
# cd bbs

Model

前から気になってたSequelを使う。MerbやRailsみたいにdatabase.ymlとかないので生で扱う。

# vim model/post.rb

require "sequel"

DB=Sequel.sqlite "sqlite.db"

class Post < Sequel::Model
	set_schema do
		primary_key :id
		varchar :name
		varchar :title
		text :body
		timestamp :created_at
	end

	before_save do
		set(:created_at => Time.now) if columns.include? :created_at
	end
end

Post.create_table if !Post.table_exists?

ほぼfeatures:ormsのまま。

  • 「DB」はグローバルな定数でSequelオブジェクトが入る。
  • デフォルト値がよくわからなかったのでbefore_saveで代用(たぶん間違ってるので注意)
  • 最後にCREATE TABLE IF NOT EXISTSみたいな処理。

modelディレクトリ内にあるファイルは自動で全部読まれるみたい。最初からやり直したくなったらsqlite.dbを消せばいい。

View

Erubis使う。他にもいろいろ使えるHamlは公式サイトのロゴが怖いというか気持ち悪いので使いたくない。

# vim view/index.rhtml

hello <%== @title %>

最初index.html.erbとかindex.erbとかやって動かなくてハマった。

コントローラがfoo、アクションがbarなら、view/foo/bar.rhtmlかview/foo__bar.rhtmlになるみたい。

Controller

# vim controller/main.rb

class MainController < Ramaze::Controller
  engine :Erubis

  def index
    @title="ramaze"
  end
end

だいたいいつもどおり。features:templatesはあまり参考にしなかった。

うごかしてみる

# ruby start.rb

んでlocalhost:7000を見てみる。ちゃんと動くのを確認したら一旦Ctrl+C。

DBに値を入れたり出したりする

controller/main.rbに書く。

# vim controller/main.rb

class MainController < Ramaze::Controller
  engine :Erubis

  def index
    @title="ramaze"
    db=Post.new({
      :title => "test title",
      :body => "test\nbody",
      :name => "ramaze test"
    })
    db.save
    @posts=Post.reverse_order(:created_at).all
  end
end

viewも変える。

# vim view/index.rhtml

<h1>hello <%== @title %> </h1>

<% @posts.each{|p| %>
	<p>
	<strong><%== p.title %></strong>
	<em><%== p.name %></em>
	<%= p.body.gsub(/\r\n|\r|\n/,"<br />\n") %>
	</p>
<% } %>

リロードするたびに増えてくはず。

formを使う

viewにformを足す。

# vim view/index.rhtml

<form action="" method="post">
<input type="text" name="title" />
<input type="text" name="name" />
<textarea name="body"></textarea>
<input type="submit" />
</form>


.. 以下同文

controllerも変える。

# vim controller/main.rb

class MainController < Ramaze::Controller
  engine :Erubis

  def index
    @title="ramaze"
    post=request.POST
    if post[:body] && post[:body].length > 0 # てきとー
      db=Post.new(post)
      db.save
    end
    @posts=Post.reverse_order(:created_at).all
  end
end

これでタイトル、名前、本文を投稿できるようになったはず。

Thinでサーバをデーモンにする

start.ruっていうRack用のファイルがあるはずなのでそのまま使う。WEBrickじゃなくてMongrelがいいとか、ポートが7000番じゃ嫌だ、とかならstart.rbのほうを適当に書き換えて使う。

# thin start -R start.ru -d

おわりに

コード自動生成とかがない。これをシンプルというか貧相というかで適正がわかるのではないか。

]]>
Operaっぽくしない、FirefoxらしいFirefoxのためのアドオンリスト http://tt25.org/blog/20080626/firefox3-addons 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 メインブラウザはOperaですがFirefoxも併用してます。

TB企画*これがないと困る!Firefox 3 のアドオンリスト :: Love & Design ::

まだそんなに使いこんでないので偏ってる。そんなに使い込んでないのに入ってるってことは、たぶん、ないと困るに近いはず。

FirefoxをOperaに近づけたところでそれならOpera使えばいいわけで、Firefoxには別のコンセプトを求めてる。そういう意味でVimperatorには期待してる。

というわけでいま入ってるアドオンはこんだけ。一覧の出力にはExtension List Dumper使った。

アプリケーション: Firefox 3.0 (2008052912)
OS: Linux (x86-gcc3)

- Adblock Plus 0.7.5.5
- Delicious Bookmarks 2.0.64
	(無効)
- Extension List Dumper 1.14.1
- Firebug 1.2.0b3
- FireGestures 1.1.2
	(無効)
- Greasemonkey 0.8.20080609.0
- Live HTTP Headers 0.14
- MeasureIt 0.3.8
- Nightly Tester Tools 2.0.2
- PrefBar 4.1.0
- Relaxed the HTML Validator 0.9.5
- S3 Firefox Organizer(S3Fox) 0.4.1
- Vimperator 1.1
- ツリー型タブ 0.7.2008062001
	(無効)

詳細なリスト。

アプリケーション: Firefox 3.0 (2008052912)
OS: Linux (x86-gcc3)

2008年6月26日

合計: 14

- Adblock Plus 0.7.5.5
	http://adblockplus.org/
	広告は過去の遺物です!
- Delicious Bookmarks 2.0.64
	(無効)
	http://delicious.com
	Access your bookmarks wherever you go and keep them organized no matter how many you have.
- Extension List Dumper 1.14.1
	http://sogame.awardspace.com/
	インストールした拡張(アドオン)の一覧を生成します。
- Firebug 1.2.0b3
	http://www.getfirebug.com/
	Web Development Evolved.
- FireGestures 1.1.2
	(無効)
	http://www.xuldev.org/firegestures/
	Executes various commands with mouse gestures.
- Greasemonkey 0.8.20080609.0
	http://www.greasespot.net/
	A User Script Manager for Firefox
- Live HTTP Headers 0.14
	http://livehttpheaders.mozdev.org/
	ウェブページとブラウジング中の HTTP ヘッダを表示します。
- MeasureIt 0.3.8
	http://www.kevinfreitas.net/pro/extensions/
	Draw out a ruler to get the pixel width and height of any elements on a webpage.
- Nightly Tester Tools 2.0.2
	http://www.oxymoronical.com/web/firefox/nightly
	Useful tools for the nightly tester.
- PrefBar 4.1.0
	http://prefbar.mozdev.org/
	A toolbar for quickly accessing and changing common preferences, run scripts and many more...
- Relaxed the HTML Validator 0.9.5
	http://relaxed.vse.cz/
	"Relaxed" is a HTML validator which validates HTML documents using it's own schema definitions written in Relax NG with embedded Schematron patterns.
- S3 Firefox Organizer(S3Fox) 0.4.1
	http://www.suchisoft.com/ext/s3fox.php
	An user friendly interface to manage Amazon's S3 (Simple Storage Service)
- Vimperator 1.1
	http://vimperator.mozdev.org
	Make Firefox behave like Vim
- ツリー型タブ 0.7.2008062001
	(無効)
	http://piro.sakura.ne.jp/xul/_treestyletab.html
	タブをツリー状に表示します。
]]>
Passenger 2.0 RC1でMerbを動かしてみた(Ubuntu 8.04) http://tt25.org/blog/20080620/merb-with-passenger 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Rails用Apacheモジュールとして話題をかっさらったmod_rubyことPassengerですが、v2.0ではRackとWSGIをサポートしたことでRails以外の(Rack対応した)フレームワーク、およびPythonのDjangoまで動くというめちゃくちゃ野心的な動きをしています(「勘違いしないでくれ。主眼はあくまでRailsのサポートであって、WSGIサポートはpassengerの柔軟なアーキテクチャを示すためだ」とか言ってますが)。

というわけでZed Shawがいかしたフレームワークと呼ぶMerbをPassenger 2.0 RC1で動かしてみた。以下Ubuntu 8.04をベースに進めていきますが、適宜読み替えてもらえば他のディストリでも大丈夫だと思います。

結論からいうと

MERB_ROOTにconfig.ruって名前のrackup用ファイルをおいとけばPassengerが勝手に読むみたい。あるいはRackBaseURIで明示的に指定っぽい。

Passenger 2.0 RC1のインストール

~/# wget http://phusion-passenger.googlecode.com/files/passenger-1.9.0.gem
~/# sudo gem install passenger-1.9.0.gem
~/# sudo passenger-install-apache2-module
--snip--
Please edit your Apache configuration file, and add these lines:

   LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-1.9.0/ext/apache2/mod_passenger.so
   PassengerRoot /usr/lib/ruby/gems/1.8/gems/passenger-1.9.0
   PassengerRuby /usr/bin/ruby1.8

/etc/apache2/mods-available/passenger.confにLoadModule....をコピペして、mods-enable/passenger.confからシンボリックリンクを張る。

とりあえず試すだけならhttpd.confにコピペしてもいい。

Merbインストール

SQLite3とかはとりあえず入ってるものと仮定。

~/ # sudo gem install activerecord merb merb_has_flash merb_helpers merb_activerecord

--snip--

~/ # merb -v
merb 0.9.3 ()

Merbでつくる

Jamie Hoover - Merb, SQLite, and Nginx on Ubuntuとかmerb.introを参考に進める。

rake dm:db:automigrateが動かなかったのでDataMapperじゃなくてActiveRecordを使うことにした。

~/ # merb-gen app testapp
~/ # cd testapp
~/testapp # vim config/init.rb

# Uncomment for ActiveRecord ORM
use_orm :activerecord

~/testapp # merb-gen resource article title:string body:text

-- config/database.ymlが無いっておこられる --

~/testapp # mv config/database.yml.sample config/database.yml
~/testapp # vim config/database.yml

---
# This is a sample database file for the ActiveRecord ORM
:development: &defaults
  :adapter: sqlite3
  :database: sample_development
  :username: the_user
  :password: secrets
  :host: localhost
  :socket: /tmp/mysql.sock
  :encoding: utf8

:test:
  <<: *defaults
  :database: sample_test

:production:
  <<: *defaults
  :database: sample_production

~/testapp # merb-gen resource article title:string body:text

  dependency  model
      exists    app
      create    app/models
      create    app/models/article.rb
  dependency    migration
      create      schema/migrations
      exists      schema
      exists      schema/migrations
      create      schema/migrations/001_article_migration.rb
  dependency    merb_model_test
      exists      spec
      create      spec/models
      create      spec/models/article_spec.rb
  dependency  resource_controller
      exists    app
      exists    app/helpers
      exists    app/views
      create    app/views/articles
      exists    app/controllers
      create    app/helpers/articles_helper.rb
      create    app/views/articles/show.html.erb
      create    app/views/articles/index.html.erb
      create    app/views/articles/edit.html.erb
      create    app/views/articles/new.html.erb
      create    app/controllers/articles.rb
  dependency    merb_resource_controller_test
      create      spec/controllers/
      create      spec/helpers/
      exists      spec
      exists      spec/helpers
      create      spec/views
      create      spec/views/articles
      exists      spec/controllers
      create      spec/helpers/articles_helpers.rb
      create      spec/views/articles/delete.html.erb_spec.rb
      create      spec/views/articles/index.html.erb_spec.rb
      create      spec/views/articles/edit.html.erb_spec.rb
      create      spec/views/articles/new.html.erb_spec.rb
      create      spec/views/articles/show.html.erb_spec.rb
      create      spec/controllers/articles_spec.rb


~/testapp # rake db:migrate

--snip--

~/testapp # merb -p 3001

とりあえず普通にMerbが動くかテスト。http://localhost:3001/articlesを見てみて「Edit this file in app/views/articles/index.html.erb」とか表示されたらOK。

続いてPassengerの設定。Merb Wiki | Phusion Passengerを参考に。


~/testapp # vim config.ru

# config.ru
require 'rubygems'
require 'merb-core'

Merb::Config.setup(:merb_root   => ".",
                   :environment => ENV['RACK_ENV'])
Merb.environment = Merb::Config[:environment]
Merb.root = Merb::Config[:merb_root]
Merb::BootLoader.run

run Merb::Rack::Application.new

~/testapp # sudo vim /etc/apache2/httpd.conf

<virtualhost *:80>
	ServerName merb-test
	RackEnv development
	DocumentRoot /home/myname/testapp/public
	ErrorLog /home/myname/testapp/log/error.log
</virtualhost>

~/testapp # sudo /etc/init.d/apache2 force-reload

/etc/hostsに「127.0.0.1 merb-test」を追加して、http://merb-test/が見れたら成功。

]]>
ドキュメント化されてなさそうなRedmineのtips http://tt25.org/blog/20080616/redmine-tips 2008-09-09T21:41:06+09:00 2008-09-09T21:41:06+09:00 Debian etch+SQLite3に注意

Unable to create the anonymous user.

何度やってもこれが出るので、AMD64だからかとかpassengerが原因かとかいろいろ疑ったりしたけど全部濡れ衣だった。

SQLite 3.3.7だか3.3.8に非互換な変更が入ったらしく、Debian etchのAPTにあるsqliteがまさに3.3.8で死亡。SQLiteにこだわりがなければMySQLとかPostgreSQLを使えば普通に動きます。

ちなみにUbuntu 8.04の3.4.2だと大丈夫でした。とりあえずUbuntuで動かして、DebianはリバースプロクシでUbuntuを見に行くようにしました。

ていうか今みてみたら公式に書いてますね

OperaでRedmineのコンテキストメニューを使う

Alt+クリック。その他のブラウザは普通に右クリックでOK。

Operaはデフォルト設定で右クリックを制御しようとするJavaScriptを拒否します。その昔、ページを右クリックすると「右クリック禁止です」とか出てくる変なスクリプトが流行ったころの名残です。

/javascripts/context_menu.jsのソース読んでたら気づきました。Operaなんかいつも無視されるのにRedmineはいいやつだ。if (window.opera && !e.altKey) { return; }はちょっとどうかと思うけどいいやつだ。

ちなみにShift+クリックとかCtrl+クリックは、ちゃんと期待したとおり複数選択してくれます。

ガントチャートとかはチケットに期限を書かないと出ない

ガントチャートなのでそりゃそうだ、といった感じですが、案外盲点なので注意。

なお、このスクリーンショットはフィクションです。

ロードマップはバージョンを設定すると出現する

なんか隠しキャラ探しみたいになってきた感もある。

なお、このスクリーンショットはフィクションです。モザイクは演出です。

リポジトリへのコミットがチケットと連動してくれない(fixesとかに反応してくれない)

ヘッダにある管理→設定→リポジトリの一番下にある「修正用キーワード」の設定を見直すこと。

チケットの「担当」とか「解決」がいまいちピンとこない

「担当(assigned)」は、担当者が決まったのであとはよろしくって状態。「新規(new)」はとりあえず問題があったのでチケット書いたよ、って状態かな。たしかに和訳が難しい。

「解決(resolved)」はチケットに書かれてる作業をしたよ、って状態。作業内容をチェックして問題なければ「終了(close)」になる、とおもう。

とりあえず漢字2字がいっぱいあると一覧性というか視認性が悪いので、家では「new」「assigned」「作業済み」「意見待ち」「おわり」「却下」に名前変えてつかってる。優先度も「そのうち」「あとで」「通常」「高め!」「緊急!!」にしてる。

チケット一覧が横に長いのでtableが崩れて読みづらい

ヘッダにある管理→設定→テーマでclassicとかalternateにしてみるといいかも。

全プロジェクトの全チケット一覧を見たい

ヘッダにあるプロジェクト→右上にある「チケットを全て見る」で見れる。

会社ではapp/views/layout/base.rhtmlを編集してヘッダに常にリンク出すようにしてある。

]]>
GoogleがホストしてるjQueryとかはgoogle.load()せずに直接呼び出せる http://tt25.org/blog/20080612/google-hosted-javascript-library-direct-request 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 第0.5回 JavaScriptの記述場所 - jQueryによるJavaScript入門 - Re:Creator’s Kansai

<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">google.load("jquery", "1.2");</script>

コメントがなぜか反映されないので、あと他にもどっかで2回くらい見たので書いておく。

つかいかた

Developer's Guide - AJAX Libraries API - Google Code

Each library is available via both google.load(), and directly via <script/> tag.

と書いてあるように、各種ライブラリは<script />からダイレクトに呼び出せる。

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>

でいい。URLの1.2.6を1.2とか1とかにするとワイルドカード指定みたいになる。

というかgoogle.load()がやってることは、このURLを作って<script>にセットしてappendChildしてる感じ。

やってくれてること

  • Expiresが1年後(キャッシュが1年間有効)
  • gzip圧縮転送

Expiresがあるので、どっかで一度でも同じURLのスクリプトを読んだことがあってキャッシュが生きてたら、サーバへのリクエストが発生しないのでGoogleが落ちててもまったく問題ない。

]]>
それ単一SQLで出来るよ http://tt25.org/blog/20080609/sql-friend-timeline 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 *注意:勘違いでした。以下はまちがってます。

PostgreSQLだけど。MySQLはサブクエリが微妙だったはずなので出来るかわからない。

使用したバージョンはPostgreSQL 8.2ですが、それほど新しくないPostgreSQLでもいけると思う。

Kazuho@Cybozu Labs: フレンド・タイムライン処理の原理と実践

1. フォローしている各ユーザーについて、そのメッセージ ID の最大値を取得

2. 1 のリストをメッセージ ID の降順でソートし、その先頭 20 件 (1ページ分) 以外を破棄

3. 2 のリストの全ユーザーについて、最新 20 件のメッセージを取得し、マージ

4. 3 のリストの先頭 20 件が、クライアントに返すべきフレンド・タイムライン

テーブル定義はこんな感じ。serial型はinteger AUTO_INCREMENT(と同じとみなしていい)。

CREATE TABLE follower
(
  id serial NOT NULL,
  followed_user_id integer NOT NULL, -- フォローされてるユーザID
  following_user_id integer NOT NULL,-- フォローしてるユーザID
  CONSTRAINT follower_pkey PRIMARY KEY (id)
);


CREATE TABLE messages
(
  id serial NOT NULL,
  user_id integer NOT NULL,-- 発言した人のユーザID
  message character varying(255) NOT NULL,-- メッセージ本体。character varying=varchar
  CONSTRAINT messages_pkey PRIMARY KEY (id)
);

データを入れていく。user_id=1の人のフレンドタイムラインを構築する予定。

-- user_id=1の人が、user_id 2〜19までをフォローする。
-- 特に19である必要も連番である意味もない。

INSERT INTO follower (following_user_id,followed_user_id) VALUES
(1,2)
,(1,3)
,(1,4)
,(1,5)
,(1,6)
,(1,7)
,(1,8)
,(1,9)
,(1,10)
,(1,11)
,(1,12)
,(1,13)
,(1,14)
,(1,15)
,(1,16)
,(1,17)
,(1,18)
,(1,19);

/*
|following_user_id|followed_user_id|
|1|2|
|1|3|
|1|4|
|...|...|
|1|19|
 */

-- 以下のようなSQLを最低でも1000回くらいは繰り返してダミーデータ生成。

INSERT INTO messages (user_id,message) VALUES
(floor(random()*50)+1,random()::text); -- user_id 1〜50の発言をランダムに生成

準備が出来たらいよいよ取得。user_id BETWEEN 2 AND 19じゃないやつが出たら失敗。


select main.* from messages as main
inner join (
	select user_id
	from messages
	where user_id IN (
		select followed_user_id from follower where following_user_id=1 -- user_id=1の人のフレンドラインを取得するので。
	)
	group by user_id
	order by max(id) DESC -- 手順1:メッセージID最大値
	limit 20 -- 手順2:21件目以降は破棄する
) as tmp on (tmp.user_id=main.user_id) -- 手順3:マージ

order by id desc
limit 20 -- 手順4:結果20件

うまくいった(はず)。

ちなみに2回出てくるlimit 20と、following_user_id=1がそれぞれ変数になります。

]]>
アップルストア心斎橋店にCSS NITEが来てたので冷やかしてきた http://tt25.org/blog/20080607/cssnite-in-apple 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 土曜といえど特に用事もないのでMacの値段調査がてら見てきた。見た発表は3つ。

写真の撮り方というかカメラの基礎知識

カメラ知識は皆無なのでいろんなパラメータの挙動が知れておもしろかった。

途中で「絞り値」と「F値」というのが出てきた。両者は相関しているらしい。表で書くと

絞り値 0 1 2 3 4
F値 1 1.4 2 2.8 4

という関係。どう見ても1.4と2.8が怪しいので一般式へとリバースエンジニアリングしてみたらf(n)=sqrt(2^n)が導き出された。変に小数にせず

絞り値 0 1 2 3 4 n
F値 √1 √2 √4 √8 √16 √(2^n)

って書いたほうがわかりやすかったのでは。

全体を通して、絞り値を増やすとこう、減らすとこう、シャッタースピードがこうだとこういう風に写るからこうしよう、といったように実例やシチュエーション付きで説明されたのですごく頭に馴染んでいった。

あと発表資料の中で「恋するタイマー」ってのが出てきたので、一緒に行った人と「恋するタイマーって何?」「マジで恋する5秒前ですよ」「あー」とかいう会話をした。

教育

さすがのアップルでもプリンタはhp製品なんだなー、とか目の前のプリンタを見ながら考えてた。そりゃいち早くFDDを取っ払ったり光学ドライブを取っ払ったりする先進的なジョブズだから紙なんて興味ないのかもしれない。

アップルはプリンタつくらないんですかって訊いたら、「紙にインクを吹きつけて一体何がしたいんだ? もし偽札をプリントしたいのなら、まずはペイントするべきだ。Macは後者の仕事をフルサポートする」とか反論しながら営業されそうだ。やるなあ妄想ジョブズ。

とか考えてたらいつの間にか終わってた。

CMSつくってる人

CSS NITEでプログラムの話はできないなあと思った。プログラムの話はプログラムの話ができる場所でするべきか。テーマがデザイナーとの協業みたいな感じだったので、悲惨なまでに背理法だなとますますいたたまれなかった。誰が悪いわけでもない。

ずっと立ちっぱなしで腰とかが痛かったのでそのままiPod関連商品を見るなどして帰宅した。Macの値段を見るの忘れた。

]]>
ITProの記事が古いときに警告を出すGreasemonkeyスクリプト書いた http://tt25.org/blog/20080605/itpro-em-refs 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 そろそろ日経LinuxがDISられた理由について解説しておくか - TokuLog 改めChumbyとどきました日記

Perl 2008年のファイルオープン - てっく煮ブログ

革命の日々! ITProのLinuxチューニングの記事がひどい事になっている件について

などのようにITProは地雷に満ちているので、注意するようにちゃちゃっとスクリプト書いた。

ソース:itpro-em-refs.user.js

itpro-em-refs

5秒経つと消えます。クリックすると消えます。Firefox/Opera両対応。

]]>
なぜSEO業者は嫌われるのか http://tt25.org/blog/20080603/seo 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 MarkeZine:◎間違いだらけの「SEO批判」~なぜSEOは嫌われるのか?

検証不可能性にかこつけてでたらめ言いたい放題だからだと思うよ

あるいはてきとうなことばっかり言うからだとおもうよ。

W3Cの勧告に従うとか、Web標準に準拠するとか、XHTMLでサイトを作成するとか。これだけでも効果はバツグンである。

W3CやWeb標準に則っていないサイトや、Internet ExplorerやFirefox、Safariでほぼ同じに表示されないページ、見出しや段落、リストで構造化されていないコンテンツは、どんなに内容が深くても、良いとは言えないのである。

W3CはXHTMLのContent-Typeにapplication/xhtml+xmlを推奨してますが、そうするとIEでは表示すらされません(ダウンロードダイアログが出ます)。

あと十把一絡げにIEとかSafariとか書いてますが、IE5.xとかSafari2以前も含めてちゃんと表示させるには、標準を捨ててtableでレイアウトするのが「ほぼ同じに表示」するための唯一の道です。

前段と後段は方向性が逆ですから、主張として矛盾しています。

検索エンジンは何を見ているか/何をすべきか

見出しと段落を使いこなす、結論を用意し論理の展開をキッチリする。そういう論文作成の手法をマスターすれば、SEOの70%は達成できる。

GoogleもYahooも文意や論理までは解析できません。出来るならワードサラダスパムはとっくに絶滅してるはずです。理解できないものは評価できませんから、検索結果にはほぼ影響しないと考えるのが妥当です。

もちろん副次的には影響します。「ほぼ」と表現したのはこれのためです。しっかりした文書は人間のためのものですし、価値の高いリンクを張るのも人間です。検索エンジンはそういうプロセスをページとリンクといった単位でとらえ、追いかけているに過ぎません。

あるいは同じキーワードを含むにしても、地の文よりも見出し、見出しよりもタイトルを重視するのは理にかなっています。地の文しかなく見出しやタイトルがなければ、検索エンジンとしても重み付けのしようがありません。

しかしそれと文書全体の評価とは別です。これは検索されたキーワードと解析済みの文書との関連性を探るには有用なアプローチですが、タイトルにキーワードが含まれているからといって文書全体が高品質だと判定するのは浅はかに過ぎます。なお、今のところ文書の品質は被リンクや更新日時(新しさ、あるいは古さ)によって計られているようです。

では具体的には何をすればいいのか。明白です。検索エンジンがより理解しやすいように体裁を整え、「検索エンジンが考える高品質な」ページをつくるべきです。

すべての文書にはパーマリンクを用意し、Googleウェブマスターツールなどを使ってページの存在を通知する。?keyword=aと?keyword=a&sort=dateと?keyword=a&sort=price、あるいはxxx.htmlとxxx.html?from=rssのように、内容がほぼ同じページが大量にあると検索エンジンは重要性をうまく判定できないので、robots.txtやmetaタグで不要なページをインデックスしないように気遣う。もちろん内部リンク網も気にしないといけません。これらはすべて人為的におこなえますが、まだ最低限の施策です。

準備が整ったら、次はもちろんページをつくる必要があります。質はリンクと更新日時によって計られますから、更新日時はともかくまずはリンクを獲得することを考えないといけません。

アクセスアップとトラップ

ここでSEOだけしか考えていないと以下のような無限ループに陥ることになります。

「リンクを獲得しないと検索エンジンで人に見つけてもらえない」

「まだ立ち上げたばかりだから誰も知らない」

「誰も知らないからリンクが獲得できない」

「リンクを獲得しないと人に見てもらえない」

このジレンマを力業で強引に解決するのが、俗に言うSEOスパムです。数万円もはたいてくだらないツールを買い、あちこちにコメントスパム、トラックバックスパム、ソーシャルブックマークスパムを繰り返すことで、なるほどリンクは獲得できます。

このスパム行為をSEOだといってはばからない、あるいは前述の無限ループを挙げてこうしなきゃどうしようもないと開き直る、言い分は様々ですがスパムはスパムです。Google先生がスパムだとおっしゃっておられるのでスパムです。

検索エンジンにバレたら、あるいはチクられたら申し開きも出来ないまま検索結果から削除されて終わりです。自称小遣い稼ぎのアマチュアスパマーならその後なんとでもできますが、商売でやってるサイトがドメインごと削除されたらなかなか一大事といえるでしょう。

このブログのケース

正直なところ、このブログを作る前は僕も上のようなジレンマに陥ってるふしがありました。いちばん最初の集客というか、発見されるという状態がどうやって起こるのか。頭ではわかっていても経験として持ってなかったので、SEOと称してスパムする人の言い分にも一理あると思っていたのです。はてなダイアリーのように内部リンクが濃密で、キーワードページなどによる発見機会もふんだんにあるならばわかるのですが、孤島に等しい独自ドメインでやる場合はどうなんだろう、と。

とりあえずブログをつくって、更新したらGoogleやテクノラティに更新Pingを打つくらいはしましたが、それだけでもGoogleからちらほらと(マイナーなキーワードでですが)アクセスはありました。他にもサイト構造を考えるとか細々としたことをやりましたが、あまり考えても埒があかないので(というか面倒なので)ふつうにブログすることにしました。すなわち言及リンクやトラックバックを活用してなんかいろいろと書く行為をしばらく続けました。

トラックバックからのアクセスはあまり多くありませんでしたし、たいていnofollow付きリンクですから被リンクという意味では効果も薄いですが、わざわざトラックバックをたどって読みに行く人というのは情報感度の高い人です。そういう人がはてなでブックマークを付けてくれると、たまにそこから2users、3usersとなって注目のエントリーに表示されるようになると、さらに4users、5usersと膨らむこともあります。

この流れがうまくいくかどうかはSEOとかを抜きにした対人間の意味でのページの質、話題性というか、そういうものによってのみ左右されます。検索エンジンの出番はまったくありません。

そうこうするなかで何の前触れもなくConnection refused - connect(2)30users以上になりました。特にトラックバックとかも飛ばしてないんですが、どうしてこうなったのか未だに因果関係がよくわかりません。

最初は「ニコニコ動画」キーワードを追いかけてるkoizukaさんのブックマークから波及したのかな、と思ったものの順番的にそうとも言えないっぽいですし。他に考えられるのは「ニコニコ動画」を含むエントリーを自動で収集し続けるニコニコ動画まとめwiki - ニコニコ動画まとめwikiからかな?

何にしろ、それなりに知識があると思ってた僕が予期しなかったかたちでアクセスがいっぱい来ました。その中にはフィードを購読してくれる人も何人かいらっしゃいましたから、発見される機会が今までと比べて大幅に増大したことになります。ちゃんとした記事を書けば、ちゃんと見てくれる人がいてくれるわけです。それにNicokitのときのようなことがまた起きるかもしれません。こうなるとSEOとかもうどうでもいいですね。

商売用サイトの場合

商売ならadwordsやオーバーチュアに広告を出したり、リンクシェアやA8と提携したりして、露出する方法はいくらでもあると思います。ネットに限らなければそれこそ雑誌や中吊り広告やビラなど、いくらでもあります。別にみんながみんな検索エンジンの検索結果をクリックして来るわけではありません。

広告の予算が採れないからタダないし格安で出来るSEO、というのはわからなくもないですが、スパムは先に書いたようにインデックス削除のリスクが高いですし、相互リンクのお願いとかしても検索結果に対しては暖簾に腕押しだと思います。コストパフォーマンスが悪すぎます。

あるいは海外のように、大量のブックマークを意識的に獲得するのもSEOといえます。上に書いたNicokitのような現象をわざと狙うわけです。極端にいえば、なんとかメーカーを作るとかですかね。これは別にスパムではありません、というか、Googleも勧めています。

で、広告なりSEOなりスパムなりで集客して何がしたいのかというと、当然ながら商品販売であったり見積りゲットだったりだと思います。にもかかわらず、売上やコンバージョン率よりも検索結果の順位にこだわる人はそれなりに居ます。そういう人はもう手遅れなので、遅かれ早かれスパムに手を出して自滅するか、利益をスパムツールの購入にあてるとか、日がな一日検索結果の浮沈に一喜一憂するなどしてがんばってください。

なぜSEOは嫌われるのか

こういった原則を一切説明せず、キーワード比率だのmetaタグだのといったオカルトじみた情報を小出しにするという霊感商法みたいなことをやってるからだと思いました。

]]>
twitterを怠惰なキッチンタイマーとしてつかう http://tt25.org/blog/20080603/twitter-timer 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00

既存のタイマーの問題点

  • 一度ないし数度しかおしらせしてくれない
  • 厳格すぎる

コインランドリーは稼働開始から45分とか50分経ったら乾燥機へ移しかえ、さらに45分だか50分待って回収しないといけない。時計を見て確認するのは開始時刻の記憶と計算が面倒だから、ぜひともタイマーが必要だ。

45分後にタイマーをセットしても、45分後は忙しかったり気が乗らなかったりしてスルーするかもしれない。スヌーズつけてても同じ。スルーしたら当然わすれる。タイマーの意味がない。タイマーが鳴ったら忘れないうちに取りに行かないといけない、という覚悟はそのまま負担になってこれまたメモの意味がない。

そもそもきっちり45分である必要がまったくないのにタイマーはどいつもこいつもパンクチュアル過ぎる(パンクチュアルって一回いってみたかったのを達成)。

かといって何もしないと忘れる。また、メモは目につくところにないと存在自体をわすれるので意味がない。

というわけで、

  • 自分がそこそこの頻度で確認する、
  • アラームが鳴るなどの強制力を持たないメモ

としてtwitter(ていうかtwippera)が最適だという結論に至りました。「N minutes ago」って具合に時間を大雑把に表示してくれるのがミソです。followが少ないなど、1時間くらい経っても自分の発言が視界に入ることが条件ですが。

具体的なつかいかた

  • 「カップ麺にお湯を入れた」
  • 「お風呂の蛇口を全開にしてきた」
  • 「カレー煮込み開始」

などのように、一分一秒をあらそわない場合にはtwitterのゆるさがフィットすることかと存じます。

関連

冷蔵庫2.0とは - はてなダイアリー

]]>
Textileが使いにくすぎるのでText::Hatenaに切り替えた http://tt25.org/blog/20080601/textile-to-hatena 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 Textileがちょっと我慢の限界を超えたのでクビにした。

  • <pre></pre>を自分で書かないといけない(略記不可)
  • 普通の!とか"とか@とかの約物が略記法に誤爆する
  • bq. でたしかにblockquoteになるけど複数行引用するときは自分で<blockquote>書かないといけない
  • 「"title":http://example.com/」でリンクになるわけですが、自動リンクの判定が甘いのでURLのあとに改行いれないとリンクがどこまでも続く

普通の文章を書くだけなら有用かもしれないけど、コードとか"とかをいっぱい使うものばっかり書いてるので、さすがにちょっと邪魔されすぎだと感じた。

Markdownとかもちらっと見たけど、なんかPHPタグの扱いがどうとかなんやかんで面倒そうなのでやめて見なかったことにしてText::Hatena README (ja)を使わせてもらうことにした。gemファイルをwgetしてsudo gem installで準備完了。ブログのほうをちょっと手直ししてあれこれして完了。のはず。

あと入力したコードやはてな記法をそのまま表示する(スーパーpre記法) - はてなダイアリーのヘルプの記法で<とかが無修正だったので、parse後に無理やり実体参照にした。

それにしても[http://example.com:title]で勝手にページのタイトル取ってきてリンクにしてくれるのはすごく便利だなあ。

]]>
OperaでMinibufferを使っていま見てるページをdel.icio.usにポスト http://tt25.org/blog/20080531/minibuffer-bookmark-command-for-opera 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 注意:以下のスクリプトは今はもう動きません。

Minibufferはオーバースペック みたいなことを書いておきながら、やっぱり「b」「B」でブックマークできる手軽さがうらやましい。Opera移植版 がなぜか動かなかったので自作することにした。自分のためだけに作ったので極めて低機能。

minibuffer-bookmarkcommand-delicious.js

ファイル名からもわかるようにdel.icio.usにしか投稿できません。あと他のコマンドとパイプでつなぐとかLDRizeと連携したりはできません。Opera移植版Minibuffer に依存しています。Firefoxで動くかはわかりませんけどたぶん大丈夫です。

できること

  • 「bookmark-current-delicious」でいま見てるページをdeliciousにブックマーク
    • --tags」でインタラクティブにタグをつけてポスト(補完付き)
    • --tags=a」「'--tags=aa bb cc'」でタグ指定してポスト
    • --comment」でコメント付きでポスト
    • --comment=おいしそう」でコメント付きでポスト
    • コマンド例:bookmark-current-delicious --tags=test
  • 「b」であらかじめ決めておいたタグ、コメントを使ってポスト
  • 「B」でタグとコメントを指定してポスト

つかいかた

ソースコードの上のほうにあるusernameとpasswordを設定したら、上の「できること」ができるようになる。

なぜMinibufferを使うのか

これくらいなら自分で専用のやつを作った方がいいんじゃないかと思いましたけども、補完付きプロンプトや表示、キーバインド処理なんかをMinibuffer任せにできるので楽かなあと思って。

]]>
Operaでbookmarklet使うときはニックネーム付きブックマークで http://tt25.org/blog/20080529/bookmarklet-at-opera 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 bookmarkletはあんまり好きくなかった。理由として、

  1. 数が増えるとツールバーとかの領域が占領される
    • 探すのが手間取る
    • 面積が減る
  2. 探してクリックするのがめんどい

というようなものがあったけど、ニックネーム付きブックマーク で解決した。Greasemonkeyのユーザースクリプトコマンドに似てるような似てないような感じ。

deliciousにポストするのはShift+F2 dで、tumblrへはShift+F2 tでいける。実際にはtumblrってニックネームにしてるけど、tで始まるニックネームが他にないので勝手に飛ぶ。あとShift+F2はちょっと押しづらいので住み慣れてきたら違うキーに設定する予定。

管理はブックマークの管理をそのまま流用できて楽。bookmarkletフォルダ作ってそこに全部いれておけば、インクリメンタルサーチと編集と削除と(Opera Linkを使えば)同期とバックアップまで出来て文句ない。

Minibuffer がちょっと豪華過ぎるので、とりあえずこれでしばらく過ごしてみよう。

]]>
63回ケチつけといた http://tt25.org/blog/20080526/how-to-unoptimize-php-code 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 PHP コード最適化 Best Practices 63+ – カタコト日記

セキュリティが便利さを生贄にすることで得られるのと同様に、高速化はコードの可読性や保守性と引きかえになる。よっぽどまずいコードでなければ。

63個もあってこれがないのかよ、っていうのはこんな感じ。

  • ファイル末尾に?&gt;をつけるな(ref. Zend Frameworkコーディング規約)
  • ==じゃなくて===使おうぜ
  • 大きいファイルならfile()やfile_get_contents()じゃなくてfopen()とか使おうぜ
  • マジッククオートを捨てろ

printをechoにしてみたりするのは、メラよりヒャドのほうがダメージ高いとかいってゾーマに連発するようなもの。とっとと勇者にバイキルト掛けるべき。パーティ編成を見直すべき。戦略を考えるべき。どうしても無理ならレベル上げるべき。

以下ケチ

01.static にできるメソッドは static として宣言しよう。(4倍速い)

$this->method()とself::method()じゃ意味が違います。self::method()だと$thisを無視できるので理論的には軽くなるけどどうでもいい。

02. echo の方が print より速い。

インデントをやめて改行も消したらより速くなるよ。

03. echo ‘文’,’字’; (カンマ区切り)の方が、’文’.’字’ (ドット連結)より速い。

.だと文字列を連結してからecho、,だととりあえず頭からechoしていく、の違い。ドットは連結した文字列を格納するから遅い。たぶん。どうでもいい。

04. ループの最大値は、ループ「内」ではなく「前」にセットしておこう。
19. for 文の条件式には count($array) のような関数をいれない。(変数に格納)
47. ループ処理には foreach を使おう。

配列のループにはforeachを使うべき。 forの出番はまずないし、あったとしてもfor($i=0; $i<10; $i++)のように数値リテラルでループ回数指定するくらいだろうからどうでもいい。

05. 大きい配列のような変数は unset() してメモリを解放しよう。

大きいって具体的にどれくらい?

最近だと気にしなくていいのかもしれないけど、循環参照の場合はunset()するようにはしてる。

06. マジックメソッド(例: __get, __set, __autoload)は使用を避けよう。

ついでにクラス定義とかrequireとかもやめて1ファイルで完結しちゃえばもっと軽くなるよ。

07. require_once はハイコストなのです。
52. require_once より require を使おう。(opcode キャッシュがらみの理由で)

require()だと同じクラスや関数を2回定義しようとしてWarningかなんかが出る可能性があるよ。人力でチェックするのとどっちがexpensiveかは状況によるかも。

数マイクロ秒 vs. 人間の神経って図式なので、数マイクロ秒のほうが重要なら人間性を無視する方向で。

08. include や require でファイルはフルパスで指定しよう。
51. ファイル指定は basename / file_exists / open_basedir よりフルパス指定が速い。

require_once dirname(__FILE__)."/foo.php";くらいならいいけど、PEARまでフルパスっていわれるとちょっと。

フルパスで指定するのは曖昧さ回避のためにするべきで、パフォーマンスのためにやるべきじゃない。

ていうかbasenameでファイル指定?

09. スクリプト開始時間は time() でなく $_SERVER[‘REQUEST_TIME’] で取得。

PHP5から。互換性のためにスクリプト冒頭で代入してしまうのも豪快でいいかもしれない。

10. 可能であれば、正規表現より strncasecmp、strpbrk、stripos を使おう。

正規表現よりその3つを使い分けるほうが難しいんじゃないだろうか。

11. strtr(str_replace の4倍速い) > str_replace > preg_replace の順に速い。

strtr() / str_replace()だと正規表現じゃないんだな、というメッセージは受け取れます。

12. 引数に配列、文字列両方を受け入れるような関数は避け、個別に関数化しよう。

ん? こういうコードのことを言ってるんだろうか。

function sample12_before($var){
    if(is_array($var)){
        // なんか処理 for 配列
    }elseif(is_string($var)){
        // なんか処理 for 文字列
    }
}

function sample12_after($var){
    if(is_array($var)){
        some_func_for_array($var);
    }elseif(is_string($var)){
        some_func_for_string($var);
    }
}

にしようってことかな。必要な状況がよくわからないけど、(array)$varでキャストしたらダメ?

13. if をたくさん使ってるなら switch を活用しよう。(訳注:ってことでいい?)

訳注とあったので原文見てみた。「It’s better to use select statements than multi if, else if, statements.」って、selectステートメントってなんやねんってことか。なるほど。

elseif連発よりswitchのほうが好ましいのでこれはあり。

14. @によるエラー制御はすんごく遅い。

この前かいた問題 。これもパフォーマンスでなく可読性とかエラー制御とかそういう観点から述べるべき、というのが僕の考え。

15. Apache の mod_deflate(Apache2系) を ON にしておこう。(No.42 参照)
42. Apache の mod_gzip(Apache 1系)は、転送量を最大80%減。

なんで急にHTTPサーバの話に。

とりあえずgzipに関してはみんな大嫌いなIEが例のごとく腐ってる ので留意すること。

16. 処理が終わったらデータベースの接続は切っておこう。

昔while()ループの中で毎回connectとdisconnectを繰り返すプログラムがありまして、実に大変なことになっていたことがあります。

軽くなるわけでもないし、意識的にdisconnectしなきゃいけないことは滅多にないかと思います。

17. $row[‘id’] は $row[id] より7倍速い。

後者の場合、PHPは「id」という定数が定義されてるか調べて、もし定義されてなければ「id」という文字列を返す、という処理をします。

後者のこの書き方は非推奨です。

18. エラーメッセージはハイコスト。

そうか! エラーメッセージを組み込まなければいいんだ! んなアホな!

20. メソッド内ではローカル変数をインクリメントするのが一番速い。

比較対象はなんだ。どうしろっていうんだ。$tmp=$this->foo;$tmp++;$this->foo=$tmp;しろとでもいうのか。

21. グローバル変数のインクリメントはローカル変数より2倍遅い。

グローバル変数……。インタプリタ的にローカル変数のほうがグローバル変数より手近だから。

22. オブジェクト変数(例:$this->v++)のインクリメントはローカルより3倍遅い。

たぶん理由は上に同じ。

23. 未定義のローカル変数のインクリメントは定義されたものより9~10倍遅い。
24. 未定義のグローバル変数についても同様。

そりゃ定義する必要がありますから当然です。あとE_NOTICE出ます。

25. メソッドの起動コストはクラス内のメソッド数とは無関係。

さすがに内部ではO(1)な実装にしてるでしょう。

26. 派生クラス内のメソッドは、基底クラスで定義されたメソッドよりも速い。

理由は21,22あたりと同じ。

27. 引数1つの空関数を呼び出すのにも、ローカル変数 $local++ 7~8回分のコスト。

そうですか。

28. ダブルクォート より シングルクォート の方が若干速い。

意味が違う。シングルクオートなら変数展開しない。

29. 03 と重複か。(注:これは複数の引数を受け取る echo でのみ有効)

らしいので03参照。

30. PHP スクリプトは静的な HTML ページよりも2~10倍の時間がかかる。

そりゃmod_php読んでスクリプト解析して実行するんだから重いに決まっています。

31. キャッシュを利用しよう。通常、コンパイル回数 x 25~100% 分速くなる。

コンパイルキャッシュのことかな。数値の根拠は不明だけど入れて損はないと思う。

32. これもキャッシュについて。memcached とか使おうよ。

今までのを読む限りmemcachedって言いたかっただけじゃないかと思ってしまう。memcachedはいいものです。

33. if (strlen($foo) < 5) を調べたいなら if (!isset($foo{5})) と書くと速い。

たしか後者の書き方はPHP5の時点で非推奨になってたはず。あとマルチバイトだと期待どおり動かない。百害あって一利なしかと。

34. $i++ より ++$i の方が速い。(そういう opcode optimizer が走ってる場合)

「そういう opcode optimizer が走ってる場合」を使えばなんとでも言える。

35. 全部が全部 OOP でなくても良い。(コストやメモリの浪費になってるかも)

それはそのとおり。カッコ内は微妙だけど。

36. すべてのデータをクラスにしようとしないこと。配列も十分便利です。

配列とクラスはまったく別物なんですが……。

37. メソッドを分割しすぎない。(どれを再利用するのかよく考えよう)

これもだいたいそのとおりだけど、関数呼び出しのオーバーヘッドって意味なら同意できない。

38. メソッドを分割したくなったら後でいくらでもできる。

実際に(気力的な観点から)やるかどうかは別として、これもそのとおり。

39. もとから用意されてる 関数たち を活用しよう。

これもそのとおり。そのほうが速いしバグもないし読みやすいから。

40. 非常に時間のかかる関数 → C言語による拡張モジュール化も検討しよう。

これもそう。重いorPHPだとめんどいなら他の言語のパワーを積極的に使うべき。

41. コードをプロファイリングしてボトルネックを見つけよう。Xdebug とか。

最適化の基本中の基本。プロファイリングもせずに最適化とか無茶。

43. : A HOWTO on Optimizing PHP with tips and methodologies を見てね。

見た。We summarize the optimization methods below:って書いてある表が、流れと効果がわかりやすくていいと思う。

48. 複雑なクラスを作ってるなぁと思ったら → Singleton モデルを検討しよう。(原文:Do consider using the Singleton Method when creating complex PHP classes.)

訳者の人も意味がわからず混乱中みたい。僕も意味がわからない。

たぶん、同じクラスを何回もnewするよりsingletonでインスタンスを使いまわせば(ミクロな意味で)速くなる、って言いたい、の、か、なあ。

おそらく無意味だから無視していい。

49. DB に入れる値なら GET より POST を使おう。(パフォーマンスが上がる)

HTTPのごく基本的なところだけでも勉強してください。

50. 可能なら正規表現より、ctype_alnum、ctype_alpha、ctype_digit を使おう。

それぞれ英数字のみ、英字のみ、数字のみのときにtrueを返す関数。

正規表現だと^とか$をつけ忘れたり、[0-9]*のように+じゃなくて*とかやってしまったりするかもしれないし、安全でいいと思う。パフォーマンスはどうでもいい。

53. 一時的なファイルを作るときには tmpfile や tempnam を使おう。

39と同様。

54. 外部サービスに XMLHTTP で接続するときにはエラー回避のため Proxy を使おう。

XMLHTTP? JavaScript? 外部に対して??

何にしろProxyでキャッシュしとけば安全な気はする。Proxyがその外部のサービスより安定してれば。

55. デバッグするときは error_reporting (E_ALL); にしておこう。

個人的にE_NOTICEとは決別しました。基本的にE_ALLでいいと思う。

56. Apache の allowoverride を “none” にしておくとパフォーマンスが向上。

PHP関係ない。そんなの言うくらいならApacheにももっといっぱいあるだろう。

57. 静的なコンテンツには thttpd のような高速なファイルサーバを使おう。

リバースプロクシろうぜ的な。

58. パスなどの設定パラメータは配列に serialize して入れ、キャッシュしておこう。

毎回parse_ini()するかunserialize()するかの2択ですね。わかります。

59. アクセスの多いページには PHP の 出力バッファリング を使おう。

ob_* な面々について。ついでにキャッシュしとけばなおよし。

60. DB 固有の prepare メソッドではなく PDO::prepare を使おう。

PHP5から。特にいうことなし。

61. SELECT * (ワイルドカード)を使うのはやめよう。

ちょっと前の話題。

JOINとかGROUP BYとかがからむならまだしも、毎回selectのキーを指定する処理のほうが人的にも機械的にも高コストだと思うんだけどなあ。クエリキャッシュのヒット率も下がるわけだし、あんまりいいことはなさそうに思う。

どうしてもっていうならビューでやるかなあ。

62. PHP より賢い DB ロジック(queries, joins, views, procedures)を活用しよう。

データベース側のビューとかプロシージャとかの機能を活用しよう、ってことかな。

コメントとか制約とかから自動的に入力フォームを作るライブラリをつくったことがあるけど、JOINで壁にぶつかって途中で飽きて捨てたなあ。

63. SQL の省略表記を活用しよう。(例:INSERT INTO MYTABLE (FIELD1,FIELD2) VALUES ((“x”,”y”),(“p”,”q”));)

あんまりやると互換性で泣くのでほどほどに。個人的には、上の例だとprepareしてforeachかなあ。

これは間違いないと判断したもの

なんか全部マニュアルに載ってることのような気がしてきた。PHPのマニュアルはすごく充実してるので暇なときに読んだりするといいと思う。

09. スクリプト開始時間は time() でなく $_SERVER[‘REQUEST_TIME’] で取得。

13. if をたくさん使ってるなら switch を活用しよう。

17. $row[‘id’] は $row[id] より7倍速い。(速度じゃなく別の意味で)

31. コンパイルキャッシュを利用しよう。

32. memcached とか使おうよ。

35. 全部が全部 OOP でなくても良い。

38. メソッドを分割したくなったら後でいくらでもできる。

39. もとから用意されてる 関数たち を活用しよう。

40. 非常に時間のかかる関数 → C言語による拡張モジュール化も検討しよう。

41. コードをプロファイリングしてボトルネックを見つけよう。Xdebug とか。

47. ループ処理には foreach を使おう。

53. 一時的なファイルを作るときには tmpfile や tempnam を使おう。

]]>
Adsenseつけてみた http://tt25.org/blog/20080522/adsense 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 アクセスも落ち着いてきたのでAdsenseつけてみた。

今日から自動車保険見積もりサイトになります – うさだBlog / ls@usada’s Workshop

いま調べてみたけどあいかわらず自動車保険見積もりは$28.52と高値だった。そのままいろんなキーワードで調べてみた。

「料理」で調べると首位の「モンゴル料理($6.33)」が2位の「大豆料理($1.85)」に大差をつけてたりとか全体的によくわからない。あと「違法ドラッグ販売」に$0.05の値がついてたりしたので心当たりのある人は早く逃げたほうがいいと思う。

]]>
onclickと本気とIE http://tt25.org/blog/20080517/attribute-vs-handler 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 Think IT 第1回:そろそろ本気で学びませんか?

本気でやるならonclick属性は避けてライブラリを活用すべき – id:HolyGrailとid:HoryGrailの区別がつかない日記

onclick 属性問題について – IT戦記

個人的には、onclick属性を書くのってCSSでいうと&lt;span style="color:red;text-decoration:underline;"&gt;こんにちはこんばんは&lt;/span&gt;みたいなもんだと思うので、長所短所もそれに準じる感じ。ただ、ちょっとしたハック(やっつけ仕事)としてやることはあるけど、本気と言うなら外に書くべきだよなあ、とは思う。

ライブラリは流行り廃りが激しいから、特定のライブラリに依存した「おまじない」ばかり覚えているのはどうかと思うなあ

やっぱり、 DOM を直接書けたほうが、知識としては幅広く使えると思いますよ。

それはそうなんだけど、最初からIE向けおまじないを意識しなきゃいけないのは茨の道だと思う。僕はOperaのUser JavaScriptから入ったのでIEが出てきたのは中盤になってからだけど、レベル1の状態でIEが出てきたら即死続出じゃないだろうか。

form=document.getElementsById("targetForm");

// 普通のブラウザ向け
form.addEventListener("submit",function(e){
    alert("submitさせないっ");
    e.preventDefault();
},false);

// IE向け
form.attachEvent("onsubmit",function(){
    alert("submitさせないっ");
    window.event.returnValue=false;
});

// クロスブラウザ手抜きver
(function(form){
var _tmp=form.onsubmit || function(){};
form.onsubmit=function(){
    _tmp.apply(this,arguments);
    alert("submitさせないっ");
    return false;
}
})(form)

メソッドから引数からイベントの扱いから、alert以外すべて非互換なんてこんな書き分けは嫌がらせとしか思えない。手抜きverも、挙動を理解してないとたぶん厳しいし毎回こんなの書くのも嫌だ。

IEを無視するためにライブラリから入る、ってのは悪くないと思うなあ。運動のために階段を使う人が居てもいいけど、エレベータも捨てたもんじゃないですよ。

どっちみち何かつくるなら、まずやることは各ブラウザの違いを吸収するラッパー作りになるので、プロダクション環境では既存ライブラリか自作ラッパーかの違いでしかないと思うけど、それはまた別の話か。

おまけ

なぜか誰も正面切って反対してなかったので書いておくけど、&lt;a href="javascript:void(0);" onclick="somefunc();"&gt;あいうえお&lt;/a&gt;とかやられると、このリンクは新規タブで開いていいのか普通に左クリックしなきゃいけないのか、と毎回判断しなきゃいけないのでやめてください。

&lt;span class="clickable" onclick="somefunc();"&gt;あいうえお&lt;/span&gt;とかCSSでそれっぽく見せるようにしてください。

]]>
Rubyからニコニコ動画をいろいろするNicokitつくった http://tt25.org/blog/20080515/nicokit 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 githubに置いてみた。

RDocでマニュアルつくってみたけど、instance_evalで独自getter定義とか気持ち悪いことしてるのであんまり役に立たない気が……。

ログイン

最初にログインしておかないと他の機能が動きません(raiseが発生します)。

mail="xxx@xxx.com"
password="xxxxxxxx"
Nicokit.login(mail,password)

マイページ

いまんとこ自分のマイリスト一覧を取ってくるだけ。

「自分のマイリスト一覧」を正しい日本語でどういえばいいのかわからない。

my=Nicokit::My.new

# 自分のマイリスト一覧
my.list.each{|l|
    puts l.title # マイリストのタイトル
    puts l.description # マイリストの説明
}

誰かのマイリスト

l=Nicokit::VideoList.new("1626019")

# マイリストのタイトルと説明
puts "#{l.title} - #{l.description}"

l.videos.each{|v|
    # マイリストに登録されてる動画一覧
    puts "#{v.url} - #{v.title}"
}

動画

v=Nicokit::Video("sm2637528")

# タグ
puts v.tags.join(", ")

# この動画を見た人は他にこんなのも見てます
v.recommend.each{|r|
    puts "#{r.url} - #{r.title}"
}

# ローカルへ保存
v.flv.save("#{v.title}.flv")

検索

タグとキーワードからそれぞれ検索できます。

s=Nicokit::Search.new("test") # デフォルトはキーワード検索
puts s.count  # 「test」でキーワード検索したときのヒット数
s.switch(:tag) # タグ検索に切り替え
puts s.count # 「test」でタグ検索したときのヒット数
puts s.switch(:search).count # キーワード検索に切り替えてヒット数表示

# 「才能の無駄遣い」タグ、再生が多い順
s2=Nicokit::Search.new("才能の無駄使い",:tag,{:sort =&gt; :pv,:order =&gt; :desc)

# 動画を10件取得
# (デフォルト30。50とかページをまたぐような件数を指定してもよしなにやってくれるけど、そのぶんリスキー)
s2.video(10).each{|v|
    puts v.id
}

# 該当する動画を全部取得 *危険
# (下記の「使用上の注意」参照のこと)
s2.video_all

使用上の注意

必要な情報があればその都度nicovideo.jpへ接続して取ってきてるので、たとえば

my=Nicokit::My.new # ここではまだ何もしない

my.list{|l| # 接続(マイリスト一覧取得)
    puts "#{l.title}" # 接続(リストタイトル取得)
    l.videos.each{|v| # l.title取得時に取得済みなので接続しない
        puts "#{v.title}(#{v.url})" # 接続(動画タイトル)
    }
    puts "#{l.title}おわり" # l.titleは取得済みなので接続しない
}

というようにコネクションを張りまくることになります。この例でいえば、l.videosが100個あるならv.titleのために100回アクセスします。

短時間に大量のアクセスをすると、ニコニコ動画に「やめてくださいエラー」を出されますのでご注意を。Nicokit側では一切ウェイトを入れてないのでeachに適宜sleepを仕込むなどしてください。今のところキャッシュなども未実装です。

データ的にはl.videosやl.titleの取得時に動画のタイトルも既知になるわけですが、現在のところそういうエコロジーな機構は用意されていません。

言い訳的なあれ

RDocとgitとその他いろいろが初物なので出来が微妙かと思いますが、リポジトリはオープンになっているので誰か添削してくれるとうれしいです。

]]>
PHPの配列の参照とデフォルト値とE_NOTICE http://tt25.org/blog/20080510/php-e-notice 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 「@」でエラー抑制すると PHP が遅くなるという噂について

大切なのは数秒のスピードアップ?それとも?

@のエラー抑制とか

0.000000092秒が0.00000092秒になったところで心底どうでもいい。バッチ処理はPHPに向いてない と思うし、最適化するにしてもそんなミクロなとこより直すべき点が他にある。Webならファイルをgzipする、キャッシュを正しく使う、APC入れる、Apacheの不要モジュールを外すなどなど。

@を全部issetに直したら許容できるパフォーマンスが出た! なんてことは未来永劫ありえない。100万回もやれば結構な差がつくだろうと思ってたのに思いのほか微差だったので900万回ループ追加したらようやく1秒単位の違いになった、ていうシチュエーションがすべてを物語ってる。

速度はどうでもいい。問題はE_NOTICEの理想と現実をどう妥協していくかだと思う。

そもそもerror_reportingのデフォルト値を変えてE_NOTICEを加えてる時点で@なんか使わない宣言をしているに等しいと思うので、if(@$a["foo"])とか書くのはやっぱりおかしい。register_globalsをオフにしておいてextract($_REQUEST) するような矛盾を感じる。

そうはいってもif(isset($_GET["foo"]) &#38;&#38; $_GET["foo"] === "bar")なんてアホらしくていちいち書いてられないってのもわかる(最初のissetがないとE_NOTICE発生)。それに毎度毎度issetを書くなんていったらPerlとかRubyとかPythonとかJavaScriptとかLuaとかLispとかSmalltalkとか(中略)あたりの人に勤勉とかいって笑われる。

現状これに対するメジャーな妥協案は、

function _g($ary,$key,$default=null){
    return isset($ary[$key]) ? $ary[$key] : $default;
}

if(_g($_GET,"foo","") === "bar"){
    // do something
}

$display=_g($options,"display","none");

みたいなことをしてデフォルト値処理を自前で実装するやり方だろうか。なんかなあ。

と思ってちょっとググってみたところ、array_mergeでデフォルト値決める のがスマートだなあと感心しました。ただ、深い配列の場合は期待と違うくっつきかたになるのでやっぱり注意。

逆に考えるんだ。例えば、E_NOTICEを避ける。

開発時にE_NOTICEを有効にすることにはいくつ かの利点があります とか書いてるけど、E_NOTICEしててよかったことが1年以上のあいだで一度あったかどうか。逆にE_NOTICEエラーのせいでheader()が機能しないなど邪魔されたことは数知れず。

このへんのバランスは変数名typoのしやすさで個人差がありそうだけど、僕の場合は割に合わないのでとっととやめます。さようならE_NOTICE。ありがとうジョジョとIPA。こんにちは世界。

]]>
大きいファイルを扱うならfile()なんか使っちゃいけません http://tt25.org/blog/20080510/each-lines 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 【PHP TIPS】 82. コマンドを使ってスピードアップ

大きいファイルを扱うならfile()なんか使っちゃいけません。速い遅い以前の問題です。

本文終わり。以下蛇足。

ベンチマーク

------------------------------
30,000 lines
------------------------------
# -- time ./php_file.php 30k.txt
24640
0.38user 0.07system 0:00.49elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+5505minor)pagefaults 0swaps

# -- time ./php_exec.php 30k.txt
24640
0.51user 0.04system 0:00.60elapsed 93%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2709minor)pagefaults 0swaps

# -- time ./php_file2.php 30k.txt
24640
0.32user 0.06system 0:00.39elapsed 97%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1691minor)pagefaults 0swaps

# -- time ./my_rb.rb 30k.txt
24640
0.11user 0.00system 0:00.11elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+764minor)pagefaults 0swaps

# -- time ./my.sh 30k.txt
24640
0.22user 0.00system 0:00.23elapsed 96%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+717minor)pagefaults 0swaps

↑3万行程度ならまだ比較対象になりうるけど、

------------------------------
300,000 lines
------------------------------
# -- time ./php_file.php 300k.txt

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate                                         38623201 bytes) in /tmp/php_file.php on line 3

Call Stack:
    0.0002      57944   1. {main}() /tmp/php_file.php:0
    0.0002      57944   2. file() /tmp/php_file.php:3

Command exited with non-zero status 255
0.02user 0.00system 0:00.02elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1660minor)pagefaults 0swaps

# -- time ./php_exec.php 300k.txt
246400
2.13user 0.15system 0:02.46elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2708minor)pagefaults 0swaps

# -- time ./php_file2.php 300k.txt
246400
1.88user 0.69system 0:02.63elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1692minor)pagefaults 0swaps

# -- time ./my_rb.rb 300k.txt
246400
1.00user 0.00system 0:01.05elapsed 96%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+794minor)pagefaults 0swaps

# -- time ./my.sh 300k.txt
246400
2.10user 0.15system 0:02.45elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+717minor)pagefaults 0swaps

↑30万行になるとレース脱落。


------------------------------
3,000,000 lines
------------------------------
# -- time ./php_file.php 3m.txt

Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 347608801 bytes) in /home/clover/tmp/php_file.php on line 3

Call Stack:
    0.0003      57940   1. {main}() /tmp/php_file.php:0
    0.0004      57940   2. file() /tmp/php_file.php:3

Command exited with non-zero status 255
0.02user 0.01system 0:00.04elapsed 89%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1663minor)pagefaults 0swaps

# -- time ./php_exec.php 3m.txt
2217600
19.76user 1.35system 0:22.80elapsed 92%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+2712minor)pagefaults 0swaps

# -- time ./php_file2.php 3m.txt
2217600
15.74user 5.97system 0:24.57elapsed 88%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+1689minor)pagefaults 0swaps

# -- time ./my_rb.rb 3m.txt
2217600
8.97user 0.30system 0:09.41elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+893minor)pagefaults 0swaps

# -- time ./my.sh 3m.txt
2217600
19.25user 1.11system 0:20.76elapsed 98%CPU (0avgtext+0avgdata 0maxresident)k
0inputs+0outputs (0major+718minor)pagefaults 0swaps

↑300万でももちろん死ぬ。

これはfile()がファイルの中身を全部メモリに置くからそうなる。30万行、37MBものデータをPHPで一度に持とうとした瞬間にメモリオーバー。要するにfile()はスケールしない。

他のやりかただとその都度必要な分だけメモリに読み込むので、ログが何億行あろうとメモリがあふれることはない(タイムアウトの可能性とかはある)。

使用したソース

元のやつとは時間計測のしかたが違う。元のはPHPスクリプト内でのおはようからおやすみまで、上でやってるのはプロセスのおはようからおやすみまで。

php_file.php

#!/usr/bin/env php
&lt;?php
$file = file($argv[1]); // ここで死ぬ予定
$counter = 0;

foreach ($file as $data) {
    if (preg_match('/02:[0-5][0-9]:[0-5][0-9]/', $data)) {
        $counter++;
    }
}

echo $counter."\n";

php_exec.php

#!/usr/bin/env php
&lt;?php
$cmd = 'grep 02:[0-5][0-9]:[0-5][0-9] '.$argv[1].' | wc -l';
exec($cmd, $cnt);
echo $cnt[0]."\n";

php_file2.php

#!/usr/bin/env php
&lt;?php
$counter = 0;

$fp=fopen($argv[1],"r");
while($line=fgets($fp)){
    if (preg_match('/02:[0-5][0-9]:[0-5][0-9]/', $line)) {
        $counter++;
    }
}
fclose($fp);

echo $counter."\n";

my_rb.rb

#!/usr/bin/env ruby
cnt=0
File.open(ARGV.shift){|fp|
    cnt += 1 if fp.gets.match(/02:[0-5][0-9]:[0-5][0-9]/) while !fp.eof?
}
puts cnt

my.sh

#!/bin/sh

grep "02:[0-5][0-9]:[0-5][0-9]" $1 | wc -l

レース結果

*php_file脱落

300万行だとRubyがぶっちぎりトップで、それ以外は横並び。3万と30万はばらついてるけど誤差かな。

ITProの記事を見る限り、php_execはphp_file2以上シェルスクリプト以下、って感じだと思ってたんだけどそうでもなかった。そもそも3万行のところでphp_fileに負けてるのが解せない。PHP CLIだからとか計測方法が違うからとか心当たりはあるけどどれもピンと来ないなあ。

Rubyがシェルスクリプトより速いのは意外だったけど、たぶんPerlとかPythonとかのほうが速い気がする。

結論

例えば、PHPを避ける。

PHPはApacheとイチャイチャしてなんぼだと思います。バッチ処理や解析は荷が重い。

]]>
発言を待ち受けるtwitterボットできた http://tt25.org/blog/20080501/twitterbot-daemon-ruby 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 ほぼRubyでGoogleTalk のパクリ。事前にsudo gem install xmpp4rが必要。

require 'rubygems'
require 'xmpp4r/client'

class BotDaemon
    def initialize(user,pass,botname)
        @botname=botname
        @client=Jabber::Client::new(Jabber::JID::new(user+"/"+@botname))
        puts "connect: #{@client.connect}"
        @client.auth(pass)
        @client.send(Jabber::Presence.new.set_show(:chat))
    end

    def parse(msg)
        tmp=msg.body.match(/^Direct from (.*):\n(.*)/).to_a
        if tmp.length == 0
            tmp = msg.body.match(/^(.*?): (@.*) (.*)/).to_a
            result={
                :user =&gt; tmp[1],
                :message =&gt; tmp[3],
                :is_direct =&gt; false,
                :prefix =&gt; "@"+tmp[1]+" ",
            }
        else
            result={
                :user =&gt; tmp[1],
                :message =&gt; tmp[2],
                :is_direct =&gt; true,
                :prefix =&gt; "d "+tmp[1]+" ",
            }
        end
        result[:botname]=@botname
        result
    end

    def daemon(&#38;block)
        mainthread = Thread.current
            @client.add_message_callback{|msg|
                next if !msg.body
                info=parse(msg)
                send=block.call(msg,info)
                p send
                m2=Jabber::Message::new(msg.from,send)
                m2.type=msg.type
                @client.send(m2)
            }
        Thread.stop;
        @client.close;
        puts("Done.");
    end
end

こんな感じで使う。

#hellobot.rb
require "botdaemon"

d=BotDaemon.new("foobar@jabber.jp","password","[twitterbotname]")
d.daemon{|msg,info|
    "#{info[:prefix]"}はろー"
}

ruby hellobot.rbするとずっと待ってる。Ctrl+Cとかで終わる。

ほっとくとJabberかなんかをタイムアウトしたりするらしいのでそのうちちゃんとかく。とりあえず@かd投げたら所定の位置の天気予報かえすやつつくって終わった。

]]>
そこにeachがあるから http://tt25.org/blog/20080427/for-or-foreach-it-is-not-problem 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 PHP 配列を回すならforかforeachか

foreach一択。

PHPに配列と連想配列の区別がない以上、イテレーションの現場にforの居場所はないと思われる。添字に数字以外が含まれるかもしれない配列をcount()と$i++で回すのはなんかリスキーだ。

ていうかメリットとして挙げられている「ループ内でデータが直感的に参照できる」てのはむしろ冗長にしか見えない。for()の中に初期化、続行/中断条件の定義、毎回実行する構文、と3つも決まりきったことを書かなきゃいけないのも面倒。

PHPにはブロックスコープとかの概念もないので、用済みのforeach用変数がループ終わっても参照できるとかどうしようもなく不細工な面もありますが、まあそれはそれで。あとforeach((array)$var as $v){echo $v;}とかするとWarningが出なくなってだいたいの場面では便利です。

各言語でのイテレーション

目的別に分けるとだいたい以下の3パターンだと思う。

  1. 配列の各要素が欲しい
  2. 連想配列の各要素と添字がペアで欲しい
  3. 決まった回数だけ何か処理をしたい

「配列の各要素と添字がペアで欲しい」は特殊な用途だと思うのでとりあえず無視。たぶん、たまたま添字が数字だけで出来てる連想配列じゃないかと思うので、2.と同じ方法で対処できるかもしれない。

「連想配列の添字だけ欲しい」は、連想配列の添字だけを配列にして(PHPだとarray_keys()、RubyでいうHash#keys)しまえば、1.と同じってことになる。

「連想配列の各要素だけが欲しい」もまずないと思われるので無視。あったとしても添字を捨てるか、array_values()相当して配列作れば同じこと。

「配列の決まった数だけ欲しい」はそもそもイテレーションじゃない。それをいうと3.もそうじゃないか、って疑念はわきますけど、まあ、その、特例ってことで許してください。なんとなくarray_slice()とかarray_filter()して配列を部分的に抜き取るのが王道じゃないかなあ。

というわけで、以下に書いた以外にもやりかたはあると思いますが、もっとも素朴な実装例ってことでひとつ。

Ruby

eachメソッドを呼べばいい。

決まった回数の反復には[0..n]な配列を作ってeachしてもいいけど、通常はNumberクラスのメソッドを使う。


arr=[1,2,3]

# 配列
arr.each{|v|
    puts v
}

# 連想配列
hash={1=&gt;"a",2=&gt;"b"}
hash.each{|k,v|
    puts "#{k} =&gt; #{v}"
}

# 特定回数
0.upto(9){|n|
    puts n
}

Array/Hashクラスのeachメソッドを呼ぶ、というスタイルなのでPHPのforeachとは主客逆転してる。

僕が知ってる範囲ではいちばんシンプルな解法だと思う。

Python

Pythonは配列(list)と連想配列(dict)で若干構文が変わるのでなんか嫌だった。


# 配列
arr=[1,2,3]
for v in arr:
    print v

# 連想配列
hash={1:"a",2:"b"}
for k,v in hash.iteritems():
    print "%s =&gt; %s" % (k,v)

# 特定回数
for n in range(10):
    print n

iteritems()がどうしても気持ち悪い。

名前こそ全部forだけど、特性はPHPのforeachに近い。特定回数の実行には[0..n]な配列を作ってforするみたい。

Pythonは3日くらいしかまともに触ってないのでまずいやりかたなのかもしれない。

JavaScript

問題児。

PHPは配列と連想配列を区別しないけど、JavaScriptは連想配列とObjectオブジェクトを区別しない。連想配列はキーと値のペアではなく、オブジェクトのプロパティとその値という扱い。

// 配列
var arr=[1,2,3];
for(var i=0,len=arr.length; i&lt;len; i++){
    alert(arr[i]);
}

// 連想配列
var hash={1:"a",2:"b"};
for(var p in hash){
    if(hash.hasOwnProperty(p)){
        alert([p," =&gt; ",hash[p]].join(""));
    }
}

// 特定回数
for(var n=0; n&lt;10; n++){
    alert(n);
}

hashはObjectオブジェクトなのでhasOwnPropertyでチェックしないと変なメソッドや不要なプロパティまで引っかかるかもしれない。あと配列走査に原始的なforしか手段がないのでイライラ。

もちろんArray.prototype.eachを定義してイテレーションする手もある。XPathResultとかNodeListとかも同様。しかしながら、連想配列=Objectであるかぎり、Object.prototype.eachにはちょっと抵抗がある(連想配列以外も含めたあらゆるオブジェクトが実質的にeachメソッドを持つことになる)。

Prototype.jsはそのためにHashクラスをつくって、連想配列とObjectを分離しようとした。Hash#toQueryStringとか、通常のJavaScriptでは心理的になかなかなしえない拡張をしまくってる。

jQueryは巨大なDSLというか、JavaScript自体を再定義してるような感じ。jQueryを使うならJavaScriptの流儀をだいぶ忘れてjQueryの流儀に染まる必要がある。

他はあんまり詳しくないけど、だいたいこういう事情があるので生のJavaScriptを触りたい人はライブラリの使用をためらう傾向にあるんだと思います。

まとめ

なんか自分用メモになってしまった。他言語での例に照らせばforeachのほうが自然だって言いたかったはずなのに。あれー。

]]>
分かりやすい表現の技術読んだ http://tt25.org/blog/20080420/jr-tokyo 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 SL250.jpg” alt=”” />

「分かりやすい表現」の技術―意図を正しく伝えるための16のルール

実家でぼーっとしてたら床に転がってるのを見つけたので拾って読んだ。JR東京駅の案内板がいかに貧弱かという実例を挙げつつ改善例を示して、徐々に深いところへと切り込んでいく感じ。

要は情報デザインの話。

僕は社内用の資料をHTMLでつくる癖があって、文書を共有しやすいって以外にも、論理構造を意識しながらh2とかulを使うので自然と情報が整理されてくるって副作用がある。さすが論文用につくられたマークアップ言語なだけある。

上の本のなかでダメな資料の例としてセミナーの日程表が出てた。元は紙の資料だと思うけど、これの悪い例といい例はそれぞれHTMLでいうとこれくらい違う。

&lt;p&gt;
6月3日プログラム&lt;br /&gt;
I. 国際的関税制度&lt;br /&gt;
1. WTO諸協定の内容&lt;br /&gt;
2. 関税関連法令の概要&lt;br /&gt;
II. 輸出通関手続きの基本&lt;br /&gt;
1. 輸出通関のチェックポイント&lt;br /&gt;
2. 再輸出と戻し税&lt;br /&gt;
6月4日プログラム&lt;br /&gt;
III. 輸入通関制度&lt;br /&gt;
1. 商品分類&lt;br /&gt;
2. 関税率&lt;br /&gt;
3. 特恵関税制度&lt;br /&gt;
IV. 減免税制度&lt;br /&gt;
1. 減免税制度概要&lt;br /&gt;
2. 再輸入減免税制度
&lt;/p&gt;
&lt;h2&gt;6月3日プログラム&lt;/h2&gt;

&lt;h3&gt;I. 国際的関税制度&lt;/h3&gt;

&lt;ol&gt;
    &lt;li&gt;WTO諸協定の内容&lt;/li&gt;
    &lt;li&gt;関税関連法令の概要&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;II. 輸出通関手続きの基本&lt;/h3&gt;

&lt;ol&gt;
    &lt;li&gt; 輸出通関のチェックポイント&lt;/li&gt;
    &lt;li&gt; 再輸出と戻し税&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;6月4日プログラム&lt;/h2&gt;

&lt;h3&gt;III. 輸入通関制度&lt;/h3&gt;

&lt;ol&gt;
    &lt;li&gt;商品分類&lt;/li&gt;
    &lt;li&gt;関税率&lt;/li&gt;
    &lt;li&gt;特恵関税制度&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;IV. 減免税制度&lt;/h3&gt;

&lt;ol&gt;
    &lt;li&gt;減免税制度概要&lt;/li&gt;
    &lt;li&gt;再輸入減免税制度&lt;/li&gt;
&lt;/ol&gt;

上の例がただベタに羅列してあるだけなのに対し、下の例は構造化されている。見出しと列挙項目が区別され、何が何に属するのか、(ブラウザで見れば)視覚的にも分かりやすい。整理されてるので頭にも馴染みやすい、つまり分かりやすい。

別にHTMLを使わなくともグループごとにわけるって意識があれば、同じグループを同じ色で塗ったり、矩形や丸で囲んだり、セクションとセクションのあいだに線を引いて区別したりできる。メールとか図を使えないただのテキストでも、改行や空行で段落を切ったり、見出しの頭に記号(*とか#とか)をつけて目立たせるなど、できることはある。

そういう本。どうでもいいけど東京の分かりづらさは半端ない。悪名高い「つぎ」「こんど」「そのつぎ」 って表示とか、JRは明らかにやる気がないうえに駅員もやる気がないので狂った街だと思う。暴動を起こせばいいと思う。

]]>
PCで目が疲れたことない http://tt25.org/blog/20080420/z 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 PCで眼が疲れない方法 – 萌え理論Blog

あんまりいうとメガネっ娘に撲殺されるのであれだけど、目に関してはヘビーユーザーだと思う。

幼稚園から小中高大学と、ファミコン〜アーケードで格ゲー、PC、夜中に読書など目に悪いとされることを恒常的にひととおりやりまくっているわりに視力はまだ1.5(左1.2、右1.5)だ。会社に入ってから左が1.5→1.2に悪化したものの、日常生活にさほどの支障はない。昔から1日数時間はテレビなりモニタなりを見続けている。

今日も8時間ほど休みなしでずっとプログラム書いてたけど、目はまったく疲れてなくて脳と体だけが疲れた。ちなみにVimのカラースキームはZenburn を愛用しています。www.vim.orgにあるやつはバージョンが古くて補完の色が変わらないので注意。

というか目が疲れたことがない。肩がこったりこめかみが痛くなることはあるけど、目が疲れるという状況はほとんどない。目が乾いたりしょぼしょぼしたりといったことはあるものの、眼精疲労なんて大仰なものではない。

眼の健康のために、部屋を暗くしてPC作業をしましょう。明るくではありません。

照明よりモニタの輝度を下げたほうがいいと思う。モニタはデフォルト設定で使うものじゃない。

モニタにもよるのでなんともいえないけど、輝度は10〜40くらいで充分。デフォルトだと100とか80とかまぶしすぎて見てられない。特に液晶モニタはCRTより発光が強いと感じる。まぶしさを発色のよさだと錯覚させる陰謀なんだと思う。

真っ白の画面(ブラウザ立ち上げてabout:blankを開いて最大化)を集中せずにぼんやりと見て、まぶしいと感じないくらい、薄いほこりがかぶってるような純白じゃない白くらいでちょうどいい。純白である必要がそもそもない。白とライトグレーを判別できれば充分。

コントラストはまぶしさを感じない範囲でできるだけ高いほうがいい。コントラストが低いとモニタに近づきすぎたり、凝視してしまったりして逆効果なのでそこらへんは要調整。デルの安いモニタは22インチワイドと面積が大きいわりに、輝度を下げると色がおかしくなって使い物にならなかったので封印してる。いま使ってるのはMITSUBISHI RDT1714VMだけど、輝度30コントラスト45で使ってる。

スキー用のサングラスをしてしまいます。掛けた状態と外した状態を見比べると、まぶしくないので、その効果が非常に分かりやすい。

というのも、要するにモニタがまぶしすぎるから遮光してるだけだと思うので、まずモニタ設定いじったほうが手っ取り早いように思う。

ていうか、僕の場合はたぶん普段からものをちゃんと見てないからだと思う。焦点があってないというか、あまりものを見ようとしていない。網膜には写ってるけど意識はそれを見てないというか。

意識して見るとピントを合わせようとして細かいシークが起きて視神経だか筋肉だかが動きまくって、結果疲れることになるんでは。エヴァ劇場版を見に行ったとき、小ネタを見落とさないよう集中して見てたらさすがに疲れたので、たぶんあれがデフォルトの人は環境に関係なく疲れる気がする。

というわけで、見るともなく全体を見る が最強のライフハックだということで締め。

]]>
電王の劇場版みてきた http://tt25.org/blog/20080420/den-o 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 本放映は1話たりとも見ておらず、予備知識も「イケメンが主人公」「イケメンがすごい」「モモタロスとかいうやつが居る」「電車」「一部で大人気」くらいしかなかったので心配でしたが面白かったです。特に映像が。

普通に主人公が街を歩いてる映像にレイヤーを重ねて声を入れ、演者がそれに合わせて演技(のけぞる、倒れるなど)するって手法が目立ちました。ちっこい女の子がワイヤーアクション+高速再生で敵を瞬時に蹴り飛ばしたり、変身シーンで別次元へ転送されることなくその場でエフェクトとともに変身したり、というのも目新しかったです。継ぎ目が見えない字義通りシームレスなエフェクトで見ててたのしい。

銃の発砲も、従来の特撮では着弾点から火花が出る程度でしたが、電王の場合は銃口からボウリング玉ほどの青い球体CGがごく自然に発射されてました。画面がスローモーションになって、撃たれた敵が倒れながらそれを避けて球体が肩をかすめたあと同様の球体を発射して反撃する、っていうシーンはマトリクスっぽかったです。

普段は映画もアニメもテレビもあまり見ないのでどうかはわかりませんが、AR的な エフェクトが流行りなんですかね。平日夜中のドラマで木村拓哉あたりが映像的に魔改造されたり、昼ドラで姑が非人間的な体術を駆使して嫁に嫌がらせするのはもう普通なんでしょうか。個人的にはメカ水戸黄門に期待しています。

あといっしょに行った人いわく内容的には微妙だったらしいですが、僕はほぼ映像技術しか見てなかったのでよくわかりません。放映終了した電王より現行ライダーであるキバのほうが強いというアピールや、全変身パターンの網羅+おまけといった、大人の事情は垣間見れました。

]]>
Google App Engine BTSに上がってる言語のサポート願いをながめてた http://tt25.org/blog/20080417/please-add-support-xxx-langs 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 http://code.google.com/p/googleappengine/issues/list

GoogleではC++、Java、Python、JavaScriptというフェンスの中で遊ぶ必要がある らしいので、次はJavaだと思う。PHPはまずないだろうな。ていうかRhino on Railsの故事からすると、サポートされないならPythonでインタプリタ書けばいいじゃない。

jrubyを使えばRubyのサポートも可能なはずだとか、GWTをフロントエンドにしてバックエンドでもJavaが動いてるのってどうよとか、JavaScriptをサポートするとどうなるだろうとか、出来が微妙なphpinfo()のパロディをGAEに上げたりとかC#を大絶賛してみたりとか 、わりとみんな好き勝手なこと言ってて面白かった。

あとだいたい/^(please)?add(for)?(.*?)(support)?/xって感じの正規表現にマッチするタイトルなのに、PHPだけ「PHP support is a must」とか言ってて調子のるなと思った。こんなに愛のないPHPコード を書くGoogleがPHPなんかサポートするわけない。

他にはSSLサポートしてくれとか、ファイルアップロードしたいとか、cron的なものが欲しいとかがあった。日本語関係の要望はとくになし。

]]>
フィードリーダー(RSSリーダー)についてのまとめ http://tt25.org/blog/20080416/feed-reader 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 このブログがgooのRSSリーダーで読めないと言われたので確認したらたしかに読めなかった。Firefox、Opera、livedoor Reader、glucoseではきちんと読めたし、フィードバリデータでもいちおうvalidだったので、goo RSSリーダーがバグってるのは間違いないと思うものの原因不明。

報告してきた人はこれを機会に乗り換えを、とかほのめかしていたのでglucose勧めといた。ついでにフィードリーダーについてまとめてみる。

経歴

RSSOwlglucose →FEEDBRINGER(サービス停止)→ Fresh Readerlivedoor Reader(LDR) と使ってきました。だんだんウェブベースのものへとシフトしていってる感じですね。iGoogleもいちおう使ってますがメインではありません。Fastladderは中身がほぼLDRなので省略してます。評判の高いbloglines はなぜか使ったことがないです。

時系列じゃなく利用期間の長さで並び替えると、livedoor Reader→glucose→FEEDBRINGER→RSSOwl→Fresh Reader。LDRが2年近く、Fresh Readerで2ヶ月くらいかな。

他に、触ってはみたもののすぐ辞めたのがFirefox(Sage)、Google Reader、Opera Mail、はてなRSS、パラボナミニ、cococあたりです。Googleは手動で既読化しないといけないのが面倒だったのとその他ですぐにLDRへ出戻り、はてなRSSはなんか微妙、その他は数を読むのに適していないという理由でそれぞれ辞めました。

とりあえず今のメインはLDR。他のに乗り換える気は今のところないです。

タイプごとの解説

デスクトップ型メーラー系

glucoseやgooRSSリーダーなんかがここです。Becky!プラグインやOutlookプラグインなんかもここに入ります。

特徴

メールのように自分で巡回してフィードを集め、メールのようにフィードを読むものです。たぶんいちばん人気のあるタイプです。

長所
  • ローカルなので他のタイプと比べて動作が速い
  • メールと同じ感覚なので導入が容易
短所
  • PCを起動してるあいだしか巡回しないため、ニュースサイトとか更新が頻繁なところだと取りこぼしがある
  • 会社と自宅など、異なる環境間で既読/未読や購読フィードを一括して管理できない
おすすめ

メールチェックのお供にどうぞ。

デスクトップ型ティッカー系

cococやパラボナミニあたりが該当します。

特徴

タスクトレイに常駐し、更新があればポップアップします。

長所
  • 更新されたら意識しなくても把握できる
短所
  • PCを起動してるあいだしか巡回しないため、ニュースサイトとか更新が頻繁なところだと取りこぼしがある
  • 見出しだけしか出ないのでブラウザを起動して読みに行く必要がある(数をこなせない)
  • 会社と自宅など、異なる環境間で既読/未読や購読フィードを一括して管理できない
おすすめ

10〜20くらいの、それほど更新が頻繁でないフィードを購読してる人向けかと。

デスクトップ型ブラウザ統合系

Firefox、Opera、IE7、Sleipnirプラグインなどです。Safariもそうだったかな。

特徴

ブラウザの機能として組み込まれています。

長所
  • たぶん導入コストはいちばん低い
  • リンクや画像などを普通にブラウジングできる
短所
  • PCを起動してるあいだしか巡回しないため、ニュースサイトとか更新が頻繁なところだと取りこぼしがある
  • 会社と自宅など、異なる環境間で既読/未読や購読フィードを一括して管理できない
おすすめ

デスクトップ型メーラー系で、いちいちブラウザ起動して本文読みに行くのが面倒な人かなあ。

ウェブ型ウェブサービス系

Google Reader、livedoor Reader、bloglines、MODIPHIなど。

特徴

フィードを読むための専用ウェブサービスです。

どちらかというとヘビーユーザーが愛用してるイメージですが、Yahoo!なんとかリーダーはそうでもないはずなので案外幅広いのかも。

長所
  • サーバが勝手に巡回してくれるので取りこぼしがほぼない
  • 異なる環境間で既読/未読管理が統一できる
  • ソフトを自分で更新する必要がない
  • ブラウザさえあればいいのでインストール不要
短所
  • 初期コストが高いかも(アカウント取得、操作の学習など)
  • いつかサービスが終了するかもしれない
おすすめ

取りこぼしをなくしたい人とか、いちいち手動で巡回するより1ヶ所で終わらせたい人向けかなあ。

サービスによって特色があるのでそれ以上はなんともいえない。

ウェブ型自分用アプリ系

Fresh Readerとオープンソース版Fastladder。

特徴

自分が管理するサーバにインストールして使う。環境にもよるけどレンタルサーバやローカルのApacheでもたぶんOK。

長所
  • ユーザーが自分だけor身内しかいないウェブサービスなので軽い
  • サーバが勝手に巡回してくれるので取りこぼしがほぼない
  • やろうと思えば自分で好きなようにカスタマイズできる
短所
  • 導入が手間ないし不可能
  • 管理する必要がある
おすすめ

ウェブサービス系が好きだけど、何かと不安や不満がある人向け、かなあ。

私的おすすめリストと寸評

livedoor Reader / Fastladder

ヘビーユーザー御用達。LDRは文学。Fastladderは人生。そんな感じの大絶賛。

キーボードショートカットを使えば1000フィード購読しててもきちんと読める(と謳ってるし、実際数千フィード購読してる人もちらほら居る)。

Greasemonkeyによるちょっとしたカスタマイズなども豊富。

glucose

軽くてシンプル。デスクトップ型メーラー系のリーダーとして実に真っ当。

長いこと使ってないから新機能については詳しくないけど、フィードリーダー古参ながら現在の開発も活発なので、全然ダメってことはまずないはず。万人におすすめできる。

iGoogle

ダラ見用として最高峰。

iGoogleを開くのは「なんか面白いものはないかな」とテレビをつけるようなものなので、そこまで関心はないけどたまに面白いものを登録しておくと便利。はてなブックマークの注目エントリなんかを登録してある。

このブログのフィードリーダー別購読者数

まったくもって微妙なデータですがいちおう。数が少ないのはご愛嬌ってことで。

  1. Fastladder(8)
  2. livedoor Reader(2)
  3. NewsGatorOnline (1)
  4. gooRSSreader (たぶん1)
  5. glucose(たぶん1)
  6. Googleデスクトップ(たぶん1)
  7. はてなRSS(0) (0だと読みに来るはずないんだけどログには残ってる。謎)

たぶん、ってついてるのは購読者数を確認できなかったものです(あるいはやろうと思えばできるけど面倒)。あとブラウザ統合型はその性質上、RSSを取りにきたのか普通に開いただけなのかが判別できないので数えてません。

]]>
Ubuntu 7.10でPDO_PGSQL使ってprepare,executeするとsegfault http://tt25.org/blog/20080413/php5-on-ubuntu 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 原因不明というか対応不能というかPHP側のバグというか。

# sudo apt-get install php5-dev php-pear postgresql libpq-dev

# sudo pecl install pdo pdo_pgsql
(php.iniとかにextension=を追記)

# cat segv.php
&lt;?php
echo "no print";
$pdo=new PDO($dsn,$user,$pass);
$stmt=$pdo-&gt;prepare("INSERT INTO table (a,b) VALUES (:a,:b)");
$stmt-&gt;execute(array("a"=&gt;"im a","b"=&gt;"im b"));

なぜかこの動作じゃ再現しなかったけど、こんな感じの状況(INSERT文をprepareしてexecuteで配列渡す)で、エラーメッセージもechoもvar_dumpも出力されず完全に無言。apacheのerror.log見るとsegfaultしてた。

[Sun Apr 13 17:20:36 2008] [notice] child pid 5930 exit signal Segmentation fault (11)

どこかの時点で真っ白になってしまうので、1行ずつecho __LINE__;exit;を追加していくと、prepareのところが原因と判明したけどどうしようもない。ググっても解決法は見つからず。仕方ないのでPDO捨ててMDB2使うことにした。

]]>
Linux Operaはやっぱ速かった http://tt25.org/blog/20080412/linux-opera-on-ubuntu-7-10 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 やっぱりKubuntuやめてUbuntuにした。日本ローカライズ版。KDE仕様のUbuntuであるところのKubuntu上で、KDEを使わずGNOMEを使った呪いかなにかで日本語入力が微妙におかしかったので。

そしたらなんか全体的に軽い。というかスムーズ。特に何かしたわけでもないのにとても快適。そして何よりもOperaが軽すぎる。タブ開くのに0.3秒 とか書いたけど、今は0.04秒くらいと桁違いに速くなった。どういうことだ。

思い当たるのはgtk-qt-engine入れてないくらいだけど、Operaに限らず全体的に速いので関係ないかもしれない。

これでブラウジング環境のためにWindowsに戻るべきか悩まなくて済むようになったものの、原因がわからないので再現しようがなくUbuntuにバインドされたともいえる。まあいいか。

原因判明?

いや、どうも原因わかった気がする。スキンをBreeze Simplified MICRO にしたら劇的に遅くなった。他のBreezeシリーズも重い。デフォルトやDimple のときに比べてかなり遅い。体感できるくらい違う。ていうかDimpleがだいぶ軽い。

くそうFirefoxのときはわりとスキンの重さも考えてたのに、Operaはうっかりしてたなあ。そうかスキンが原因か。Breeze SImplified MICROはDImpleの半分くらいのタブ幅なので重宝してたのになあ。

]]>
skin.iniを触ってタブをコンパクトにする http://tt25.org/blog/20080412/opera-tab-minimize 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 というわけで DImpleのskin.iniを書き換えた。[Pagebar Button Skin]セクションの

padding top=5
padding bottom=2

を、

padding top=0
padding bottom=0

にしただけ。

あと色が青くて落ち着かないので、カラースキームはシステムカラーにしてる。

Dimpleデフォルト Dimple変更版

上から見ていくと作業履歴っぽいですね。

]]>
Debian lennyからKubuntu7.10へ鞍替えした http://tt25.org/blog/20080411/kubuntu-7-10 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 apt-get upgradeしたらlennyのXが起動しなくなった。NVIDIAドライバを入れ直したり古いカーネル使ったりしてもダメ。FTEだかFPEだかの勘定があわないとかで起動してくれない。ていうかlennyはいわばベータ版なので自力で対応できないなら使うべきではないですね。

諦めて床に転がってたKubuntuのCDで再インストールした。とりあえず旧環境を残しておいてデュアルブート出来るようにした。

もうすぐ次期安定版Ubuntuが出るとかいうタイミングで7.10を入れるタイミングの悪さ、KubuntuなのにGNOME入れて使ってる意味のなさ、などなど突っ込みどころも多く取り揃えております。あーあ。

]]>
UnicodeEncodeErrorで3時間食われる http://tt25.org/blog/20080411/python-unicode-error 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 PythonかGAEか知らないけど使えなさすぎる。

Amazon ECSのXMLをparseしようとしてエラーに遭遇した。

UnicodeEncodeError: ‘ascii’ codec can’t encode characters in position 0-1: ordinal not in range(128)

Amazonが返すXMLには当然日本語が入ってる。それをどういうわけかasciiと判断して処理しようとしてエラーになってるらしい。

コードの先頭行でencode: utf-8は指定してる。import sys; sys.setdefaultencodingは存在しない。xml.dom.minidom.parseStringに文字エンコーディングを指定する方法はない。

ていうか&lt;?xml version="1.0" encoding="UTF-8"?&gt;くらい読め。お前はIEか。

xml.dom.minidom.parseString(xmlstr) # error
xml.dom.minidom.parseString(xmlstr.decode("utf-8")) # error
xml.dom.minidom.parseString(xmlstr.decode("utf-8","utf-8")) # error

uxmlstr=unicode(xmlstr) #error
uxmlstr=unicode(xmlstr,"utf-8") #error

print "あ".encode("utf-8") #error
print "あ".encode("utf-8","utf-8")  #error
print "あ".decode("utf-8") #error
print "あ".decode("utf-8","utf-8") #error

print unicode("あ") # error
print unicode("あ","utf-8") # error
print u"あ" # error

print "あ" # ok

どーせーっちゅうねん。がーーーー

]]>
GAEで自分用ブックマーク作ろうとしてつかれた http://tt25.org/blog/20080410/gae-bookmarks 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00

LDRのアカウントが会社用と自宅用で2つあって、LDR上でiするとそれぞれ会社用、自宅用のlivedoor clipに分散してめんどいのでGoogle App Engine使って管理しようかと思い立った。

とりあえずスクリーンショットの状態まで持ってくるのに2時間近く掛かって疲れ切ったのでまた明日。Python初挑戦のわりにここまで出来たらとりあえず満足だ。

&lt;html&gt;が2つあったりprint dir(なんか)がそのままだったりと実用には耐えないガラクタだけど、いちおう動作はしてる。

app.yaml

application: mybookmark
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: main.py

bookmark.py

from google.appengine.ext import db
from google.appengine.api import users

class Bookmark(db.Model):
    title=db.StringProperty(required=True)
    url=db.StringProperty(required=True)
    comment=db.StringProperty()
    append_date=db.DateTimeProperty(auto_now_add=True)
    is_open=db.BooleanProperty(default=True)
    owner=db.UserProperty()

main.py

from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.ext import webapp
import wsgiref.handlers
import os

from google.appengine.ext.webapp import template

from bookmark import *

def bookmark_list(bookmarks):
    path=os.path.join(os.path.dirname(__file__),"list.phtml")
    return template.render(path,{"bookmarks": bookmarks})

class MainPage(webapp.RequestHandler):
    def get(self):
        self.response.out.write(dir(template))
        path=os.path.join(os.path.dirname(__file__),"form.phtml")
        self.response.out.write(template.render(path,{}))

        pets=db.GqlQuery("SELECT * FROM Bookmark ORDER BY append_date DESC");
        self.response.out.write(bookmark_list(pets))

class UserForm(webapp.RequestHandler):
    def post(self):
        user=users.get_current_user()
        if not user:
            self.redirect(users.create_login_url(self.request.uri))
        else:
            #print dir(user)
            self.response.out.write(self.request.get("title"))
            tmp={}
            #for key in ["title","url"]:
            #    tmp[key]=self.request.get(key)
            Bookmark(
                title=self.request.get("title")
                ,url=self.request.get("url")
                ,owner=user
            ).put()
            self.redirect("/")

app=webapp.WSGIApplication(
    [
        ("/",MainPage)
        ,("/post",UserForm)
    ],debug=True
)
wsgiref.handlers.CGIHandler().run(app)

form.phtml

&lt;html&gt;
  &lt;body&gt;
&lt;form action="/post" method="post"&gt;
&lt;table&gt;
&lt;tr&gt;
    &lt;th&gt;title&lt;/th&gt;
    &lt;td&gt;&lt;input type="text" name="title" /&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
    &lt;th&gt;URL&lt;/th&gt;
    &lt;td&gt;
        &lt;input type="text" name="url" /&gt;
    &lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
&lt;input type="submit" /&gt;
&lt;/p&gt;
&lt;/form&gt;
  &lt;/body&gt;
&lt;/html&gt;

list.phtml

&lt;html&gt;
  &lt;body&gt;
  &lt;ul&gt;
{% for b in bookmarks %}
  &lt;li&gt;
    &lt;a href="{{ b.url}}"&gt;{{ b.title }}&lt;/a&gt;({{ b.append_date.isoformat}})
    &lt;/li&gt;
{% endfor %}
&lt;/ul&gt;

  &lt;/body&gt;
&lt;/html&gt;
]]>
Google App EngineのためにPythonを超速効で理解する http://tt25.org/blog/20080409/python-hyper-quick-learning 2008-09-09T21:41:05+09:00 2008-09-09T21:41:05+09:00 超おおざっぱに覚えたことを書き留めておく。

Rubyでいうrequire

import。fooモジュールだかクラスだかをfoo.pyってファイルに記述して、import fooするらしい。

名前空間を使うなんかよくわからないやり方はfrom org.tt25.foobar import baz。この場合bazモジュールだかクラスだかが読み込まれる。

感覚的にはimportが先だと思うんだけどなあ。from foo.bar.bazって書いてるうちに何をimportしようとしたか忘れる。

配列/ハッシュ

Pythonでの名前はlist/dictっぽい。たぶんだけど。

定義のしかたはそれぞれ

l=[1,2,3]
d={"key": "val","key2": "val2"}

っぽい。JavaScriptと同じか。

Array#joinなんてものはない

arr=[1,2,3]
arr.join(",") # error
",".join(arr) # ok

なんでArray#joinじゃなくString#joinなのかはFAQらしいのでググれ。

[1,2,3].each相当

for(l in [1,2,3]):
    print l

ついでにハッシュ(辞書?)。

dict={"a": 1,"b": 2}
for(k,v in dict.iteritems():
    print k,v

obj.methods相当

dir(obj)

str=”こんにちは#{where}の#{user}さん。”

str=”こんにちは%sの%sさん” % (where,user)

あるいは、

str="こんにちは%(where)sの%(user)sさん。" % {"user" : user,"where" : where}

ERB相当

from string import Template、でいいのかどうかよくわからん。

パッケージ?

/A/B/foo.pyってファイルレイアウトがそのままfrom A.B import fooみたいにネームスペースになるらしい。詳細不明。Google App Engine SDKのディレクトリが__init__.pyだらけだったけど、こいつの正体も不明。

]]>
Linux Operaのタブ開閉がすっとろいので風博士試した http://tt25.org/blog/20080409/kazehakase 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Ctrl+T押してから0.3秒くらい待たされるのが耐えられないので風博士を試すことにした。風博士だとだいたい0.1秒くらい。戻る進むとかも充分早い。軽さの点では文句ない。次なる問題は全体の使い勝手だ。

ざっと気になったのはこのへん。

  • Ctrl+E/Ctrl+Kで検索ボックスにフォーカスしない
    • アドレスバーからの検索はいちおう出来るもののGoogle固定?
    • 検索ボックスからの検索もGoogle固定(というか検索時の切り替え不可)な気がする
  • ページ開いたあともフォーカスがアドレスバーに残留するのでキーボードでの操作が遅れる
    • 設定?
  • タブにfaviconが出ない
    • 設定?

逆に良い点。

  • タブ幅をピクセル単位で指定できる
  • デフォルトでタブ縦置きに対応
  • デフォルトでタブバー上のホイールコロコロでタブ切り替えに対応
  • マウスジェスチャ/キーバインドのカスタマイズがわりと楽
  • 閉じたタブリスト機能装備
  • タブツリー機能装備
  • UIの複雑さを初級/中級/上級で選択できる

などなど。特にタブ幅指定はいいなあ。

で、本日のメインイベントUser JavaScriptないしGreasemonkeyのエミュレートなわけですが、風博士はRubyで拡張できるらしいのでそれを使ってやろうと思ってた。結論からいうと無理だった。

Ruby拡張によるGreasemonkeyエミュレート

ドキュメントがない。「風博士 ruby」で検索したときのトップが2006年1月のMLへの投稿 で、状況は「kazehakase ruby」とか英語圏を視野に入れてもさほど変わらず。

仕方ないのでMLにあるact_helloを導入してみて、p actionとかp kzとかp kz.methodsしてみて何が出来るのか把握しようとする。

3つある引数のうち、最初の2つはGTKオブジェクトなのでたぶん関係ないと判断し3つめのkzを集中的に調べてみる。とりあえずクラス名がkz::windowらしいのでそれでググる。CVSリポジトリばっかり出てくるけどドキュメントらしきものは皆無。仕方ないのでソースをあたる。Cなんて読めん。

ext/ruby の中にRubyとCの架け橋らしきものを発見したので、とりあえずkz-rb-window.cを開いてみる。15 months agoとか書いてるけど気にしない。

Userscripts.org のフッタに書いてある「Because it’s your web.」なら、風博士のRuby拡張は「Because it’s your webbrowser.」みたいな違い。あるいはSleipnirプラグインなんかも風博士に近いのかも。

いちおうkz.load_url('javascript:var s=document.createElement("script");s.src="foo.js";document.body.appendChild(s);void(0);')とかしたら似たようなことは出来たけど、何かしらの操作(マウスジェスチャやキー押下)が必要な点でしょせん回りくどいbookmarkletでしかない。ページ読んだら自動で実行してほしい。

ext/ruby/にあったCを全部読んだけど、求めるものはここにないってことで諦めた。

ローカルプロクシによる水際でのHTML変換

これも失敗。

WEBrickでプロキシサーバを作って遊ぶ とかを参考にいろいろやってみたけど、動作が安定しない。slashdotでは動くのにGoogleでは動かないなど。文字コードとかの影響かもしれないけどslashdotもGoogleもUTF-8だしなんやかやで投了。

bookmarklet

妥協案。

ローカルのloader.jsをbookmarkletで挿入して、loader.jsで何をするか制御する。file:///で読み込もうとするとabout:configからsecurity.checkloaduriをfalseにしてもセキュリティおばさんが怒って読んでくれないので、ローカルで稼働してるApacheにVirtualHost振ってHTTP経由で読むようにした。

まとめ

FirefoxはFirefox3 beta 5入れたらたぶんプロファイルごとぶっ壊れたっぽくて放置中。3の正式版待ちかなあ。

Operaは冒頭のようにタブ開閉が遅い。タブ開閉が重いのはGNOMEでもXfceでもfluxboxでも大差ないので、QtかOperaの問題っぽい。タブ開閉以外はそれほど気になる重さでもない。

風博士は軽さの点で現時点最強だけど、あれとかこれとかができない。

次はVMWare上のWindows Operaを試そうかと7割くらい考えてるけど、とりあえずはOperaメインで風博士をLDR用にして様子を見る。

]]>
Google App Engine SDKをLinuxで動かしてみる http://tt25.org/blog/20080409/google-app-engine 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Linux(Debian lenny)デスクトップで動いた。

インストール

SDKを落として解凍してgoogle_appengineディレクトリに移動する。

# unzip google_appengine.zip
# cd google_appengine

python 2.4.5には対応してないといわれたので2.5を入れる。/usr/bin/pythonが/usr/bin/python2.4へのシンボリックリンクになってるので、dev_appengine.pyのshebangを/usr/bin/env python2.5に書き換え。書き込み権限がないのでvimで:w!して強制書き換え。んで再度実行。

# sudo apt-get install python2.5
# (略)
~/google_appengine #  head -n 1 ./dev_appserver.py
#!/usr/bin/env python2.5
~/google_appengine # ./dev_appserver.py demos/guestbook/

localhost:8080を見てみると稼働してる。

開発

次にGetting StartedのHello World を試してみる。google_appengine/demos/testにディレクトリを作り、hw.pyとapp.yamlをそれぞれつくる。

# cat app.yaml
application: hel
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: hw.py

# cat hw.py
print "Content-Type: text/plain\n\n"

print "hew"

さっき起動したdev_appserver.pyをCtrl+Cでいったん落として、./dev_appserver.py demos/test/で再起動。localhost:8080にhewって出た。applicationがどこに反映されたのかわからないけど、Googleに持っていったときに使うらしい。要するにfoo.appspot.comを使うならapplication: fooって設定しないといけないらしい。

デプロイ

まずGoogle App Engineのユーザー画面からアクティブにする。

続いて./appcfg.py update demos/test/を実行すると、ユーザー名(メアド)とパスワードをきかれるので入力する。あとは自動でアップロードとかをやってくれて終了。

つかいみち

App Engine上で本気出すのは中長期的に考えてちょっと怖いので、高可用性と高応答性を生かしたハブ的なものが適してそう。キャッシュサーバとか。ひどい使い方だな。

]]>
いいキーボード買った http://tt25.org/blog/20080409/libertouch 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 ML115に付いてきたhpのキーボードが剛性不足でべこべこしてて打ってて疲れるので、Libertouch FKB8540-052/B のホワイトを買った。

すこすこって感じの打鍵感が軽くてよいです。音も静かめ。本体がずっしりしてるので安定感も充分。

なんとなくRealforceに似てるけど、Realforceの打鍵音が「ぽこぽこぽこ」なのに対し、libertouchは「かすかすとこ」って感じ。

ニュアンスだけで書いてますけど、伝わるわけがないですね。あんまり詳しくないのでこれくらいが限界です。

]]>
アインシュタインであそぼ http://tt25.org/blog/20080407/10-loose-lessons-using-albert-einstein 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 曲解、偏見、我田引水が飛び交うえいごであそぼシリーズ。えいごをもてあそぼ、のほうがたぶん意味的に近い。

今回のターゲットはこれ。

いちおう英語の勉強の一環なので誤訳はないつもりですが、意訳や妙訳や超訳は多分に含まれています。ご指摘いただければとてもありがたいです。

1.

A person who never made a mistake never tried anything new.

彼が失敗することはないだろう。新しいことにチャレンジしていないのだから。

教訓

失敗したくない、あるいは失敗が許されないのであれば新しいことに手を出すな。

関連語:フィーチャーフリーズ

元の適当訳

Most people don’t try new things because of their fear of failure. Failing is not something to be afraid of. It is often the losers who learn more about winning than the winners. Our mistakes always give us opportunities to learn and grow.

多くの人は失敗を恐れて新しいことに挑戦しない。失敗は怖くない。敗者が勝者より多く学ぶこともある。失敗とは学習と成長のための機会である。

2.

Education is what remains after one has forgotten what one has learned in school.

学校で学んだことをすべて忘れたあとに残るのが教育である。

教訓

忘却能力がなければ教育とそうでないものを区別できない。あるいは混同する。博学はまだ判断ではない。

元の適当訳

30 years from now, you won’t possibly remember what chapters you had in your science book; you’d only remember what you learn on your way. Life lessons stay with you forever. Real education starts from within.

30年経ったら科学の教科書にどんな単元があったかすら思い出せないだろう。思い出せるのは自分が独自に学んだことだけだ。人生の教訓は常に自分とともにありつづけ、真の教育はそこから始まる。

3.

I am enough of an artist to draw freely upon my imagination. Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world.

私は想像を自由に描けるという点でアーティストだといえる。想像は知識よりも重要だ。知識は有限だが、想像は世界を取り囲む。

教訓

百聞は一見に等しいらしいが、一見は一妄想に劣る。あるいは、一を聞いて十を知り百を妄想する。

元の適当訳

When you reflect on how far we humans have come from the prehistoric caves to mind-blowing technological advancements, you would feel the power of imagination. What we have now was built from the imagination of our forefathers. What we will have in future will be built from our imagination.

人類が原始時代の洞窟からどれくらい遠くに来たかと思いを馳せると、イマジネーションの力を感じられるだろう。我々の祖先はイマジネーションによって現代の生活を築き上げた。我々もまたイマジネーションによって未来を築くだろう。

4.

The secret to creativity is knowing how to hide your sources.

創造性の秘密? それは出所の隠しかたを知ることだ。

教訓

パクリだとバレないように創造性を発揮しろ。

元の適当訳

Creativity and uniqueness often depends on how well you hide your sources. You can get inspired and influenced by other great people; but when you are on stage with the whole world watching, you must become a unique, individual force that learnt different values from different people.

クリエイティビティとユニークさはしばしば、いかに上手に元ネタを隠せたかに依拠する。あなたが誰か偉大な人間に感激したり感化されたりしたとする。そこであなたは世界中の人々が監視するステージに立った。あなたは他の人々とは違うということを学び、ユニークでなければならない。

(なんでユニークさの話になるのか不明。アメリカだからか?)

5.

The value of a man should be seen in what he gives and not in what he is able to receive. Try not to become a man of success, but rather try to become a man of value.

人の価値は、彼がどれだけのものを得られるかではなく、どれだけのものを与えられるかで見るべきだ。成功した人ではなく、価値ある人を目指すべきだ。

教訓

何もくれないご主人様に価値はない。

元の適当訳

If you think of all the top people in the world, they would have added something of value to the world. You must give in order to take. When your purpose is contributing or adding value to the world, you will be elevated to a higher level of living.

トップクラスの人々を考えると、彼らは世界に対して何かを与えた人だと気づく。何かを得たらそれを適切に与えるべきだ。寄付や貢献を目的とするなら、より上位の生活へと昇っていけるだろう。

6.

There are two ways to live: you can live as if nothing is a miracle; you can live as if everything is a miracle.

生活するには2通りの方法がある。奇跡なんてないと思うか、何もかも奇跡だと思うかだ。

教訓

日本では昔、ただの偏西風を神風とかいって盛り上がりましたね。と書いてみて偏西風だったかどうか気になったのでググったら、冬将軍と神風がイチャイチャしてるという妄想を発見してさすが世の中は広いなと思いました。

というわけで教訓は、奇跡の有無にかかわらず世の中って広い。

元の適当訳

When nothing is a miracle, you gain the power of doing anything you want and you have no limits. And when everything is a miracle, you stop by to appreciate even the smallest of beautiful things in the world. Thinking both ways will give you a productive and happy life.

もしも奇跡がなかったら、したいことを実現するための力を得るが際限がない。もしもすべてが奇跡なら、ちょっとしたことに多大な価値を見出すだろう。どっちがあなたを生産的にするか、あるいは幸福にするか考えてみよう。

7.

When I examine myself and my methods of thought, I come to the conclusion that the gift of fantasy has meant more to me than any talent for abstract, positive thinking.

自分の思考法や自分自身について考えを巡らせてみると、目の前に吊るされたニンジンはどんな才能よりも活躍してくれているという結論に至った。要するに、攻めの姿勢だ。

教訓

すっぱいブドウよりすぐにでも取れそうなニンジンを。

元の適当訳

Dreaming about all the great things that you can achieve is the key to a life filled with positivity. Let your imagination run amuck and create the world that you would wish to be in.

自分で出来る範囲のささやかな成功を夢見ることは、積極的に生活するための鍵だ。イマジネーションを縦横無尽に走らせ、自分が居たいと思う世界を創造しよう。

8.

In order to be an immaculate member of a flock of sheep, one must above all be a sheep oneself.

一点の曇りもない羊の群れの一員であるためには、まずは何よりも羊でなければならない。

教訓

のび太のくせに生意気だ。

元の適当訳

疲れてきたので省略。なんか起業家がどうとか書いてある。

9.

You have to learn the rules of the game. And then you have to play better than anyone else.

ルールを学べ。より良く振る舞え。

教訓

KY。

ていうか空気はルールじゃないな。あとここではKYを「空気読め」の命令形で使ってるけど、いつの間にか「空気読めない」という状態説明になったのはなんでだ。

適当訳

10.

The important thing is not to stop questioning. Curiosity has its own reason for existing.

質問を止めるな。好奇心はそのためにある。

教訓

勢いあまって質問に質問を返すと吉良吉影に爆破されるので、やっぱり好奇心は猫をも殺すんじゃないかと思いました。

おわりに

半分くらいでだいぶ疲れた。8以降の手抜きっぷりがまぶしい。

英文読む→意味がわからない単語を辞書で引く→文意がわからなければ一文まるごとExcite翻訳へ突っ込む→意味を理解する→日本語でその意味を記述する→教訓をでっちあげる

というプロセスを繰り返した。でっちあげた教訓のなかでは1の逆説と4の循環が好き。

試み

よくできたのか間違ってるのかよくわからない訳たち。

I am enough of an artist 〜 私は〜という理由でアーティストといえる
gift of fantasy 目の前に吊るされたニンジン
positive thinking 攻めの姿勢
positivity 積極的に
adding value to the world (世界への)貢献

何がなんでも「ポジティブ」って言葉を使いたくなかったせいもある。

]]>
VMware Player上のDebian etchのネットワークがつながらなくてあせった http://tt25.org/blog/20080402/debian-etch-on-vmplayer 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 sshfsで遠隔地のストレージにあるmp3をローカルで聞いてるわけですが 、なぜか遠隔地vmplayer上のDebianがネットワークを検出できないかなんかでつながらない。ping 192.168.0.1すらunreachedになるし、sudo /etc/init.d/networking restartとかしても無駄無駄無駄。VMware Playerを3.0.2から3.0.3にインストールし直しても同じ。

結論としては、etchではMACアドレスがどうとかでネットワークの設定か.vmxファイルをいじらないといけないみたい。

/etc/udev/rules.d/z25_persistent-net.rulesを確認するとeth0とeth1があって、eth1は.vmxファイルと同一のMACアドレスが書いてあった。sudo vim /etc/network/interfaceして、eth0をeth1に書き換えたあとOS再起動すると無事ネットワークがつながった。このやり方だとまた.vmx側のMACアドレスが変わって意味ない気がするけど、原因と対策は得られたのでまあいいか。

]]>
なぜか筑波大学から花見のお誘いメールがきた http://tt25.org/blog/20080402/mail-from-tsukuba 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00

デザイン&感性情報の皆様

春を迎え、お花見のシーズンがやってまいりました。

今年もデザイン&感性のお花見を開催したいと思っています。

って文面で始まるメール。新入生は参加費無料らしい。

Fromは@kansei.tsukuba.ac.jp、Toが@mail2.accsnet.ne.jpで、CCやToにも変なところはない。スパムにしては商売っ気がなさすぎるし、僕と筑波大学には何のつながりもない。あとkansei.tsukuba.ac.jpは筑波大学の感性情報学科?かなんかのドメインらしい。

ということでFromの筑波大生の人に「なんか知らんけどうちにメールきたから操作ミスってないか確認してみたら?」ってメールを返した。

その翌日、Fromの人から返信がきて「あーそうだったような気がする迷惑かけたすまない」「気にするな」「これも何かの縁ですしていうか人数集まらないんでもしお暇なら来てみませんか」「じゃあ行く」「場所は最初のメールにも載ってますけどあれなら迎えに行きます」「駅まで来てくれ頼んだ」てことで花見に参加して電車の中でこれなんてYou’ve got mailだとか考えてたら駅に着いてFromの人と対面してToの人はあの人ですよあどうもToの人こんにちはあその節はどうもとか挨拶しながらやっぱり大学生は若いなあと感じて酒飲んで仕事の話が新鮮に聞こえるみたいなのでいろいろ話してる最中に、当該メールのヘッダ1行目にDelivered-To: があるのを見つけてしまったので現実によって妄想が急冷されてToの人がメール転送設定ミスっただけだろうってことでいろんな意味で落着した。

Delivered-Toとか死ねばいいのに。妄想を返せ。SMTP爆発しろ。Toの人のミスなんだったら、Fromの人のミスだということで成り立っていた妄想がすべて瓦解するわけでそのへんまた最初から設定し直さないといけないから上半身裸で桜の木によじ登ってた人の行方とかが迷宮入りしてしまって続きが気になってしょうがない。現実と明らかに矛盾する設定の妄想を続けられるほど妄想スキルが高くないのでもう無理だ。誰かにrm -rf /された気分だ。がーーー。

とか考えながらメールに返信すらしていません。ちなみに返信期限は4月2日なのでToの人は花見に参加できませんでした。たぶん。

]]>
意味わかるシリーズ:AutoPagerizeで分割されたページを自動でひとつにくっつけるの巻 http://tt25.org/blog/20080402/wakaru-autopagerize 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 このブログが意味わからんって苦情が2件あがってきているので、たまには意識的に意味分かるようなことを書こうと思いまして。

今回は意味わからんクレーム2号の人に教えたら大好評だったAutoPagerizeのご紹介です。

AutoPagerizeとは

ページを下へスクロールするだけで、自動で次のページを継ぎ足してくれるものです。前に上げた動作サンプルを再掲。

この動画では

  1. Googleの検索結果
  2. ITmediaのニュース記事

でそれぞれ実行されています。他にも各ニュースサイト、ニコニコ動画のランキングページ、YouTube検索結果、Amazon検索結果、はてなブックマークなどいろんなサイトに対応しています。

インストールせずに試してみる

クロスブラウザ対応bookmarklet が公開されてます。これをブラウザに登録して、Googleの検索結果で実行してみるとどういうものか体験できます。

インストール

毎回bookmarkletを実行するのは面倒。ページを開いたら勝手に実行して欲しい。

そういうときはインストールして使いましょう(というかこの使い方が本来)。素のIE6では出来ません。

インストール手順はFirefox,Opera,Safari,Sleipnirなどなどでそれぞれ異なって来ますが、やってることは「特定のJavaScriptをローカルに保存してページ読み込むたびに自動実行してる」だけです。

Firefox

  1. Greasemonkey っていうアドオンを入れます。
  2. Firefoxを再起動します。
  3. AutoPagerize のページ右上にある「Install this script」をクリックしてインストールします。
  4. Googleでなんか適当に検索してみて、次のページがあるときに右上に黄緑の四角が出てくれば動いてます。

Opera

  1. Cドライブにuserjsフォルダを作ります。
  2. ツール>設定>詳細設定>コンテンツ>JavaScriptオプション(画像) を開いて、一番下の「ユーザー JavaScript ファイル」を「C:\userjs」に変更します。
  3. AutoPagerizeのOpera移植版、oAutoPagerize をさっき作ったフォルダに保存します。
  4. Googleでなんか適当に検索してみて、次のページがあるときに右上に黄緑の四角が出てくれば動いてます。

別にフォルダ作る必要はないんですが、デフォルトだとC:\Document and Settings\username\Application Data\.....とか奥深く過ぎて意味がわからなくなるので、あえてC:\userjsということにしました。

もちろんD:\userjsでもC:\opera\aaaでもなんでもいいです。

Safari

Mac持ってないのでSafariはよくわかりませんが、GreaseKitとかいうのを使うらしいです。Windows版Safariだと使えないみたい。

http://d.hatena.ne.jp/os0x/20080109/1199849910 を参考に試してみてください。

Sleipnir

Sleipnirも1.66以降ほとんど触ってないのでよくわかりませんが、SeaHorseを使って出来るみたいです。

http://furyu.tea-nifty.com/annex/2008/03/autopagerlike_s_56ec.html を参考に試してみてください。

IE7

IE6だと不可能ですが、IE7だとIE7pro経由でできるみたいです。

AutoPagerizeと関係ない余談ですが、IE7はIE7proを入れてはじめて真っ当なブラウザになれるので、IE7を使ってるけどまだ入れてないって人はインストールしておくといいと思います。マウスジェスチャとか付きます。個人差はありますが、これでSleipnirの5〜8割くらいの便利さを享受できます。

AutoPagerizeのある暮らし

使用前 使用後
「次へ」のリンクが押しづらい そもそも押す必要がない
次のページへ行き過ぎてどれだけ前のページに戻ったらいいかわからない 上にスクロールする、ページ内検索する
Googleの表示件数を100件にしないと探すの面倒だけど、そのせいでちょっと遅くなった Googleの表示件数を10件にしてデフォルトの軽さを取り戻す
ニュースサイトで記事が10ページに分けられてるのが果てしなくうっとうしい 心穏やかな毎日

他にも何か便利な使い方や体験談、心温まるエピソードなどがあればぜひ教えてくださいませ。

]]>
Rails抜きのActiveRecordに再挑戦 http://tt25.org/blog/20080402/active-record-exclude-rails-again 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 いまいちRailsの機能とActiveRecordの機能を区別できてない。create_tableでテーブル定義して# rake migrateなんかしても、Ruby的には意味不明みたいだ。これはたぶんRailsの機能。たぶん。おそらく。

マイグレーションは面倒そうだったのでまずはO/Rマッピングから攻めていくことにする。

RubyからSQLite3を操作する

sudo apt-get install ruby1.8-dev sqlite3 とかsudo gem install sqlite3 active_recordしておく。準備が出来たらこう書く。

require "rubygems"
require "sqlite3"

sql=&lt;&lt;SQL
CREATE TABLE students (
    id INTEGER PRIMARY KEY NOT NULL UNIQUE,
    test_text TEXT NOT NULL DEFAULT 'default_text',
    aa INTEGER NOT NULL DEFAULT 0
);
SQL

SQLite3::Database.new("aa.db").execute(sql)
students
id
test_text
aa

これくらいならわかる。SQLite3クラス(モジュール?)が他に何を出来るかは知らないけど、newでデータベース選択、executeでSQL実行がわかればそれでいいや。

ちなみにSQLiteでINTEGERかつPRIMARY KEYなフィールドは、MySQLでいうauto_increment相当になる。

ActiveRecord先生によるCRUD

require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter =&gt; "sqlite3", :database =&gt; "aa.db"
)

class Student &lt; ActiveRecord::Base
end

Student.create({:aa =&gt; rand(100)})

p Student.find(:all)
p Student.find(:all,:conditions =&gt; "aa &lt; 100")
p Student.find(:all,:conditions =&gt; ["aa &gt; ?",50])
p Student.find(:first)

s=Student.find(:first)
s.test_text = "huhahahahahah"
s.save

p Student.find(:first,:conditions =&gt; "test_text &lt;&gt; 'default_text'")

Student.find(:first).destroy

まず最初にestablish_connectionでDB接続。続いてStudentクラスの定義、というか、これはクラス名を使って裏側でいろいろな処理をしてくれるので空でいい。楽だ。

Student.createでINSERT相当。

Student.find(:all)でSELECT * FROM students相当。find(2)ならWHERE id=2が付く。ActiveRecorde::Base#find のAPIはだいたいこんな感じ。

Student.find(:first)の返り値のプロパティがそのままカラムになってるので、s.test_textに適当な値を入れてsaveするとUPDATE相当。

destroyでDELETE相当。

これでひととおりのCRUDはわかった。

ActiveRecord先生の華麗なるユーティリティ

with_scope

ファイルを改めて、ここ を参考にまずwith_scopeを試してみる。

require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter =&gt; "sqlite3", :database =&gt; "aa.db"
)

class Student &lt; ActiveRecord::Base
end

p Student.find(:all)

Student.with_scope(:find =&gt; {:conditions =&gt; "id &lt; 8"}){
    p Student.find(:all)
}

実行するとidが8未満のやつだけが列挙されるはず。

/var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:1532:in `method_missing’: protected method `with_scope’ called for Student(id: integer, test_text: text, aa: integer):Class (NoMethodError)

えーprotected? 仕様変更? まあ意図はわからんでもないので別にいいけど、コピペ学習が止まるのは困るので公式 からサンプルをあさる。

require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter =&gt; "sqlite3", :database =&gt; "aa.db"
)

class Student &lt; ActiveRecord::Base
    def self.scope_test
        with_scope(:find =&gt; {:conditions =&gt; "id &lt; 8"}) {
            find(:all)
        }
    end
end

p Student.find(:all)

p Student.scope_test

ん、OK。

公式のサンプルにもある:createがよくわからないので、勘で試してみる。

class Student &lt; ActiveRecord::Base
    def self.scope_test
        with_scope(:find =&gt; {:conditions =&gt; "id &lt; 8"}) {
            find(:all)
        }
    end

    def self.scope_test_create
        with_scope(
                :find =&gt; {:conditions =&gt; "id &lt; 8"},
                :create =&gt; {:test_text =&gt; "scope test"}
            ) {
            create({:aa =&gt; 999})
        }
    end
end

p Student.find(:all)

p Student.scope_test
p Student.scope_test_create #&lt;Student id: 15, test_text: "scope test", aa: 999&gt;

なるほどなるほど。なんかインデントが気持ち悪くなったけど意味はわかった。そのスコープ内でcreateするときのデフォルト値みたいなもんだな。

続いてscopeのネスト。

    def self.scope_test_nest
        with_scope(:find =&gt; {:conditions =&gt; "id &lt; 30"}){
            with_scope(:find =&gt; {:conditions =&gt; "aa &lt; 40"}){
                find(:all)
            }
        }
    end

id &lt; 30かつaa &lt; 40なスコープが定義できたのを確認。

ActiveRecord先生のリレーショナルユーティリティ

「リレーショナル」と前出の「華麗なる」が地味に韻を踏んでいる点に注意。

リレーション(公式にはアソシエーションらしい)は要するにhas_manyとかbelongs_toとか、一時期よく見かけたやつですね。

どうでもいいけどbelongって単語を見ると未だにall your base are belong to us が頭に浮かぶ。いい加減記憶をアップデートしたい。

require "rubygems"
require 'active_record'

ActiveRecord::Base.establish_connection(
  :adapter =&gt; "sqlite3", :database =&gt; "aaa.db"
)

class Student &lt; ActiveRecord::Base
    belongs_to :group
end

class Group &lt; ActiveRecord::Base
    has_many :students
end

if !Student.find(:first) # =&gt; nil
    10.times{|n|
        Student.create({:aa =&gt; n, group_id =&gt; n})
        Group.create({:group_name =&gt; "group #{n}"})
    }
end

p Student.find(:all)

の前にまずgroupsテーブル作らないと。どうせならってことでaa.dbじゃなくてaaa.dbを新規作成する。

require "rubygems"
require "sqlite3"

sql=&lt;&lt;SQL
CREATE TABLE students (
    id INTEGER PRIMARY KEY NOT NULL UNIQUE,
    test_text TEXT NOT NULL DEFAULT 'default_text',
    group_id INTEGER ,
    aa INTEGER NOT NULL DEFAULT 0
);

CREATE TABLE groups (
    id INTEGER PRIMARY KEY NOT NULL UNIQUE,
    group_name VARCHAR NOT NULL DEFAULT '',
    ggg INTEGER NOT NULL DEFAULT 0
);
SQL

SQLite3::Database.new("aaa.db").execute(sql)

students groups
id id
test_text group_name
group_id ggg
aa  

な感じ。groups LEFT OUTER JOIN students ON (students.group_id = groups.id)される予定。

んで先のコードを実行すると

/var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/connection_adapters/sqlite3_adapter.rb:29:in `table_structure’: Could not find table ‘groups’ (ActiveRecord::StatementInvalid)

なんでやねん。

groupsテーブルがないと言われて考えられることはgroupsテーブルがないってことなので、ちょっと考えてCREATE TABLEのほうをこうしてみた。

require "rubygems"
require "sqlite3"

sql=&lt;&lt;SQL
CREATE TABLE students (
    id INTEGER PRIMARY KEY NOT NULL UNIQUE,
    test_text TEXT NOT NULL DEFAULT 'default_text',
    group_id INTEGER ,
    aa INTEGER NOT NULL DEFAULT 0
);
SQL

SQLite3::Database.new("aaa.db").execute(sql)

sql=&lt;&lt;SQL
CREATE TABLE groups (
    id INTEGER PRIMARY KEY NOT NULL UNIQUE,
    group_name VARCHAR NOT NULL DEFAULT 'alone',
    ggg INTEGER NOT NULL DEFAULT 0
);
SQL

SQLite3::Database.new("aaa.db").execute(sql)

んで再度実行すると、きちんとStudent.find(:all)が動いた。「;」で区切って複数同時に実行しようとしてもダメみたい。よく考えたらDatabase.newなんだから当然か。

何やってたっけ。ああそうそうbelongs_toとかか。

これ を参考にいろいろ試してみることにする。

g=Group.find(1)
p g.students # =&gt; [#&lt;Student id: 1, test_text: "default_text", group_id: 1, aa: 5&gt;]
puts g.students.size # =&gt; 1

s=Student.create({:test_text =&gt; "tesuto"})
g.students &lt;&lt; s

p g.students # =&gt; [#&lt;Student id: 1, test_text: "default_text", group_id: 1, aa: 5&gt;, #&lt;Student id: 11, test_text: "tesuto", group_id: 1, aa: 0&gt;]
puts g.students.size # =&gt; 2

g.students.create({:test_text =&gt; "tset2"})
puts g.students.size #=&gt; 3

だいたいわかった。1対1も多対多も似たようなものだろう。

まとめ

SQLを書きたい子なのでO/Rマッピングには消極的なほうですが、これはたしかに便利だなあ。ていうか、あんなにひどいテーブル定義かいといてSQL書きたいもないな。

]]>
どう書く?orgとTumblrのアカウント取ってみた http://tt25.org/blog/20080331/doukaku-org-tumblr 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 どう書く?orgのアカウント取った。

今日はだいぶ気持ち悪いJavaScript を書けて満足。Rubyの勉強のためにと思ってたけど、MVCとかなんとかきっちりしたコーディングに嫌気がさしたときの鬱憤晴らしにも活躍しそうだ。

それにしてもまだまだお題の意味がわからなかったりするのでダメだなあ。

あとTumblrも始めた。

tt25にするつもりが間違えてtt23にしてしまったのでタイトルはmisnamed。tumblrといえばエロ画像とかグロ画像とかってイメージがあるけど、まあなんかてきとうに。

Tumblr公式bookmarkletをOperaのニックネーム付きブックマークに入れて、Shift+F2 t(umblr)でreblogできるようにしといた。Shift+F2でbookmarklet使うのは、WindowsでいうWin+Rの高級版(MacでいうとQuicksilver? )みたいな感触でわりと好き。

LDRize(というかMinibuffer)はなんかもっさりしてるので使うの諦めた。

]]>
Acid3 Testを通過したSafari/Webkitがひどすぎる http://tt25.org/blog/20080328/webkit-update-for-acid3-test-only-made-by-apple 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 WebkitとOperaがそれぞれAcid 3 Testをクリアしたらしいけど正直なところあんまり興味ない。それでも時代はWeb標準を求めている というより率直にいって「IE死ね」だと思う。

テスト通過よりも、Safari/Webkitのチート100点 について、前例 もあることだし「またか」って感じではあった。Appleのページに置いてあるグラフも、信用できないテストスイートの結果 だし。数年前からやってることがまったく進歩してない。

まあ、いわれてみればSafari/Webkit陣営がWeb標準とか主張してるのあんまり見たことないから純粋に宣伝とかマーケティングのための開発であって、その戦略においてチート100点とか空実装とかあれとかこれとかは実に合目的的だ。爆発してしまえ。

Windows版Safariもなんか得体のしれないことをやってるんだろうな。Appleのリンゴには農薬が残留していますが死にはしませんし何よりきれいです、みたいな。

]]>
Mercurialとマスターしたい12の文彩を試した http://tt25.org/blog/20080328/mercurial 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 いまどきバージョン管理してないなんてね。

デベロッパはMercurialを使うことができる。多くの調査の結果同氏がいきついたバージョン管理システムがMercurialである。

http://journal.mycom.co.jp/articles/2006/05/15/bsd4/001.html

このようにFreeBSDデベロッパも認める 地上最強のバージョン管理システムことMercurialを試してみた。

インストールはいつもどおりsudo apt-(ry

Subversionとの対比

Subversionはリポジトリを本家と分家に区別して分家が本家にマージされていくモデルだけど、Mercurialは本家と分家の区別がなく本家のコピーも本家であると主張する。

SFの諸概念でいうとSubversionがタイムパラドックスに悩む一方、Mercurialはパラレルワールドを構築する。

Mercurialを試す

インストール直後の全然まったく完璧に純粋無垢な初期状態で

  1. hg initで今いるディレクトリにリポジトリ構築
  2. hg commit -A -m "コメント" でコミット
  3. hg update -r 2 でリビジョン2へロールバック、
  4. hg update で最新リビジョンへ更新

なんかをコリコリ試すとMercurialのスパっとした操作体系がふよふよと頭に入ってくるはず。

他にも、なんと他の場所にあるリポジトリとsshやhttpで接続してこんなことが出来る。

  1. hg clone ssh://remote.example.com/hg/test test2 でremote.example.comの/hg/testにあるリポジトリをtest2へコピー
  2. (test2へ移動)
  3. hg pathでこれがremote.example.comからコピーしたものであると確認
  4. hg pullでremote.example.comからtest2へ最新版をコピー
  5. hg pushでtest2からremote.example.comへ最新版をコピー

コマンドはhgとかhg --helpとか打てば確認できるので軽く試してみませんか。

追記

この行で「移動する」を満たして終了。

「レトリックのすすめ」でマスターしたい12の文彩 をとりあえず全部実装してみた。各行で1個ずつやってる。

]]>
mod_rewriteの例が悪い http://tt25.org/blog/20080327/apache-mod-rewrite-rule 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 URLをリダイレクト・リライトする場合の.htaccessの設定例 がなんか全体的に微妙。

「product.php?12」を「product/ipod-nano/12.html」にリライト

正しくは「product?id=12」だけどこれはご愛嬌。

まず原文もそうだけど見出しの語順が逆だと思う。「product/ipod-nano/12.htmlをproduct.php?id=12にリライト」が処理の流れ的に正しい。

で、問題のコードですが、

RewriteEngine on
RewriteRule ^product/([a-zA-Z0-9_-]+)/([0-9]+).html$ product.php?id=$2

元記事のコメント欄で突っ込まれてる とおり、product/awdftgyhjiiko/89818917897871.htmlとか、product/microsoft-zune/12.htmlまで同じ内容になるのはさすがにダメだろう。

RewriteEngine on
RewriteRule ^product/ipod-nano/([0-9]+).html$ product.php?id=$1

RewriteEngine on
RewriteRule ^product/([a-zA-Z0-9_-]+)/([0-9]+).html$ product.php?genre=$1&#38;id=$2

が正しい。

あとこの例だとmod_rewrite使わなくても、

Options +MultiViews

とするとproduct.php/ipod-nano/12.htmlやproduct/ipod-nano/12.htmlがそのまま通るので、PHP側で受け方をちょっと変えればrewriteしなくてもいい。404とかを手動でやらないといけないから面倒だけど、rewrite使えない環境もあるだろうから考慮に入れといて損はないかと。

さらに蛇足だけどproduct/ipod-nano/12.htmlてのはURLの設計がおかしいと思う。これがproduct/ipod-nano/specとか、product/12.htmlとか、product/ipod-nano.htmlならわかる。12って何のパラメータなんだ? あとせっかく()でキャプチャしてるわりにPHPには渡してないので「ipod-nano」は不要なんじゃないのか? 

「yoursite.com/user.php?username=xyz」を「yoursite.com/xyz」にリライト

RewriteEngine On
RewriteRule ^([a-zA-Z0-9_-]+)$ user.php?username=$1
RewriteRule ^([a-zA-Z0-9_-]+)/$ user.php?username=$1

間違いではないけど冗長。

RewriteEngine On
RewriteRule ^([a-zA-Z0-9_-]+)/?$ user.php?username=$1

で充分。

あと元記事でもコリスでもSEOについて触れてるわりに、これだとパーマリンクが/の有無でばらけてしまってSEOが甘い。3行目を削除して、/ありURLをNot Foundにしてしまってもいいけど、ちゃんと書き直すとこう。

RewriteEngine On
RewriteRule ^([a-zA-Z0-9_-]+)$ user.php?username=$1
RewriteRule ^([a-zA-Z0-9_-]+)/$ $1 [R=301]

最後に/がついてるURLにアクセスしてきたら/なしのURLに301リダイレクトして一本化する。このブログでもそうしてる。

「サブフォルダのもの」を「ルート直下」にリダイレクト

例:test.com/new

RewriteEngine On
RewriteCond %{HTTP_HOST} ^test.com$ [OR]
RewriteCond %{HTTP_HOST} ^www.test.com$
RewriteCond %{REQUEST_URI} !^/new/
RewriteRule (.*) /new/$1

それだと最終的にtest.com/new/newを見に行くことになるけどあってる?

たぶん元々の意図は、「http://test.com/old-permalinkへのアクセスを、内部的にhttp://test.com/backup/old-contentにリライトしたい(古いコンテンツを全部backupディレクトリに移してしまいたい)」なんだと思うけどあんまり自信ない。

あ、違うか。逆だ。元記事だとこう書いてるな。

Suppose the you’ve redeveloped your site and all the new development reside inside the “new” folder of inside root folder.Then the new development of the website can be accessed like “test.com/new”.Now moving these files to the root folder can be a hectic process so you can create the following code inside the .htaccess file and place it under the root folder of the website. In result, www.test.com point out to the files inside “new” folder.

↑適当訳:サイトをリニューアルして、新しいファイルをnewディレクトリに置いたとしよう。これで一応新しいサイトにはhttp://test.com/new/でアクセスできる。そこから新しいサイトのファイルを一番上のディレクトリに移していくなんていう死ぬほどめんどくさい作業をしなくても、以下のように.htaccessを書けばほぼ同じことができる。要するに、http://test.com/へのアクセスをhttp://test.com/new/へとリライトするわけだ。

RewriteEngine On
RewriteCond %{HTTP_HOST} ^test\.com$ [OR]
RewriteCond %{HTTP_HOST} ^www\.test\.com$
RewriteCond %{REQUEST_URI} !^/new/
RewriteRule (.*) /new/$1

か。なるほど。rewriteを使って、/public_html/じゃなくて/public_html/new/をルートにするわけか。それだと古いコンテンツへのリンクがたぶん死ぬけど、まあリニューアルなんだし仕方ないか。http:/test.com/new/へのアクセスがトップページと同じ内容になるけど、秘密にしとけば実用的には問題ないか。

あとRewriteCondのHTTP_HOSTチェックはこの例の場合どうでもいいと思う。

mod_rewriteは黒魔術です

mod_rewriteとか使わないに越したことはない。

膨大な設定例やドキュメントがあるにもかかわらず、mod_rewrite は黒魔術である。かなりイケてるっぽい黒魔術だが、やっぱり所詮は黒魔術である。

http://www.net-newbie.com/trans/mod_rewrite.html

プログラム界隈で黒魔術というと、「難解だけどよく動いてるコード」「ダークサイドに堕ちた 」などなど、強力だけど普段使いするな、リスクを理解してから使え、みたいなニュアンスです。カリカリにチューニングされたコードとか、極限まで短くされたワンライナーなんかが該当します。汚さと引き換えに強力なパワーがどうとか。ただのぐちゃぐちゃなコードはスパゲッティとか呼ばれて、黒魔術とは区別されます。

Cool URI の考えに始まり、SEOが云々言い出して、Railsが突貫して以降、rewriteは当たり前のものみたいになってるけど、静的ページがメインのサイトで必要になることはまずないはず。最後の例の/new/みたいに不用意に使えばユーザーと管理者が無駄にメダパニるだけなので、そのへん注意して用法を誤らず用量を適正に保ちつつ清く正しく使わないとそのうち誰かが流れ弾に当たって死にます。

]]>
RedMineをsvn updateできることに気づいた http://tt25.org/blog/20080324/redmine-svn-update 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 svn coで落としてきたものなのでsvn updateできて然りだということに気づいた。安定版の0.6 stableではなく開発版を使ってるので、svn updateしてみたらやっぱり更新されてた。安定版も3月12日に0.6.4が出た らしいので、試してみてもいいかも。設定やsqliteのファイルは維持される。

手順は、

  1. redmineのディレクトリに行く(cdする)
  2. svn update

で終わり。これはSubversionの機能。

update前後で気づいた違いは「問題」が「チケット」になったくらい。URLはissueのまんまだったけど、テキストは全部「チケット」で統一されてた。言語設定を「英語」にするとIssuesとかNew issueだったので翻訳が変わっただけみたい。トラッカーの「担当」が変わってるか見てみたかったけど、自分で名前変えてしまったのでどうなったかは知らない。

]]>
RackでMongrel動かせた http://tt25.org/blog/20080324/rack-mongrel-handler 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Rubyをウェブアプリ化するのにMongrelとかWEBrickはよくわからん、lighttpd+fastcgiもよくわからん、CGI.outがよくわからんというわけでRackを試してみることにした。

RackでWebアプリのWebサーバー依存を無くす とかRack を使って Web サーバで統一されたインターフェイスの利用する を参考にしてとりあえず動かしてみた。

インストール

rackとかmongrelとか。

# sudo gem install mongrel rack

hello world

require "rubygems"
require "rack"

include Rack

hw=Proc.new {|env|
    req=Request.new(env)
    Response.new.finish{|res|
        res.write "hello,world!"
        res.write req.GET.to_s
    }
}

apps=URLMap.new([
    ["/",he]
])
Handler::Mongrel.run apps,:Port=&gt;10081

http://localhost:10081/?aa=gaeijaijとか見てみて、ちゃんとGETパラメータが取れてるのを確認できた。日本語もおk。

fastflickrつくった

「2007グラビアアイドル ランキング」にランキング入りしたグラビアアイドル全画像 にインスパイアされてなんとなくつくった。

fastflickr

暇だったのでつい。途中で飽きたのでピンを開いたりはできないですしIE無視してますし都道府県もうろ覚えなのでそのうち消えます。

flickrから画像取ってくるソース

Flickrからアルバムを生成するPythonコードflickr apiのドキュメント を大いに参考にしました。

=begin
flickr=Flickr::Photos::Search.new({
    "api_key"=&gt;"APIKEY",
    "format"=&gt;"json",
    "nojsoncallback"=&gt;true,
    "per_page"=&gt;50,
    "text"=&gt;keyword,
    "sort"=&gt;"date-posted-desc",
})

flickr.to_json(keyword)
flickr.to_json(keyword,callback)
flickr.to_a(keyword)
=end

require "open-uri"
require "cgi"
require "rubygems"
require "json"

module Flickr
    REST_URL="http://api.flickr.com/services/rest/?"
    class Photos
        class Search
            PHOTO_URL="http://farm%s.static.flickr.com/%s/%s_%s.jpg"
            PERMALINK="http://www.flickr.com/photos/%s/%s"
            def initialize hash
                hash["method"]="flickr.photos.search"
                @hash=hash
            end

            def fetch id
                cache="cache/#{id}.json"
                if File.exist?(cache) == false
                    q=@hash.map{|k,v|
                        CGI::escape(k.to_s)+"="+CGI::escape(v.to_s)
                    }.join("&#38;")
                    url=Flickr::REST_URL+q
                    puts url
                    data=open(url).read
                    File.open(cache,"w"){|fp|
                        fp.puts(data)
                    }
                else
                    data=open(cache).read
                end
                data
            end

            def to_json id,callback=nil
                json=to_a(id).to_json
                if callback
                    "#{callback}(#{json})"
                else
                    json
                end
            end

            def to_a id
                data=fetch(id)
                result=JSON.parse(data)
                (result["photos"]["photo"] || []).map {|p|
                    {
                        :img=&gt;sprintf(PHOTO_URL,p["farm"],p["server"],p["id"],p["secret"]),
                        :link=&gt;sprintf(PERMALINK,p["owner"],p["id"]),
                        :title=&gt;p["title"],
                    }
                }
            end
        end
    end
end

]]>
ThatspingのタグページがRSSはいてないので変換するやつ書いた http://tt25.org/blog/20080324/thatsping-tag-feed 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 ザッピング がなかなか面白そうな気がしたので見てたけど、タグページがRSSはいてない。仕方ないので自分でスクレイピングして変換するようにした。Hpricot必須。

そのうち公式に対応するだろうから、ウェブアプリ化する気もPHPで書く気もない。あとなぜか「自殺掲示版」タグを上手く取って来れない。

=begin
require "thatsping.rb"

tag=CGI::escape("foobar")
path="/path/to/save/{tag}.xml"
body=Thatsping::tag(tag)
File.open(path,"w"){|fp|
    fp.puts(body)
}

=end

require "open-uri"
require "cgi"
require "rubygems"
require "hpricot"

class Thatsping
    BASE_URI="http://thatsping.com/"

    def tag tag
        Thatsping::tag(tag)
    end

    def Thatsping::tag tag
        url=Thatsping::BASE_URI+"tag/"+tag
        result=[]
        open(url){|f|
            body=f.read
            Hpricot(body).search('//div[@id="blue"]/dl/dt'){|link|
                tmp={}
                tmp[:link]=CGI::escapeHTML(link.at("a").attributes["href"])
                tmp[:title]=CGI::escapeHTML( link.at("a").inner_html )
                link=link.next_sibling
                tmp[:tags]=link.at("span").search("/a").to_a.map{|n|
                    CGI::escapeHTML(n.inner_html)
                }
                result &lt;&lt; tmp
            }
        }
        feed=&lt;&lt;FEED
&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;rss version="2.0"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xml:lang="ja"&gt;
    &lt;channel&gt;
        &lt;title&gt;thatsping::#{tag}&lt;/title&gt;
        &lt;link&gt;#{url}&lt;/link&gt;
FEED

        result.each{|v|
            feed+="
            &lt;item&gt;
                &lt;guid isPermalink=\"true\"&gt;#{v[:link]}&lt;/guid&gt;
                &lt;title&gt;#{v[:title]}&lt;/title&gt;"

            v[:tags].each{|t|
                feed+=" &lt;category&gt;#{t}&lt;/category&gt; "
            }
            feed+="&lt;/item&gt;"
        }
        feed+="&lt;/channel&gt;&lt;/rss&gt;"
    end
end

やっぱりなんかスマートじゃないなあ。

]]>
Safari3.1 for windowsを試した http://tt25.org/blog/20080324/safari3 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 第一印象

軽い。往年のOpera6くらい軽い。それでいてGmailやGoogleマップも動くので実用面での不備はほぼないと思われる。

インストールの罠

Apple製品は油断するとQuickTimeとかBonjourとかQuickTimeとかQuickTimeを入れられるのでインストールから気が抜けない。案の定Bonjourをインストールするか聞かれたので速攻で拒否しておいた。Bonjourってなんだろう。

インストール完了後にそのまま起動したら、現在Safariは既定のブラウザではないが既定にするか? といつもの質問をされた。「いいえ」を押そうとして右下にカーソルを動かしたが右下は「はい」だったので危うく押すところだった。Macの常識とはいえWindows下においてはこれも罠だ。

拡張性の壁

AutoPagerizeがないとブラウジングする気が7割減なのでSafari用User Script制御アプリのGreaseKit (FirefoxでいうGreasemonkey)を入れようとしたら、ダウンロードできるものがdmgしかなかった。dmgが何なのかよくわからないけどMac向けの何かであることはわかる。「greasekit windows」でググると失敗した旨を告げるtwitter が出てきたので素直に諦めた。

bookmarklet版 はMnibufferへの入力がなぜか大文字になるのと、LDRizeで立てたピンを開くとタブじゃなくウィンドウで開いた以外は動いたけど、うーん。

あと個人的な問題点として、マウスジェスチャができない(別アプリを入れてSafariと関連付けて設定したらできるらしい)、タブが縦に置けない&多段表示できない、検索エンジン追加がわからん、などなど。

キーボードショートカット

Ctrl+Fで検索、Ctrl+Tで新規タブ、Ctrl+E / Ctrl+Kで検索窓にフォーカス、Ctrl+Lでアドレスバーにフォーカス、Ctrl+Wでタブ閉じるのは確認した。だいたい互換性は考えてあるっぽい。

アンチエイリアス

4段階の強度があって、デフォルトだと2番目に強い「中」なんだけど、3番目の「弱」ないし4番目の「標準」がいちばん読みやすいと思う。「中」だとたまに文字がつぶれる。

Safari3 アンチエイリアス「標準」MS Pゴシック

Safari3 アンチエイリアス「中」MS Pゴシック(デフォルト)

なんかこうやって見ると「中」と「標準」の違いがわかりにくいな。実際に変更するとわりとはっきり違うので動画してみたけど、あんまり変わらないかも。VNC越しだしなあ。

いちおう以下参考。

XP IE6

Linux Opera(xfce/IPA P ゴシック)

Macは持ってないので知らないけど、Linuxと同等かそれ以上にきれいだったはず。

あとアンチエイリアスのせいか配色ミスかは知らないけど、ツールバーの文字が死ぬほど見にくい。

総括

IEユーザーに薦めるならFirefoxよりSafari3のほうが向いてるかと思います。

]]>
PHPUnitを駆使しつつこのブログにコメント機能つけてみた http://tt25.org/blog/20080320/phpunit 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 インストール

PHPUnitのドキュメント を参考にまずはインストール。バージョンは3.2。

# pear channel-discover pear.phpunit.de
# sudo pear install phpunit/PHPUnit
# phpunit -v
PHPUnit 3.2.15 by Sebastian Bergmann.

これの一番上にあるサンプルコード で動作テスト。

# vim ArrayTest.php
# phpunit ArrayTest

ちゃんと動くのを確認できたら完了。

最初、ググって出てきたv2.3のドキュメント見ながらやってたらエラー出まくって大変だった。

つかってみる

まずCommentsクラスとCommentsTestを作る。

# cat Comments.php
&lt;?php
class Comments {
}
# cat CommentsTest.php
&lt;?php
require_once "PHPUnit/Framework.php";

class CommentsTest extends PHPUnit_Framework_TestCase {
}

とりあえず思いつくままCommentsのほうにメソッドを定義していく。

# cat Comments.php
&lt;?php
class Comments {
  function __construct($permalink){
    $this-&gt;permalink=$permalink; // コメント先記事のIDみたいなもの
    $log=$this-&gt;permalink.".txt";
    if(!is_file($log)){
      touch($log);
    }
  }
  function push($comment){
  }
  function remove($commentId){
  }
  function find(){
  }
}

次にとりあえずfindメソッドのテストを書いてみる。

どのテストにせよCommentsオブジェクトが必要なので、最初に作って使いまわしたりしたいところだ。そういうときはsetUpメソッドの中でやるといいらしい。

# cat CommentsTest.php
&lt;?php
require_once "PHPUnit/Framework.php";

class CommentsTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    require_once "Comments.php";
    $this-&gt;comments=new Comments("dummy/dummy");
  }
  function testFind(){
    $this-&gt;assertEquals($this-&gt;comments-&gt;find(),array());
  }
}

ここでPHPUnitを実行してみる。Comments#findは何もしてない(空配列返してない)ので、テストに失敗するはず。

# phpunit CommentsTest.php
PHPUnit 3.2.15 by Sebastian Bergmann.

F

Time: 0 seconds

There was 1 failure:

1) testFind(CommentsTest)
Failed asserting that
Array
(
)
 matches expected value &lt;null&gt;.
/path/to/CommentsTest.php:12

とりあえずComments#findで空配列を返すようにしてみる。

# cat Comments.php
&lt;?php
// changelog
// 1. Comments#find()が空配列をreturn
class Comments {
  function __construct($permalink){
    $this-&gt;permalink=$permalink; // コメント先記事のIDみたいなもの
    $log=$this-&gt;permalink.".txt";
    if(!is_file($log)){
      touch($log);
    }
  }

  function push($comment){
  }

  function remove($commentId){
  }

  function find(){
    return array();
  }
}

もっかいテスト。これは上手くいくはず。

# phpunit CommentsTest.php
PHPUnit 3.2.15 by Sebastian Bergmann.

.

Time: 0 seconds

OK (1 test)

さて。Comments#pushを書く前に保存形式やら項目やらを考えないといけない。とりあえず名前と本文の2つを持つことにしよう。形式はCSVでいいか。

# cat Comments.php
&lt;?php
// changelog
// 1. Comments#find()が空配列をreturn
// 2. コンストラクタで$this-&gt;logに$log
//    Comments#push()を実装
//    Comments#find()をちゃんと実装

class Comments {
  function __construct($permalink){
    $this-&gt;permalink=$permalink; // コメント先記事のIDみたいなもの
    $log=$this-&gt;permalink.".txt";
    if(!is_file($log)){
      touch($log);
    }
    $this-&gt;log=$log;
  }

  function push($comment){
    $fp=fopen($this-&gt;log,"a");
    fputcsv($fp,$comment);
    fclose($fp);
  }

  function remove($commentId){
  }

  function find(){
    $result=array();
    $fp=fopen($this-&gt;log,"r");
    while($post=fgetcsv($fp)){
      $result[]=$post;
    }
    return $result;
  }
}

卒倒しそうなほど適当だけど、まあこんなもんか。続いてテストを書く。ていうかこの順番だとテストファーストじゃないような気がする。まあいいか。

# cat CommentsTest.php
&lt;?php
require_once "PHPUnit/Framework.php";

class CommentsTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    require_once "Comments.php";
    $this-&gt;comments=new Comments("dummydummy");
  }

  function testFind(){
    $this-&gt;assertEquals($this-&gt;comments-&gt;find(),array());
  }

  function testPush(){
    $arr=array("name"=&gt;"なまえ","body"=&gt;"どうもどうも");
    $this-&gt;comments-&gt;push($arr);
    $this-&gt;assertEquals($arr,$this-&gt;comments-&gt;find());
  }
}
There was 1 failure:

1) testPush(CommentsTest)
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ -1,5 +1,9 @@
 Array
 (
-    [name] =&gt; なまえ
-    [body] =&gt; どうもどうも
+    [0] =&gt; Array
+        (
+            [0] =&gt; なまえ
+            [1] =&gt; どうもどうも
+        )
+
 )

ああなるほど。返り値が二次元配列になって一致しないのはテストのしかたが悪いからともかくとして、添字が失われて数値になるのは困るな。じゃあ、

# cat Comments.php
&lt;?php
// changelog
// 1. Comments#find()が空配列をreturn
// 2. コンストラクタで$this-&gt;logに$log
//    Comments#push()を実装
//    Comments#find()をちゃんと実装
// 3. $this-&gt;colsを作ってfind()でarray_combine()するようにした。
//    Comments#push()で、ちゃんとname,bodyの順に保存されるように保証。

class Comments {
  function __construct($permalink){
    $this-&gt;permalink=$permalink; // コメント先記事のIDみたいなもの
    $log=$this-&gt;permalink.".txt";
    if(!is_file($log)){
      touch($log);
    }
    $this-&gt;log=$log;
    $this-&gt;cols=array("name","body");
  }

  function push($comment){
    $data=array($comment["name"],$comment["body"]);
    $fp=fopen($this-&gt;log,"a");
    fputcsv($fp,$data);
    fclose($fp);
  }

  function remove($commentId){
  }

  function find(){
    $result=array();
    $fp=fopen($this-&gt;log,"r");
    while($post=fgetcsv($fp)){
      $result[]=array_combine($this-&gt;cols,$post);
    }
    return $result;
  }
}

ついでにテストのほうもまずかったので直す。

&lt;?php
require_once "PHPUnit/Framework.php";

class CommentsTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    require_once "Comments.php";
    $this-&gt;comments=new Comments("dummydummy");
  }

  function testFind(){
    $this-&gt;assertEquals($this-&gt;comments-&gt;find(),array());
  }

  function testPush(){
    $arr=array("name"=&gt;"なまえ","body"=&gt;"どうもどうも");
    $this-&gt;comments-&gt;push($arr);
    $this-&gt;assertEquals($arr,array_pop($this-&gt;comments-&gt;find()));
  }
}

で、テスト。

1) testFind(CommentsTest)
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ -1,9 +1,3 @@
 Array
 (
-    [0] =&gt; Array
-        (
-            [name] =&gt; なまえ
-            [body] =&gt; どうもどうも
-        )
-
 )

ん? なぜ今ごろtestFindが。とりあえずもっかい実行してみる。

1) testFind(CommentsTest)
Failed asserting that two arrays are equal.
--- Expected
+++ Actual
@@ -1,15 +1,3 @@
 Array
 (
-    [0] =&gt; Array
-        (
-            [name] =&gt; なまえ
-            [body] =&gt; どうもどうも
-        )
-
-    [1] =&gt; Array
-        (
-            [name] =&gt; なまえ
-            [body] =&gt; どうもどうも
-        )
-
 )

あー。テストのたびにログが蓄積されていくから、あの適当なtestFind()のほうが通らなくなってるのか。じゃあログを全消去するComments#clearでも作るか。

function clear(){
  unlink($this-&gt;log);
  touch($this-&gt;log);
}

で、各テストの最初に呼ぶようにする。ついでにtestClearを追加。

&lt;?php
require_once "PHPUnit/Framework.php";

class CommentsTest extends PHPUnit_Framework_TestCase {
  function setUp(){
    require_once "Comments.php";
    $this-&gt;comments=new Comments("dummydummy");
  }

  function testClear(){
    $this-&gt;comments-&gt;clear();
    $this-&gt;assertEquals(filesize($this-&gt;comments-&gt;log),0);
  }

  function testFind(){
    $this-&gt;comments-&gt;clear();
    $this-&gt;assertEquals($this-&gt;comments-&gt;find(),array());
  }

  function testPush(){
    $this-&gt;comments-&gt;clear();
    $arr=array("name"=&gt;"なまえ","body"=&gt;"どうもどうも");
    $this-&gt;comments-&gt;push($arr);
    $this-&gt;assertEquals($arr,array_pop($this-&gt;comments-&gt;find()));
  }
}

ようやくそれっぽい流れになってきた。次はbody(本文)がないのをpushされたときの処理をテストしよう。変なのが来たらComments#pushはreturn falseするようにして、テストはassertEquals($this->comments->push($invalidArray),false)かな。

  function push($comment){
      if(empty($comment["body"])){
        return false;
      }
    $data=array($comment["name"],$comment["body"]);
    $fp=fopen($this-&gt;log,"a");
    fputcsv($fp,$data);
    fclose($fp);
  }
  function testInvalidPush(){
    $this-&gt;assertEquals($this-&gt;comments-&gt;push(array()),false);
  }
Time: 0 seconds

OK (4 tests)

余裕余裕。こんな感じでやっていけばいいのか。しかし絶対順番が逆だ。まずはテストを書くべきだな。

というような流れで

コメント機能が付きました。

スパム対策が適当だけど、突破されたらまたそのとき考えることにする。

]]>
gtk-recordmydesktopでスクリーンキャストを保存した http://tt25.org/blog/20080320/gtk-recordmydesktop 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Windows時代はWink 使ってたけど、「linux スクリーンキャスト」あたりで調べてみたらいくつかあった。名前がわかりやすいrecordmydesktopのgtk版を使うことにする。

インストール

sudo apt-get install gtk-recordmydesktop

実際にはSynapticから入れたけど、それにしてもAPTは偉大だなあ。setup.exeとかsetup.msiとかがいかに面倒かよくわかる。

使う

適当に範囲指定したあと「録音」ボタンを押す。終わったら停止。

ていうかなんで「録音」なんだ。せめて「録画」じゃないのか。他にも訳が怪しいところが多々あるけど、使用には問題ないからどうでもいいか。

使ってみた

特に思いつかなかったのでAutoPagerizeの動作を録画してみた。音は要らないのでキャプチャしないようにチェックボックスを外す。

動画の保存形式が.ogv(ogg video)なので、なんとなくYouTubeアップ前にflvに変換してみた。ついでにリサイズも。

ffmpeg -s 320x240 -i autopagerize.ogv -f flv autopagerize.flv

なぜか画面全体が薄い黄緑? かなんかで着色されてる。昔のモニタみたいだ。まあいいか。

何回か練習してまともなのが撮れたので、YouTubeのアカウント取ってアップロードした。だいたい5分くらいで見れるようになった。

ちょうど1分なのはたまたま。

]]>
アジャイルプラクティス読んだ http://tt25.org/blog/20080318/agile-practice 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 SL200.jpg” alt=”” />

アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣

タイトルにプログラマとか開発者とか入ってるけど、別にプログラミングに限らない気がするなあ。あとアジャイルにも限定されないと思う。

プロダクティビティ向上のために、アンチパターンや注意すべき点や成功体験などなどがいっぱい書いてあるのでこれは啓蒙書になるんだと思う。知識として持ってても、やらなきゃ無価値。やらない理由探しを抑止するため、最初に悪魔の囁きがエピグラフっぽく提示してある。

例えば「わかるまで質問する」っていうセクションだとこんな感じ。

言われたとおりにしてりゃいいんだ。「ここが問題なんだ」と言われたら、そこだけ見てればいい。幻影を追い求めても時間の無駄だ。

そしてこの行動がいかに間違っているかを説明し、最後に正しい道を歩んでいるときの「こんな気分」という短いセンテンスでまとめる。

まるで宝石を採掘しているかのような気分だ。無関係の物質をふるい落としながら深く深く掘り下げていった末、ついに光り輝く石を見つける。症状だけでなく、真の原因も突き止めていることを実感している。

蛇足的に「バランスが肝心」と題して、いくら正しくても盲目的にそのとおり振る舞っていては意味がないので意識的に取り組もうみたいなことが書いてあって1セクション終わり。

最初に書いたようにこれは啓蒙書なので、意固地な人とかが読んでも綺麗事を羅列したただのポエムにしか見えない。意識の高い人が読んで、さらにそれを実践してこそ良書たりうる。僕はそれほど意識が高くないので、悪魔の囁きが間違ってる理由の理解と、「こんな気分」の心境を妄想して済ませまくった。

あとちょっとしたエピソードとして、Rubyのインタプリタについて言及されてたのが面白かった。RubyインタプリタというのはRubyで書かれたスクリプトを解析してCだかマシン語だかに変換するプログラムです。

ロジックが適切な命名とともに整然と記述されていれば、ほとんどコメントはいらない。実際、アンディとデイヴ・トーマスが最初にRubyの書籍を執筆したときは、Rubyインタプリタのコードを読むだけで言語全体のドキュメントを書けた。これは、Rubyの生みの親(まつもとゆきひろ)がコメントに頼らず、そのコードで雄弁に物語ってくれていたおかげだ。なにせアンディとデイヴは二人とも「スキヤキ」と「サケ」以外の日本語なんて知らないんだから。

こういう実例もいろいろ散りばめられてる。主題の説得力が増強されつつ読み物として面白いのでやっぱり良書だなあ。まあ何はなくとも実践ですが、とりあえずテストから始めようかなと思った。

各ブラウザでのレンダリングも自動でテストできたらいいのに。やるとしたらスクリーンショット撮って画像の同一性をチェックするか、JavaScriptで各ノードのoffsetTopやらoffsetHeightをチェックするとかかなあ。

]]>
会社帰りに何を買おうとしてたのか忘れたのでRedMineに頼ることにした http://tt25.org/blog/20080317/redmine-todonote 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 ローカルでいい とか言ってたけど、やっぱり外から見れると便利そうだと思ったので外から見れるようにした。

マシンは2台あって、このブログをホストしてるサーバと、RedMineがましますデスクトップ。RedMine用のサブドメインをVirtualHostして、普通にmod_proxyでhttp://desktop:3000/へプロクシることにする。

設定

sudo a2enmod proxyでproxy有効に。VirtualHostをだいたいこんな感じで書く。

&lt;VirtualHost *&gt;
  ServerName redmine.tt25.org
  CustomLog /var/log/apache2/r.log common
  RewriteEngine on
  RewriteRule ^/(.*)$ http://desktop:3000/$1 [P,QSA]
&lt;/VirtualHost&gt;

デスクトップからredmine.tt25.orgを見にいくと待望のエラー。500でも404でもなく403ってことはどっかでDeny from allしてるはず。RewriteRuleの[P]を外すと(リダイレクトになるものの)エラーは出ない。

いろいろAllow from allしてみるけど事態が一向に改善しないので、error.logのメッセージ「client denied by server configuration: proxy:」でググると即座に解決した。&lt;Proxy *&gt;&lt;/Proxy&gt;の存在を忘れてた。

そういやDebianはDocumentRoot以外の全ディレクトリもデフォルトでDeny from allされてたっけな。httpd.confに書いてもいいけどまた忘れそうだったので、/etc/apache2/mods-enabled/proxy.confのほうにAllow from allを追記してforce-reloadして見にいくといけた。

htdigest

アクセス制限しなくてもいい気がしたけどやっとかないといけない気がしたのでダイジェスト認証することにした。sudo a2enmod auth_digestで有効に。せっかくいろいろ削って軽量化したApacheがどんどんごてごてしていくなあ。

# htdigest -c /path/to/redmine/.htdigest "redmine" tt
Adding password for tt in realm redmine.
New password:
Re-type new password:

で.htdigestをredmineディレクトリに生成。次にApache側の設定。

&lt;VirtualHost *&gt;
  ServerName redmine.tt25.org
  # CustomLog /var/log/apache2/r.log common
  RewriteEngine on
  RewriteRule ^/(.*)$ http://desktop:3000/$1 [P,QSA]
  &lt;Proxy *&gt;
    AuthType Digest
    AuthName "redmine"
    AuthUserFile /path/to/redmine/.htdigest
    Require valid-user
  &lt;/Proxy&gt;
&lt;/VirtualHost&gt;

最初&lt;Proxy&gt;じゃなくて&lt;Directory&gt;に書いてしまってまったく機能しなかったけど、さっき思い出したばかりの&lt;Proxy&gt;にしてみたら期待通り動いた。ついでにもう用済みっぽい感じがするCustomLogもコメントアウトしておく。

完成

できた。 そうか朱肉を買おうと思ってたんだった、というのが退社前にばっちりわかるようになりました。あと面倒だったので「雑務」とか「feature」とかのトラッカーは既存のものを流用した。だいたい意味は通じるから大丈夫だろう。

]]>
AoE3はじめました http://tt25.org/blog/20080316/aoe3 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00

Civ4やりすぎて疲れてきたのでたまには違うゲーム。AoE3はたぶんまだ延べ20時間くらいしかやってないけど、そろそろCiv4の癖は抜けてAoE的な、あるいはRTS的なプレイに慣れてきた。

食料はいつも草に生えてる苺みたいなのを採る→粉ひき所建造→粉ひき所強化って順番で確保してて、家畜を飼ったり、カピバラを射殺してナイフでくっちゃくっちゃしたりは(近くに苺がない限り)しないんですが、カピバラ10匹で4000食料にもなるんだったらもうちょい活用したほうがいいのかなあと思った。

あと粉ひき所強化もそうだけど、採取促進系のアップグレードの効果がいまいち体感できてない。アップグレードより入植者増やした方が効率いいんだろうか。苺つむのと粉ひくのと動物狩るのとだとスピードが違うのかどうかもわからん。

]]>
Civilization4 http://tt25.org/blog/20080316/civilization4 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 SL200.jpg” alt=”” />

シヴィライゼーション4 デラックスパック

僕がカピバラって言いたいがためにこれ をでっち挙げた直後、『世界樹の迷宮II』日記 を読んで我が身を恥じたのでもうちょいまともな何かを書いてみようかと思いました。

Civ4は想像ですが無印200時間、WL50時間、BtS100時間ほどやってると思います。学生時間で言えば不眠不休で冬休み丸ごと潰したようなものです。想像なので何とでも言えます。

ごく初期はごく単純な「ゲームで世界史追うのって面白え」という白い衝動に突き動かされていましたが、そういった刺激に慣れてくるといよいよゲーム内ルールに意識が準拠していくことになります。斧兵とかライフル兵とかはただのアナロジーである、と開眼してからがゲームの始まりです。

世襲制のために宗教ルート研究するのめんどいからある程度は資源や建物で幸福を確保しようとか、速攻で音楽研究して芸術家に文化ボムさせないと自国が立地的にやばいなとか、未プレイヤーからしたら意味不明な繰り言が頭の中を占領しはじめます。これはCiv4に限らずゲーム全般の醍醐味といえますが、戦略ゲームであるCiv4の真骨頂もまたここにあります。

労働者をつくるか戦士をつくるかはたまたピラミッドを建造すべきかというミクロな判断の積み重ねがゲーム終了に向けて蓄積されていくのもゲームですが、いかにして自国を強化し他国を引きずり下ろすかという単純な問いに対して数多の戦略を立て、実践し、修正し、対応していくサイクルもまたゲームです。目先の判断と大局的な判断が同居し、時に対立したりする状況下で事態を適切に処理していかなければなりません。ゲームも人生も、人生ゲームのようにルーレットを回してコマを動かすだけの簡単なお仕事ではないのです。

毎ターン大小の問題が配給されてくるなかで意識が朦朧としてきて曖昧な判断を下し自国首都が陥落したり、自分の描いた計略どおりに他国を滅亡させたものの同盟国だと思っていた隣国が宣戦布告してきて大量に都市を奪われたり、宇宙開発勝利を捨てて文化勝利狙いに切り替えたらあっさり勝てたり、といった失敗や成功の体験をもとに徐々に上達していく感覚を何度も味わってこそゲームを最大限に楽しめるのだと思います。

カジュアルゲームを否定する気はありませんが、たまには取り憑かれたプレイもいいんじゃないでしょうか。ときどき無駄なことに時間をつぎ込まないと感受性が摩耗していってなんか怖いです。

]]>
Linux→Windows→VMWare Linux→TeraStationでsshfs http://tt25.org/blog/20080314/ssh-ssh-ssh 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 自宅からsshで実家Linuxにつなぎ、そこからさらに実家LAN内のTeraStation(ファイル置き場)へSambaでつないでmp3を再生したりしています。

 ★-----sshfs(~/tera &lt;- /foo/bar)----◇----smbmount(/foo/bar &lt;- \\landisk\disk1)----◇
自宅                               実家Linux                                    TeraStation

↑実家Linuxが実家TeraStationをsmbmountで/foo/barへマウントし、自宅の~/teraへsshfsを使って実家Linuxの/foo/barをさらにマウントしてる。最終的に、自宅の~/teraの中に実家TeraStationの中身がマウントされます。

どう見ても実家Linuxを中継する意味がわかりかねますが、TeraStationがSSHを持ってないから仕方ないのです。

sshfsとは関係ないですが、他にもたまに実家WindowsへsshポートフォワーディングしてVNCしたりもしています。

 ★-------------ssh(1234 &lt;- 5900)-------------◇
自宅                                      実家Windows

 ★----------VNC(localhost::1234)--------------◇
自宅                                       実家Windows

ところが昨日から実家Linux機が死んでしまっていて、自宅—TeraStation間の接続経路がなくなってしまいました。実家Windowsを中継させようにも、「ネットワークドライブの割り当て」は期待通り動かず、実家Windows(のcygwin)からTeraStationをマウントする方法がわからなくて諦めてた。

そこで登場するのがVMWare。実家Windows上でVMWareを起動し、VMWare上のLinuxで実家TeraStationをマウントすることにしました。

 ★--------ssh(1234 &lt;- 2222)----------◇--------ssh(2222 &lt;- 22)---------◆
自宅                              実家Windows                          VMWare

 ★-----sshfs(localhost:1234)-----◇-----ssh-----◆----smbmount------◇
自宅                         実家Windows        VMWare           TeraStation

相当むちゃくちゃだと思いますが、これで一応実家TeraStation上のmp3を自宅で再生できています。今週末には実家帰ろう。

]]>
タブを100個オーダーで大量に開くとかそういうの http://tt25.org/blog/20080314/software-evolution 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00

だいたいタブを40ほど開いて2,3日ほったらかしていると徐々に重くなってくるのが解せない。Operaはそんな弱い子じゃないはずだ。

って書いたら 「そんな使い方してるやつ居ねえ」と言われたので調べた。調べたっていうか、前に似たような使い方をいろいろ見た気がするので発掘した、ってほうが正しい。あとタブ40個で2,3日、というのは僕の感覚からすると桁が足りない相当控えめな数値であることをここに補足しておきます。

とりあえずいくつか見つけたものをリンクしとく。 http://mala.tumblr.com/post/2995353 とか http://d.hatena.ne.jp/Sybian/20070829/p1 とか http://www.flickr.com/photos/yuiseki/2136919308/ とか http://kuruman.org/diary/2007/05/01/opera-originarity#usage-light とか。全部Operaがらみなのがあれですけど、傍証としては充分かなと。

僕が初期(0.9.xごろ)のFirefoxにそれほど興味を持たなかったのはOpera/Sleipnirほどの軽快さがなかったからで、機能には憧れたものの実用には耐えなかった。UIがXULというマークアップ言語で書かれてる仕様が永遠にボトルネックになりそうって感じたから、拡張機能はチェックするけどメインに据える気はまったくなかった。今もあんまり気乗りしないのはひとえに重さのせい。重いというのはあらゆるアプリケーションにとって絶対的かつ致命的に悪だ。Netscape 4.xをかたくなに使い続ける人を支持する気はないけど、気持ちはわからんでもない。

タブブラウザやMDIエディタが出たてのころは、リソースの無駄とか片付けながら使えとかいう批判がけっこうあった。今やタブがないブラウザなんてないし、メジャーなエディタでいってもTerapadくらい? コンピュータの処理能力向上によってデメリットが無視できるほどに小さくなったとき、IEユーザー以外はみんなタブを必須要綱として挙げはじめた。他の似たような批判に、ウェブページに画像を載せるなら可能な限り減色しろとか、fontタグの分重くなるからチャットで発言に色を付けるな、なんてのもあった。要するに、過渡的なリソース上の制約を楯にUIを批判するのは的外れっていう確認。

次に使い方、というかスタイルというか。タブブラウザの常識化に伴ってブラウジングが劇的に変わった。気になるページはとりあえず新規タブで開いておいて後で読む、LDR/fastladderでピン立ててoで一気に10個開く、というのもタブブラウザありきだ。IE6ユーザーから見たら奇人にしか見えないと思う。けど、IE6なんて捨てた人からしたら、IE6ユーザーのほうこそ蛮族に見えるはず。

別に僕が最先端とかいうつもりはまったくなくてですね、キーボードのテンキーを切断 とか(ノコギリで切断したまんま回路丸出しで使ってる、もっとインパクトある画像を見た覚えがあるけど見つからなかった)、真っ黒なデスクトップ とか、デイトレーダーのマルチディスプレイ環境なんかも、極端な例だけど、要するに、使い方は環境と用途次第でいくらでも変わるし、違う環境下の人の違う使い方は奇異に見えるって話。

あれ、最初は何を言いたかったんだっけか。ええととにかくですね、タブ40個でひいひいいうようなブラウザにブラウザを名乗る資格はないとかそういうことを言いたかったはずです。タブ3桁を余裕で開けるようになったら、次は目当てのタブを簡単に探すための方法を模索しないといけないし、やることはいっぱいあると思う。Firefoxは現時点だとまだタブ40個すらまともにマネジメントできないのでもっとがんばれ。Operaもがんばれ。Webkitもがんばれ。IEは潔く死ね。

]]>
人間臨終図鑑読んだ http://tt25.org/blog/20080313/many-people-died 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 SL200.jpg” alt=”” />

人間臨終図巻〈1〉

人間がどういうふうに死んでいったかを集めた本。図鑑といいつつ図はないのでグロ描写が気になる向きにも安心。1人の人生はだいたい1〜4ページくらいにまとめられてるのですらすら読めるし、適当なところからぱらぱら読める。

解説や品評といった手垢は一切なくただ事実を書いてるだけなので、想像や思考の余地が最大限残されてる。人生について本気出して考えたり、諸行無常の響きを聞いたり、美学を伴った死に様に感銘したりできる。(語弊のある表現だけど)誤読の自由が保証されてるのは、国語の授業的読書に縛られた人の意識を解放すると思う。

ごく私的な願望をいえば、この本は享年の若い順に並べられてるけど、時期/享年/場所が完全にランダムなほうが良かったなあ。

]]>
Amazonのウィッシュリストを公開するよ http://tt25.org/blog/20080312/amazon-wishlist-from-2005 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 密かな趣味が全公開—Amazonのウィッシュリスト、改め「ほしい物リスト」に注意?:CNET Japan Staff BLOGCNET Japan

J-CASTニュース : アマゾン「自分がほしい物リスト」 全部公開されて大騒動

今はもう見れないみたい。つまらん。自分のウィッシュリストの設定を見たら「特定の人に公開する(「友だちに知らせる」でEメールを送信した人にのみ公開されます)」になってた。

自分のウィッシュリストのスクリーンショット

ウィッシュリストは使うどころか存在を忘れてた。とりあえず全部カゴに入れておいて、買うときに「いまは買わない」をチェックするといった使い方をしてるので6個しかなかった。しかもそのうち5個は日付が同じ。

3年前のデータをちゃんと持ってるAmazonはえらいなあ。いや普通か。いい機会なのでちょっと振り返ってみよう。

ロクメンダイス、

SL200.jpg” alt=”” />

なんでラノベなのかわからなかったけど思い出した。ライトノベル4大奇書 でドグラマグラに位置付けられてたからだ。他に理由が思い当たらない。未読and未購入。いま買おうと思ったら在庫切れだった。

Θは遊んでくれたよ

SL200.jpg” alt=”” />

たしか実家にあったしもう読んだ。くらげ君が現代的な理系メガネだったのは覚えてるので、その筋の人は読んで悶えるといいと思います。その筋の本ではなくて、ジャンルでいえばミステリです。

なんでウィッシュリストに入ってるのかは不明。

教科書には載らないニッポンのインターネットの歴史教科書

SL200.jpg” alt=”” />

買おうかどうか迷ってたはず。2005年5月といえばたぶん無職だったので、1000円以上する本を買うのは思い切りが必要だった。

Wayback Machineのストック を読んだりなんやかやしてて忘れてたみたい。いま買ってもいいかなあ。

あとWayback Machineにあるページが(2003年なのに)XML宣言つきXHTML 1.1で書かれてたのが印象的だった。W3C主義者だからじゃなくて、たぶんこの文書を永続化しようとした帰結だなきっとそうに違いないうんそう絶対そう、とか当時思った。実際そういう意気込みが感じられる。

PLUTO (2)

SL200.jpg” alt=”” />

今は浦沢直樹ってだけでなんとなく購買意欲がなくなる。2巻か3巻までは読んだ気がする。ロボットが死んだり殺されたりしてたのは覚えてる。

阿修羅ガール

SL200.jpg” alt=”” />

実家近くで買って読んだ。

舞城王太郎のキャッチコピーに「圧倒的文圧」ってのがありまして、その表現の妥当性はともかく特徴的な文体なのはたしかです。一文がやたら長いけど勢いで読める、登場人物が福井弁、ちょくちょく2chみたいなのが出てくる、擬音が独特(赤ちゃんの泣き声がフギャーイッヒとか)などなどが共通してる感じ。

阿修羅ガールはどんな内容だっけか。忘れた。

監督不行届

SL200.jpg” alt=”” />

庵野秀明がいかに私生活においてもオタクで安野モヨコもオタクかという日記。今でいうととなりの801ちゃん になるのかなあ。

]]>
MoreCSSが気持ち悪すぎる http://tt25.org/blog/20080312/less-javascript 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 MoreCSS は名前と文法こそCSSだけど全然違う。CSSの文法を採用したただのDSLだ。MoreCSSのまったく潰しが効かない文法覚えるくらいなら素直にJavaScriptを勉強すべき。

ソース読んだけど、だいたい以下のような流れで処理してる。

  1. MoreCSS用に書かれたファイルをAjaxで取得
  2. style作って(media=”screen”固定)、その中に豪快にAjaxで取ってきた中身をぶち込む
  3. 出来たstyleをheadに挿入する
  4. Ajaxで取ってきた中身を”}”で分割して正規表現で1個ずつ解析し、既定の処理を実行

2.のそのままというのは、未知のプロパティは無視すべきっていうCSSの仕様を上手く使っててなるほどとは思った。将来どうなるかとかブラウザのバグとか考えると微妙なところだけど、発想は好きだ。

あとええとグローバル汚染しすぎとかいろいろあるけど、際立ってたちが悪いのがバリデーション。適当すぎて有害。

function validate(element, styles, pseudoClass) {
    var pattern = getStyleAttribute(styles, "validate", "");
    var noticeText = getStyleAttribute(styles, "validate-notice-text", "");
    var noticeClass = getStyleAttribute(styles, "validate-notice-class", "");
    var noticeExecute = getStyleAttribute(styles, "validate-notice-execute", "");

    var submitForm = true;
    var formElement = getParentNodeByTagName(element, "form");
    formElement.onsubmit = function() { executeValidation(); return false; };

    function executeValidation() {
        switch(pattern) {
            case "not-empty" : pattern = "/.+/"; break;
            case "alphabetic" : pattern = "/^[a-z]+$/i"; break;
            case "numeric" : pattern = "/^[0-9]+$/"; break;
            case "alphabetic-numeric" : pattern = "/^[0-9a-z]+$/i"; break;
            case "filename" : pattern = "/^[0-9a-züäö_\.\-]+$/i"; break;
            case "email" : pattern = "/^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i"; break;
            case "url" : pattern = "/^http:[a-z._%-%/]+$/i"; break;
        }

        eval('pattern = new RegExp(' + pattern + ');');

        if(trim(element.value).match(pattern)) formElement.submit(); else {
            if(noticeText != "") alert(noticeText);
            if(noticeClass != "") element.className = element.className + " " + noticeClass;
            if(noticeExecute != "") eval(noticeExecute);
        }
    }
    elementReady(element);
}

メールの/^[a-z0-9._%-]+@[a-z0-9.-]+\.[a-z]{2,4}$/はまだお茶目な序の口。

URL/^http:[a-z._%-%/]+$/iはもうftpやhttpsを通す気すらないし、ファイル名の/^[0-9a-züäö_\.\-]+$/iとかどんだけ悪夢だ。英語圏に限っていってもスペースが含まれてるだけでアウト。そもそもファイル名のバリデーションって何の意味があるんだ。

ついでにいうと、そこはswitchじゃなくハッシュ使え。evalの必要ないし、evalなんかするくらいなら最初からRegExpオブジェクト返せ。addEvent定義してるのになんでonsubmitに関数入れてるんだ。var submitForm = true;の意味がまったくない。executeValidationとかわざわざ定義せずonsubmitにインラインで渡せ。getParentNodeByTagNameをそこでしか使わないならインラインで書いたほうがちょっと速い。最初の引数patternがnot-emptyとかのどれにもヒットしない変な値のときの挙動が未定義。バリデーション通ったときわざわざformElement.submit()とか書かずにinvalidなときにイベントキャンセルしろ。

といったことを盛り込むとこうなった。動かしてないからバグってる可能性高。あといろいろ言ったけどこれでもまだ全然ダメな気がする。

function validate(element, styles, pseudoClass) {
    var pattern = getStyleAttribute(styles, "validate", "");
    var noticeText = getStyleAttribute(styles, "validate-notice-text", "");
    var noticeClass = getStyleAttribute(styles, "validate-notice-class", "");
    var noticeExecute = getStyleAttribute(styles, "validate-notice-execute", "");

    var formElement=(function(node){
        return (!node.parentNode) ?
            node // 元の挙動どおりだけどこんなんでいいのか? nullを返すか例外投げるべきだと思う。
            : node.parentNode.nodeName.toUpperCase() == "FORM") ?
                node.parentNode
                : arguments.callee(node.parentNode);
    })(element);

    // これもformだと決め付けてるけどformじゃない可能性があるのでどうかと思う。
    addEvent(formElement,"submit",function(e){
        var e=e || window.event;
        var tmp={
            "not-empty": /^./
            ,"alphabetic": /^[a-z]+$/i
            ,"numeric": /^[0-9]+$/i
            ,"alphabetic-numeric": /^[a-z0-9]+$/i
            ,"filename": // 意図が不明なので省略
            ,"email": // メアドバリデーションの必要性を感じないので省略。
            // ↑強いて書くなら/^[^@]+@[^@]+\.[a-z]{2,}$/くらい大雑把にしないと冤罪が発生して困ると思う。

            ,"url": // ググればいくらでもありそうだけど面倒なので省略
        }
        var pattern=tmp[pattern] || throw new Exception("invalid validate label("+pattern+")");
        if(!trim(element.value).match(pattern)){
            if(noticeClass) element.className+=" "+noticeClass; // addClassとかはないのか。別にいいけど。
            if(noticeExecute) eval(noticeExecute);
            if(noticeText) alert(noticeText);

            // 細かいけど、こういう保険がないとなんか不安。
            if(noticeClass || noticeExecute || noticeText || false === false) alert("form error!");

            // IE判定とかしてきちんとしたいところだけど面倒なので手抜き。
            try{
                e.preventDefault();
            }catch(ex){
                e.returnValue=false;
                e.cancelBubble=true;//うろおぼえ
            }
        }
    });
    elementReady(element);
}

で、ですね

どっかから拾ってきたスクリプトというのは常にこういう問題が潜んでるわけです。ある日、httpsのリンクを張ろうとしてエラー出てどうしようもなくなったり、IE7じゃ動かなかったけどもうスクリプトの更新止まってるしどうしようとか慌てたり、前任者から引き継いだもののMoreCSSなんて知らないので途方に暮れたりするわけで、盲目的にJavaScriptを使うのはリスキーな行為だと認識しないといけないと思います。

CSSの中にいろいろ書けて便利、というのはたしかにそうでしょうけど、リスクに見合うだけのものが得られるかどうかを見極めないといけないと思います。MoreCSSなんてマイナーなもの、知ってる人のほうが少ないわけで、何か困ってもトラブルシューティングはまったく期待できません。自力で解決できなかった場合、もし問題が起きたら以後ずっと起きっぱなしになるかMoreCSSの除去作業が発生するわけで、きれいなツールチップとかちょっとした効率化に見合うとはとても思えません。これはMoreCSSに限ったことではなくて、JavaScript全般に対していえることです。

冒頭にも書いたように、こんなの勉強するくらいならJavaScript自体を勉強したほうがはるかに有意義だと思います。

]]>
Firefoxをブラウジングに使う http://tt25.org/blog/20080311/firefox-for-browsing 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 最近はもっぱらFirebug+Live HTTP Headers目当てで使ってるので普通に使うために整備するのは久しぶりだ。2年ぶりくらい?

アドオン

とりあえずInfoLister の結果。わりと普通だと思う。

*** Extensions (enabled: 12, disabled: 2; total: 14)
Adblock Plus 0.7.5.3
All-in-One Gestures 0.18.0
DOM Inspector 1.8.1.12
Firebug 1.05
Greasemonkey 0.7.20080121.0
InfoLister 0.9f.2
Live HTTP Headers 0.13.1
PrefBar 4.0.0
Relaxed the HTML Validator 0.9.2
Speed Dial 0.7.0.8
Tab Mix Plus 0.3.6
Talkback 2.0.0.12
Tree Style Tab 0.6.2008030904 [disabled]
Vimperator 0.5.3 [disabled]

*** Themes (15)
ColorGnome
Firefox (default)
FormalGnome Jumbo
GNOME
Halloween
iFox Graphite
iFox Metal
iFox Smooth
Industrial
Orbit Grey Custom
Phoenity Aura
Qute
rein [selected]
Tango
Unofficial Tango

*** Plugins
Default Plugin

テーマがやたら入ってるのはいろいろ試した名残です。

Mouse Gestures じゃなくてAll-in-one Gestureなのは、右クリック+ホイールコロコロでタブを切り替えたかったから。

HTML Validator じゃなくて Relaxed the HTML Validator なのは、Linuxに対応してないっていわれたから。

Tree Style Tab はちょっと試してみたかっただけ。Vimperatorも同様。もうちょいFirefoxに慣れてきたら使う。

あとLastTab 入れようとしたら、Tab Mix Plusに統合されてるけどいいのか? ってきかれた。いつのまに。

userChrome.css

タブを縦置きにしたいのでこのuserChrome.css をコピペ。

タブバーは左じゃなく右に表示させたいので、Tab Mix Plusオプションで表示>タブバー>タブバーの位置を「下部」に。

about:config

  • browser.cache.disk.parent_directoryを作って値を/tmp/firefoxに
    • うちの/tmpはtmpfsなのでHDDより速い
  • network.dns.disableIPv6をtrueに
    • IPv6の可能性を無視
  • network.http.pipeliningをtrueに
    • pipeliningを試す。不具合ありそうならあとでfalseに戻す
  • mousewheel.withnokey.sysnumlinesをfalseに
  • mousewheel.withnokey.numlinesを8に
    • デフォルトのスクロール量が小さいので大きくする。とりあえず8で様子見。

Greasemonkey

とりあえず御三家のみ入れた。

  • AutoPagerize
  • Minibuffer
  • LDRize

この3つはOperaにも 移植されてる ので、Firefoxの強みを生かすならもっといろいろ入れないとなあ。

最終形

こうなった。

この段階ならまだOperaでも再現できるなあ。もっと”Firefox”を使わないと。

]]>
今さらながらウマウマ見たりした http://tt25.org/blog/20080310/umauma 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 数ヶ月ほど動画サイト見てなかったので、もう完全に乗り遅れてるわけであんまり気乗りしなかったけど、暇だったので見た。たぶんニコニコ動画から持ってきたんだろうけど、関連動画が大量に上がってる。

並み居る動画の中で

が一番よかった。Ultimate Utopia とかもそうだけど、外人は動画というと仲間が集まってショートムービー撮ることを指すみたい。楽しそう。

出てるのはフランス人か。道理でコスプレが堂に入ってるわけだ(特に根拠なし)。メインの人が女装か女の子か判然としないけど、いっぱいコスプレしてるので良いと思います。女装もコスプレの一種なので問題ないです。コスプレなんとかの店はコスプレに興味ない人が行くところです。

いや単純によくできてると思う。これに5以外のレーティングする人の意味がわからない。劇中でなんかのアニメ(のMAD的なもの)がオマージュで出てくるけど、ニコニコ動画には今後も蠱毒の壷として活躍し、生成されたよくわからないものを広めていってほしい。

]]>
LinuxにしてOperaの戦闘力が下がった http://tt25.org/blog/20080310/linux-desktop-opera 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Windows上ではOperaを調べもの用、LDR用、開発などその他用と3つのウィンドウに分けてタブがそれぞれ50近く開いてる生活をしてた。メモリもたしか常に400〜600MBくらいは食ってたし、数週間ほど立ち上げっぱなしはザラ。端的にいって酷使だと思うけど、それでもOperaは過労死することなくよく働いてくれた。

同じ生活をLinuxでもやろうとしたものの、どうもWin版に及ばない。NVIDIA公式ドライバを入れて、キャッシュファイルをtmpfs上に作らせ、GNOMEからXfceに乗り換えたら、操作の快適さこそWindows Operaに近づいたものの安定性は遠く及ばない。最新安定版の9.24も、最新不安定版のOpera 9.50 weekly buildも、Windows Opera 9.24ほど安定してない。特にFlash開いたときと、長時間起動しっぱなしのときによく死ぬ。だいたいタブを40ほど開いて2,3日ほったらかしていると徐々に重くなってくるのが解せない。Operaはそんな弱い子じゃないはずだ。

逆にFirefoxの戦闘力は見違えて上がった。せっかくなのでVimperator入れたり、Minibuffer+LDRize入れたりして変態的ブラウジングを束の間楽しんだ。この変態さにどっぷり浸かると旧来のブラウジングが遊びに思えるのかな。

タブ切り替えとかスクロールはOperaのほうが快適だけど、操作で見ればFirefox(+Vimperator)の圧勝かなあ。しばらくOperaと普通のFirefoxを併用してみることにする。

]]>
Amazonを自動でチェックして新刊情報を自分に配信したい。Rubyで。その2 http://tt25.org/blog/20080309/aws-check2 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 前回 の懸案事項であったメールとERBが両方とも解決した。Amazonから情報取ってきてHTMLメール送るまでは出来た。

メール

外部にメールを送れなかったのは、外部にメールを送らないようMTAを設定していたからでした。これ以上ないほど単純明快な理由。

sudo dpkg-reconfigure exim4-configで、最初に出てきた設定のインターネットなんとかを選択して解決。たぶんこれが「ローカルのみ」になってたんだと思う。

ついでにメールの送信方法

Rubyで日本語メールを送信 を参考に引数とかをちょっと改造した。TMailはgem化されてるのでsudo gem install tmailで入れる。

require "rubygems"
require "tmail"
require "net/smtp"
require "kconv"

class MyTMail
    def MyTMail.send (mail,host,mime="text/plain")
        m=TMail::Mail.new
        m.to=mail[:to]
        m.from=mail[:from]
        tmp=Kconv.tojis(mail[:subject]).split(//,1).pack("m").chomp
        m.subject="=?ISO-2022-JP?B?"+tmp.gsub("\n","")+"?="
        m.body=Kconv.tojis(mail[:body]||"")
        m.date=Time.now
        m.mime_version="1.0"
        mime=mime.split("/")
        m.set_content_type mime.shift,mime.shift,{"charset"=&gt;"ISO-2022-JP"}

        m.write_back
        Net::SMTP.start(host||"localhost"){|smtp|
            smtp.sendmail(m.encoded,m.from,m.to)
        }
    end
end

#MyTMail.send({
#    :to=&gt;"mymail@example.com",
#    :subject=&gt;"aaてstてst",
#    :from=&gt;"foo@localhost",
#    :body=&gt; &lt;&lt;BODY
#てすと
#てすとp
#BODY
#},nil,nil)

最初TMailなのにTmailと書いてて1時間くらいハマったのはひみつ。だってエラーメッセージがuninitialized constant MyTMail::Tmail (NameError) とか`send’: {:subject=>”test”,:to=>”foo@example.com”} is not a symbol (TypeError)なんて言うからー、どっかでスコープが狂ってるのかなー、引数おかしかったっけなー、なんかしたっけなーってググってたからー。

わざわざクラスにしてるのは、最近僕がグローバル空間に関数定義するのイヤイヤ病を患ってるからです。MyTMailが定義されるんだから同じことなのにね。

しかしそれにしてもRubyぽくない書き方だ。せめてdef TMail::Mail.send_ja mail,server=”localhost”,mime=”text/plain”じゃないのか。出来るか知らないけど雰囲気的に。

あとmy.*はサンプルコードでのみ許されるものであって実際に使うなバーカって誰かが言ってた気がする。全然ダメだ。

ERB

グローバルな領域にbindingって変数があるみたい。p bindingで確認したらBindingオブジェクトとのこと。この説明 じゃbindingはメタ変数というかfooとかhogeの類だと思うじゃないですか。違うんですよ。

というわけで、

require "erb"

erb=ERB.new &lt;&lt;TEMPLATE
&lt;p&gt;&lt;%= i[:num] %&gt;&lt;/p&gt;
TEMPLATE

dummy=[{:num=&gt;1},{:num=&gt;2}]
dummy.each{|i|
  erb.run(binding)
}

でめでたく目的を達成できましたとさ。

ちなみに、erb.runはputs erb.resultと同じようなもんみたい。puts erb.runすると末尾に毎回nilが出るのが不思議だったけどそういうことか。

新たにハマったところ

DateとTimeと、時々、DateTime

DateとDateTimeはrequire “date”しないと使えない。

require "date"
puts DateTime.now.strftime('%Y-%m-%d %H:%M:%S') #=&gt; 2008-03-10 00:12:23

strftimeは万国共通のはずなのでそんなに迷うこともなかった。問題はnowをnewだと2分くらい思い込んでたことだ。

次回予告

Amazon::ItemSearchをリファクタリングしようとした僕は、いつしかXMLツリーが生い茂るREXMLのジャングルへと迷い込む。XPathの道標はあらぬ方向を指し示し、もはやGoogleの光も届かない。疲弊と苛立ちの極限状況下で垣間見た異形のスクリプトとは? この日、Amazon河流域で僕はRubyを発見する。

まだ何もしてないけどだいたいそうなるはずなので乞うご期待。

]]>
Textile Referenceにobake.gifなんてのがある http://tt25.org/blog/20080309/obake-in-textile-reference 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 Textile Reference のImage Alignmentsって項目にあるobake.gif 。ghost.gifならまだしもなぜobake.gifなのか。知ってる範囲では「obake」が英語圏に輸出されたって話は聞いたことがない。ざっと思いついた理由は以下の3つ。

  1. 製作者が日本人ないし日本かぶれ
  2. Googleイメージから適当に拾ってきた
  3. その他

1.の可能性はおそらくない。トップページ をざっと見たけど、なんというか、実に外人だ。Ruby=Made in Japan && ゴーストって日本語でなんだ?=obake!とかまったく考えそうにない。

2.の可能性を潰すためにobake.gifでイメージ検索してみる。162件全部見たけど一致するものはなし。念のためWeb検索で「obake filetype:gif」「inurl:obake.gif」で検索してみるけどヒットなし。ていうかTextile Referenceに実在してるんだから理想的にはヒットしてしかるべきなのにヒットなし。まあいいや。もし見つかったら次はどんなクエリで検索したのか考えないといけなかったけど、その必要もなかったようでひとあんしん。

あやふやな消去法によって、可能性は3.のその他に託された。

とりあえずobakeでググってみると英語版Wikipedia が見つかった。あれ英語化されてたのか? と思って中を読んでみても、単に「日本のモンスターや霊魂のことだよ」「だいたい本性は動物だよ」って説明してるだけで、一部で流行ってる雰囲気すらない。だいたいobake.gifはどう見てもお化けっていうよりゴーストっぽい。戯画化が進んでるところは日本ぽいけど、ドット絵だしなあ。

obakeでググっても日本語サイトばっかり引っかかるので、URLに&hl=enを付けて英語版Googleを見ることにした。すると日本語版には居なかった怖い感じの人が出てきた。 さっきイメージ検索したときはこんなの見なかった。そのまま画像検索結果へ飛ぶとabout 13,000 for obake。なんで日本語版の100倍もヒットするのか意味不明ですけどまあいいや。Googleのイメージ検索のショボさがまたひとつ浮き彫りになっただけだ。

なんで丑三つ時にお化け画像を延々と見てるのか疑問に思いながら次へ次へ。たまごっち の画像がちょっと似てると思ったけど、比べてみたら そんなに似てない。けど、たしかに元のobake.gifはたまごっちっぽいタッチだなと思った。

貞子が2回も出てきたけどそれはお化けじゃないだろうと思いながら次へ次へ。お化けじゃないならなんだろう。妖怪でもないし怨念とかでもないし。間違いない答えは超能力者だろうけど、あんまり適切な呼称でもないよなあ。じゃあ、とか考えてたら8ページ目でついに発見。 サイト下方にある画像はファイル名もばっちりobake.gifで、ファイルサイズやフレーム数も一致する。経緯は知らないけどここから持ってきた画像なのは間違いなさそう。

ちなみにこのサイト はたまごっち墓地ってサイトで、自分のたまごっちが死んだらここに投稿するらしい。書き込んでるのはたぶん子供だろうけど、けっこう悲しんでるみたいなのはなんとなくわかった。I loved you so much Daniel!!!とか、実際に墓の前で叫んでたらもらい泣きする人多数だろうな。

というわけで、他にも「このサイトの作者はなぜobake.gifなんて付けたのか」「Textileの人はどういう経緯でこのサイトに来たのか」「タイトルのcemeteryの綴りが間違ってたりトップの画像やURLだとGraveyardだったりするのはなぜか」「貞子はカテゴリ的にどこに属するのか」「なんで丑三つ時に英語のお悔やみの言葉を読みふけってるのか」などなど謎は尽きない。

]]>
世界のナベアツ in JavaScript http://tt25.org/blog/20080308/nabeatzz 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 そろそろ FizzBuzz に飽きた – にぽたん研究所 はやっぱりJavaScriptのほうがいろいろできて面白そうだったのでやってみた。

期待する結果:

実行結果

世界のNabeAtzz

]]>
Apacheのログ解析用にVisitors入れた http://tt25.org/blog/20080306/visitors 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00

秒速10万行でログを解析してくれるらしい。インストールはapt-get install visitorsしただけ。

visitors -A /var/log/apache2/access_log > result.htmlで上手くいくのを確認したので、シェルスクリプト書いてcronに入れておいた。

#!/bin/sh

file="/path/to/save/`date +'%Y%m%d'`.html"
cmd="visitors -A /var/log/apache2/access_log &gt; "$file
eval $cmd
chown nobody $file

たぶん普通はワンライナーというかcrontabに直接書くんだと思うけど初心者なので仕方ない。eval $cmdもなんか不穏な気配がする。そのうち出来るようになろう。

あと、このブログは管理画面とかないので探しても無駄です。IE5が血迷ったかのように比率高いのは、YahooFeedSeekerがMSIE 5.5を自称してるせいです。

]]>
Amazonから発売日情報を取ってきてメール送ったりしたい。Rubyで。 http://tt25.org/blog/20080306/amazon-checker 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00

yotubato:
    Title : "よつばと!"
    filter: "comic"

sigurui:
    Title: "シグルイ"
    filter: "comic"

こんな感じで検索対象をYAMLで書いてRubyに渡してAmazon Web Serviceから情報取ってくるまでは出来た。問題は、

  1. Rubyからメールが送れない(RubyていうかどうもSMTP的な理由ぽい)
  2. 発売日が月までしかなくて日がわからんことがよくある
    • 今日発売のやつとか、明日発売みたいなことをやるのに支障がある
    • ソート問題
  3. 条件をあんまり厳密にすると取りこぼしが出てくるし、ゆるいと本以外がひっかかる

といったところ。実用というよりRubyの練習なのでPlaggerとかは使いません。

とりあえず腑に落ちてないところを列挙してみる。

ERBがわからん

require "erb"
erb=ERB.new &lt;&lt;ERB
title: &lt;%= title %&gt; &lt;- error
title: &lt;%= item["title"] %&gt; &lt;- error
ERB

items=[{"title"=&gt;"foo"},{"title"=&gt;"bar"}]
items.each{|item|
  puts erb.run(item) # error
}

とかやると、itemを起点にしてtitle: fooが出てくると思ってたけどどうも違う。ERB#runの引数bindingの意味がいまいちわからない。

Moduleの中のClassがいまいちわかってない

Module Amazon
  BASE_URI ="http://ecs.amazonaws.jp/onca/xml?"

  Class ItemSearch
    def meth
      puts BASE_URI # error
      puts Amazon::BASE_URI # ok
      puts self::BASE_URI # error(selfはItemSearchで、Amazonを継承してるわけでもないので未定義)
      puts super::BASE_URI # error(superがない)

      include Amazon
      puts BASE_URI # ok
    end
  end

end

Moduleは抽象クラス兼ネームスペースって考えといたらいいのかな。

obj.classの返り値はStringじゃなくてClass

hash={:foo=&gt;"bar",:hoge=&gt;"fuga"}

case obj.class
  when Symbol.class, String.class
    hash[obj.to_sym] || {}
  when Hash.class
    obj
end

的なことをしたときにハマった。

hash={:foo=&gt;"bar",:hoge=&gt;"fuga"}

case obj.class.to_s
  when "Symbol", "String"
    hash[obj.to_sym] || {}
  when "Hash"
    obj
end

みたいにしたけど、明らかにダサいのでなんかやり方がありそう。

行頭に.とか,があるとエラー

ng={
  "foo"=&gt;"foo"
  ,"bar"=&gt;"bar"  # ここでエラー
}
ok={
  "foo"=&gt;"foo",
  "bar"=&gt;"bar",
}

Klass.new
  .method # ここでエラー
  .method
  finish

# これだといける
Klass.new.
  method.
  method.
  finish

行末にあると視界に入らなくて不安になるので行頭に書く癖がある。手癖だけの問題ならまだしも、不安を伴ってるのでなかなか矯正できなくて困る。

]]>
飛び道具の歴史読んだ http://tt25.org/blog/20080305/throw-fire 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 SL200.jpg” alt=”” />

飛び道具の人類史—火を投げるサルが宇宙を飛ぶまで

私たちは爆発する飛翔体の魅力にとりつかれている。

らしい。僕を含めた一部の人が、ドラクエで好きな呪文を訊かれてメラゾーマを挙げる理由がここに解明された。ルーラとかアバカムとか言わずただただ相手を焼き尽くすロマンチスト。見方によってはサディストとも言う。

主に軍事的な観点から投擲能力と放火能力の進歩を追っていく内容。殴る蹴る噛むなどの近接攻撃ではなく、離れたところから石を投げたり弓を撃ったりするのは動物的に前代未聞の攻撃方法だった。その優位を生かしてアフリカの外にも進出していった結果、中ボス的な大きいサイズの動物が世界のいろんなところで同時多発的に絶滅し、人類はその弱そうな体躯と裏腹に自然界を支配していく。

中ボスをあらかた片付けるとラスボスたる人類との戦いが始まる。石をより効果的に投げるための道具を考案したアステカ文明が圧倒的な軍事力を誇ったり、城壁を破壊するために巨大な投石機で岩をガンガン投げたり、動物の肉を火で焼くとおいしいことを発見したりして、そのうち道教の人が火薬を発見し、火力を使って投擲する武器であるところの火槍が生まれ、強化されて大砲になり、マスケット銃だのライフル銃だのがヨーロッパあたりで配備され、新大陸に居た先住民が銃よりも強力かつ正確な投石の能力を持っていて驚いたり、ナチスが爆撃機や戦闘機を空に飛ばしながらミサイル研究に血道をあげつつも崩壊したのち、最強兵器の水爆が完成し、人類は月を歩いて人工衛星を飛ばす。

物を投げて、離れたところに時間差で影響を及ぼすってところを著者が3回くらい強調してる。この能力(というか知覚)がなければアクションとリアクションは常にほぼ同時に起こるしかないわけで、「投資」とか「投げる」の字がついた概念は絶対に理解できないからなんとかかんとか。いわれてみれば、要望を投げるとか、視線を投げるとか、そういう非同期な要素を含んだ語彙は案外多い。

他にも、人類の普遍的な娯楽として花火を挙げている。あれは火薬の詰まった玉を火力で上空に「投げ」、「爆発させる」という一連の流れがエンターテインメントたり得るからだとかなんとか。

というような「投げる」「燃やす」という視点で歴史をつづっている本です。あとはこの本が投げ捨てられて焚書なればささやかなオチがついていいんじゃないかなと思う次第。

]]>
デビルマン読んだ http://tt25.org/blog/20080304/デビルマン 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 SS100.jpg” alt=”” />

デビルマン 全5巻セット

デビルカッターは岩くだく、とかサビで延々と必殺技が紹介されるあのデビルマンをマンガで読んだ。読後、例の歌が能天気過ぎて撲殺したくなるほどマンガ版は重厚な内容だった。

第1話こそ学園ラブコメみたいに主人公と幼なじみがイチャイチャしてるものの、悪魔の存在が早々に披露され、主人公が悪魔と合体しデビルマンになってからは一転。

悪魔側の偉いのが相当な策士で、人類全体を国家レベルで混乱させ疑心暗鬼にさせる。普通に悪魔vs人類で戦争するよりは人類の自滅を狙ったほうが効率いいから。悪役がちゃんと賢いのは高ポイント。

結果、完全にバグった人類はといえば、自衛隊による魔女狩りが始まっておばちゃんが拷問器具で惨殺されたり、民間でも近所の人たちがたいまつ片手に家を襲撃し幼稚園児の首を包丁でぶった切って生首もってニヤニヤしたり、女子高生の四肢を引きちぎって棒に刺して宴会したりする。殺されたり踊ったりしてる人々はみんな、第1話では普通に生活してた人たちばっかり。人民の人民による人民のための地獄絵図。

主人公のデビルマンは悪魔たちと戦うため、中途半端に悪魔と合体して困ってる民間人の半デビルマンを集めて組織化するけど、上述のように人類がキチガイすぎるのを次々と目撃するにつれ、戦うべきは悪魔じゃなくて人類とか言い出して、いろいろあって、最終決戦ののちにマンガは終わる。

普通の人がキチガイ化するとこはなんとなく漂流教室を思い出したけど、あっちは閉じた世界での出来事なのに対して、デビルマンはごくオープンな日常生活での一コマ。のび太が恐竜と戦ってもなんとでもなりそうだけど、サザエさんのイササカ先生がアルツハイマー発症してボケたら、それはもうサスペンスだよな、みたいな。

たぶん子供が読んだらパニクりながら号泣して頼むから今晩一緒に寝てくれって訴える。なので、たとえ一夜でも子供と添い寝したい人にはオススメの5冊。自分で書いといてなんだけど、この濃度でたった5冊てのがいまいちピンと来ない。

]]>
RedMineをthin/mongrel/webrickでそれぞれ動かしてabしてみた http://tt25.org/blog/20080303/thin-mongrel-webrick 2008-09-09T21:41:04+09:00 2008-09-09T21:41:04+09:00 thinていうMongrel代替みたいな軽量サーバがあるらしいのでどれくらい軽量なのか軽く試してみた。mod_proxy_balancer+クラスターを使わずab直撃、かつ裏でmp3再生しながらOperaでタブ70個開いたりしてるので厳密なベンチマークではないことにご留意を。

コマンド

# ruby -v
ruby 1.8.6 (2007-09-24 patchlevel 111) [i486-linux]

# /usr/sbin/ab -c 25 -n 500 http://localhost:3000/
This is ApacheBench, Version 2.0.40-dev &lt;$Revision: 1.146 $&gt; apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/
...

thin

# thin --version
thin 0.7.0 codename Spherical Cow
# thin start -d -e production
Server Software:        thin
Server Hostname:        localhost
Server Port:            3000

Document Path:          /
Document Length:        3183 bytes

Concurrency Level:      25
Time taken for tests:   8.948900 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Total transferred:      1753000 bytes
HTML transferred:       1591500 bytes
Requests per second:    55.87 [#/sec] (mean)
Time per request:       447.445 [ms] (mean)
Time per request:       17.898 [ms] (mean, across all concurrent requests)
Transfer rate:          191.20 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:   305  443 150.0    423    1292
Waiting:      192  303 125.8    286    1080
Total:        305  443 150.1    423    1292

Percentage of the requests served within a certain time (ms)
  50%    423
  66%    439
  75%    453
  80%    478
  90%    517
  95%    742
  98%   1079
  99%   1292
 100%   1292 (longest request)

mongrel

# mongrel_rails start --version
Version 1.1.4
# mongrel_rails start -d -e production
Server Software:        Mongrel
Server Hostname:        localhost
Server Port:            3000

Document Path:          /
Document Length:        3183 bytes

Concurrency Level:      25
Time taken for tests:   14.808434 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Total transferred:      1769500 bytes
HTML transferred:       1591500 bytes
Requests per second:    33.76 [#/sec] (mean)
Time per request:       740.422 [ms] (mean)
Time per request:       29.617 [ms] (mean, across all concurrent requests)
Transfer rate:          116.69 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       1
Processing:   358  728 666.2    483    3413
Waiting:      358  728 666.2    483    3413
Total:        358  728 666.3    483    3413

Percentage of the requests served within a certain time (ms)
  50%    483
  66%    501
  75%    556
  80%    581
  90%   1580
  95%   2704
  98%   3241
  99%   3333
 100%   3413 (longest request)

webrick

# ruby script/server webrick -e production -d
Server Software:        WEBrick/1.3.1
Server Hostname:        localhost
Server Port:            3000

Document Path:          /
Document Length:        3183 bytes

Concurrency Level:      25
Time taken for tests:   19.188079 seconds
Complete requests:      500
Failed requests:        0
Write errors:           0
Total transferred:      1774000 bytes
HTML transferred:       1591500 bytes
Requests per second:    26.06 [#/sec] (mean)
Time per request:       959.404 [ms] (mean)
Time per request:       38.376 [ms] (mean, across all concurrent requests)
Transfer rate:          90.26 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.1      0       2
Processing:   120  926 584.8    656    3060
Waiting:      118  923 585.1    652    3053
Total:        120  926 584.8    656    3060

Percentage of the requests served within a certain time (ms)
  50%    656
  66%    716
  75%    754
  80%   1517
  90%   2064
  95%   2196
  98%   2288
  99%   2324
 100%   3060 (longest request)

結論

僕が普段使ってる環境で動かすならMongrelよりthinのほうがお得。WEBrickは遅いけど、数ヶ月前に見た噂によると勉強には適したソースコードだとか。

ていうかそういう意図なら-c 25である意味はまったくなかったなあ。まあいいか。

]]>
RedMineをSQLite3/Mongrelで動かした http://tt25.org/blog/20080301/redmine 2008-09-09T21:41:03+09:00 2008-09-09T21:41:03+09:00 なぜsqliteかというと、redmineディレクトリをまるごとrsyncするだけでバックアップできるから。

インストール

当然ながらrubyとかsubversionとかrubygemsは予めインストールしておかないといけない。既に入ってたので省略。

# sudo gem install mongrel sqlite3-ruby RedCloth -y

# svn co http://redmine.rubyforge.org/svn/branches/0.6-stable redmine-0.6

# cd redmine-0.6

必要ファイルは揃った。他にもrakeとか必要かも知れないけど省略。しかし手軽すぎる。

設定

続いて設定。config/database.yml.exampleをconfig/database.ymlにコピーして、そいつを編集する。SQLite3なのでユーザーとかはてきとう。

production:
  adapter: sqlite3
  database: redmine_data.sqlite3.db
  host: localhost
  username: root
  password:

んでmigrate。DB作ったりとか、要するにイニシャライズ。

redmine-0.6# rake db:migrate RAILS_ENV=production
(中略)
-- create_table("custom_fields_projects", {:id=&gt;false, :force=&gt;true})
   -&gt; 0.1168s
(中略)

サーバ起動

redmine-0.6# mongrel_rails start -e production
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with production environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM =&gt; stop.  USR2 =&gt; restart.  INT =&gt; stop (no restart).
** Rails signals registered.  HUP =&gt; reload (without restart).  It might not work well.
** Mongrel 1.1.3 available at 0.0.0.0:3000
** Use CTRL-C to stop.

http://localhost:3000/を見るとちゃんとRedMineのトップページが表示されたのでCtrl+Cでいったん終了。改めてmongrel_rails start -e production -dしてデーモンにする。

ローカルで使うだけなので、Apache2.2+mod_proxyとかの設定はしない。

所要時間

約15分。rake migrateが通らなくて困った。rake db:migrateが正しいみたい。

]]>
ActiveRecode+SQLite3 http://tt25.org/blog/20080228/aws 2008-09-09T21:41:03+09:00 2008-09-09T21:41:03+09:00 sudo gem install rails sudo apt-get install sqlite3 sudo gem install sqlite3-ruby

sqlite3がどうのこうのとエラー。

sudo apt-get install sqlite3-dev sqlite3-ruby
sudo gem install sqlite3-ruby

入った。

require "rubygems"
require "active_record"

ActiveRecord::Base.establish_connection(
    :adapter=&gt;"sqlite3",:dbfile=&gt;"amazon.db",:database=&gt;"amazon.db"
)

InitialSchema &lt; ActiveRecord {
    def self.up
        create_table :items{|t|
            t.column :url,:string
            t.column :asin,:string
            t.column :title,:string
        }
    end
}
InitialSchema.migrate(:up)

エラー。

# cat s.sql
CREATE TABLE items (
  id INTEGER PRIMARY KEY NOT NULL
  ,title VARCHAR NOT NULL
  ,asin VARCHAR NOT NULL -- CHARの桁数数えるのめんどかった
  ,url VARCHAR NOT NULL
);

# sqlite3 -init s.sql amazon.db
sqlite&gt; [Ctrl+D]

#

ここまで来るのに2時間くらい掛かって嫌になってやめた。

]]>
できた http://tt25.org/blog/20080228/initial 2008-09-09T21:41:03+09:00 2008-09-09T21:41:03+09:00 ブログサービス使ってもよかったけど、サーバの勉強も兼ねて自鯖で。

サーバはML115 にメモリ3GB積んで、Debian etch AMD64版いれてる。

]]>
Amazonから届いた http://tt25.org/blog/20080228/amazon 2008-09-09T21:41:03+09:00 2008-09-09T21:41:03+09:00 amazon

なぜか同じ本が2冊入ってた。

]]>