「あるエンジニアがプログラムを紡いでいく様を見てみる」ライブコーディング・リプレイ
あるエンジニアがプログラムを紡いでいく様を見てみるでしたライブコーディングで言ったことや言わなかったことを書いてみます。
意識してるのは「コードをどまんなかに」です。
……あ、このスライドのブログ書き忘れてた。
スライド中の「えらぶ」はだいたいIDEの機能を指します。なのでライブコーディング中に使用したIDEの機能も挙げようと思います。基本的にデフォルトのつもりだけど、vimとの兼ね合いで変更してるのもあるので、そこはごめんなさい。あとMacです。今回はメソッド抽出とかクラス間移動とかダイナミックなのがなくて地味だけど、便利な子たちなので使ってあげてください。
リプレイ
今日の公開コーディングはスゴい新鮮だった🎵
— かわべ たくや (@kawakawa) 2018年3月5日
コミット後のソースには、どこに悩んだのか、どこにこだわったのかは残らないのですね。
実際のコーディングを見させて頂く事で、気づかされる事が多かったです。ほんと有り難うございました🎵#DevKan
ってことなので、コミットを追いながらいってみます。私の意図はどの程度コードに残ってるのかなー。
■最初はREADME
会場で何をするかを話して、READMEに書いてみました。思ったよりもでなくて、参加された方もどう参加したものかと手探りだったんだと思います。
■プロジェクトを作成して動作確認
Javaアプリケーションを作るときにまずやってること - 日々常々 で書いた内容ですね。 見事に 動作しませんでした 。本番あるある。
原因はオフライン時のライブラリ解決です。いつも通りと言いつつネットワークの無い状態なのでいつも通りじゃない。普段と違うことをしたら詰まるのは当たり前です。てことでネットワーク繋げて再開しました。(数時間前にネットワーク切断して素振りした時は大丈夫だったんです。ほんとですよ?)
SpringBootのApplicationクラスを作って実行、動作した、よし。ってところで最初の10分で目標にしてたところをクリア。
使用したIDEの機能
- 前回 挙げたやつ
Alt
+Ctrl
+R
Mainやテストの実行
使用するものの選択
build.gradle
に何を使うかを書きます。今回はJava 8、Spring Boot 2.0とJUnit 5、Assert J。
Spring Bootを使うのは後でSpring Shellを使おうと思っていたと言うのもありますが、Isolating the Domainに準じて作るつもりだったので。
■パッケージ名変更
前段であがった「 todo
を doto
とtypoしている」の指摘はあえてスルーしてました。typoを直すよりも動作確認を優先したかったからです。仮にtypoを直したとしても動作しなきゃ無駄になるし、typoは動作に影響しないことは知っているので、後回しです。動作確認ができたので、ここでリネーム。もちろんリネーム後はアプリケーションを実行して確認します。
新たに使用したIDEの機能
Shift
+F6
ファイル名のリネームAlt
+R
前回実行したものの再実行
■初期モデル作成
環境ができたら「TODOリスト」に対して現在捉えてるモデルを書きます。 実装としてはYAGNIですが、ホワイトボードや紙に走り書きするのと同じ感じでラフにコーディング。
たまにクラス図を眺めてみたり。この時点で頭の中で描けないようなのは詳細に立ち入りすぎかなーとも思います。
「先に設計しますよね?」
「先にある程度考えてたんですか?当然、普段は先に設計しますよね?」と言った質問がありましたが、冒頭に挙げた通り「コードをどまんなかに」なので、これが初期設計です。その時点でわかっていることを雑にコードで書きます。ただし、この時点はモデルの関連だけ。基本的に操作は書きません。この日は控えめだけど、基本的には認識してるものは全部書くので、もう少し多くなる。かも。
強調しておきますが、これは「初期モデル」です。絶対変わるし、間違ってる。立ち止まって落ち着いて確認する時間は後から何度でも取ります。欲しいのはコードからのフィードバック。間違っても完成と思わないくらいにラフなのを、ささっと短時間で書きます。間違ってたとしてもいいんです。と言うか、間違って捉えてたことのフィードバックを得るために書いてるところがあります。
新たに使用したIDEの機能
■タスクが取れる
TODOリストなんで、タスクがみれなきゃ困ります。 テストから書きたいところですが、テスト対象クラスでテストへ切り替えのキーを押せばテストクラスを作ってくれることは知っています。必ずセットで作るのはわかっているので、テスト対象クラスから作ります。無理矢理テストから作っても遅くなるだけなので。クラスを作り、テストクラスを生成し、テストを書き始める。ファイルの移動も少なくてスムーズです。メソッド名が微妙だなーと思いつつ、REDからGREENへ、とにかく通す。
テストメソッドは忠実にアサートファーストで書きます。だけど先走って型をStringにしてしまって「あれ?」と思ってたら、うらがみさんに突っ込まれました。素直に直したら「感心してたのに・・・」と残念がられました。くそう。
新たに使用したIDEの機能
⌘
+Shift
+T
新規テスト作成test
+Tab
(LiveTemplate)テストメソッド作成Alt
+Enter
(空気読め機能)TodoService.new
+Tab
(Postfix)コンストラクタ呼び出しに変換
■メソッド名変更
スペルチェックで警告だされてるし、それほどこだわりのない名前なので、メソッド名を変えます。割れ窓理論信者なので、コードを健全な状態で保ちたいのです。警告が多いと気づかなくなるのが勿体無い。もしこの名前にこだわりがあるなら、スペル辞書に登録して警告を出なくします。警告が出ている状態はREDに近い扱いですね。
新たに使用したIDEの機能
- スペルチェック(勝手に波線が出てるだけ)
Ctrl
+T
(リファクタリングメニュー)メソッド名のリネームShift
+F6
でもいいのだけど、押しにくいのでCtrl
+T
で選ぶ(と言っても一番上なのでEnter押すだけ)ほうが早い
■タスクが登録できる(RED)
ツッコミがあったところ。賛否両論、と言うか賛はあまりない感じでしたかね。
TODOリストの肝であるタスク登録に着手します。ベイビーステップで行くこともありますが、これくらいならいけるだろうと大きめの歩幅でいきます。ダメだったら戻ればいいんですよ。
まず検証方法を考えます。先ほどとれるようになったタスクをユーザーが見る場合、何かしらの形で文字列化されるはずです。なのでタスクを文字列化したらタスク名が入っている、としましょう。
- assertThat(actual).isNotNull(); + assertThat(actual.asText()).contains("yarukoto");
で、これが登録できればいい。タスクを登録する。
+ sut.add(task);
タスクはタスク名をつけて作る。タスク名はさっきアサーションで書いたやつ。
+ Task task = new Task(new TaskName("yarukoto"));
アサートファーストで下から順番。アサート書いて、それを実現するための操作(タスク登録)を書いて、必要な準備(タスク生成)をする。
新たに使用したIDEの機能
「テスト変えちゃうの?」
元のテストを残さずに変更しています。タスク登録してない状態のアサーションがなくなりました。これで「タスク登録しないとnullかもしれない」と疑心暗鬼になれます。
変更してしまう理由はいくつかありますが、まず第一にタスク未登録でnullになるような実装はしないので変更前の検証がエラーになることはありません。基本的に初っ端に書いたクラスやメソッドを導くためのテストは、メンテに向いていません。テストリファクタリングをしてもいいのですが、落ちないテストに価値はありませんし、未登録時のふるまいなんかはそのうちモデルとテストが要求してきます。なので「未登録時はnullでない」程度のテストは維持せず変えちゃうわけです。
「ドメインモデルとしては……」
「asText
がドメインにあるのがおかしい」と言った指摘がありましたが、ドメインは自身が文字列で表現されるときにどうするかを知っていると思います。「メソッド名 asText
にドメイン感がない」と言ったところはあるかもしれませんが、妥当な名前が出てくればその時変えればいいです。この時点ではそれなりな名前で十分だと思っています。名前には閉じた世界の中での一貫性が重要で、その検討をするにはドメインの理解や登場するモデルの数が不足しています。この時点でメソッド名の検討は時期尚早。早く作ってフィードバックを得るのが良いです。材料が少ない状態で検討しても的外れになりやすいですし、何より分析麻痺になるのが怖い。よほど精通したドメインでもない限り、モデルが出揃ってない状態で検討できることではありません。
コードスメル: 知識がテストに書かれている
このコードは少し異臭がします。「TaskのコンストラクタがTaskNameを受け取る」と言う知識はテストクラスが持っているべきではないことでしょう。Taskインスタンスの生成方法を知る必要はありません。こう言うのがテストにあると、Taskを要求する全ての場所に同じコードがクローンされます。そこはかとなく臭いので、たぶん近い将来このコードはテストからは消え去ると思われます。でもまだ重複していないので、何かを考えるには時期尚早という判断をします。本当に複数箇所にクローンされるのか、生成のバリエーションが出ないか、などを少し待ってみてもいいかなー、と思ったり。もちろん油断すると大変なことになるので、臭いには気をつける。
■タスクを登録できる(GREEN)
さて、最速でテストを通しましょう。add
されたタスクをリストに保持し、asText
メソッドはタスクリストを文字列表現すればよい。タスクはタスク名を文字列表現できればよい。タスクの文字列表現は、とりあえず toString
でいいや、と。余計な色気は出さず、とにかく最速でGREENにします。(それでもなんか詰まった気がするけど、忘れた。)
実はこの時点でリストを持ち出すのはYAGNIで、テストを通すだけならフィールドで十分だったりします。でも気にせずリストにしちゃいます。フィールドだとnullが登場しかねないから避けたいと言うのもある。リストを導くために複数登録するテストにしちゃうのはありかもだけど、面倒なのでしない。
新たに使用したIDEの機能
⌘
+Alt
+F
ローカル変数のフィールド化
「テストの検証が厳密でない」
この実装だと実際の文字列は "[yarukoto]"
で、テストの検証は contains("yarukoto")
になっていますす。当然 isEqualTo("[yarukoto]")
と書いてもテストは通りますが、後者でないことに違和感があったようで、疑問の声がありました。
前段のテストで描いた未来は「登録したタスク名が読み取れる」です。登録した文字列が含まれてればOK。前後に何がついていようと別に困りません。困るのは "yarukoto"
を登録したのに "yarukoto"
って文字列が見当たらないこと。なので contains
を選択しています。Taskのequalsなどで検証していないのは、同じTaskかどうかにあまり興味はないことと、文字列化はアプリケーションに必要なことと、テストのためだけのhashCode/equalsは開発を加速しないからです。
「登録したタスク名が読み取れる」は、おそらく完成したとしても変わりません。テストはピンポイントで重要なところだけ抑えないと、テスト対象をコールタールに沈めて身動き取れないものにしてしまい、開発の足を引っ張ります。例えば仮に isEqualTo("[yarukoto]")
としてしまうと、テストが ArrayList
の文字列表現に依存します。本来そこに依存関係はないので実装詳細に寄りすぎる検証となり、例えば「改行区切りにしたい」となったら即死です。(「改行区切り」を実現したくなったら、複数件を登録して「改行区切りになるよー」ってテストを書くとは思いますが。)
現地ではうまく説明できなかったと思っていたのだけど、存外伝わってたようでほっとしました。
やってみて
ってところで、ちょいちょい説明しながら、ネットワークとキーボードのトラブルもあったけど、合計20分とちょっとかな?最後はSpringShellを紹介するのに使いました。
「説明できるのが良いコード」としょっちゅう言ってる私ですが、だいたいこんな感じで自分に説明(言い訳)はしてる感じです。無意識にやってるものも多いですが、たまに確認はしてます。 でも今回のイベントで他人に説明するのは面白かったです。思った以上に言葉が出てこず、伝わってる感じもしなくて。変なこと言っちゃってるなーと内心ドキドキしながらやってました。
そういえばスライドでは「説明する機会はない」とか言ってたけど、なんかその機会貰えましたね。ありがとうございました。
- 作者: Kent Beck,和田卓人
- 出版社/メーカー: オーム社
- 発売日: 2017/10/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る
せっかく買ったし、久々に読み直そう。
Javaアプリケーションを作るときにまずやってること
DevLOVE関西で実験的なイベントをさせてもらいました。
このイベントのおかげで、自分がアプリケーション作るときに最初にどうしてるかを確認できたので晒してみます。誰かの役に立つかどうかは知らない。あ、Macです。ある程度はWindowsでもいけると思うけど、ある程度だと思う。
なおSTSとかIDEのプロジェクト生成機能を使えば一発で済むことだったりする。宗教上の理由で使わないけど。
やってること
基本的にマウスを触る必要はないです。それぞれの詳細は次の通り。長く見えるかもしれませんが、(IDEAの起動とgradleのjarのダウンロード時間を除けば)全部で30秒くらいです。
作業ディレクトリを作る
% git init {作るもの} % cd {作るもの}
mkdir {作るもの}
して cd {作るもの}
して git init
することもあります。
たまにこの段階で README.md
を作ってコミット、ライセンスファイルを作るときはGitHubで作ってからcloneします。
ビルドスクリプトを用意する
記憶かける程度ならそれで。
% vi build.gradle
だいたいこれくらい。たまに cat>build.gradle
で書いたりもしますが、気分です。
apply plugin: 'java' repositories.jcenter() dependencies { testCompile 'junit:junit:4.12' }
Javaのバージョンやエンコードの設定などが無いですが、一旦作ってから後で変えればいいと思っているのでこだわりません。
これよりも多く書く場合(作るものが決まってるとか)は次のいずれかになります。イベントでは「手元のテンプレートをコピる」をしました。
- 手元のテンプレートをコピる
- ググる
- Spring Initializr
手元のテンプレートをコピる
% cp {テンプレートのあるとこ}/build.gradle ./
ググる
ググって出てきたの(主にGettingStarted)を切り貼りします。
MavenCentralRepositoryを検索
使いたいgroupIdとかがわかっている場合はこっちで検索。
Spring Initializr
便利ですね。
必要なディレクトリを作る
% mkdir src/main/java src/main/resources src/test/java src/test/resources
これをするエイリアスを作っています。前段のテンプレートをコピるのをあわせたのもあるので、そっちを使うことが多いです。
alias mkjavadirs='mkdir src/main/java src/main/resources src/test/java src/test/resources' alias initSpringProject='mkjavadirs && cp {テンプレートのあるとこ}/springboot.gradle ./build.gradle'
と言う感じ。
IDEを起動して取り込む
% idea build.gradle
IntelliJのコマンドラインランチャーを使います。
Tools / Create Command-Line Launcher
から作れるもので、 idea
コマンドで新しくプロジェクトが開けます。
コマンドラインラインチャーを作らないなら、IDEAを立ち上げてからOpenでbuild.gradleを選ぶのですが、若干面倒。
テストを書いて実行する
こんな感じのテストを作って実行します。
import org.junit.Test; public class HogeTest { @Test public void test() throws Exception { } }
即捨てるのでデフォルトパッケージです。確認したいのは環境です。テストが実行できる環境になっているかとか。
面倒に思えるかもしれませんが、テストの作成は以下でできます。
- Projectの
src/test/java
ディレクトリを選択[Command]+1
から上下キー、もしくはマウスクリックします
[Command]+n
で新規のメニューを開いて[Enter]
- 新規クラス作成のダイアログが出る
HogeTest
[Enter]
- クラスができる
[Esc]
- エディタがアクティブになる
O
- 新規行ができる。vimすばらしい。
test
[Tab]
- Live Templatesによりテストメソッドが生成される
test
- テストメソッド名を入力
[Ctrl]+[Alt]+r
[Enter]
- テストが実行される
キーを押す回数は30回程度、今やってみたら12秒でした。( HgeTest
ってtypoったけど。)
これをサボると、たまに「なんかビルドできない」とかで悩んだりして馬鹿らしいので、ビルドスクリプトを切り貼りしたときや、環境の更新をした後などは必ずやります。
SpringBootアプリケーションの場合
テストではなく Application
クラスで代用します。
起動すれば環境としては問題無いと判断できます。
コミットする
% git add . % git commit -m"init"
いつでもここに戻ってこれるように。
この段階のリポジトリ
GitHub - irof/DevKan20180305 at a4ccd5d761ccb58cd66bdbec1516cfb2f41852c6
コミットとしてはこの2つ。
- https://github.com/irof/DevKan20180305/commit/50e8815aa1d9e9d748187d5fd042e0777f3754a8
- https://github.com/irof/DevKan20180305/commit/a4ccd5d761ccb58cd66bdbec1516cfb2f41852c6
さらに前段のことや、周囲のことや、マルチプロジェクトの場合とか、とかとか
WEB+DB PRESS vol.98 「実践!イマドキのビルド環境」に書いてます!
- 作者: 丸山晋平,前佛雅人,横田真俊,小原薫,小笠原空宙,高橋征義,牧大輔,大沢和宏(Yappo),久田真寛,のざきひろふみ,うらがみ,池田拓司,ひげぽん,遠藤雅伸,海野弘成,はまちや2,竹原,日高正博,WEB+DB PRESS編集部,-
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/22
- メディア: 大型本
- この商品を含むブログを見る
Spring Boot Gradle Pluginの話(Spring Boot 1.5.x & Gradle 4.5)
Spring Boot 1.5.x とGradle 4.5 の話です。Spring Boot 2.0とGradle4.6が出たので、埋葬がてら書いておきます。 どんな感じで要らない話になったのかは最後に書いてます。
Spring Boot Gradle Pluginの機能
たいだい*1 だいたいリファレンスは先頭が重要です。なぜか読み飛ばされがち。コードがあったらそっち読んじゃうのはわかるけど。
The Spring Boot Gradle Plugin provides Spring Boot support in Gradle, allowing you to package executable jar or war archives, run Spring Boot applications and use the dependency management provided by spring-boot-dependencies.
- allowing you to package executable jar or war archives
- executable jarを作るための
bootRepackage
タスクが追加され、jar
タスクの後に自動的に実行されるようになる。
- executable jarを作るための
- run Spring Boot applications
bootRun
タスクが追加される。
- use the dependency management provided by spring-boot-dependencies
dependencies
のうち、Springが管理されるものはバージョンの記述が不要になる。
これからわかるのは、Spring Boot Gradle PluginはSpringBootアプリケーションプロジェクト(Mainクラスが入ってるやつ)には適切だけれど、SpringBootアプリケーションから使用されるモジュールにはdependency management以外は不要なので微妙ってことです。2/3を使ってないわけですからね。
単一モジュールで作っているなら問題にならないだろうけど、共通で使うモジュールとか作る際にうっかり使っちゃうと困ることになる。使わないもの( bootRun
タスクがそれ)はまだいいのだけど、勝手に動作する( bootRepackage
タスクがそれ)のが問題なのです。
起こりうる問題
たとえばこういう問題が起こります。
Main
クラスがないのでビルドエラーになる。- (仮に最初のを回避したとして)依存ライブラリがjarの中に入る。
他にもあるかもしれないけど、知らない。
対処
- (だめ) Mainクラスを作る
- (だめ)
bootRepackage.layout = 'MODULE'
bootRepackage
をしない- Dependency management plugin だけ使う
それぞれ書いていきます。
Mainクラスを作る
Mainクラスがないからエラーとか言われるからってMainクラスを作るやつ。要らないもの作っちゃだめですね。
レイアウトをMODULEにする
Mainクラスがなくてもビルドできます。モジュールを作ってるんだからそれっぽく見えるかもしれませんが、jarの中に依存ライブラリがぶっこまれます。そんなjarいらない。無駄にでかくなるし。
bootRepackageしない
そもそもrepackageする必要がないので止めちゃいましょう。
bootRepackage { enabled = false }
シンプルだし、プロジェクトによって使うGradle Pluginを変えなくていいというのはメリットかもしれません。技術スタックは少ない方がいいので。
でも使用しないタスクが入るところは微妙かも。
Dependecy management pluginを使う
Sprign Boot Gradle Pluginは中で Dependency management plugin を使っています。
コードではこんな感じでとりこんで、こんな感じで
spring-boot-starter-parent
をインポートしてます。これをDependency management Plugin を使う形で build.gradle
に書くとこうなります。
plugins { id "io.spring.dependency-management" version "1.0.4.RELEASE" } ... dependencyManagement { imports { mavenBom 'org.springframework.boot:spring-boot-starter-parent:1.5.10.RELEASE' } }
これで余計なタスクも入らない。Spring Bootアプリケーションを作ってるわけではないのだから、たぶんこれでいいはず。
モジュールの独立性とか言ったら(bomとはいえ)SpringBootに依存するのはどうなのとかあるでしょうけど、バージョンの整合性を取るのは面倒だし、自分たちしか使わないモジュールならこれでよかろーです。
Spring Boot 2.0では
マイグレーションガイドに書かれている通り、Spring Boot Gradle Pluginからdependency managementは除外されています。
Spring Boot 2.0 Migration Guide · spring-projects/spring-boot Wiki · GitHub
あと変わったことといえば、Spring BootのリファレンスのSpring Boot Gradle Pluginの記述がスッキリしてます。(最初に出したのと見比べて見てください)
リンクされてるSpring Boot Gradle Plugin Reference Guideがしっかりしたものになってる。あとAPIドキュメントもリンクされててて開きやすくなったのも嬉しい。
Gradle 4.6では
BOMをインポートする機能がはいったのでDependency management pluginもいらない。 かも。まだ試してない。
bom をインポートする機能はfeature previewで設定を追加しないといけなかったはず https://t.co/xXSWw1nMWw
— 仄暗い水の底から (@wreulicke) 2018年3月3日
リリースノートに書いてる通り、 settings.gradle
に1行いります。
こんな感じ。
*1: たぶんこの記事でここが一番大事。
「たいだいリファレンスは先頭が重要です。」
あ、あれ?たいだい・・・?
たぶん疲れ目なので30分後に見たら違うはず https://t.co/9HeRi7Gj4B
*2:Agent指定の何かとかエンコーディング変えたりとかもあるのだけど、これらは補助的なものなので書かれてないっぽい。