JJUGナイトセミナー「Java SE 10 / JDK10 リリース特集」に行ってきた #jjug

先日、JJUGナイトセミナー「Java SE 10 / JDK10 リリース特集」に行ってきました。簡単に所感をまとめます。

jjug.doorkeeper.jp

メモから抜粋。(資料が公開されたら貼っておきます ⇒ 2018/03/29 追加しました)

JDKリリースモデル変更について

  • 5/17 Java Day Tokyo 2018
  • OpenJDK
    • 無償提供
    • GPLv2 + Classpath Exception
    • 6ヶ月単位でリリース
    • 3ヶ月単位でパッチリリース
  • Oracle JDK
    • 有償のみ
    • 1バージョン8年サポート (他のOracle製品同様)
    • 3ヶ月単位でパッチリリース
    • 3年間隔でリリース
    • 新しいリリースモデルに準拠するのは Java 11 以降
    • 9, 10 は OpenJDK のリリースモデルに準拠
  • ドキュメントは OpenJDK から提供される
  • Deprecated の運用ルールはそのまま (1年で削除される)

JDK10の追加機能解説 - JEP286/ローカル変数の型省略(var記法)を中心に

www.slideshare.net

  • JEP 286
  • ローカル変数の型推論
  • 初期化のときに型宣言を省略して読み易くする
  • ローカル変数は for ループ, for-each, try-with-resources を含む
  • ローカル変数の型推論 != 動的型付け
  • コンパイラの話でありランタイムレベルの話ではない
  • 従来のコードが簡潔に書けるようになる
  • 型が明確であれば読み辛くなることはないはず (もちろんすべてに当てはまるわけではない)
  • なぜローカル変数だけなのか
    • フィールドやメソッドの引数などの影響は複数のファイルにまたがるため
  • Denotable types
  • varが常に何らかの型でリプレースできるとは限らない
  • varは型の名前には利用できない (Varは大丈夫)
  • 変数名には利用できる
  • var var = new var();
  • 書き易さより読み易さ
  • ローカルコードで理解できることが大事 (どう動くか判断できること)
  • IDEなどのツールに依存するべきではない
  • 明示的に型を宣言するのはトレードオフ (すべてのローカル変数をvarにすることは避ける)

既存コードを一気に var に置換するとかは NG です。あと、原則やガイドラインは以下にまとまっています。とても重要。

orablogs-jp.blogspot.jp

個人的に興味深いのはこれ。これが問題になることはあまりないとは思いますが。

// ORIGINAL
List<String> list = new ArrayList<>();

// Inferred type of list is ArrayList<String>.
var list = new ArrayList<String>();

あと、おもしろかったのはこれ。

PriorityQueue<Item> itemQueue = new PriorityQueue<Item>();

// OK: both declare variables of type PriorityQueue<Item>
PriorityQueue<Item> itemQueue = new PriorityQueue<>();
var itemQueue = new PriorityQueue<Item>();

// DANGEROUS: infers as PriorityQueue<Object>
var itemQueue = new PriorityQueue<>();

// DANGEROUS: infers as List<Object>
var list = List.of();

var とダイアモンド演算子を併用すると Object として型推論される模様。後日、同僚と話してたんですが、var とダイアモンド演算子を併用するときはコンパイルエラーにするとか、IDE で警告出すとかしてもよさそうですねという話になりました。

もう1つおもしろかったのはこれ。

// ORIGINAL
byte flags = 0;
short mask = 0x7fff;
long base = 17;
 
// DANGEROUS: all infer as int
var flags = 0;
var mask = 0x7fff;
var base = 17;

たぶん32ビット以下の値が int として型推論される模様。

あと、きしださんのエントリ。

d.hatena.ne.jp

Java 10でぼくたちの生活は どうかわるの?

www.slideshare.net

  • Java XX で動かしてみる
  • Java XX でコンパイルしてみる
  • jdeps や jdeprscan で内部APIか非推奨APIを使ってないか確認する
    • jdeps : 依存関係を調べる
    • jdeprscan : 非推奨APIを使ってないか
  • Parallel Full GC は効果的だが、そもそも Full GC が頻発するような状況自体がよくない
  • LTS のバージョンごとにアップデートするといきなり削除されている API が出てくるかも
    • 基本的には @DeprecatedforRemoval=trueAPI が将来的に削除される
  • immutable コレクションの確認/作成を自前でやってるところは標準 APIcopyOf で置き換え推奨
  • RuntimeMXBean でプロセスIDが取得できる

Java 9, 10 は Java 11 サポートに向けて検証するために利用するのがよさそうとのこと。Java 8 から 9 を乗り越えるのが大変っぽい。それ以降はそれほど大きな壁はないはず。

Spring の @Validated と Bean Validation の javax.validation.groups.Default と groups について

実際の挙動がイメージとちょっと違うのでメモる。

パターン1

バリデーショングループのインタフェースで javax.validation.groups.Default を継承しないパターン。

interface Group {}

@NotNull
private String foo;
@NotNull(groups = Group.class)
private String bar;

Controller で @Validated とする場合、groups が未指定 or Default.class を指定したアノテーションのフィールドがバリデーション対象になる。ここでは foo のバリデーションが実行される。

一方、Controller で @Validated(Group.class) とする場合、Group.class を指定したアノテーションのフィールドがバリデーション対象になる。ここでは bar のバリデーションが実行される。

パターン2

バリデーショングループのインタフェースで javax.validation.groups.Default を継承するパターン。

interface Group extends Default {}

@NotNull
private String foo;
@NotNull(groups = Group.class)
private String bar;

Controller で @Validated とする場合、groups が未指定 or Default.class を指定したアノテーションのフィールドがバリデーション対象になる。ここでは foo のバリデーションが実行される。

一方、Controller で @Validated(Group.class) とする場合、Group.class を指定したアノテーションgroups が未指定 or Default.class を指定したアノテーションのフィールドがバリデーションの対象になる。ここでは foobar のバリデーションが実行される。

まとめ

当初、パターン2の挙動がイメージと違ってたんですが、こうやってまとめてみると別におかしくないですね...。Group インタフェースは Default インタフェースのサブインタフェースなので、is-a の関係になります。なので、パターン2において、Default を指定したときは Group.classアノテーションは対象外になるし、Group を指定したときは Default.classGroup.classアノテーションが対象になるのは当たり前ですね。

SAStruts のフォワードとリダイレクトについて

Seasar/Struts チョットデキルマンのみなさんには常識かと思われますが、@Execute の属性に redirect=true を指定してるのに実際はフォワードで遷移することがあったり、そのあたりの挙動がイマイチ理解できてないので少し調べてみます。

デフォルトはフォワード

Super Agile Struts - Annotation Reference

redirect要素(オプション) 遷移先にリダイレクトで遷移するかどうかを指定します。 デフォルトはfalseで、リダイレクトはしません。

redirect=true を指定するとリダイレクトになる。

エラーメッセージがあるときはフォワード

sa-struts/ActionWrapper.java at master · seasarorg/sa-struts · GitHub

if (redirect && ActionMessagesUtil.hasErrors(request)) {
    redirect = false;
}

ActionWrapper の execute の中でエラーメッセージがあるかチェックしてフラグを上書きしている。この場合、@Executeredirect=true を指定してもフォワードになる。

Action メソッドで返却するパスの末尾に redirect=true を付けるとリダイレクト

Super Agile Struts - Feature Reference

リダイレクトで遷移したい場合は、 パスの最後にredirect=trueを追加します。 =の前後に余分な空白は含めないでください。 redirect=trueの部分は、実際に実行されるときには、消去されます。

sa-struts/S2ActionMapping.java at master · seasarorg/sa-struts · GitHub

if (path.endsWith(REDIRECT)) {
    redirect = true;
    path = path.substring(0, path.length() - REDIRECT.length() - 1);
}

S2ActionMapping の createForward の中で redirect=true があるかチェックしてフラグを上書きしている。この場合、エラーメッセージがあってもなくても強制的にリダイレクトになる。

現場からは以上です。

JSUG勉強会 2018年その2 SpringとAPI時代の動く仕様への取り組み に行ってきた #jsug

JSUG勉強会 2018年その2 に行ってきました。簡単に所感をまとめます。
(2018/03/12) スライドのリンクを追記しました。

jsug.doorkeeper.jp

www.slideshare.net

  • システム設計の謎を解く
    • "設計とは考えること、アウトプットはその一側面である"
  • テストできないテストケース
    • 観点は書かれている
    • テストするための事前条件が書かれていない
  • テンプレート、フレームワーク
    • given: 事前条件、状態
    • when: 対象に対する操作
    • then: どうなるか
  • Spock: テストフレームワーク

    • ↑のテンプレート形式で書ける
  • 自動テストは動く仕様である

    • プログラムで表現できれば繰り返し検証できる
    • 動かして確認できる仕様という位置付け
  • テストメソッドの名前を given - when - then の形式で書く

  • テストで確認したい部分が仕様

    • プログラムレベルや操作レベルの内容はテストコードから隠蔽してよい
    • 仕様とは直接関係ないもの?
  • 状態中心のテスト

    • テスト対象の処理前後で状態の変化を検証する
  • 相互作用中心のテスト

    • テスト対象のオブジェクトのやり取り(どのメソッドが何回呼ばれたかなど)を検証する
  • 仕様はビジネスに使う言葉で表す

  • 仕様の粒度に気を付ける

漠然と思ったことなのですが、単体テストコードって仕様に対する品質保証より、プログラムに対する品質保証というかプログラマ心理的な安心を得るための役割の方が大きいのかなという気がしています。変更やリファクタリングするときとか。

なので、自動テストとは言っても、E2E のテストはさておき、JUnit などでやるようなメソッド単位のテストは、仕様を確認するというには少し粒度が細かすぎるのではないかという印象。

www.slideshare.net

後半は、Swagger, SpringFox, Spring REST Docs の話。なんとなくツールを使うモチベーションがズレてる気がして、ちょっと話を聞いててモヤモヤが...。

Vue.js + Vuex + axios

某案件で Vue.js + Vuex + axios を採用しました。備忘録としてまとめます。

前提

それぞれのバージョンは以下の通り。

"dependencies": {
  "axios": "^0.17.1",
  "es6-promise": "^4.1.1",
  "vue": "^2.5.2",
  "vue-router": "^3.0.1",
  "vuex": "^3.0.1"
}

開発には Visual Studio Code を利用。以下のプラグインを入れると便利。

  • HTML Snippets
  • vetur
  • VueHelper

vue-cli

vue-cli の webpack テンプレートを利用。当初、イチからプロジェクトを準備しようと思っていたが、vue-cli で作成した方がいろいろと確実と思われたので。ハマりどころも多いかもしれないが...。

webpack / source maps

source maps については、webpack の 公式ドキュメント と以下が参考になりました。

t-hiroyoshi.github.io

devtool の指定によってマッピングファイルの生成有無や中身、js ファイルのサイズが変わってくる模様。試したところ以下のような感じ。

デバッグ△は変数名が minify される程度、js サイズ△は kByte 程度。(分かり辛い...)

devtool デバッグ js サイズ source maps
source-map あり
eval-source-map なし
inline-source-map なし
cheap-eval-source-map なし
eval なし
minify なし

結局、開発環境向けには eval-source-map を指定、プロダクション環境向けには source-map を指定。

その他、webpack のちょっとした小細工は以下を参照。

vue-cli の webpack テンプレートでテスト環境向けにビルドしたい - kntmr-blog

プロジェクト構成

├─build
├─config         ... 設定ファイル
├─dist
├─node_modules
├─src            ... エントリーポイントの js を配置
│  ├─api         ... API アクセス
│  ├─components  ... 共通コンポーネント / 基底コンポーネント
│  ├─pages       ... ページコンポーネント
│  ├─router      ... ルーティング (今回は利用しない予定)
│  └─store
│      └─modules ... 状態管理
├─static
└─test           ... テストコード

このあたりは Vuex の examples などを参考にしました。shopping-cart とか。

github.com

ページコンポーネントはアプリケーション内で単一となるコンポーネント。共通コンポーネントや基底コンポーネントはアプリケーション内で使い回されるコンポーネント。共通コンポーネントは複数の基底コンポーネントで構成される。

共通コンポーネントや基底コンポーネントに id 属性を付けると、ページ内で id が重複する可能性があるため注意。ページコンポーネントには id 属性、共通コンポーネントと基底コンポーネントには class 属性を指定するといいかもしれない。

スタイルガイド

Vue.js の スタイルガイド は必読。

store の namespace

複数の store に分割するときは、namespace を指定してモジュールを管理する。

モジュール - Vuex

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}

モジュール間でデータを受け渡す場合は dispatch する。(この方法がベストかどうかはちょっとあやしい...)

API

axios に依存するコードをまとめて抽象化する。ここには業務的なロジックは実装しない。ここで export した関数を store の actions から呼び出す。コンポーネント内には API 呼び出しの処理は書かない。

gist.github.com

mixins / filters

mixins は Vue コンポーネントの実装を共通化する用途で使う。今回はローディングやエラー処理など、各画面共通で使う computedmethods の実装を mixins にまとめた。filters は日付やテキストをフォーマットする用途で使う。

反省点など

コンポーネント命名規則がイマイチ

このコンポーネントがどういう役割を持っているのか名前から判断できるとよい。そのためには単一責務のコンポーネントとして切り出すようにするのが理想と思われる。

再掲: スタイルガイド

actions / mutations / state

今回、1つの action の中では基本的に1つの mutation を呼び出すような形で mutations を書いたが、ある action の中でどの state が書き換わるのか分かり辛い感じになってしまった。ある程度、state の粒度に合わせて mutations を書いた方がいいのかもしれない。

APIコンポーネントの分離

API の項にも書いたが、API の変更をコンポーネント側に波及させたくなかったので、コンポーネント内に API 呼び出しの処理は書かないことにした。が、結局、API のレスポンスオブジェクトがコンポーネントのあちこちで参照されていたため、このあたりはきれいに分離できなかった。

APIコンポーネントを分離するために、store 内で変換するような仕組みを持たせてもよかったかもしれない。ただ、store の責務がどんどん膨らんでしまうのが懸念。API と store の間にもう1レイヤあるとよかったのだろうか。

デザインとコンポーネントの設計

当たり前だが、デザインとコンポーネントは密接に関係する。きれいなコンポーネント設計にするためには、当然、それを意識したデザインを作らないといけない。

今回、開発体制的な理由で基本的にデザインはこちらでコントロールできるものではなかったため、一部のコンポーネントに重複や無駄が入り込んでしまった。が、これはしょうがないのかもしれない。

IE 対応

babel や es6-promise は使っていたものの、Edge では動くが IE10 では動かないということがあった。が、npm update してパッケージをアップデートしたら動くようになった...。詳しくは追い切れていないが、babel あたりの更新が要因か。

割と短期間で挙動が変わるような更新があるのは、ちょっと腑に落ちないところがある。このあたりもきちんと追えるようにしたい。

テストコード

今回、Avoriaz や Sinon.JS でコンポーネントのテストコードを作成する予定だったが、メンバーの教育に時間がかかりそうだったため諦めることにした。(その代わり社内で使われている画面系のテスト仕様書でテストした)

まとめ

Vue.js の特長として、シンプルな構成で、かつ拡張性が高いということが挙げられる。また、ミニマムにスタートできるメリットがある。ただ、この ミニマム にフォーカスされるためか、世間からは「小規模向け」というイメージを持たれているように思える。(主観ですが...)

決してそんなことはなく、小規模なアプリケーションはもちろん、大規模なアプリケーションにも十分適用できると思われる。

vue-cli の webpack テンプレートでテスト環境向けにビルドしたい

やりたいこととしては、dev.env.jsprod.env.js 以外に、テスト環境向けの config が欲しい。ちなみに、ここで言う「テスト環境」とは、ローカル以上、プロダクション未満のような環境を指します。

で、stg.env.js のような config を新しく用意しようかと思ったのですが、webpack.prod.conf.js を見ると、普通に webpack を使ってる分には test.env.js が使われてなさそう...?

というわけで、次の方法でビルドできるようにしてみました。

前提

  • vue-cli@2.9.1

コマンド

ローカル環境向け (dev.env.js)

> npm run dev

テスト環境向け (test.env.js)

> npm run build

プロダクション環境向け (prod.env.js)

> npm run build -- production

変更点

build/build.js

const targetEnv = process.argv[2] === 'production' ? 'production' : 'testing'
process.env.NODE_ENV = targetEnv

npm run build-- に続けてパラメータを渡すと、process.argv で受け取ることができる。

あとは、コンソールに表示するスピナーとか完了メッセージをいい感じにする。

...
// スピナー
const spinner = ora(`building for ${targetEnv}...`)
...
// 完了メッセージ
console.log(chalk.cyan(`  Build complete. (build for ${targetEnv})\n`))

現場からは以上です。

アロー関数で空のオブジェクト返そうとしてハマる

普通に考えれば当たり前なんですが、最初なにが起きてるのか分からなくてハマった...。

const func = () => {}
func() // => undefined

つまり、{} がオブジェクトのリテラルではなく、関数のブロックとして認識されている模様。で、return 文がないので undefined が返る。

こうする。

const func = () => { return {} }
func() // => {}

もしくはこう。

const func = () => new Object()
func() // => {}

あまり需要はないかもしれないですが。

その他

どうやら ES6 より ES2015 が推奨っぽいので、今日から ES2015 と呼ぶことにしよう。

追記 (2018/01/11)

括弧で囲めば式として扱われる模様。

const func = () => ({})
func() // => {}