Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

RxJavaはVert.x 3でSQL書くのに便利

自分にとっての新技術を試すための個人プロジェクトでVert.xを使っているのだが、PostgresクライアントがTechnical Previewになったのでオンメモリに乗せていたデータをpostgresに乗せるようにしたところ、2重3重のネストがコードに現れるようになった。

そこでRxJavaを利用することでコードの短縮化を図った。以前利用したReactive Streamsとは別物とのこと。

結果

Handlerベースの実装だとClassがだいたい100行だが、Observableベースの実装だと40行強で済んだ。ネスト数も抑えられている。

何が効果的だったのか

コードの見通しを良くしたのは例外処理。 Handler (コールバック)内で処理が失敗したかどうか毎回確認していたのが、Stream API同様のmap処理に置き換えられた。

サービスを書いているとよくある「getConnectionしてトランザクションを開始してSELECTしてINSERTしてCOMMITする」という処理が、Handlerで書くと5重ネストと5回の例外処理に化けてしまう。ネストをメソッドチェーンに置き換えたうえで例外処理を一本化(主にSubscriber側の処理になる)できるのは非常に見通しが良い。

// before
        postgreSQLClient.getConnection(connected -> {
            if (connected.failed()) {
                future.fail(connected.cause());
                handler.handle(future);
            } else {
                SQLConnection con = connected.result();
// after
        return postgreSQLClient.getConnectionObservable()
            .flatMap(con -> {

使用感:DB操作をObservableとしてを扱うコードを書く上での課題

postgresクライアントのInstantの扱いにバグがある模様。現時点ではStringとして取り出す必要がある。Technical Previewだし仕方ない。

非同期APIなのでリソースの解放にtry-with-resourcesやLoanパターンが使えない点に注意が必要。例えばDB接続を使い終わった時点で接続を閉じるには、以下のように Observable#doAfterTerminate(Action0)を使う必要がある。これなら処理が正常終了した時も異常終了した時も実行される。

        return postgreSQLClient.getConnectionObservable().flatMap(con -> {
            return con.queryObservable(
                    "SELECT id, uploaded_file_name, resolutions, generated FROM task")
                    .doAfterTerminate(con::close);
        }).flatMap(selected -> {

なおリソース解放用に用意されているObservable#using()を律儀に使おうとすると以下のようになってしまい、EclipseJavaコンパイラだとうまくコンパイルできないことがある。第一引数にFunction0(Resource)ではなくFunction0(Observable<Resource>)を使えるなら、() -> conなどという無意味なコードを書かなくて済んだのだが……。

実装を見るとusing()doAfterTerminate()とは全く違う複雑な処理をしているので、doAfterTerminate()で完全な代替になるとは思わないほうが良さそうだ。Observable#using()を自然に使えるような書き方を探す必要があるが、Vert.xのサンプルプロジェクトには現状usingを使ったサンプルは無い。

        return postgreSQLClient.getConnectionObservable().flatMap(con -> {
                return Observable.using(
                        () -> con,
                        connection -> connection.queryWithParamsObservable("SELECT uploaded_file_name, resolutions, generated FROM task WHERE id = ?", params),
                        connection -> connection.close());
        }).flatMap(selected -> {

もっと利用できそうな機能

今のところ更新系など「値を返す必要はないけど正常終了したかどうかだけ確認したい」場合にObservable<Void>を返しているが、Single<Void>を使うほうがシンプルかもしれない。複数の結果が帰ってくることがないとわかっている以上、それを型として明示したほうが何かと混乱がなさそう。

現在のプロジェクトではSchedulerを一切使っていないが、Vert.xと組み合わせて使う以上その必要性は薄そうなので、GUIアプリで試す必要があるかも。

まとめ:RxJavaは何に便利か?

自分が想像できるのは非同期I/Oを多く持つサーバで、多数のI/Oをマージしてクライアントにレスポンスを返すような処理を書くこと。もちろんブロッキングI/Oも混ぜることも可能(Vert.xの機能に寄せるかSchedulerを利用する)。Stream APIとほぼ同じ感覚で扱える。

またObserverSubscriber利用するスレッドを指定できるので、UI Threadを極力あけたい&イベントベースで書きたいAndroidのようなGUIアプリにおいても活躍が期待されているように見える。もうずっとSwingに触れていないがおそらくSwingでも使えるだろうし、JavaFxバインディングは公式に存在するようだ。

Impact of error-prone

Findbugs is really good solution to improve quality of code

As an author of findbugs-slf4j, I really love Findbugs. It lets us find problems by static bytecode analysis. This tool improves quality of each module, reduce reproducing known bug, and point problem quickly in IDE. We do not have to use code review to find known problems and/or antipatterns, Findbugs will do it before developer sends pull-request. This 'push notification' is really cool, we can point problem even if developer has no motivation to find it.

It is really ideal solution also for large scale development team. Even if your team has hundreds developers, they can get help from Findbugs from the beginning of working day. Even though some of them have less experience and knowledge about our depending framework, module and language, Findbugs can help them to catch up quickly.

Findbugs is constructed based on both academic and practical experience, and now it's OSS so not only limited contributors but also everyone who wants to improve documentation comment, message translation, new detectors and so on.

Problems in Findbugs

But I have a dream to implement Findbugs alternative, to replace Findbugs with better implementation. In my personal opinion, it has some problems:

  • Difficult to maintain: Less documentation comment to describe usage*1, Less fresh document to describe (e.g. how to implement plugin), Too wide scope,
  • Slow performance: Not ready to use multi-thread so efficiently. No support for incremental analysis provided by build tools.
  • Difficult to test: To test Findbugs plugin, test-driven-detectors4findbugs was good solution but it doesn't work with Findbugs 3.0.1 due to internal implementation change. We also need a hack to test by Maven.

Yes Findbugs is OSS, so in theory we can solve them by own. But recently this project is not so active, and there is no popular fork for now. It would be problem to solve.

New alternative: error-prone

Last week I found an interesting static analysis tool, named error-prone.

github.com

This does not check bytecode, it applies hack to javac so it handles AST under com.sun.source.tree package. In my understanding, it has many good points:

  • Providing automated correction.
  • Providing simple but enough guideline, so we can write unit test to test rules to detect bug pattern.
  • It's product from Google so it has detailed documentation comment.
  • Using Annotation Processor to manage metadata of bug pattern, so we do not have to maintain XML files,

I haven't tried it with large amount of code, so I cannot compare its performance with Findbugs yet. I hope it's better than Findbugs. About openness and activeness in its community, I'm not sure yet. I've sent a pull-request to add bug pattern, I hope it's open even to non Google developers.

Do we need to switch to error-prone?

In my understanding, problem in error-prone is as follows:

So if you're using Findbugs, I think you do not have to switch to error-prone for now, its merit is really limited and almost all of them are not for users but for contributors.

But if you're author of Findbugs plugin, I think error-prone has value to keep watching on it. If its agility and resource is enough, it might grow and replace other solution later.

*1:I've sent some patches and pulll-requests but it's not enough yet

エラーログと例外の扱いについて

社内勉強会向けに作成したエラーログと例外の扱いについて説明したスライドを、Speakerdeckで公開しました。一部社内特化部分(ログレベルポリシーやMSA向けMDC使用法 など)を削除して、口頭で補足した部分を補っています。

これらの理想的な使い方はシステムを設計から運用までひととおり経験してみないとわかりにくいものですが、それなりの規模のシステム企業における業務開発でその経験を得ることはかなり難しいのではないでしょうか。結果、何の意味もなさないログが量産されてシステム運用の邪魔になってしまいます。きちんとポリシーを組んで統一していくか、各開発者が運用を知り納得して良いログを書けるようにしていく必要があります。

マネジメントとは挑戦と成長のための環境づくりだと考えている私にとって、ポリシー策定よりは「きちんと運用を知り自発的に支えになれる開発者」になれるための情報伝達こそがManagerとしてやるべきだと考えており、そのためのアクションがこのスライドというわけです。前回もそうでしたが、しばらくは非機能要件について考えるきっかけと大まかな方向性を提示していければと思っています。

関連する投稿

docker-composeで開発環境を配布する

上海Microsoftで行われたShanghai Docker MeetupのLTにて発表した、docker-composeを使って開発環境をContributorに対して配布するというユースケースに関するスライドをSpeaker Deckに公開しました。

Partake開発の際はVagrantで開発環境を配布していたのですが、今ならdocker-composeの方がシンプルかつ幅広く使われているのではと思います*1。中国からだとToolboxのダウンロードやDocker registryからのダウンロードが安定しないという問題があるのですが、中国Microsoftでミラー等の公開を検討しているそうです。

なおこのスライドを作るときにdocker-compose.ymlフォーマットのバージョン2が公開されている事に気づいたのですが、残念ながらnginx-proxyが未対応だったのでまだ自分のプロダクトでは使えていません。


追記:nginx-proxyがdocker-compose.ymlフォーマットのバージョン2に対応したそうです。

*1:VagrantからDockerを使う必要性はあまり感じていません

コード内コメントとJavadocの書き方

社内勉強会向けに作成したコード内コメントとJavadocの書き方についてのスライドを、Speaker DeckとSliDeckで公開しました。

このスライドは概要とメリットについてざっくり説明し理解と学習の動機を作ることを目的としていますが、これは日本ないし中国の大学ではコメントについてそこまで扱わないらしい*1という前提に基づいたものです。

ここに記載のない発展的な内容としては以下が挙げられますが、これらについて学ぶのはまず手を動かした後で良いと考えています。

プレゼン後に「publicでないクラスやメソッドJavadocを書くべきか?」という質問を受けましたが、私の答えは「書くべき」です。自分自身の理解を深め、備忘にもなり、将来のメンテナンスも助けてくれます。少しでも直感的でないコードがあるならば、時間を取って書きたいです。 まぁ書くべきものは厳密には対外的なAPI Specではなく、内部実装方針や歴史的経緯等になるかもしれませんので、Javadocを書くべきというよりは開発者向けドキュメントを書くべきと言うべきなのかもしれません。

また、ドキュメンテーションコメントはOSS contributionの良い題材になるとも話しました。typo修正くらいなら敷居は低いですし、多くのOSSでは喜ばれるはずです。自分の経験上、リジェクトされることはほとんどありませんでした:

なお自分のJavadocに関する理解は、Oracle公式ドキュメントに加えて「エンジニアのためのJavadoc再入門講座」に依るところが多いです。英語の解説書でこのくらい幅広く丁寧に説明したものが欲しい……。

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方

エンジニアのためのJavadoc再入門講座 現場で使えるAPI仕様書の作り方

*1:共同開発ないし保守については優先度が低い印象を受ける

(メモ)クラスタ管理関連

  • etcd
    • KVS
    • Leader/Follower
    • Raft
  • fleet
    • Deploy
    • Service Discovery
  • Hashicorp Consul
    • CP
    • needs several nodes as server per Datacenter
    • each nodes in Datacenter installs Consul agent
  • Hashicorp Serf
    • AP
    • is used by Consul
  • Docker Compose
    • Define set of container instances
  • Docker Swarm
  • Docker Machine
    • Create and initialise instance to deploy Docker container
    • Supports both remote and local
  • Registrator
    • Hook for docker command, to register/unregister container instances
  • Netflix Eureka
    • Service Discovery
  • SkyDNS2
    • CP
    • DNS but not only address but also ports
    • Depends on etcd
  • Hashicorp Terraform
    • Deployment
  • Hashicorp Otto
  • Hashicorp Packer
  • Netflix Asgard
  • systemd
  • Sensu

Guava Release19がそろそろ来そう

現時点でRC2まで出ています。

個人的に興味深いのはCharMatcherにファクトリメソッドが導入された理由。staticフィールドだとクラス初期化時に初期化されなければならないが、ファクトリメソッドならメソッド実行時までインスタンス生成を遅延できるためと説明されています。

// before
  public static final CharMatcher WHITESPACE =
      new NamedFastMatcher("WHITESPACE") {
          :
          :
          :
      };
// after
  public static CharMatcher whitespace() {
    return Whitespace.INSTANCE;
  }

  @VisibleForTesting
  static final class Whitespace extends NamedFastMatcher {
    static final Whitespace INSTANCE = new Whitespace();
    :
    :
    :
  }

なおこうした内部クラスを利用する方法*1はマルチスレッド環境でもsynchronizedや明示的なロックが必要ないことで知られています*2。 Stringでもprimitive型でもない定数、特に正規表現のような初期化処理が重いものは、積極的にファクトリメソッドで置き換えると良さそうです。

*1:このページのlazy initialization holder class idiom

*2:だからこういう問題も起こるのですが