2005-06-21(Tue) [長年日記]
■1 QuickJUnitプラグイン: 3.1対応、救援求ム
quick-junit-develは35人しかsubscribeしてないので(!)、こっちにも書いておこう……。
Eclipse3.1系プラグインを触っている方の助けを求めています。
Eclipse 3.1M7で、「PDE enforces code accessibility」というのが導入されたそうで、おかげでinternal系のクラスが軒並、参照できなくなってる模様。
プリファレンス系のAPIも大きく変わっちゃってる……。振り切られました。あうあう。
私が3.1系のプラグインのつくり方を勉強して直していると、3.1リリース時点での対応は間に合わないこと確実。なので、3.1系のプラグインのことがわかる方、アドバイスをいただけないでしょうか。メーリングリストでお待ちしております。
■2 車輪の再実装: 簡易DIコンテナ on Groovy with AOP
前回、39行でDI機能を実装したGroovyのダイコン。今度はTraceInterceptorが必要になったので、実装してみた。AOP(というかInterceptor)の実装のことはよくわからないのだけれど、TDDでやったら動いちゃった。TDDすごいなあ。コンテナ部分のコードは45行増えて、84行になった。
利用イメージ
たとえば、こんなクラスをDIしてTraceInterceptしたいとする。
class TraceStub {
doSomethingWith(arg1, arg2) {
"doneSomething"
}
void throwException() {
throw new RuntimeException("expected")
}
}
コンテナに登録するときはBlockInjectionだよね、フツウ。
dicon = new DIContainer()
dicon.regist("myComp") { new TraceStub() }
登録したmyCompにinterceptorを適用したいときは:
dicon.intercept("myComp").with { TraceInterceptor.class }
こうでなくっちゃ。で、
dicon.myComp.doSomethingWith("someArg", "otherArg")
って呼んだら:
BEGIN com.kakutani.gradicula.TraceStub#doSomethingWith('someArg', 'otherArg')
END com.kakutani.gradicula.TraceStub#doSomethingWith('someArg', 'otherArg') : doneSomething
と出て欲しいし、
dicon.myComp.throwException()
って呼んだら:
BEGIN com.kakutani.gradicula.TraceStub#throwException() END com.kakutani.gradicula.TraceStub#throwException() Throwable: java.lang.RuntimeException: expected
と出て欲しい。
DIContainer.groovy
45行も増えてしまった。なんでこんな設計になっているのか自分でもよくわかってない。テストを通るように実装したらこうなっちゃった。たぶんいろいろマズいと思うけど、自分たちが使うには充分だ。
1 package com.kakutani.gradicula
2
3 class MethodInvocation {
4 target
5 methodName
6 args
7
8 proceed() {
9 target.invokeMethod(methodName, args)
10 }
11 }
12
13 class InterceptProxy {
14 target
15 proc
16
17 InterceptProxy with(closure) {
18 proc = closure
19 this
20 }
21
22 invokeMethod(String name, Object args) {
23 try {
24 return metaClass.invokeMethod(this, name, args)
25 } catch(MissingMethodException e) {
26 interceptor = proc.call()
27 if (interceptor.class == Class.class) {
28 interceptor = interceptor.newInstance()
29 }
30 m = new MethodInvocation(
31 target:target,
32 methodName:name,
33 args:args)
34 return interceptor.invoke(m)
35 }
36 }
37 }
38
39 class DIContainer {
40 instances
41 services
42 interceptors
43
44 DIContainer() {
45 instances = [:]
46 services = [:]
47 interceptors = [:]
48 }
49
50 DIContainer regist(name, closure) {
51 services[name] = closure
52 this
53 }
54
55 InterceptProxy intercept(name) {
56 if (interceptors[name] != null) return interceptors[name]
57 proxy = new InterceptProxy(target:instance(name))
58 instances[name] = proxy
59 interceptors[name] = proxy
60 }
61
62 Object instance(name) {
63 if (instances[name] != null) return instances[name]
64 closure = services[name]
65 component = closure.call(this)
66 instances[name] = component
67 }
68
69 getProperty(String name) {
70 try {
71 return metaClass.getProperty(this, name)
72 } catch(MissingPropertyException e) {
73 return instance(name)
74 }
75 }
76
77 invokeMethod(String name, Object args) {
78 try {
79 return metaClass.invokeMethod(this, name, args)
80 } catch(MissingMethodException e) {
81 return instance(name)
82 }
83 }
84 }
TraceInterceptor.groovy
S2のTraceInterceptorの丸パクリにインスパイアされました。出力はロギングAPIじゃなくてprintlnで手抜き。
1 package com.kakutani.gradicula
2
3 class TraceInterceptor {
4 Object invoke(invocation) {
5 buf = renderSignature(invocation)
6 println("BEGIN " + buf)
7 try {
8 result = invocation.proceed()
9 buf << " : ${result}"
10 result
11 } catch (Throwable t) {
12 buf << " Throwable: ${t}"
13 throw t
14 } finally {
15 println("END " + buf)
16 }
17 }
18
19 private StringBuffer renderSignature(invocation) {
20 buf = new StringBuffer(100)
21 buf << "${invocation.target.class.name}#"
22 buf << "${invocation.methodName}(${renderArgs(invocation.args)})"
23 buf
24 }
25
26 private String renderArgs(args) {
27 buf = new StringBuffer()
28 buf << args.toList().inject("") {|str, a| "'${a}', " }
29 if (0 < buf.length()) buf.setLength(buf.length() - 2);
30 buf.toString()
31 }
32 }
さらに先に進むために
Interceptorの登録の仕方のバリエーションを増やす。特定のメソッドにだけinterceptするための指定とか。
リーン開発の現場 カンバンによる大規模プロジェクトの運営(Henrik Kniberg/角谷 信太郎/市谷 聡啓/藤原 大)
『なるほどUnixプロセス ― Rubyで学ぶUnixの基礎』
SCRUM BOOT CAMP THE BOOK(西村 直人/永瀬 美穂/吉羽 龍太郎)
実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる(Steve Freeman/Nat Pryce/和智 右桂/高木 正弘)
The RSpec Book (Professional Ruby Series)(David Chelimsky/Dave Astels/Zach Dennis/角谷 信太郎/豊田 祐司/株式会社クイープ)
アジャイルサムライ−達人開発者への道−(Jonathan Rasmusson/西村 直人/角谷 信太郎/近藤 修平/角掛 拓未)
アジャイルな見積りと計画づくり ~価値あるソフトウェアを育てる概念と技法~(Mike Cohn/マイク コーン/安井 力/角谷 信太郎)
インターフェイス指向設計 ―アジャイル手法によるオブジェクト指向設計の実践(Ken Pugh/角谷 信太郎(監訳)/児島 修)
アジャイルプラクティス 達人プログラマに学ぶ現場開発者の習慣(Venkat Subramaniam/Andy Hunt/木下 史彦/角谷 信太郎)
JavaからRubyへ ―マネージャのための実践移行ガイド(Bruce A. Tate/角谷 信太郎)










好みの問題ですが、"register(動詞)" が "regist" と省略されてるのを見るとなんとなく落ち着かないです。和製英語「レジストする」を連想するからかもしれません。
ううっ。そもそものMatzダイコンもregisterなので、そうします。