tokyo rubykaigi 01 t-wada
DESCRIPTION
TRANSCRIPT
それRubyでもやりたい
ライブラリ移植と機能拡張のためのパターンランゲージ
和田 卓人 (a.k.a id:t-wada)
Aug, 21, 2008 @TokyoRubyKaigi 01
多様性
RegionalRubyKaigiに光あれ
続け!
自己紹介名前: 和田 卓人 (わだ たくと)
メール: [email protected]
ブログ: http://d.hatena.ne.jp/t-wada
Twitter: t_wada
Wassr: twada
自己紹介タワーズ・クエスト株式会社 プログラマ 兼 取締役社長
これまで書いたものWEB + DB PRESSvol.35 「実演! テスト駆動開発」vol.37 「実演! リファクタリング」vol.42 「現場で使えるREST」LifeHacks PRESSオープンソースマガジン(リレーコラム)他いろいろ
gihyoコラボ企画『[動画で解説]和田卓人の“テスト駆動開発”講座』http://gihyo.jp/dev/serial/01/tdd/全20回すべて動画付き解説ニコニコ動画でも見れます
WEB+DB過去記事の特設サイトや動画
デブサミ• デベロッパーテスティング・ライブ - 自信を持ってコードを書
くための心・技・体 -
• 【徹底討論】テストなんていらない?!-テストを、どこまでやるべきか?
• そしてデブサミ 2009 へ
• テストトラックのコンテンツ委員になりました
よろしくおねがいします
Agenda
•移植した中身のこと•移植する方法のこと•Q & A
第一部移植した中身のこと
背景
始めに問題意識ありき•複雑な テーブル構造•既にレールに乗れない•多くのテーブルを JOIN しなければならない要件
餅は餅屋
•CASE句•UNION ALL•RDBMS固有の関数
SQL書きたい
S2Dao•80:20•2WaySQL•手書きのSQL をサポート•SQL を知っていれば書ける
2 Way SQL
代入コメント(1)
SELECT *FROM empWHERE job = /*ctx[:job]*/'CLERK'AND deptno = /*ctx[:deptno]*/20
外部SQL
SELECT *FROM empWHERE job = /*ctx[:job]*/'CLERK'AND deptno = /*ctx[:deptno]*/20
からくり
SELECT *FROM empWHERE job = /*ctx[:job]*/'CLERK'AND deptno = /*ctx[:deptno]*/20
Given
ctx[:job] = 'MANAGER'ctx[:deptno] = 30
When
SELECT *FROM empWHERE job = ?AND deptno = ?
Then
と、 ['MANAGER', 30]
代入コメント(2)
SELECT *FROM empWHERE job = /*ctx[:job]*/'CLERK'AND id IN /*ctx[:ids]*/(10,11)
Given
ctx[:job] = 'MANAGER'ctx[:ids] = [30,40,50]
When
SELECT *FROM empWHERE job = ?AND ids IN (?, ?, ?)
Then [ 'MANAGER', 30, 40, 50 ]
IF コメント
SELECT * FROM empWHERE job = /*ctx[:job]*/'CLERK' /*IF ctx[:age]*/AND age > /*ctx[:age]*/20/*END*/
Given
ctx[:job] = 'MANAGER'ctx[:age] = 30
When
SELECT * FROM empWHERE job = ? AND age > ?
Then
と、 ['MANAGER', 30]
SELECT * FROM empWHERE job = /*ctx[:job]*/'CLERK' /*IF ctx[:age]*/AND age > /*ctx[:age]*/20/*END*/
Given
ctx[:job] = 'MANAGER'ctx[:age] = nil
When
SELECT * FROM empWHERE job = /*ctx[:job]*/'CLERK' /*IF ctx[:age]*/AND age > /*ctx[:age]*/20/*END*/
Then
SELECT * FROM empWHERE job = ?
Then
と、 ['MANAGER']
BEGINコメント
SELECT * FROM emp/*BEGIN*/WHERE /*IF ctx[:job]*/ job = /*ctx[:job]*/'CLERK'/*END*/ /*IF ctx[:age]*/AND age > /*ctx[:age]*/25/*END*//*END*/
Given
ctx[:job] = nilctx[:age] = 35
When
SELECT * FROM emp/*BEGIN*/WHERE /*IF ctx[:job]*/ job = /*ctx[:job]*/'CLERK'/*END*/ /*IF ctx[:age]*/AND age > /*ctx[:age]*/25/*END*//*END*/
Then
SELECT * FROM empWHERE age > ?
Then
と、 [35]
SELECT * FROM emp/*BEGIN*/WHERE /*IF ctx[:job]*/ job = /*ctx[:job]*/'CLERK'/*END*/ /*IF ctx[:age]*/AND age > /*ctx[:age]*/25/*END*//*END*/
Given
ctx[:job] = nilctx[:age] = nil
When
SELECT * FROM emp/*BEGIN*/WHERE /*IF ctx[:job]*/ job = /*ctx[:job]*/'CLERK'/*END*/ /*IF ctx[:age]*/AND age > /*ctx[:age]*/25/*END*//*END*/
Then
SELECT * FROM emp
Then
と、 [ ]
第一部完
第二部移植する方法のこと
検討
本当に移植すべきか?•定番ライブラリではダメか•代替案はないのか•早急に結論を出す前に、定番を調べるべし
全部移植すべきか?
•対象ライブラリの美点は何か•既にあるライブラリでも実現できることは何か
移植
最初に確認すること
•対象コードのテストはあるか•対象機能(の美点)は切り出し可能か
テストが無い場合
• 残念ながら、レガシーコードです
• 本日は詳細は割愛
• WEwLC 読書会を行っています
• http://groups.google.co.jp/group/legacy-code
大原則:一度に複数を相手にしない
動作する、きれいなコードへ
きれい
汚い
(すぐには)動かない 動作する
二つの道がある
逐語訳的移植
Context 言語間で移植しようとしている
Force ともかくまず動くところまで持っていきたい
Solution 設計や名前を変えずに、まず言語間の移植のみに集中する
逐語訳的テスト移植
Context テストコードを移植しようとしている
Force 安全に移植したいが、テストのテストは存在しない
Solution テストの設計や語彙を変えずに、まず言語間の移植に集中
変えないもの•xUnit という共通語彙•設計•クラスやメソッドの名前•クラス構造 (interface除く)
変えたもの•言語間の違い•静的型付けから動的型付けへ•予約語•xUnit の方言
Java コードpublic void testNext() throws Exception { String sql = "SELECT * FROM emp"; SqlTokenizer tokenizer = new SqlTokenizerImpl(sql); assertEquals("1", SqlTokenizer.SQL, tokenizer.next()); assertEquals("2", sql, tokenizer.getToken()); assertEquals("3", SqlTokenizer.EOF, tokenizer.next()); assertEquals("4", null, tokenizer.getToken());}
Ruby コードdef testNext sql = "SELECT * FROM emp" tokenizer = SqlTokenizer.new(sql) assert_equal(SqlTokenizer::SQL, tokenizer.go_next(), "1") assert_equal(sql, tokenizer.getToken(), "2") assert_equal(SqlTokenizer::EOF, tokenizer.go_next(), "3") assert_nil(tokenizer.getToken(), "4")end
PTSCTCPWPort The Simplest Case That Could Possibly Work
Context 逐語訳的テスト移植を終えた
Force グリーンが見たいが、レッドになるテストが多すぎる
Solution 最も単純なテストケースから実装し、それ以外は pending
Context 移行元コードに不明点がある
Force 不明点をおいたまま先に進めたくない
Solution 移行元コードの現在の振る舞いを自動テストに落とし込む
Characterization Test
洗練
TDDのサイクル1. テストを書き2. そのテストを実行して失敗させ(Red)3. 目的のコードを書き4. 1で書いたテストを成功させ(Green)5. テストが通るままでリファクタリングを行う(Refactor)
6. 1~5を繰り返す
TDDとコード
きれい
汚い
(すぐには)動かない 動作する
Red
Green
Refactoring
TDDと黄金の回転
きれい
汚い
(すぐには)動かない 動作する
Red
Green
Refactoring
実装のテストから仕様のテストへ
Context 移植の最初のステップが終わったので、内部を改善したい
Force 実装内部を思う存分変更したい
Solution ホワイトボックステストをブラックボックステストに変換
例えば、RSpec を使ってみる
郷に入っては郷に従え
Context 逐語訳的移植を終えた
Force コードをきれいにしたい
Solution 移植対象言語のイディオム、ベストプラクティスで書き換える
王道を知る
Context 逐語訳的移植を終えた
Force コードをきれいにしたい
Solution設計の王道を調べ、移植対象言語に実装が存在したら使用を検
討する
パーサジェネレータracc
ご存知、な (ry
ごめんなさいごめんなさい
raccデカルチャー
ごめんなさいごめんなさい
割愛ごめんなさいごめんなさい
そして移植の最後に
恩返し
第二部完
Q & A
おわりに
大原則:一度に複数を相手にしない
TDDと黄金の回転
きれい
汚い
(すぐには)動かない 動作する
Red
Green
Refactoring
続け!
ご清聴ありがとうございました