コンピュータやソフトウェアのあれこれ@道民(&元道民)
Java
mavenで依存ライブラリが同包されている実行可能なjarファイルを作成する
2月 23rd
実行可能なjarと依存ライブラリが梱包されたzipアーカイブを作る、という方法で実行可能モジュールを作成していたのだけど、かなり無駄な構成になっているなと感じていた。
一つのjarファイルに全てのライブラリが同包されて、そのjarが実行できたほうがずっといい。
ということで今回の目標
- コマンドから java -jar で実行可能なエントリーポイントを含むjarを作る
- 上記jarで必要なライブラリ類はjarに同包されていること
外部に依存することなく1つのjarファイルだけでエントリーポイントからの処理が実行できるようにする。
使用するプラグインは maven-assembly-plugin。
maven-assembly-plugin
デフォルトアーカイブ jar-with-dependencies を使用し、エントリーポイントとなるmainClassを定義しておくだけで、目的のjarファイルが作成される。
<pom.xml>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>sample.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-sample-jar</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
これだけ。前と比べてpom.xml自体もとても簡潔になった。
最初にこの設定が出来なかったのは、親モジュールに[descriptors]が定義されていたため。
親モジュールにこれが定義されていると、アセンブリファイルの位置の上書きはできるけど、アセンブリファイルを”使わない”という設定ができない。
[descriptorRefs]を使用してもアセンブリファイルがないとビルドエラーとなる。
今回はプロジェクトの構成をもう一回見なおして、[descriptors]を親モジュールで定義する必要がないとわかったので、それぞれのモジュールで[descriptors]か[descriptorRefs]を定義させるようにして解決。
mavenは使い方になれるまで難しい。
[java][scala]Scalaの空括弧とUnit
2月 2nd
Unit周りでハマったのでメモ。
まず、() => Any という型はあるが、()という型はない。
scala> def id(x: () => Int): () => Int = x
id: (x: () => Int)() => Int
scala> def id(x: ()): () = x
<console>:1: error: '=>' expected but ')' found.
def id(x: ()): () = x
^
Unit型の唯一の要素が()である。
scala> ().isInstanceOf[Unit]
res67: Boolean = true
() => Any は Unit => Any とは違う。
// 型が違って代入できない scala> val emptyParentheses: () => Any = { () => 10 } emptyParentheses: () => Any = <function0> scala> val unit: Unit => Any = emptyParentheses <console>:17: error: type mismatch; found : () => Any required: Unit => Any val unit: Unit => Any = emptyParentheses ^ // 型が違って代入できない scala> val unit: Unit => Any = {(u: Unit) => 20} unit: Unit => Any = <function1> scala> val emptyParentheses: () => Any = unit <console>:17: error: type mismatch; found : Unit => Any required: () => Any val emptyParentheses: () => Any = unit ^
が、Unit => Any は空括弧で呼び出すことができる。
scala> def one(u: Unit): Int = 1 one: (u: Unit)Int scala> one(()) res68: Int = 1 // これ、なんでエラーにならないのかわからず (Value Discardingから??) scala> one() res69: Int = 1 scala> one <console>:18: error: missing arguments for method one in object $iw; follow this method with `_' if you want to treat it as a partially applied function one ^ // これは、要請がUnit型なら{ e; () }に変換するルール(Value Discarding)より scala> one("DUMMYYY") res71: Int = 1 // これもValue Discarding から?? scala> one(1, "XXX", Nil) res72: Int = 1
[scala][java]Scalaのvalとvarとdef
2月 2nd
valは定数、varは変数、defはコード、というのが大まかな理解になる。これらは名前空間を共有しているので、定数、変数、コードに同じ名前をつけることはできない。ただし、JSやPythonのように単純にフィールドにメソッドオブジェクトが入っていると思うとハマる部分があるので、その辺を含めて調べたことをメモ。(なお、このエントリは 2.9.1.final での挙動について書いている。)
クラス定義のvar、def、val はメソッド
valとvarはfinal付き、final無しの変数として解釈されると思われるが、クラスのフィールドとして定義した場合にはJVM上ではメソッド経由でアクセスすることになる。ので、abstract defをvarやvalでオーバーライド*1したりできる。
class ValVarDef { val x = 1 var y = 2 def z = 3 }
% javap -private ValVarDef public class ValVarDef extends java.lang.Object implements scala.ScalaObject{ private final int x; private int y; public int x(); public int y(); public void y_$eq(int); public int z(); public ValVarDef(); }
メソッドと関数型のフィールドは違う
JSなどと違い、def hoge(x: String): Int (メソッド)とdef hoge: String => Int (関数オブジェクトのフィールド)はまったく違うもの。
// 名前付き引数で呼べる scala> def hoge(x: String): Int = x.length hoge: (x: String)Int scala> hoge("abcde") // ←一見同じ振る舞いをするように見える res140: Int = 5 scala> hoge(x = "abcde") res141: Int = 5 // 名前付き引数で呼べない scala> def hoge: String => Int = x => x.length hoge: String => Int scala> hoge("abcde") // ←一見同じ振る舞いをするように見える res142: Int = 5 scala> hoge(x = "abcde") <console>:11: error: reassignment to val hoge(x = "abcde") ^ // 引数には自動的に名前がつく (コメント欄参照) scala> hoge(v1 = "abcde") res3: Int = 5
JVMのレベルで見ると、前者はメソッドに直接処理を記述したもので、後者は処理を記述されたFunctionNオブジェクト(ここでは MyTest$$anonfun$hoge2$1)を返すメソッドになる。
class MyTest { def hoge1(x: String): Int = x.length def hoge2: String => Int = x => x.length }
public class MyTest extends java.lang.Object implements scala.ScalaObject{ public int hoge1(java.lang.String); public scala.Function1 hoge2(); public MyTest(); } public final class MyTest$$anonfun$hoge2$1 extends scala.runtime.AbstractFunction1 implements scala.Serializable{ public static final long serialVersionUID; public static {}; public final int apply(java.lang.String); public final java.lang.Object apply(java.lang.Object); public MyTest$$anonfun$hoge2$1(MyTest); }
defの定義で、引数なしと空括弧は微妙に違う
def hoge = 1 と def hoge() = 1 は振る舞いが微妙に違う。特に関数オブジェクトが入っている場合。
scala> def func1 = (_: Int) + 1 func1: Int => Int scala> def func2() = (_: Int) + 1 func2: ()Int => Int // 引数無しで定義すると"func1()"では呼び出せない scala> func1 res136: Int => Int = <function1> scala> func1() <console>:9: error: not enough arguments for method apply: (v1: Int)Int in trait Function1. Unspecified value parameter v1. func1() ^ // 空括弧で定義すると"func2()"でも"func2"でも呼び出せる scala> func2 res138: Int => Int = <function1> scala> func2() res139: Int => Int = <function1> // func1は括弧付きで呼び出せないので、意図したように解釈される scala> func1(5) res131: Int = 6 // func2を5を引数として呼び出したと解釈されるため、エラーとなる scala> func2(5) <console>:9: error: too many arguments for method func2: ()Int => Int func2(5) ^ scala> func2()(5) res135: Int = 6
=> はJVM上では関数オブジェクト
まあそうだよね。
class MyTest { def hoge1(x: Int) = x + 1 def hoge2(x: => Int) = x + 1 def hoge3(x: () => Int) = x() + 1 }
public class MyTest extends java.lang.Object implements scala.ScalaObject{ public int hoge1(int); public int hoge2(scala.Function0); public int hoge3(scala.Function0); public MyTest(); }
lazy val はJVM上ではメソッドとして定義される
まあそうだよね。
class MyClass { def hoge1: Int = { val x = 1 lazy val y = 1 x + y } def hoge2: Int = { val x = 1 lazy val y = 1 x + y } }
public class MyClass extends java.lang.Object implements scala.ScalaObject{ public int hoge1(); Code: ..略.. 24: invokespecial #21; //Method y$1:(Lscala/runtime/IntRef;Lscala/runtime/VolatileIntRef;)I ..略.. 27: iadd 28: ireturn public int hoge2(); private final int y$1(scala.runtime.IntRef, scala.runtime.VolatileIntRef); private final int y$2(scala.runtime.IntRef, scala.runtime.VolatileIntRef); public MyClass(); }
objectはstatic
objectはシングルトンオブジェクトを定義するものだが、JVM上ではstaticとして解釈される互換性のためstaticメソッドも用意される。
class MyTest { val inClass = 1 }
object MyTest { val inObject = 2 }
% javap MyTest MyTest$ public class MyTest extends java.lang.Object implements scala.ScalaObject{ public static final int inObject(); public int inClass(); public MyTest(); } public final class MyTest$ extends java.lang.Object implements scala.ScalaObject{ public static final MyTest$ MODULE$; public static {}; public int inObject(); }
*1:コメント欄も参照
mavenで実行可能なjarファイルと依存ライブラリを含めたzipアーカイブを作成する
2月 2nd
以下の条件を満たすアーカイブを作成したいなーという状況が発生したときのmaven設定を忘れないように残しておく。
- コマンドから java -jar で実行可能なエントリーポイントを含むjarを作る
- 上記jarで必要なライブラリ類を同包したアーカイブを作る
外部に依存することなくエントリーポイントからの処理を実行する事が目的。
使用するプラグインは maven-jar-plugin と maven-assembly-plugin。
maven-jar-plugin
jarファイルのManifestファイルを設定することができる。
Manifestファイルを作成しておくことで、メインクラス(-jarで実行する際のエントリーポイント)を指定することができる。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>sample.hoge.Main</mainClass>
<addClasspath>true</addClasspath>
<addExtensions>false</addExtensions>
<classpathPrefix></classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
mainClass: メインクラスをフルパスで
addClasspath: pom.xml の dependenciesをクラスパスとしてマニフェストファイルに出力するか否か
addExtensions: pom.xml の dependenciesの拡張情報もマニフェストファイルに出力するか否か
classpathPrefix: addClasspathがtrueの場合にクラスパスにつける接頭語(lib/とかext/とか)
maven-assembly-plugin
配布用のアーカイブを作成するためのプラグイン。
必要なライブラリのjarも含めたアーカイブを作成するように設定すれば、外部に依存しない完結したアーカイブファイルを作成できる。
<pom.xml>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>batchfile/hogetarou</finalName>
</configuration>
<executions>
<execution>
<id>make-sample-batch-zip</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
finalName: 出力するファイル名 targetディレクトリからの相対パスになる。
executionタブでの設定は packageフェーズに assembly:single の実行を含むための設定です。
こうしておけば、mvn assembly:single を直接実行しなくても、mvn package 実行時にアーカイブができる。
デフォルトのアーカイブ形式は以下。
- bin: バイナリ配布用のアーカイブ
- jar-with-dependencies: プロジェクトと依存するライブラリをまとめた 1 つの JAR ファイル
- src: ソース配布用のアーカイブ
今回実現したい事は、jar-with-dependencies 形式を指定すれば実現できそうなのだけど、親モジュールとの関係で独自形式でアセンブリの設定をすることに。
依存関係の断ち切り方を時間内で発見する事ができなかったのです(assembly-plugin を使う場合、アセンブリファイルの配置が必須になってしまっていた)。※
独自形式でアセンブリの設定をする場合は pom.xml の他にアセンブリ記述子を配置する必要があります。
アセンブリ記述子はデフォルトで src/main/assembly 配下に置くことになっています。
<executable.xml>
<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
<id>sample</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<unpack>false</unpack>
<scope>runtime</scope>
<outputDirectory>output/</outputDirectory>
</dependencySet>
</dependencySets>
</assembly>
output というディレクトリ内にdependencyとMainのjarを含めたzipファイルを作成しますよ、という設定。
上記を設定で maven package を実行すると
- target/batchfile/hogetarou.zip ができている
- hogetarou.zipをunzipするとoutputというディレクトリが展開される
- outputのなかにはMainのjarと依存ライブラリのjarが同列に格納されている
- java -jar [Mainのjar] でプログラムが実行される状態になっている
と、なりました。
この記事がとても良くまとまっていて、自分が作成した方法以外も勉強になりました。
Sacrificed & Exploited – Mavenで配布用zipファイルを作成する
http://d.hatena.ne.jp/cnaos/20100102/1262430319
今後、m※の部分をなんとかできそうだったら、なんとかしてみたいと思います。時間があれば。
その他、四苦八苦している際に参考にさせてもらったサイト
ありがとうございました。
http://www.in-vitro.jp/blog/index.cgi/Maven/20060215_01.htm
Groovyのsortキーの話
1月 24th
最近お仕事でJava+Groovyを使っています。
Groovyを使うと、Javaのまどろっこしいところを気持ちよく書けます。
Javaのお仕事をしながらスクリプト言語に触れることができ、他の言語を学習する際にも良い影響を受けています。
で、最近知ったGroovyのソートキーの挙動についてちょっとハマったので残しておく。
GroovyでのCollectionのソート
GroovyでCollectionをソートするとき、ソートの引数としてjava.util.Comparatorの他に、クロージャを渡すことができます。
ソート対象のオブジェクトのプロパティ値を返すクロージャーを渡すことで、そのプロパティ値をソートキーとすることができるのです。
def member = [[name:"a", age:22],[name:"ab", age:18],[name:"abc", age:42],[name:"b", age:33]]
println member.sort{it.name}
//-> [[name:a, age:22], [name:ab, age:18], [name:abc, age:42], [name:b, age:33]]
Google先生でこのソート方法を調べていると
「ソート対象のオブジェクトのプロパティ値をリストで返せば、第1ソートキー・第2ソートキーとして使用することができる」
という情報がちらほら。なんと便利な。
しかし、Grな日々(uehajの日記)の記事 – Groovyのsortにまつわる衝撃の事実では、このソートは実装されているわけではなく単なる偶然だ、と書かれていました。
この記事が書かれたのは2009年、今はどうなっているのかな?
ということで、ちょっと試してみました。
試してみた
def member = [[name:"a", age:22],[name:"ab", age:18],[name:"abc", age:42],[name:"b", age:33]]
// 1) 文字列型のプロパティを返す
println member.sort{it.name}
//-> [[name:a, age:22], [name:ab, age:18], [name:abc, age:42], [name:b, age:33]]
// 2) 文字列型のプロパティを要素に持つリストを返す ★
println member.sort{[it.name]}
//-> [[name:a, age:22], [name:b, age:33], [name:ab, age:18], [name:abc, age:42]]
// 3) 数値型のプロパティを返す
println member.sort{it.age}
//-> [[name:ab, age:18], [name:a, age:22], [name:b, age:33], [name:abc, age:42]]
// 4) 数値型のプロパティを要素に持つリストを返す
println member.sort{[it.age]}
//-> [[name:ab, age:18], [name:a, age:22], [name:b, age:33], [name:abc, age:42]]
// 5) 数値型のプロパティと文字列型のプロパティを要素に持つリストを返す ★
println member.sort{[it.age, it.name]}
//-> [[name:a, age:22], [name:b, age:33], [name:ab, age:18], [name:abc, age:42]]
リストを返すクロージャを渡した時、かつ、プロパティ値が文字列型の場合、期待するソート結果が返ってきませんでした。
リストを返すクロージャーで複数ソートキーを指定できるのだとしたら、1)と2)で挙動が異なる時点で明らかにおかしいです。
先ほど紹介させていただいた Grな日々(uehajの日記)の中でその理由として、「リストについて特別扱いをしていない。リストとしての辞書ソートみたいなものが実現された結果、ソートキー順にソートされているように見えていた」と書かれています。なるほど。
記事が2009年だったので今のバージョン(1.8.5)の該当ソースコード(ここからダウンロードできる)を読んでみました。
現行バージョンでも、リストを返すクロージャーについて特別扱いしていないなぁ。(単純なリストの比較のみ)
結論:
現バージョンでもクロージャーにリストを返したとき、それらの要素がソートキーとして働くような実装はされていない
ちなみにプロパティの型が数値(Integer)だと上手いこといくようにみえます。
def member2 = [[weight:40, age:22],[weight:75, age:18],[weight:75, age:42],[weight:65, age:33],[weight:50, age:18]]
println member2.sort{it.weight}
//-> [[weight:40, age:22], [weight:50, age:18], [weight:65, age:33], [weight:75, age:18], [weight:75, age:42]]
println member2.sort{[it.age]}
//-> [[weight:50, age:18], [weight:75, age:18], [weight:40, age:22], [weight:65, age:33], [weight:75, age:42]]
println member2.sort{[it.age, -it.weight]}
//-> [[weight:75, age:18], [weight:50, age:18], [weight:40, age:22], [weight:65, age:33], [weight:75, age:42]]
Groovy JDK API SpecificationのCollection#sort(Closure)にもソートキーをリストで渡す方法は明示されていませんでした。
第1ソートキー、第2ソートキーを使用したい場合は自分でComparatorを実装するのが正解ですかね。
TDD Boot Camp 札幌 2.3に参加しました。
12月 12th
12月10日に行われた、TDDBC札幌 2.3に参加しました。
今回はJavaで参加。
お題は「ボーリングのスコア計算」でした。
事前イメージがあった
今回は、お題を聞いて最初におぼろげに実装をイメージしていました。
実装がイメージしやすかった分、当日も取り組みやすかったなと感じました。
int score = new Game("omuko").bowling(1,2)
.bowling(3,4)
.bowling(5,2)
.bowling(6,2)
.bowling(8,0)
.bowling(9,0)
.bowling(6,4)
.bowling(2,3)
.bowling(1,1)
.bowling(5,2)
.score();
assertThat(score, is(68));
※スコアはだいたい普段の成績、残念。
イメージ通りにはいかない
当日のプログラムはこちら https://bitbucket.org/irasally/tddbc2.3
細かい部品から作っていったのですが、どうしても(Javaの言語の性質もあると思いますが)かっちり作ろう、という方向に流れがちでした。
たぶん、一番エラーチェックが多いチームだったと思う。
「クラスに状態を持たせすぎるとテストが大変」というのを身をもって体験した感じ。
でも、部品を作り込んでいた分、部品の組み合わせは思ったよりも楽だった。
この辺りのバランス感覚は、磨いていきたいなと思いました。
「まず、1本簡単な実装を最初から最後まで通す、細かいことは2週目に考える」
途中からその言葉を何回か言いながら開発を進めていきました。
手を動かしたくてたまらなかった
わりと、テストで方向性がつかみやすかったことと、取り組みやすいお題だったことで、
悩んで考える時間よりも、手を動かしてコードを書きたい時間が長かった。
「どうやってテストを書こうか…」と悩む時間が今までで一番少なかったように思いました。
TDDをやりながら開発速度が上がるというのはこういう経験なのかもしれない。
自分が体験して思ったことは
- IN/OUT、得たい結果がはっきりしているお題であること
- お題(取り組むべき相手)が技術的に自分の手に負えること
この二つが揃うと、TDDがとても楽しくなりますね。
特に2は、自分が技術力を上げれば上げるほど広がる幅なので、精進あるのみ!です。
次回も是非参加したいと思います。
主催者の@shuji_w6eさん、今回もありがとうございました!
[Java][JUnit][TDD] JUnit のセカイ #JJUG
12月 6th
Java Festa 2011に参加しました
11月 22nd
11月18日(金)札幌コンベンションセンターで行われた「Java Festa 2011」に参加しました。
午前中から夕方まで充実のセッションを聞いてきました。
JavaOne 2011 サンフランシスコの最新レポートについて
7月にもセッションを行なっていただいた日本オラクル寺田佳央さんのお話。
サンフランシスコで行われたJava Oneの現地レポートを中心に、これからのJavaがどうなっていくのかについてお話を聞きました。
技術面ではSwingがメンテナンスモードになって、これからはJavaFXメインで行くというのに一番驚きました。
あとはJavaEE。Strutsはもう古い、古すぎる技術、時代に置いていかれていることを認識したほうが良い。と。今のJavaEEは本当に使いやすくなったようです。(これから業務で使います。)
「Moving Java Forward」
コミュニティ面、技術面含めて(いろいろあって不安に思っている人もいるかも知れないけど)これからもJavaは前向きにじゃんじゃんやっていくよ!
というメッセージと意気込みが伝わって来ました。
この動画は驚いた。Javaっぽくない!本当にお祭りなんだなぁ。
仕事柄、使う時間の長い言語だもん、もっといろんなことを知って楽しく使いたい。(今も楽しいけど)
来年4月に東京で行われる Java One Tokyo 行きたいな!
北海道だけにこっそり教えるアジャイルコンサルタントの秘密~アジャイル開発 禁断の予言書~
株式会社 匠BusinessPlace 牛尾剛さんのお話。
今回、一番理解するのに苦労したセッションでした。
冒頭、「アジャイルやってますかー?」という質問から始まったのですが、この「アジャイル」という文脈がなかなかつかめなかった。
Twitterなどでいろんな意見を聞きながら話を聞いていたのですが
「うまくいっていないプロジェクト(チーム)を”アジャイル”というキーワードで立て直す方法」
「アジャイル導入に不安を持っている(特に)リーダー層へ向けてアピールする方法」
という文脈で聞くと、そういう方法もあるのだなあと考えることができました。
普段自分が考えているアジャイルとはまた違った文脈の話が聞けた気がします。
クラウド時代のアーキテクチャ設計 – 次世代アーキテクトが押さえるべきキーポイント –
Amazon Data Services Japan 株式会社 玉川憲さんのお話。
Amazonのクラウドプロダクトについてと、クラウド環境をインフラとして取り入れる際に考えるべき7つのポイントについて。
AWSは触ったことがなかったので、プロダクトの多さにびっくりしました。
時代の流れは確実にクラウドを押さえなければいけないですね。
「適材適所、レゴブロックのように用途に合わせて組み合わせていく技術が大事になってくる」、レゴブロックってわくわくしますね。
今度サービスを作りたいと思った時に選択肢の一つにできるように勉強しておかないとなぁ。
アジャイル開発の現在・過去・未来 ~今を知り、源流を訪ね、先を見据える~
前日に引き続き、株式会社チェンジビジョンの平鍋健児さんのお話を聞きました。
今年の1月に行われた「平鍋さんとアツク語る会2011札幌」から続いているお話だと理解して聞いていました。
あの頃から、自分はかなり変わったなあと実感。
(だいたい、平鍋さんとアツク語る会のこと、ブログに残してなかったし・・・)
お客さんと向き合う時の考え方など、普段のモヤモヤを少し緩めるきっかけになりそうなエネルギーをもらえました。
アジャイル的な開発を現場で取り入れようとするときに、「教科書通りにやっても必ず失敗する」「その場その場で現場で考えることが大切」というところは、現時点のモヤモヤと合わせて、どうやったら気持ちよく皆が幸せになれる仕事をしていけるか、ということを考える際に忘れてはいけないことだなと再確認。
「Energized Work」
“「人生があって、仕事があって、プロジェクトがある。」しかし、「プロジェクトがあって、人生がある」となってしまっていることがある。いっときそういうこともあるかもしれないけれど、ずっとそうなっているのは健全な形ではない”
私たち開発を行う側も心地よく、エネルギーを持って仕事に取り組めるようにすること、これを大事にするという考え方は、うまく言えないのだけれど、救われるところが大きかったです。
自分の仕事に自信を持って、胸を張って毎日楽しく取り組む、簡単そうで難しい、常にそうありたいな、と思いました。
最後に、壇上から「オム子さん」と名前を呼ばれ、平鍋さんがアジャイルサムライ読書会の紹介をしてくださいました。
読書会を開催していなければありえなかったこと。
ますます、頑張っていこうと思いました。
Java Festaのこと
なんだかんだで社会人になった頃からほぼずっと参加しているイベントです。
(参加しているというよりは”セミナーを聞きにいっている”という感覚に近い時期もありましたが)
今年は
- 無線LANが解放されていた
- セッションがUstream中継された
という点が、例年と大きく違っていたなと感じました。その点とてもよかったです。
快適にtwitterでリアルタイムに感想をかけたり、あとから動画を見ることもできます。
(公開されている動画の一覧や公開された当日の資料はJavaFestaの公式サイトにあります。)
ただ、「Java」Festaに参加している者としては、Javaの話が少なかったのはとても残念でした。
Javaのレガシープロジェクトを改善した話とか、最近のFWの話とか、JRubyの話とか、Groovyの話だとか、もっとJava界隈の技術的な話があったら面白かったのになと感じました。
毎年参加できていることに感謝して、そして来年も参加したいと思います。
[Java][Cucumber][ATDD][TDDBC] cuke4duke を使い Cucumber を Java で使う
9月 29th
TDD Boot Camp 札幌 2.1に参加しました
9月 29th
9月24日に行われたTDD Boot Camp 札幌 2.1に参加してきました。
当日の様子については@sue445さんによるまとめ – TDD BootCamp 札幌 2.1 #TDDBC があります。
今回もJavaチームのTAとして参加。
演習では「会議室の予約を管理するよくある社内システム」を構築するというのをslim3&GAEを使って行いました。
セッション等については他の方が書いていると思いますので、TAとして思った事を書いていきたいと思います。
反省点
いきなり、反省点から。
slim3というほぼ初体験のFWを使ったTDDBCだったにも関わらず、事前準備があまりできませんでした。
前日のJenkins勉強会の懇親会時点で、環境構築で戸惑っていた始末。
(なのに懇親会に行ったのか?・・・@sue445さんに懇親会でお会いできたからこその当日だったのです)
ミスリードはしないまでも、slim3を使った開発という面での技術的支援はあまりできなかったです。ごめんなさい。むしろ、slim3とGAEでバリバリ開発経験のある@t_hachinoheさんから学ぶ事が多かった!大変感謝しております。
翌週なんてどうなるかわからないから、時間のあるうちにできることをしておけ、と身をもって学びました。
TAとして伝えたかった事
今回は「実際の仕事で(時間的制約がある中で)どこを抜いて、どこは締めるのか」をチームプログラミングを通じて少しでも伝えられていたら良いなと思って演習を進めていました。
仕事には、期限があります。決められた時間の中でより良いもの、質の高いものを作っていく、そのために大事な事の一つとしてTDDという開発手法があります。そう思っています。
実際に1時間で1ユースケースをこなすのは時間が足りなすぎた。
完璧なロジックで、理想の形でTDDをやる(つまりはすべてのパターンのテストを作って開発していく)のはなかなか難しい。
で、目的を達成するために外してはいけないところはどこなのか。
今回のシステムの場合、短納期のプロジェクトだったらどうするか、そこで出した答えは
「GUIのテストは省く」
でした。まずはデータだな、と。
データの取得更新がシステムとして正しい事、これだけは確実にしておこう。
ロジックとして引数が美しくないところもありましたが「そこは次のイテレーションがあったらそこで改善する」と割り切りました。その分データ更新の部分はテストからきちんと書くことにしました。
全体の流れ、ユースケースを実現する事を意識して。
実際、本当に必要なテストも若干足りていなかった(バリデーションのパターンが足りなかったなど)のですが、そこについては
「ここは絶対にやっておいたほうがよいところ、間違いなくTODOコメントを残しておく事」
と適宜話していく感じで、外してはいけない部分は伝えられたかなと思います。
テストからインターフェースを考える、というあたりをチームメンバーが既に把握していたというのもあり、実践として、自分が仕事で得た経験を・・・共有できて・・・いたかな。
「時間的制約」を意識してもらえていたら良かったなと思います。
制約のある中できちんとしたものを作りたい
↓
短い時間の中でもTDDで開発したコードは動くという自信が持てる
↓
TDDで作っておけば少々コードがアレでもあとで安心してリファクタリングできる
↓
そもそも技術力が上がればもっとたくさんのコードがかけるよね
このどれかでも持ち帰っていただければ!
だんだん書いていて不安になってきました。これでよかったんだろうか。
たのしかったこと
みんなで考えながら、プログラミングをする事が純粋に楽しかったです。
いままでは「テスト駆動開発をしよう」という緊張感のようなものがメンバーにありましたが、今回はTDDを行う事は当たり前、自然な事であるという流れができていたような気がします。どうテストをするかという悩みもありましたが、それよりもその先をどうしようか「どう作っていこうか」という話し合いが多くできた事がとてもよかったです。
(準備をもっとしていればはまらなかった事もあったのですが)、みんなで頭をひねって考える時間、ソースを見ながらあーだこーだ言う時間がもてたのは「参加型」としてよかったことだったかもしれないな、なんて。
あと、自分のチーム懇親会参加率が100%だった。えへ。
終わったあとの鶏とビールは最高でした。

謝辞
毎回、素晴らしいスピード感でTDDBCを開催してくださる@shuji_w6eさん、今回も本当にありがとうございました。
JavaのTAとして事前準備をまかせっぱなしにしてしまい、さらに前日から(お酒の席にも関わらず)サポートいただいた@sue445さん、今回お会いできて本当によかったです。ありがとうございました。
そして、同じチームだった@hope_784さん、@onjiroさん、@t_hachinoheさん、当日撮った写真では大変疲れた様子でしたが、楽しんでいただけましたでしょうか。至らないところもたくさんありましたが、皆さんとプログラミングができて、私はとても楽しかったです!ありがとうございました。
次回はみっちり自分も鍛え上げられるtddbcにも参加してみたいです。