日々常々

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

MacBook Pro 16インチが届いたのでセットアップのメモ

f:id:irof:20191202194728p:plain

手が滑って購入ボタン押してしまったので仕方がない。

やったこと

  • 起動
  • Apple IDのログイン
  • macOSの設定
    • キーボードショートカットの切り替え
      • OSのデフォルトのショートカットはほとんどOFFにする
      • 入力ソースの切り替えを ⌘+スペース に変更する
    • 日本語変換の便利っぽいけど重くなるのを切る
      • ライブ変換、タイプミスを修正、推測候補表示
      • これ切るだけでだいぶもたつきがマシになった。デフォルトONなのにまだイマイチなのね……
  • iTunesのアカウント紐付け
    • 2台しか使ってないのに5台の制限超えてるって言われたので全解除……
    • 以降は音楽流しながら
  • パスワード管理ソフトをインストール
  • Chromeをインストール
    • Googleアカウントのログイン
    • いちいち拡張機能インストールしなくていいのが楽だった
  • homebrew をインストール
  • sdkman をインストール
    • sdk install java 11.0.2-open
    • sdk install gradle
    • sdk install visualvm
  • GitHubの鍵登録
  • irof/dotfilescloneして設定
  • brewでインストールしたiTerm2に切り替え
    • iTerm2のconfigファイルは手動で読み込み
  • dein をインストール
    • vimそのまま起動したらエラーって言われたんで。

だいたいインストールは終わったと思ったら

f:id:irof:20191202201505p:plain

お、おう……せやな。

しばらく セキュリティとプライバシー のウィンドウを開きっぱなしにして、brewでインストールしたアプリを起動→許可を繰り返す。

以降はアプリの個別設定

  • IntellJ IDEAインストール
    • 今まではIDEA直接インストールしてたけど今回はToolBox APP経由でインストール
    • IDEのショートカットは(IDEAに限らず)ほぼデフォルトなので特に問題ない、はず。リファクタリングメニュー開くやつくらい。
  • Slackを起動してワークスペースの追加
    • これが一番面倒だった……同じメールアドレスはいちいちログインしなくてもいいんだけど、いちいちブラウザとアプリの往復になって、即時反映されなくて、うん。
  • 以降普段使ってるdockerイメージをいろいろダウンロード……(継続)

完了

これ書きながらで1時間ちょっと。たぶん2時間はかかってない。

まだ効率化できるだろうけど2年に一度ならこんなもんかなぁ。しかし昔を思えば楽になったものですねぇ。

SpringBoot2.2でJUnit5がデフォルトになったのでbuild.gradleを書き換える

SpringBoot2.2でJUnit5がデフォルトになったー。

他の更新はリリースノート見てくださいまし。 ちなみに私に関係ありそうなのはツイートした。触れてないのは単に使ってないってだけで、重要じゃないってわけじゃないです。

build.gradle の書き換え

SpringBoot2.1以前

implementation platform('org.springframework.boot:spring-boot-dependencies:2.1.6.RELEASE')
testImplementation("org.springframework.boot:spring-boot-starter-test") {
    exclude(group: 'junit')
}

testImplementation platform("org.junit:junit-bom:5.4.2")
testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("org.junit.jupiter:junit-jupiter-params")
testRuntimeClasspath("org.junit.platform:junit-platform-launcher")
testRuntimeClasspath("org.junit.jupiter:junit-jupiter-engine")

まず junit の除外。 入ってても実行時の悪さはしないんだけど @Test 使うときとかにJUnit4のが補完ででてきたりすると邪魔だし、うっかり使ったりしてても困るのでやっている人は多いと思います。

で、JUnit5を使うために諸々追加。 junit-bom は使わなくても spring-boot-dependencies で定義されてるんだけど、そっちで入る 5.3.2 じゃなく 5.4.2 を使いたかったから。 あとはSpringBoot使っててもSpringBootTestを使わない場合とかもあるんで、このセットで入れることはままあります。

SpringBoot2.2以降

implementation platform('org.springframework.boot:spring-boot-dependencies:2.2.0.RELEASE')
testImplementation("org.springframework.boot:spring-boot-starter-test") {
    exclude(group: 'org.junit.vintage')
}

シンプル。 SpringBootよりも新しいJUnit5を使いたくなったらまた記述は増えるんだろうけど、そこまで最新にこだわらないのなら勝手に入るのに任せてもいいと思います。

でもJUnit4は要らないから org.junit.vintage は除外する。

おまけの話(こっちが本編)

単にバージョンだけあげると

こんな警告でてきます。

10月 27, 2019 4:06:30 午後 org.junit.platform.launcher.core.DefaultLauncher handleThrowable
警告: TestEngine with ID 'junit-vintage' failed to discover tests
java.lang.NoClassDefFoundError: junit/runner/Version
    at org.junit.vintage.engine.JUnit4VersionCheck.checkSupported(JUnit4VersionCheck.java:32)
    at org.junit.vintage.engine.VintageTestEngine.discover(VintageTestEngine.java:61)
    at org.junit.platform.launcher.core.DefaultLauncher.discoverEngineRoot(DefaultLauncher.java:177)
    at org.junit.platform.launcher.core.DefaultLauncher.discoverRoot(DefaultLauncher.java:164)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: java.lang.ClassNotFoundException: junit.runner.Version
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
    ... 9 more

警告がでるのはexclude(group: 'junit') を指定している場合で、junit-vintage-engine 経由で入るはずの junit:junit が入らないから。なんだ自業自得じゃん。警告なんで無視してても動きはします。

警告って難しくて、それ単体なら無理に対応しなくてもいいんだけど、出続けると警告が出てることに慣れてしまうので、逐次潰していかないと他の警告も見なくなってしまう。これは人間だから仕方ないと思います。でもきっちり対応しようって思うとそこそこかかるんですよね。悩ましい。

正しい(?)対応は以下の通り。

  • exclude(group: 'junit') を削除
  • exclude(group: 'org.junit.vintage') を追加
  • 明示的に追加してた junit-jupiter たちを削除(これは方針次第)

そこそこかかった対応

警告に出てる例外を出しているのは junit-vintage-engineJUnit4VersionCheck で、自分が動こうにもクラスパスに junit.runner.Version がない。だから例外を投げる…… junit-vintage-engine としては正しいかな。

警告ログを出しているのは junit-platform-launcherDefaultLauncher で、例外を受けてログ出して処理続行。 そうしてるのは知ってるTestEngineをループしながら「実行できる?」って聞いてってるから。 TestEngineServiceLoader でクラスパスにあるのを拾ってきたり、設定で追加されたりされるもの。どんな TestEngine 入ってくるかは実行時任せだから、一つ実行できないのがあったからって止めるのは無しなんでしょう。

状況から VintageEngineServiceLoader に拾われていることがわかった。で、使う気のない VintageEngine を追加してるのが誰かを gradle dependencies で見たら spring-boot-starter-test だった。

この辺で「あーね」となって上の対応になりました。

こう言うのを秒で対応できるようになりたい。

あと build.gradle.kts に乗り換えようとしてるんだけど、なかなか捗らない。

脱ブランチファースト

あるいは「プルリクエストをやめてみた」

チーム構成とかにもよるんだろうけど。Gitかつフォークされないプロダクトでの話です。OSSとかは全然話は変わります。

問題とアプローチ (2019-10-25T15:20 追加)

表出している問題と、ここでのアプローチを書いておきます。

ブランチファースト(造語)

「ブランチファースト」はこのエントリでの造語です。コードベースに変更を加える際に「まずブランチを作成する」から始めることを指します。 作業単位でブランチを作成、ブランチでコードを変更してプルリクエスト、レビューを経てメインライン( master ブランチ)に反映までがブランチファーストのスコープになります。 よくあるスタイルだと思いますし、ブランチだけ作成して変更せずプルリクエストを作成する拡張もありますね。

プルリクエストを挟まずにメインラインにマージするものは含みません。 ……名前微妙な気がしてきた。

表出し ている がちな問題

  • マージに時間がかかっている
  • マージミスが起こりがち
  • マージしたらテストが通らない(個々のブランチは通るのに)
  • フィーチャーブランチの生存期間が長い
  • フィーチャーブランチが乱立している
  • リリースブランチにしかないスクリプトが忘れられがち
  • ボーイスカウトルール的なリファクタリングが重量プロセスになる
  • プルリクエストが滞留している
  • プルリクエストのレビューは変更部分以外にコメントしづらい

アプローチ

  • 全員がメインラインに直接 commit して push しよう

と言うことで続きをどうぞ。

フィーチャーブランチをやめてみた

フィーチャーブランチ。タスクブランチとかいろんな呼び方があると思うけど、特定の作業毎に作るブランチ。チケットやissue単位で作るブランチもこれに該当します。

フィーチャーブランチを作るとメインラインとの間でマージが必要になる。メインラインにフィーチャーブランチをマージする際の負荷を制御するために、メインラインからフィーチャーブランチへのマージを頻繁に行っておくとかあるけど、フィーチャーブランチを作るから発生する作業にみえるんです。やること増やしちゃってないかなって。

リリースブランチをやめてみた

リリース作業を行うためのブランチ。 開発できたものを取り込んで、リリースのための作業をして、えいやとリリース。 リリース先はステージング環境だったり、プロダクション環境だったり。 環境ごとにリリースブランチを作って、今どんなソースがリリースされてるかわかる。

OKなるほど。タグでよくないかな。

メインラインとリリースブランチに差があることがある。その差分をメインラインに入れれないのはなんでだろうって見てみると、リリース用の設定だとかスクリプトとか。他にもフレームワークなどがメインラインに入れづらい設計だったりすることもある。かもしれない。少なくとも私はリリースブランチがなかったら不可能というのは記憶にないです。

メインラインとリリースブランチが一致すると、ほんとタグがわりに使ってるだけになる。無駄にブランチの形してるから、リリースのたびにマージが発生する。FastForwardで済むんだけど、リリースの記録とか残したいし、で --no-ff したりする。いやほんとタグでいい。

リリースのための設定やスクリプトであってもメインラインに置いて、リリース以外の時も使えるものってあったりするので、メインラインに置いて目に入りやすくしておくのがいいかな。

使いたくなるのはわかる

ブランチ。使いたくなるのはわかるつもりです。私も「ブランチが軽量に使えるGit超便利!」とか思ってました。

ブランチって機能があるし、どこでも使ってるし、それなりに上手くいってる(上手くいくってなんだ?)ように見える。なんちゃらフローとか実績のあるブランチ戦略もある。 ブランチ戦略って言葉格好いい。 使うには十分な理由かもしれません。

ブランチを使うことで作業が独立して行えて、変更差分のレビューがしやすい。なるほどわかる。

けど。けどね。

ブランチを使うと背負いこむこと

ブランチを使うと作業がブランチに閉じます。閉じるのがいいって話があるのかもしれないけれど、例えばフィーチャーブランチならそのフィーチャーと関係のないリファクタリングがしづらくなります。

例えば名前変更、パッケージ変更なんて顕著ですね。フィーチャーブランチの効果の一つに「差分がわかりやすくなる」と言うのがあるので、フィーチャーブランチの中でその手の作業をするのは抵抗が生じてきます。

この例だと、

  1. フィーチャーブランチの外でリファクタリングのフィーチャーブランチを別に作成
  2. リファクタリングのフィーチャーブランチで作業してメインラインにマージ
  3. メインラインからフィーチャーブランチにマージ

のようなステップを踏むことで、フィーチャーブランチはフィーチャーに閉じれます。うまくいくように見えますが、全体に渡る大規模な名前変更の場合、3のマージのコストが高くなりがちです。その場合にマージを諦めてフィーチャーブランチを破棄してやり直すと言う手が取れますが、なかなか捨てづらいようでマージ頑張るのをよくみます。で、破棄しやすいようにフィーチャーブランチの生存期間を短くするルールが作られて、これはこれでいいと思うんですが、ルールでがんじがらめになっていくのはあまり好きじゃない。

と言うか名前変更のためにこんなステップ踏みたくないし、これを終えるまでフィーチャーブランチの中は違和感ある名前でやることになる訳だし、フィーチャーブランチで作業してる中で見出した名前変更を単独で適用するのもなんだかなーとか……。

ともかく、ブランチを作るとマージのコストとリスクを背負いこみます。 マージはメインライン origin/master からローカルブランチ master へのマージ以外でやりたくない。

つまり、プルリクエストをやめてみた

プルリクエストのような差分だけに注目するレビューが無駄とは言わないのですけど、いつも必要な訳でもないと思うんです。

差分レビューの欠点の一つはボリュームが大きくなったらみづらくなる、場合によっては見なくなること。変更差分が大きいほどしっかり確認する必要があると思うんですが、差分が小さい時だけ見るのを促進する仕組みには疑問を感じています。

もう一つは差分を含めた全体のレビューがしづらいこと。差分だけだと見づらいぎこちなさは気づけませんし、プロダクトとしては個々の差分より全体に価値があると思うのですよ。そして差分で出ていないところへのコメントもつけづらい。コードのリンク併用で書けばいいんだけど、そういう特別な対応が必要になるんですよね。

なので とっととメインラインに入れてしまって、必要ならそこから直してしましょう でいいと思う。

「それだとどんなことするかわからない」とかお声が聞こえてくる気がします(実際言われたこともある)が、それって何をするかわからないメンバーを既にチームに入れてるってことですよね。コードだけ取り繕ってる場合ではないのでは……。

チームに入ったばかりだとかでお互い様子見してるときは「閉じた安全な環境」としてブランチ+プルリクエストもいいと思います。でも最初のうちだけかな。 ブランチ+プルリクエストをやってるうちは、プロダクト全体への責任感なんて出てきづらいと思う訳です。 ペアプロでもモブプロでもなんでもして、早くメインライン直接触ってもらえるようにする。で、コードのどこでも自分の意思で触っていい、触ってダメなら戻せばいい、とかやってるうちに「ちょっと名前わかりづらいかな」とかをカジュアルにリファクタリングできるようになってくるかなーと。

この辺り、根底には「できるようになってからやりましょう」ではなく「やってることしかできるようにならない」という考え方があります。失敗しても死なないんだから、準備はそこそこにしてやっちゃえばいいと思うんだ。

例えば、作りかけがコミットできなくなると言う話

抽象化によるブランチ でなんとかなるはずです。橋の架け替えとか、ストラングラーパターンとか。色々手はあります。

「いろんなところを一度に変えなきゃいけないからブランチが適切」はたぶん変更箇所を局所化するリファクタリングを先に行うのが適切なんですが、コストがかかるのも事実。でもそう言うのに挑むことで付く設計力とかもあると思います。ブランチファーストはそれを阻害してるんじゃないかなって。

「レガシーコードからの脱却」にも記述があるので引用しておきます。

コードがシステムに統合されると、リスクはほぼゼロになる。コンポーネントが別ブランチになっていてリリース前に統合される場合、リスクは統合されるまでわからない。ブランチを使う代わりに、フィーチャーフラグを使用して、使う準備のできていない機能を無効にしよう。

7章 プラクティス3 継続的に統合する/ 7.7 実践しよう/ 7.7.2 リスクを減らす7つの戦略 / ブランチを避ける

レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス

レガシーコードからの脱却 ―ソフトウェアの寿命を延ばし価値を高める9つのプラクティス

もちろん、適切な場合には使う

ブランチがあっているものもあります。なので禁止したいわけじゃない。 ブランチファーストになっているのに違和感があって、必要な時だけ使うのがいいなと思ったのでやめてみたと言う話です。 禁止したいわけではありません。 作り捨ての実験とか。複数バージョンをメンテナンスしなきゃいけないとか。抽象化によるブランチの設計コストが全く見合わないとか。他にも色々あると思う。「ブランチが適切」となる場合もあります。適切なら素直に使うのは当然のお話。

参考

このエントリには直接関係ないし、私が読んだのも結構前でだいぶ忘れてるけど、構成管理本挙げておきます。

パターンによるソフトウェア構成管理 (IT Architects’ Archive―ソフトウェア開発の課題)

パターンによるソフトウェア構成管理 (IT Architects’ Archive―ソフトウェア開発の課題)

2006年(原著は2002年)、10年以上前ですか。古い本に挙げていいと思うけれど、それほど陳腐化してないと思います。一読しておくと整理しやすくなるんじゃないかな。