日々常々

ふつうのプログラマがあたりまえにしたいこと。

「あるエンジニアがプログラムを紡いでいく様を見てみる」ライブコーディング・リプレイ

あるエンジニアがプログラムを紡いでいく様を見てみるでしたライブコーディングで言ったことや言わなかったことを書いてみます。

意識してるのは「コードをどまんなかに」です。

speakerdeck.com

……あ、このスライドのブログ書き忘れてた。

スライド中の「えらぶ」はだいたいIDEの機能を指します。なのでライブコーディング中に使用したIDEの機能も挙げようと思います。基本的にデフォルトのつもりだけど、vimとの兼ね合いで変更してるのもあるので、そこはごめんなさい。あとMacです。今回はメソッド抽出とかクラス間移動とかダイナミックなのがなくて地味だけど、便利な子たちなので使ってあげてください。

リプレイ

ってことなので、コミットを追いながらいってみます。私の意図はどの程度コードに残ってるのかなー。

github.com

■最初は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に準じて作るつもりだったので。

■パッケージ名変更

前段であがった「 tododototypoしている」の指摘はあえてスルーしてました。typoを直すよりも動作確認を優先したかったからです。仮にtypoを直したとしても動作しなきゃ無駄になるし、typoは動作に影響しないことは知っているので、後回しです。動作確認ができたので、ここでリネーム。もちろんリネーム後はアプリケーションを実行して確認します。

新たに使用したIDEの機能

  • Shift + F6 ファイル名のリネーム
  • Alt + R 前回実行したものの再実行

■初期モデル作成

環境ができたら「TODOリスト」に対して現在捉えてるモデルを書きます。 実装としてはYAGNIですが、ホワイトボードや紙に走り書きするのと同じ感じでラフにコーディング。

f:id:irof:20180308171121p:plain

たまにクラス図を眺めてみたり。この時点で頭の中で描けないようなのは詳細に立ち入りすぎかなーとも思います。

「先に設計しますよね?」

「先にある程度考えてたんですか?当然、普段は先に設計しますよね?」と言った質問がありましたが、冒頭に挙げた通り「コードをどまんなかに」なので、これが初期設計です。その時点でわかっていることを雑にコードで書きます。ただし、この時点はモデルの関連だけ。基本的に操作は書きません。この日は控えめだけど、基本的には認識してるものは全部書くので、もう少し多くなる。かも。

強調しておきますが、これは「初期モデル」です。絶対変わるし、間違ってる。立ち止まって落ち着いて確認する時間は後から何度でも取ります。欲しいのはコードからのフィードバック。間違っても完成と思わないくらいにラフなのを、ささっと短時間で書きます。間違ってたとしてもいいんです。と言うか、間違って捉えてたことのフィードバックを得るために書いてるところがあります。

新たに使用したIDEの機能

  • Alt + Enter (空気読め機能)コンパイルエラーのフィールド型名から新規クラス作成
  • + N コンストラクタ作成
  • Tab 補完
  • + E 最近開いたファイル

■タスクが取れる

TODOリストなんで、タスクがみれなきゃ困ります。 テストから書きたいところですが、テスト対象クラスでテストへ切り替えのキーを押せばテストクラスを作ってくれることは知っています。必ずセットで作るのはわかっているので、テスト対象クラスから作ります。無理矢理テストから作っても遅くなるだけなので。クラスを作り、テストクラスを生成し、テストを書き始める。ファイルの移動も少なくてスムーズです。メソッド名が微妙だなーと思いつつ、REDからGREENへ、とにかく通す。

テストメソッドは忠実にアサートファーストで書きます。だけど先走って型をStringにしてしまって「あれ?」と思ってたら、うらがみさんに突っ込まれました。素直に直したら「感心してたのに・・・」と残念がられました。くそう。

新たに使用したIDEの機能

  • + Shift + T 新規テスト作成
  • test + Tab (LiveTemplate)テストメソッド作成
  • Alt + Enter (空気読め機能)
    • コンパイルエラーのローカル変数名からローカル変数定義作成
    • コンパイルエラーのローカル変数型名から新規クラス作成
    • コンパイルエラーのメソッド呼び出しから対象クラスにメソッド作成
  • TodoService.new + TabPostfix)コンストラクタ呼び出しに変換

■メソッド名変更

スペルチェックで警告だされてるし、それほどこだわりのない名前なので、メソッド名を変えます。割れ窓理論信者なので、コードを健全な状態で保ちたいのです。警告が多いと気づかなくなるのが勿体無い。もしこの名前にこだわりがあるなら、スペル辞書に登録して警告を出なくします。警告が出ている状態は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の機能

  • Alt + Enter (空気読め機能)
    • コンパイルエラーのコンストラクタ呼び出しから対象クラスにコンストラクタ作成
    • コンストラクタの仮引数名からフィールドを作成

「テスト変えちゃうの?」

元のテストを残さずに変更しています。タスク登録してない状態のアサーションがなくなりました。これで「タスク登録しないと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 の文字列表現に依存します。本来そこに依存関係はないので実装詳細に寄りすぎる検証となり、例えば「改行区切りにしたい」となったら即死です。(「改行区切り」を実現したくなったら、複数件を登録して「改行区切りになるよー」ってテストを書くとは思いますが。)

mdstoy.hatenablog.com

現地ではうまく説明できなかったと思っていたのだけど、存外伝わってたようでほっとしました。

やってみて

ってところで、ちょいちょい説明しながら、ネットワークとキーボードのトラブルもあったけど、合計20分とちょっとかな?最後はSpringShellを紹介するのに使いました。

「説明できるのが良いコード」としょっちゅう言ってる私ですが、だいたいこんな感じで自分に説明(言い訳)はしてる感じです。無意識にやってるものも多いですが、たまに確認はしてます。 でも今回のイベントで他人に説明するのは面白かったです。思った以上に言葉が出てこず、伝わってる感じもしなくて。変なこと言っちゃってるなーと内心ドキドキしながらやってました。

そういえばスライドでは「説明する機会はない」とか言ってたけど、なんかその機会貰えましたね。ありがとうございました。

テスト駆動開発

テスト駆動開発

せっかく買ったし、久々に読み直そう。

Javaアプリケーションを作るときにまずやってること

DevLOVE関西で実験的なイベントをさせてもらいました。

devlove-kansai.doorkeeper.jp

このイベントのおかげで、自分がアプリケーション作るときに最初にどうしてるかを確認できたので晒してみます。誰かの役に立つかどうかは知らない。あ、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とかがわかっている場合はこっちで検索。

https://mvnrepository.com/

Spring Initializr

https://start.spring.io/

便利ですね。

必要なディレクトリを作る

% 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つ。

さらに前段のことや、周囲のことや、マルチプロジェクトの場合とか、とかとか

WEB+DB PRESS vol.98 「実践!イマドキのビルド環境」に書いてます!

WEB+DB PRESS Vol.98

WEB+DB PRESS Vol.98

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の機能

67. 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 タスクの後に自動的に実行されるようになる。
  • run Spring Boot applications
    • bootRun タスクが追加される。
  • use the dependency management provided by spring-boot-dependencies
    • dependencies のうち、Springが管理されるものはバージョンの記述が不要になる。

つまり、プラグインの機能は主に3つ*2ということ。

これからわかるのは、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 を使っています。

GitHub - spring-gradle-plugins/dependency-management-plugin: A Gradle plugin that provides Maven-like dependency management functionality

コードではこんな感じでとりこんで、こんな感じ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の記述がスッキリしてます。(最初に出したのと見比べて見てください)

69. Spring Boot Gradle Plugin

リンクされてるSpring Boot Gradle Plugin Reference Guideがしっかりしたものになってる。あとAPIドキュメントもリンクされててて開きやすくなったのも嬉しい。

Gradle 4.6では

Gradle 4.6 Release Notes

BOMをインポートする機能がはいったのでDependency management pluginもいらない。 かも。まだ試してない。

リリースノートに書いてる通り、 settings.gradle に1行いります。

github.com

こんな感じ。

*1:

*2:Agent指定の何かとかエンコーディング変えたりとかもあるのだけど、これらは補助的なものなので書かれてないっぽい。