マイペースなRailsおじさん

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

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

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

目標

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を復帰させる方法がないので、やはりリスタートには対応していません。近いうちにリスタート対応版を追記します。

React + Reduxをさっくりと覚えた

こんなのつくりました

ytnk531.hatenablog.com

(さっくりと)覚えたこと

動機

  • フロントエンドのコードを読みたい
    • 障害の原因調査が捗りそう

問題

  • 読めない
    • [...array1, ...array2]
    • { key1 } = object
    • {...object1, ...object2}
  • なにやってるかわからない
    • 階層化されたコンポーネント
    • イベントを処理する流れが追いかけられない
    • reducerとかいう謎の単語

さっくりと覚えていく

React

動的なUIを宣言的に書ける。

やったこと

  • 公式サイトのチュートリアル
  • create-my-react-appで作ったテンプレートをいじくる
  • ソートするやつを作る

メモ

  • DOMコンポーネント
    • HTMLの塊を部品として定義して再利用可能にする
    • props
    • state
      • コンポーネントの内部で保持している値(フィールド)
      • stateの更新はめんどくさい
    • propsやstateが変化すると、DOM要素が再描画される(儀式が必要)

Redux

stateを一か所に集めて、stateの更新と反映を行えるようにする。
Observerパターンのちょっとマッチョなやつ。
Reducerというコンポーネントにロジックをまとめられる。

やったこと

メモ - dispatcherで生成したアクションをreducerで受け取って新しい状態を計算する - stateをsubscribeして画面を再描画する

Recat-Redux

propsにstateとdispacherを渡せるようにする。
状態の更新⇒再描画のための儀式がゴッソリ消える。
ReactDOMコンポーネントはビューを返すためだけに使えるようになる⇒スッキリする

やったこと

  • Reduxの流れで一緒に学習
  • ソートするやつをリファクタ

新しい発見

  • コンパイルエラー相当の静的解析はしてくれる
    • トランスパイルするから当たり前なんだけど盲点だった
  • 環境構築めっちゃラク
  • ライブラリごとに使うメソッドの数が少なかったので、あんまり覚えなくてよかった
  • 画面で動くの楽しい

つまづいたところ

  • 当初、Reduxを使うことにあんまりメリットを感じなかった
    • とりあえずReactコンポーネントで状態管理してみた
    • ⇒カオスになった(Reduxを使うまではそこまでカオスだとも思ってなかった)
  • Presentational ComponentとContainer Componentの構成
    • どうやってState Treeを構成すべきか悩ましい
  • Presentational Componentにどれだけロジックを入れるか悩ましい
  • SVGのアニメーションむずい
  • sleep()ないんですか

つまづかなかったところ

  • npm
    • gradleとだいたい同じ
  • Reduxの概念
  • 純粋関数

今後

  • テストしたい
  • reduxで副作用したい⇒redux-saga
  • アニメーションしたい

reactのstateがごちゃつき始めるくらいまで使ってみた。

stateがごちゃついてなんかつらいなこれ…ってなり始めるくらいまでreact.jsを使ってみました。 初心者なりにつらかったところを記録しておきます。 react.jsの環境構築や使い方については、初心者向けの記事は良い記事がたくさんあるので、そちらにお任せします。

つくったもの

ソートするやつをつくりました。 0~99までの100個の数値を、バブルソートでソートします。

f:id:ytnk531:20180617203631p:plain
ソートするやつ
もっとちゃんとしたやつをどこかで見たことがるって?はい、私も見たことがあります。いいのです。学習用なので、これでいいのです。
herokuにデプロイしてみました。 https://evening-brushlands-42735.herokuapp.com/ https://github.com/ytnk531/react-tutor ソースコードも公開しています。ただしフロントエンドド素人が作ったものであるため、くれぐれもお手本にはしないように( ´∀` )

github.com

reactのコンポーネントで状態管理するのはつらい?

下記の記事では、reactのコンポーネントの状態管理はreduxに任せてしまおう、と言っています。 qiita.com reactのコンポーネントはstateと呼ばれるプロパティを持っていて、通常はこのstateプロパティを変化させてコンポーネントの状態を変更します。
reduxは、コンポーネントごとに状態を持つのはやめて、reduxを使うことで一か所で管理してしまおう、というライブラリです。こういったライブラリが推奨されるということは、コンポーネントで状態管理することに辛さがある、ということが示唆されているわけですが、いまいちそのつらみが理解できませんでした。

つらくなるまでやってみた

reactを使うならreduxと一緒に使いなさい!と教えていただきましたが、いまいち嬉しさがわかりませんでした。reduxで状態管理する嬉しさがわからないというよりは、reactで提供されているstateを使って状態管理する困難さがわからなかったのです。
ということで、ある程度つらくなるまで使ってみました。 フロントエンドは経験も知識ありませんでしたが、react自体は難しい物じゃないのでわりとすんなりと使えるようになりました。

作るのに必要だった知識

子要素は筆者の事前の理解度や利用経験。

stateで状態管理するつらさ

stateの更新がめんどくさい

stateを操作するときは、以下のように記述します。

// value1を1増やす
this.setState(prevState => ({
  value1: prevState.value1 + 1
});

状態を変えるたびにこれをやるのはしんどいってもんですね。

stateの反映されるタイミングが謎

setState()は非同期で呼ばれます。いつstateが更新されるかは定かではないのです。
例えば、下記のコードは無限ループします。 '''javascript // finishの初期状態はfalse while(this.state.finish) { this.setState({finish: true}); } '''

やっぱり散らばるのはよくない

コンポーネント間で共有したい値があったりします。 stateを外部から参照する方法があるにはあるのですが、とてもやりづらいうえに非推奨だそうです。

minotaur.badwitch.io

コンポーネントの状態が、変更されているかわかりにくくなってしまうのは、確かに悪いことに感じます。 状態がいつ変更されているのかを追跡する手段が用意されていなければ、バグを見つけにくくなってしまうでしょう。 一つのクラスの中にstateが留められていたとしても、それはやはり同じでしょう。

結局つらかったか

すみません、stateを持ったコンポーネントを一つしか作らなかったので、そんなにつらくなかったです。 というか、stateもったコンポーネント同士で親子関係を作ると値の変更がしにくいので、子コンポーネントは状態を持たずにpropsで渡した値が状態になるような作りにするほかなかったです。
つまるところ、規模が大きくなってくると、一つのコンポーネントでたくさんの状態を管理したり、コンポーネント間で値が伝搬していくように作らなければならないように思います。
これがコンポーネントで状態を管理するつらさ…で…いいのか…な…。

つまづいたところ

コンポーネントをループで生成する h3poteto.hatenablog.com

Docker ToolBoxのDocker環境をWindows 10 HomeのBash On Windowsから使う

結論

docker-machine.exe start default
docker.exe $(docker-machine.exe config default) run hello-world

はじめに

Windows 10 HomeでDocker Toolboxで入れたDockerをBash On Windowsを使う方法を説明します。 Docker for Windows + Bash on Windowsだったり、Dockerを入れたVM + Bash on Windowsとはちょっと違う環境の話です。 Docker for Windows使えればよかったんですけどねぇ。

注意

本ページに登場するコマンドの.exeは省略しないでください。 省略すると、コマンドが見つからずエラーになるか、Linux Subsystemの方にインストールされたコマンドが実行されてしまいます。

環境

Windows 10 HomeではDocker for Windowsが利用できないので、Docker Toolboxを使います。Docker ToolboxとBash on Windowsはインストール済みという前提で説明していきます。

BoWからdocker.exeはうまくいかない

BoWからDocker Toolboxでインストールされたdocker.exeを実行すると次のようなエラーになります。

$ docker.exe ps
error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.31/containers/json: open //./pipe/docker_engine: The system cannot find the file specified. In the default daemon configuration on Windows, the docker client must be run elevated to connect. This error may also indicate that the docker daemon is not running.

docker.exeはDockerのクライアントですから、docker daemonというdockerのコンテナを実際に動作させるサーバと接続しなければなりません。 BoWからdocker.exeを起動した場合、接続先の設定がされていないためエラーになります。 Docker Quick Start Terminalでは起動時にこの設定を行ってくれるようです。以下では、Docker Quick Start Terminalに頼らず接続設定を行う方法を説明していきます。

docker-machine.exeでdocker-daemonを起動する

Docker Toolboxで使えるようになるDocker daemonは、実はdocker-machineで作られたVM上にあるので、docker-machine.exeを使って確認できます。

$ docker-machine.exe ls
NAME      ACTIVE   DRIVER       STATE     URL   SWARM   DOCKER    ERRORS
default   -        virtualbox   Stopped                 Unknown

defaultという名前のdocker machine(docker daemonを動作させるVM)があるのがわかりますね。Docker Quick Start Terminalではこのdocker machineを利用しています。STATEがStoppedなので、まずは起動しましょう。私の環境では起動に3分くらいかかりました。

$ docker-machine.exe start default
Starting "default"...
(default) Check network to re-create if needed...
(default) Waiting for an IP...
Machine "default" was started.
Waiting for SSH to be available...
Detecting the provisioner...
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.

Docker Quick Start Terminalは、起動時このdocker machineの立ち上げも自動で行っていたというわけです。

Dockerを使う

起動したdocker machineを介してdockerを操作するには、下記のコマンドを使います。

docker.exe $(docker-machine.exe config default) [dockerコマンド]

[dockerコマンド]にはrun mysqlpsなど、docker操作用のコマンドを入れてください。docker-machine.exe config defaultでdefault(Docker Machine)に接続するためのdocker.exeのためのオプションが生成されるので、これを評価したものを引数にしています。

では実行してみます。

$ docker.exe $(docker-machine.exe config default) run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9bb5a5d4561a: Pulling fs layer
9bb5a5d4561a: Verifying Checksum
9bb5a5d4561a: Download complete
9bb5a5d4561a: Pull complete
Digest: sha256:f5233545e43561214ca4891fd1157e1c3c563316ed8e237750d59bde73361e77
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

BoWからDocker ToolboxでインストールしたDockerを操作することができました。

おまけ: もうちょっと楽にする

docker machineの起動と、docker操作用のコマンドにaliasを張っておきます。

alias dmsd='docker-machine start default'
alias docker='docker.exe $(docker-machine.exe config default)'

docker machineの起動は/etc/rc.localに書いておけばうまくいくでしょうか? すみません、試してないのでわかる方いらっしゃいましたらコメントか何かで教えてください😷

余談

Docker Toolboxのほうでは、おそらくdocker-machine.exe env default --shellで生成される設定スクリプトを起動時に実行して環境変数を設定しているのですが、これと同じ方法はBoWでは使えません。というのも、docker.exeはBoWで設定された環境変数を読んでくれないからです。 ならば、Windowsにインストールしたdocker.exeでなく、BoWにインストールしたdockerクライアントを実行すれば環境変数を読めるのでうまく動きそうです。結果は、これもダメでした。docker-machine.exe env default --shellで設定した環境変数では、pemファイルのパスがWindowsの形式(C:\foo\bar\的な)で設定されてしまい、BoWからはそのパスをたどれないからです。

【メモ】DDD実践ドメイン駆動設計 第1章と第2章

実践ドメイン駆動設計の第1章と第2章を読んだメモ。

DDDへの誘い

  • DDDとは何か
  • ドメイン
    • 組織が行う事業やそれを取り巻く世界
  • ユビキタス言語
    • 開発チームとドメインエキスパートが全員合意の上で決定された、ドメインを取り巻く言葉。
    • ユビキタス言語は、「境界付けられたコンテキスト」内で閉じている
      • 同じ言葉でも、コンテキストが違えば意味が異なる。業務で使う言葉にはそういう性質がある。
  • ドメインエキスパート
    • システム化する業務に精通した人。
    • DDDの大きな目的は、ドメインエキスパートの持つ知見をシステムに取り入れることで、実際の業務を忠実に再現したモデルの上にシステムを作り出すこと。
  • ドメインモデル

  • DDDの利点

    • ドメインエキスパートと開発チームで業務知見を共有できる。
      • 開発を通して業務を改善する方法を見いだせる。
      • 業務上の戦略の変化に対応できるようになる。
    • ドメインエキスパートと認識の祖語が起きにくくなる。
      • ドメインオブジェクトの仕様を確認してもらえば、ドメインエキスパートがロジックに誤りがないことを確認できる。
    • ドメインの変化、後から手に入れた業務知見をコードに取り入れられる。
      • 最初から完璧なものを作る必要が無い。
  • DDDの課題

  • サブドメイン

    • ドメインの一部分
    • 基本的に業務やモノ単位で分割する。ただし、ドメイン分割に明確なルールは無い。チームで合意をとって決める。
  • コアドメイン
  • 境界付けられたコンテキスト
    • 特定のモデルを定義・適用する境界を明示的に示したもの。
    • 代表的な境界の例は、サブシステムやチームなど。