プログラミングはできるのにデバッグが下手なひと

この note の記事、なかなかおもしろいので未読の方はぜひ読んでみてください。

狭い観測範囲で恐縮ですが、このようなひとの特徴を考えてみました。

  • 切り分けができない
  • 仮説&検証ができない
  • 推測が正しくない

切り分けができない

問題の切り分け、どこまでが正常でどこからが異常なのか、対象を絞り込むスキルが必要です。絞り込む際、過去の経験からすぐに思い当たることもあれば、調べる範囲を絞りつつ問題の所在を探すこともあります。

また、大局的には切り分けられるけど、局所的に調査できないひともいる気がします。このようなひとは、debugger を使ったり、(原始的ですが) print debug しながらロジックを追うことをしていないように見えます。あと、ログ (スタックトレース) を見てない。

問題はここにあるという思い込みを捨てて、まずは現状をできる限り正確に把握することが大事です。

仮説&検証ができない

仮説を立てて検証することは、デバッグというかトラブルシューティングの基本かと思います。

仮説を立てるにはある程度の経験や動作原理の理解が必要で、最初のうちは愚直に調べるしかないかもしれないけど、「XXのときはYYになる」のように、条件と想定する結果を書き出して1つずつ検証するのがよいかと思います。

あと、個人的に重要だと思うのが、

  • 1つずつ検証すること (1度に複数のことをやらない)
  • 書き出した仮説はできる限りすべて検証すること

特に2点目については、副次的に別の事象が発見される可能性があるので、水平展開としてやっておくとよさそう。

手当たり次第ではなく、順序立てて体系的に分析することが大事です。

推測が正しくない

個人の思い込みによって推測にバイアスがかかることがあるような気がします。仕組みや原理を理解不足もあるかもしれない。下手にあれこれ推測するくらいなら、まずは手元の環境で事象を再現させるところから始めた方がいいと思います。

前述の2点と重複しますが、切り分けと仮説&検証のプロセスを繰り返すことで、推測の確度を高められるのではなかろうかと思われます。

まとめ

ここまで書いておいてアレだけど、人間というものは気持ちに余裕がないと冷静に判断できなくなりがちで、ついつい「思い込み」に走ってしまうもの。

2018年のふりかえりと2019年のこと

年末&元日は帰省先で賑やかに過ごし、三が日が過ぎた今は自宅でマターリ。

2017年のふりかえりと2018年のこと - kntmr-blog

というわけで、基本的に何か目標がないとだめだめマンみたいなので、今年は英語勉強の目標として TOEIC で600点あたりを目指してみようかな。あと、どこかで Java 9 と Spring 5 は腰を据えて学ばなければ。現場からは以上です。

これのふりかえりと2019年のことでも書いてみようかと。

英語

2018年前半は進捗ゼロで、結局 TOEIC は受けられず。が、夏頃から『スタディサプリENGLISH』のプレミアム会員に登録し、通勤時間を利用して1日30分程度勉強しています。課金駆動学習。今年は1日1時間程度は勉強できるようにしたいところ。

eigosapuri.jp

Java & Spring

Java や Spring の新しい機能はなかなか試すことができず。ただ、Java のリリースモデルについてはそこそこキャッチアップできました。仕事で使わずとも新しいバージョンの情報は収集していきたいところ。特に、クラウド (サーバーレス) 関連の機能やフレームワーク/ライブラリはウォッチしておかねば。

その他

普段、仕事でクラウド関連のサービスを使う機会がなく、なかなか手付かずの状態だったのですが、2018年は AWS のオンラインセミナーを受けたり、無料枠でいくつかのサービスを試してみました。可能であれば仕事を通して AWSGCP などのクラウド関連のスキルを身に付けたいところですが、引き続き、基礎的なところは自己学習で補おうかと。ついでに認定でも目指してみようかな。

あと、全然たいしたものではないのですが、初めて GitHub で PR や Issue を投げてみました。


とりあえず、2019年は英語の勉強を中心にしようかと。まずはリスニングとリーディング。その他は必要に応じて。

Vue.Draggable で並び替えたときのイベントハンドラの呼び出し順

最近、Vue.Draggable を使う機会があったのですが、ドラッグ&ドロップで要素を並び替えたときにどのような順序でイベントハンドラが呼ばれるのか気になったので調べてみました。とりあえず、以下に記載されているイベントハンドラを対象にします。ちなみに、move イベントは SortableJS の move プロパティを利用するようです。

github.com

サンプルコードはこちら。

gist.github.com

結果

呼び出しの順序は以下の通り。右端の数字は操作の番号と対応する。

パターン1

1) クリック ⇒ 2) ドラッグ ⇒ 3) 入れ替え ⇒ 4) ドロップ

  1. onchoose > (1)
  2. onclone > (2)
  3. onstart > (2)
  4. onmove > (3)
  5. onupdate > (4)
  6. onsort > (4)
  7. onend > (4)

パターン2

1) クリック ⇒ 2) ドロップ (※ドラッグなし)

  1. onchoose > (1)

パターン3

1) クリック ⇒ 2) ドラッグ ⇒ 3) ドロップ (※入れ替えなし)

  1. onchoose > (1)
  2. onclone > (2)
  3. onstart > (2)
  4. onend > (3)

パターン4

1) クリック ⇒ 2) ドラッグ ⇒ 3) 入れ替え ⇒ 4) 戻す ⇒ 5) ドロップ

  1. onchoose > (1)
  2. onclone > (2)
  3. onstart > (2)
  4. onmove > (3)
  5. onmove > (4)
  6. onend > (5)

パターン5

1) クリック ⇒ 2) ドラッグ ⇒ 3) 入れ替え ⇒ 4) 入れ替え ⇒ 5) ドロップ (※2行移動)

  1. onchoose > (1)
  2. onclone > (2)
  3. onstart > (2)
  4. onmove > (3)
  5. onmove > (4)
  6. onupdate > (5)
  7. onsort > (5)
  8. onend > (5)

まとめ

調べてはみたけど、Sortable の README.md などを読めばなんとなくイメージできるかもしれないですね...。

github.com

現場からは以上です。

GraphQL を Spring Boot で試してみる 3

GraphQL Advent Calendar 2018 の18日目です。

最近、GraphQL を試しに触り始めてみたという程度です。普段は Java や Spring を使っています。というわけで、今回は Spring Boot で試してみたときの備忘録シリーズの第3弾となります。第1弾と第2弾はこちら。Query と Mutation を試してみたときの備忘録です。が、本当に触ってみた程度の内容です...。

今回は Subscription を試してみます。相変わらず触ってみた程度の内容ですが、サンプルコードはこちら。

kntmr/playground/graphql-spring-examples - GitHub


以下、備忘録。

依存ライブラリ

Subscription の Resolver からは Publisher<T> を返す。取り急ぎ、Spring WebFlux を追加。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

schema / データクラス

スキーマに Subscription の IF を定義。

// ... (略)
type Subscription {
    added(id: ID!): ToDo
}

Resolver

Subscription の場合は、GraphQLSubscriptionResolver インタフェースを実装する。今回は EmitterProcessor<T>Publisher<T> を返すことに。また、引数で指定された ID で filter することで、Subscribe したユーザーごとに Subscription を返すようにする。

@Component
public class TodoSubscriptionResolver implements GraphQLSubscriptionResolver {
    private TodoEmitterProcessor processor;
    public TodoSubscriptionResolver(TodoEmitterProcessor processor) {
        this.processor = processor;
    }
    public Publisher<ToDo> added(int id) {
        return processor.getProcessor().filter(todo -> todo.getUserId() == id);
    }
}

EmitterProcessor<T> を持つクラス。

@Component
public class TodoEmitterProcessor {
    private EmitterProcessor<ToDo> processor;
    public TodoEmitterProcessor() {
        processor = EmitterProcessor.create();
    }
    public EmitterProcessor<ToDo> getProcessor() {
        return processor;
    }
    public void sendMessage(ToDo todo) {
        processor.onNext(todo);
    }
}

Mutation で更新するときに EmitterProcessor#onNext する。

// ... (略)
ToDo newTodo = new ToDo( ... );
todoDao.add(newTodo);
processor.sendMessage(newTodo); // 追加

リクエスト / Subscription

アプリを起動するとコンソールに以下のログが出力されている。

Registering ServerEndpointConfig: ServerEndpointRegistration for path '/subscriptions': class graphql.servlet.GraphQLWebsocketServlet

というわけで、今回は GraphiQL ではなく、WebSocket で /subscriptions にリクエストする。

const ws = new WebSocket('ws://localhost:8080/subscriptions')
ws.onopen = (e) => {
  console.log('ws opened.')
  ws.send(JSON.stringify({
    query: `subscription {
      added(id: 1) { // ★
        id,
        content,
        completed
      }
    }`
  }));
}
ws.onmessage = (e) => {
  console.log('ws message.')
  var resp = JSON.parse(e.data);
  // ... (略)
}
ws.onclose = (e) => {
  console.log('ws closed.')
  ws.close();
}
ws.onerror = (e) => {
  console.log('error!!')
  ws.close();
}

GraphiQL などから Mutation でデータを更新すると、ws.onmessage でイベントが通知される。Resolver 側で ID で filter しているので、他ユーザーのデータを Mutation に投げた場合はイベントは通知されない。

所感

まだまだあやしいところはありますが、ここまでで Query / Mutation / Subscription の3機能をざっくり試してみました。今後はもう少し実践的な内容を調べていきたいと思います。特に、自動生成などの開発ツール周りを調べたい。

とりあえず、以下のドキュメントをもっと読み込んでみようかと。

GraphQL Java

その他

先日の JJUG CCC 2018 Fall で GraphQL のセッションがありました。

www.slideshare.net

デモのリポジトリはこちら。

github.com

JJUG CCC 2018 Fall に行ってきた #jjug_ccc

JJUG CCC 2018 Fall に行ってきました。簡単に所感をまとめます。

www.java-users.jp

セッション資料は以下で公開されると思います。

GitHub - jjug-ccc/slides-articles-2018Fall: JJUG CCC 2018 Fall 登壇資料まとめ

今回は午後からの参加だったのですが、GraalVM とか午前のセッションでいくつか気になるものがあるのであとで見てみようと思います。

今回参加したセッションは以下の通り。メモから抜粋。

思考停止しないアーキテクチャ設計

www.slideshare.net

  • Classicalなアーキテクチャ設計
  • 非機能要求からアーキテクチャ設計☆
  • 品質特性のシナリオ
    • 特に環境の観点が抜けがち
  • タクティクス☆
  • 品質特性間のトレードオフ
  • アーキテクチャパターンはタクティクスのパッケージ
  • 代表的なアーキテクチャパターン
  • アーキテクチャパターン間のトレードオフ
    • 例) レイヤードはアプリケーションが巨大になりがちで、アジリティやデプロイ容易性は下がる
  • アーキテクチャに時間をかけると手戻りのリスクは減る
    • 時間をかけすぎてもいいものではない
    • コード規模によって Sweet Spot は変わる
  • 品質特性シナリオを使ってアーキテクチャ設計が十分が評価する
  • アーキテクチャの課題をいろいろな視点から見ることが大事☆
  • Architecture Decision Records☆
  • Architectural Significant Requirements (アーキテクチャ要求)
  • 非機能要求からのアプローチではなく、機能要求パターンからASRのアプローチ☆
  • デグラデーション
    • サービス全体のダウンを防ぐ
  • 柔軟さとテスト可能性のトレードオフ
  • 予期しないデータパターンによる不具合
  • 適度な柔軟さ (インタフェース)
  • 柔軟すぎる実装かどうかの判断☆
    • データパターンを網羅するテストケースが作れるか
  • 技術的負債バジェット
  • 技術的負債を金額換算する (時価)
    • 開発時間全体に対する負債返済の時間比を決める
    • 時価額の高いものから対処する
  • アーキテクチャ設計は終わりのない仕事

参加者が多くて人気のあるセッションでした。改めてアーキテクチャ設計の難しさを感じました。定期的に読み直したい資料です。冒頭で紹介されていた起業クエストはこちら。

エムスリーでのKotlinへの取り組み

  • サーバー/モバイルに関わらず多くのプロジェクトで Kotlin を採用している
  • Better Java
  • 技術選定の方針☆
    • 習熟度、周辺ライブラリ、既存システムとの連携
  • 移行パターン
  • 新規開発分に適用、あとから徐々に既存部分を変更する
  • フルリニューアル (アーキテクチャ再設計、機能の棚卸し)
  • 自動コンバート (IntelliJ)
    • Kotlin らしいコードにはならないので、手動リファクタリングが必要
    • コンバートと機能開発を混在させない (差分が分からなくなる)
    • リグレッションテストで品質担保
      • 自動コンバートをコードレビューしても仕方がない
      • Golden file testing でテストを量産する☆
  • Swagger, OpenAPI
  • GraphQL
  • Objectives and Key Results (OKR) ☆
    • 工数の20%程度を使うなどして Kotlin 化を進めた
  • Lombok ⇒ data class 移行
    • Kotlin では Builder パターンの代わりに名前付き引数を使う
  • コードレビューの指摘内容を中心にWikiに解説をまとめる

Kotlin というより、どのように技術選定をしているのか気になって参加しました。OKR でリプレースやリファクタリングを進めるのはよさそうです。が、Key Results を客観的に判断できるように定量化するところがなかなか難しそうでキモかもしれません。ところで、Builder パターンの代わりに名前付き引数を使うというのは、なんかメリットの捉え方が少しズレてるような気がしますが、どうなんでしょう...。

Migration Guide from Java 8 to Java 11

www.slideshare.net

  • 互換性
  • 非互換性
    • 非互換性ポリシー☆
    • 6バージョンごとのLTSのみ使っている場合はいきなり消えることがある
    • OpenJDK コミュニティでは非互換性は厳しくチェックされるらしい
  • Compatibility & Specification Review (CSR) ☆
    • Release Notes にだいたいは書いてある
    • CSR を読むとなおよし
  • マイグレーション
  • だいたいは Java 11 の時点でモジュール化する必要性はないかもしれない
  • 実行時例外☆
    • Options, API, JDK Internal APIs, Reflection
    • Java EEJava 11 以降はモジュール自体が削除されている
    • JDK モジュール化に伴い、リフレクションで JDK Internal API や private メンバへのアクセスが不可
  • 実行の動作不整合/パフォーマンス☆
  • コンパイル/テスト環境においてはほとんどのライブラリやプラグインを更新する必要がある
  • Java 11 では出力されるバイトコードに大きな変更が入ってる
    • JEP181 の影響が最も大きい
    • バイトコードを扱うツールは Java 11 対応を待つ必要がある
  • コンパイル時例外/警告
    • ソースレベルの非互換性
  • 動作の不整合のところは特に要注意

とても学びの多いセッションでした。ここまで資料がまとまってるというのは素晴らしい...。要復習。

GraphQL vs Traditional Rest API

www.slideshare.net

  • REST
    • クライアント/サーバー
    • ステートレス
    • キャッシュ
    • レイヤード
  • GraphQL
    • GraphQL is specification
  • Entity や Repository のコードは REST/GraphQL で同じ
  • REST
    • Schema Optional
    • Good practice, but still optional
  • GraphQL
    • schema is MANDATORY
  • REST
    • HATEOAS
  • Protect from abuse
    • Time out
    • Max Query Depth
    • Max Query Complexity
    • Throttling
  • GraphQL rich SDL

今回、最も聴きたかったセッション。なかなか graphql-java の話を聴くことがないので貴重でしたが、内容自体は割と基本的なところだったかもしれません。デモのリポジトリはこちら。要復習。

github.com

1番気になっている Subscription については少し触れていましたが、英語が聞き取れず...。悲しい。

マイクロソフト牛尾さん渡米直前記念」外資系企業で働くエンジニアの生産性向上物語

  • アメリカではお客さんでも技術力が高い (バリバリコーディングできる)
  • 学んだことを自分の言葉で書くことで理解を深める
  • 日本とUSの違い
    • とにかくラクをする
    • 無駄なものはなくす
    • 技術に明るい
    • 意思決定のスピードがとても早い
  • 英語の読み書きの速さが世界のエンジニアとの差
    • 目的の情報にアクセスするスピードが違う

その他

今回、海外からのスピーカーが多くて豪華でした。参加できなかったセッションの資料はあとで見てみようと思います。

あと、JJUG だけではないですか、最近、Kotlin がテーマのセッションが多くなった気がします。数年前はこのポジションに Scala がいたような。ある意味、Scala は枯れてきたと言えるのかもしれません。先日の Spring Fest 2018 でも Kotlin サポートの話がありましたね。

Spring Fest 2018 に行ってきた #jsug - kntmr-blog

最後のセッションで、「英語の読み書きの速さの差が日本と世界のエンジニアの差」という話がありましたが、まさに直前のセッションで英語が聞き取れなかったところなので、身に沁みました...。英語の勉強をしなければ。

Micronaut で Hello World on Windows

WindowsMicronaut を試してみたので備忘録。

インストール

一般的には SDKMAN を使うケースが多いかと思いますが、今回は Windows 環境ということでバイナリを使います。ダウンロードして Path を通す。

2.1.2 Install through Binary on Windows

初回の mn コマンド実行時に依存解決する模様。無念にもプロキシ環境の場合は事前に設定する。

$ set MN_OPTS=-Dhttps.proxyHost=proxy.com -Dhttps.proxyPort=8080

mn コマンドを叩いてみる。

$ mn --version
| Micronaut Version: 1.0.1
| JVM Version: 1.8.0_172

プロジェクト作成

無念にもプロキシ環境の場合は事前に設定する。

$ set JAVA_OPTS=-Dhttps.proxyHost=proxy.com -Dhttps.proxyPort=8080

プロジェクト作成。

$ mn create-app micronaut-examples
| Generating Java project...
| Application created at C:\Users\ ... \micronaut-examples

起動。

$ cd micronaut-examples
$ gradlew run
> Task :run
[main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2238ms. Server Running: http://localhost:8080

環境起因なのか、起動は思っていたほど早くない気がする...?

Controller 作成

CLI から Controller を作成すると、Controller クラスとテストクラスが生成される。

$ mn create-controller Hello
| Rendered template Controller.java to destination src\main\java\micronaut\examples\HelloController.java
| Rendered template ControllerTest.java to destination src\test\java\micronaut\examples\HelloControllerTest.java

デフォルトでは 200 OK を返すコードが生成される。

@Controller("/hello")
public class HelloController {
    @Get("/")
    public HttpStatus index() {
        return HttpStatus.OK;
    }
}

gradlew run で起動して、http://localhost:8080/hello にアクセスするとレスポンスが返る。

テストクラスはこちら。gradlew test でテストを実行する。

public class HelloControllerTest {
    @Test
    public void testIndex() throws Exception {
        try(EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class)) {
            try(RxHttpClient client = server.getApplicationContext().createBean(RxHttpClient.class, server.getURL())) {
                assertEquals(HttpStatus.OK, client.toBlocking().exchange("/hello").status());
            }
        }
    }
}

Controller のメソッドとテストを追加してみる。

@Get("hello2")
public HttpResponse<String> index2() {
    return HttpResponse.ok("Hello Micronaut");
}

テストメソッドはこんな感じ。

@Test
public void testIndex2() throws Exception {
    try(EmbeddedServer server = ApplicationContext.run(EmbeddedServer.class)) {
        try(RxHttpClient client = server.getApplicationContext().createBean(RxHttpClient.class, server.getURL())) {
            assertEquals("Hello Micronaut", client.toBlocking().exchange("/hello2", String.class).body());
        }
    }
}

最初、自動生成されたテストコードを参考に HttpResponse#exchange(String) で試してみたが、これだとどうしてもレスポンスボディが取得できない...。で、HttpResponse#exchange(String, Class<T>) でレスポンスの型を指定してみたら無事に取得できた。

client.toBlocking().exchange("/hello2").body() //=> null
client.toBlocking().exchange("/hello2", String.class).body() //=> "Hello Micronaut"

API ドキュメント にもそのような感じのことが書いてあるっぽいが、これは Reactive Streams の仕様なんだろうか...。

Returns: An Optional of the type or Optional.empty() if the body cannot be returned as the given type

ビルド

以下のコマンドを叩くと、build フォルダにいろいろ生成される。

$ gradlew assemble

その他

とりあえず、動かすことはできました。ドキュメントは割と充実しているようなので読んでみたいと思います。

User Guide - MICRONAUT DOCUMENTATION

Micronaut は、起動の早さや省メモリが特徴のようで、クラウド環境、特にサーバーレスな環境で Java アプリケーションを使うときに適しているようです。クラウド以外にも、起動が早いということからバッチアプリケーションとして使えるかもしれません。このあたり、Spring Batch と比較してみようかなー。