マイペースなRailsおじさん

Ruby、Ruby on Rails、オブジェクト指向設計を主なテーマとして扱います。だんだん大きくなっていくRuby on Rails製プロダクトのメンテナンス性を損なわない方法を考えたり考えなかったりしている人のブログです。

飲み会あったので日記つけるのつらかった

調べたこと

  • gitのコンフリクト
    • checkout --theirs [ファイル名]とcheckout --oursを駆使する

やったこと

考えたこと

Windows 10 Enterprise Editionは開発機として使えるか

エンジニアはMac、その常識をWSLが覆そうとしている。というような話を聞いてから久しい。 Windows10 homeを使ってみて、やはりそんなことはなかったなぁと思っていた。
そんな私のもとに、職場都合でWindows10 enterprise editionがやってきた。以下、1日触った感想。

Windows10 EE のよいところ

  • フォントレンダリングがきれい
    • Win7に比べて、だけれど。やっぱりMacにはかてない。ヒラギノがつよいのか。なんなのか。 - Windows Subsystem for Linuxが最高すぎる
    • ターミナルエミュレータがヤバいのを除けば最高。だってLinuxがそのまま使えるんですもの。
  • docker for Windowsがいいかんじ
    • 仕組みはまだよくわかっていないけれど、Docker Tool Boxみたいに後ろで動いている VMを気にしなくていい。
    • WSLからもdocker.exeで使える

Windows10の残念なところ

  • インストールめんどくさいい。
    • 後からわかったけど、Chocolatyというパッケージマネージャを使えばよかった。
  • ターミナルエミュレータが残念
    • cmd.exeがちょっとだけよくなったくらいのやつ。
    • i18n対応バグなのか、フォントの設定をしてもMSゴシックに戻ってしまう。
    • VcXsrvを使うとX Window Systemが使えるので、GNOMEとかのいい感じのターミナルが使えるんだけどまだ未設定。

設計ムズスギィ

先輩エンジニアに指摘して頂いて、自分のやっている設計がまだまだオブジェクト指向を使いこなせていないのがよく分かった。 設計ばかりは数をこなすしかないよ、と優しくアドバイスしてもらっているけれど、何とかしなくては。
DDDをもっと理解していけば何か見えてくるような気がしているので勉強のモチベが上がっている。

思ったより調べてなかった

今日調べたことはあまりなかった。 なんか調べたような気がしたけど忘れてしまった。 はてなブログのアプリが結構便利なので、休み時間とかにメモるようにしておこう。

毎日投稿している人すごすぎ

今日は飲み会があったので記事書くの大変でした。あきらめようかと思ったけど踏ん張りました。 日に日に内容が薄くなっていくのを実感。

今日は1時間~

Mockitoメモ

呼び出し時の変数を使って値を返すスタブ

doAnswer (かwhenのthenAnswer)を使うと、スタブの呼び出しの引数を使って返値を決めることができます。

doAnswer

例えば、スタブの返す値を引数によって変化させたいとき、引数と返値のマップを用意しておくとこのようにスタブが書けます。

        doAnswer(invocation ->
                someMap.get(invocation.getArguments()[0]))
                .when(mock).someMethod(anyString());

doAnswerは、Answerオブジェクトを引数にとります。 Answerクラスは、InvocationOnMockオブジェクトを引数にとって値を返すanswer()メソッドだけを持つクラスです。 ということは上記のようにラムダ式が使えます。

InvocationOnMock (Mockito 1.10.19 API)

サンプルコード

引数を使ってスタブの返値を決める

日記

調べたこと

  • mockito
    • verify()
      • メソッドが呼び出された回数を検証する
    • doAnswer()
      • メソッドの引数を使って返値を設定できる
  • eclipse local history
    • 半日かけて書いたコードをEclipse上で消してしまった。
    • ゴミ箱にはいかずに消えていた。
    • Local Histryを使うと復元できる!

やったこと

  • mockitoのverify()とTheoryでドハマりした。
    • Theoryで複数パターンをテストするとき、verify()で使う呼び出し回数はリセットされない。
    • reset()でMockをリセットしてやらないといけない。
    • デバッグで変数追いかけまくってやっとわかった…

考えたこと

Theoryはデバッグしづらさがある

TheoryとFixtureを使ったテストは最高に簡単に書けるので大好きだったのですが、デバッグしにくいのが難点かもしれませんね。何回目の呼び出しでバグったかわかりにくいし、コンディショナルブレークポイントを刺さないといけないし…

ラムダ式を深く追いかけたい

仕事中、ブレークポイントを刺してバグを見つけることは多いですが、あまり時間をかけられません。で、よく気になるのがラムダ式。なんかよくわからないところに飛びまくるんですよね。時間があるときに深追いしよう。

Springアノテーション地獄

アノテーションはおまじないになりがち。アノテーションがやってくれている処理にたどり着くまでが遠いんですよねぇ。これも時間があるときに深追いしよう。

日記を書くのはいいことだ

一度アウトプットしておくと、頭の中が空っぽになったかのような気分で次の新しいことを学べる。(気がする) 自分の学習の傾向を後から振り返って作戦を立てるなんてこともできますわよ。

今日は30分で書きました。内容がうすい~。

日記(トピック: JavaのflatMapがちょっと気に食わない)

調べたこと

やったこと

  • VMUbuntuvimをセットアップした
    • vim8のターミナルモード、つかいやすい。neovimはターミナルモードのウィンドウから移動するのがだるかった。
  • gatsby
    • GraphQLでクエリを投げてみた。
    • gatsby developでwatchがちゃんとできてないっぽかったけど解決できなかった。

考えたこと

Java.util.StreamのflatMap()はなんか微妙

flatMapの引数はStreamを返す関数。
てことは、リストのリスト(e.g. List<List>)からリスト(List)を得るみたいなありがちな処理は以下のように書くことになる。

注目してほしいのはこの部分。

List<Integer> list = listOfList.stream()
    .flatMap(List::stream) // .flatMap(l -> l.stream())でもいい
    .collect(Collectors.toList());

flatMapは、streamに対して関数を適用して、そこで得られたstreamをすべてくっつけて一つのstreamにします。 てことは受け取ったstreamの中身はリストなので、それをstreamに変換してくっつけると、リストの中身を取り出すことになるわけですね。

いやしかし私はflatMapの引数がstreamを返す関数というのがどうも気に入りません。
単にくっつけるときのstreamの中身を返すだけにしてしまうと、 streamへの変換を行わなければいけないくてそれを自動でやるのは難しいというところでしょうか。 それで、どのように変換するかはプログラマに任せないといけないという事情だと思います。が、どうになならないのかなーと思いました。
というか、その問題を解決しているライブラリがありそうなので調べてみます。

ファイルの重複行を削除するアルゴリズムってどうなってるんだろう

  • メモリに全行読み込む系
    • ハッシュテーブルを使えば、重複の検出は素早く済みそう
  • メモリに全行読み込まない系
    • ファイルをチャンクに分けて少しずつ見ていって、↑に書いたやつをメモリに書く代わりにファイルに書きながら進める
    • 一度ソートしてファイルに書く⇒次の行が同一か見るならいけそう
      • じゃあどうやってメモリに全行読み込まずにソートするんだろう問題

i18nのこと、そろそろちゃんと考えたい

qiita.com

記事のテーマ:i18n(ソフトウェアの国際化対応)は、単に文や時間(タイムゾーン)を差し替えれば済むような簡単なものじゃない。自分たちの文化にはない決まりにしたがって文ができていることがある。場合によっては、そのためのロジックを加えなければ対応できない。そのような対応が必要になるような文化を数多く持った国家で育つと、i18nに対応したプログラムを特に意識せずに書くことができるようになるのではないか。

今の仕事では、英語のUIを持ったサービスを作っているけど、ユーザーの使用言語は3つある。 ので、そのうちi18n対応をすることになりそうな気がしている。 とても興味深かった、というか、そのうちまたお世話になりそう

これからやること

  • flatMapをもう少しきれいに書けるようなライブラリがないか調べる。
  • gatsbyAPIと連携させる

今日は一時間20分くらいで書きました。

書くことに慣れたいので目標を考えました

毎日いろいろ調べてはいるけど記事にしようと思うと手が止まります。 調べて発信する力は非常に役に立つ力だと思うので、目標を決めて取り組みます。

目標

3か月後、未知の技術を調べる~記事にするを2時間でできる。

1カ月後の目標

記事を書くのにかかる時間を予想できる。

2か月後の目標

未知の技術を4時間で調べる~書くまでできる。

ねらい

記事の質にはこだわらず、書くことを習慣化することに重点を置きました。 100点のものを作ろうとして手が止まってしまうことがよくあるので、まずは記事を公開するという行為への心理的ハードルを下げられれば良いなと思っています。

目標達成のためにやること

  • 毎日記事を書いて公開する。
    • 調べたこと
    • 考えたこと
    • やったこと
  • 週に一回、一週間学んだ中で特に気に入ったものについてまとめてQiitaで公開する。
    • 今日の分は達成⇒次は8/15

↓書いたもの qiita.com

Object.equals()とObject.hashCode()のデフォルト実装

java.lang.Objectのequals()とhashCode()の実装について調べました。

Object.equals()

JDK 8 での実装は下記の通り、参照値を比較しているだけです。

    public boolean equals(Object obj) {
        return (this == obj);
    }

jdk8u/jdk8u/jdk: a71d26266469 src/share/classes/java/lang/Object.java

参照値とはすなわちオブジェクトへのポインタなので、Object.equals()は同一のインスタンスであるときだけtrueを返します。

The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.

参照値(単に参照とも)はオブジェクトへのポインタまたはnullです。nullはオブジェクトを参照しない特殊な参照値です。

Chapter 4. Types, Values, and Variables

Object.hashCode()

hashCode()はJVMの実装に依存します。

srvaroa.github.io

こちらの記事でとても詳しく説明されていました。 結局のところ

  • OpenJDK 8, OpenJDK 9
    • スレッドごとに持つ値をXorshiftした数
  • OpenJDK 7, OpenJDK 6
    • 乱数

とのことでした。 XorShiftは、排他的論理和とビットシフトのみで(高速に)疑似乱数を生成できるアルゴリズムです。 hash codeは一度生成されるとObject Headerと呼ばれるインスタンスごとに持つ領域に記録されます。 二回目以降のhashCode()の呼び出し時には、Object Headerに記録されたhash codeを返します。

OpenJDK8でhash codeを生成するコードは次の通りです。

     // Marsaglia's xor-shift scheme with thread-specific state
     // This is probably the best overall implementation -- we'll
     // likely make this the default in future releases.
     unsigned t = Self->_hashStateX ;
     t ^= (t << 11) ;
     Self->_hashStateX = Self->_hashStateY ;
     Self->_hashStateY = Self->_hashStateZ ;
     Self->_hashStateZ = Self->_hashStateW ;
     unsigned v = Self->_hashStateW ;
     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
     Self->_hashStateW = v ;
     value = v ;

jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/synchronizer.cpp

スレッドのメンバ変数_hashStateX_hashStateWを使ってハッシュコードを生成します。 xor-shiftのアルゴリズムは理解できていないのですが、hashStateX, hashStateY, hashStateWをアルファベット順で一つ前の名前を持つ変数に値を移し、hashStateWを生成したハッシュコードにすることで、次の呼び出し時にもユニークな値が生成できるようにしています。

_hashStateはスレッド生成時に次のように初期化されます。

  _hashStateX = os::random() ;
  _hashStateY = 842502087 ;
  _hashStateZ = 0x8767 ;    // (int)(3579807591LL & 0xffff) ;
  _hashStateW = 273326509 ;

jdk8u/jdk8u/hotspot: 87ee5ee27509 src/share/vm/runtime/thread.cpp

Spring BatchのReaderである程度複雑なことをする

やりたいこと

Readerである程度複雑なことをしたい。 ある程度複雑なこととは、例えば

  • 複数のテーブルからデータを読み込んでJavaで結合したい
  • テーブルからすべてのレコードを読み込んだ
  • DBのデータとRest APIのデータを結合したい

などです。

Readerでできること

Readerは、ItemReaderインターフェースの実装クラスです。 よく使いそうなItemReaderの実装クラスはSpring Batchで17種類提供されていますが、一回の読み込みで複数回クエリを発行するようなReaderやREST APIを叩くはものはありません。

このため、複雑なReaderは自分で実装しなくてはいけません。 ItemReaderインターフェースは、T read()を提供します。では、単純にread()にデータ取得処理を書けば良いのかというとそうでもありません。

Readerはreadメソッドが呼ばれるたびに新しいデータを一つ返します。 これは、一回のクエリ発行で一つのデータが返るような処理をすればいいということではありません。 read()はnullを返すまで実行されるので、そのようなクエリを発行したら無限ループしてしまうことになるからです。 ということは、簡単に言うと次のような実装が必要になります。

public class MyItemReader<T> implements ItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    T read() {
        if (data == null) {
             data = getData();
        }

       if (data.size() < 1) {
             return null;
       }

        return data.remove(0); 
    }
}

Spring Batchはリスタート機能も備えているのですが、このままでは外部からReaderを再開してあげる方法がないので、このクラスはリスタートに対応していません。

AbstractItemCountingItemStreamItemReader

リスタートできるように、どこまで読み込んだとかの管理をうまくやってくれるクラスはないのかなー。ありました。

抽象クラスAbstractItemCountingItemStreamItemReaderは、Readerが返している要素の数と返せる要素の数(最大値)を管理してくれます。 Readerで読み込んだ要素の管理は自分でしないといけませんが… JdbcPagingItemReaderなどは、この抽象クラスを継承して実装しているので、参考になるでしょう。

public class MyItemReader extends AbstractItemCountingItemStreamItemReader<T> {
    private List<T> data;
    
    private List <T> getData() {
        // なんか複雑な処理でデータをとってくる
        return data;
    }

    @Override
    protected void doOpen() throws Exception {}

    @Override
    protected Employee doRead() throws Exception {
        if (results == null) {
              data = getData();
        }

        return results.get(getCurrentItemCount() - 1);
    }

    @Override
    protected void doClose() throws Exception {}
}

実はこのままだと、やっぱりdataを復帰させる方法がないので、やはりリスタートには対応していません。近いうちにリスタート対応版を追記します。