<< <
2018 年 3 月
> >>
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
2016/1/1 から日記を書いています。
今まで 901 件の日記を書きました。

このサイトは個人の日記帳です。下らん日記とも言います。 コジオンこと Hideyuki Kojima の割とどうでもいい日記帳です。 毎日何かしら書くことを目標としています。

私は Java, Kotlin, Python, PHP あたりのプログラマです。 Qiita にも幾つか記事を投稿しています。 Android アプリを公開中で、代表的なアプリは「コジごみカレンダー」です。 Google Play を参照してください。

私は月配列 K という月配列 2-263 式を四段に拡張した配列を開発・使用しています。 この月配列に関しては特設サイトに情報をまとめてあります。

連絡はメールでお願いします。 メールアドレスは kojionilk あっとまーく gmail ドット com です。

最近のコメント

瑞鳳殿と仙台城址

瑞鳳殿

今日は実家の父を迎えに行ってから仙台へ一泊二日の旅行に出かけた。 新幹線で仙台まで行って「るーぷる仙台」という観光目的で周遊する為のバスに乗った。 このバスはイメージよりずっと混んでおり、かなりの距離立ったまま乗車したのでそれで疲れてしまった……。

最初は瑞鳳殿へ行った。 るーぷる仙台の一日乗車券があると少し割引を受けることが出来る。 日光東照宮のようなものかなと想像していたのだが、規模的には割と小さめだった。 子どもたちはまだ歴史を学んでないのでちょっと興味を持って見るには難しいようだったが、色鮮やかな建築物を見て楽しんだ。

その後は仙台城址に行った。 ここはあまり見る場所は無いようだったが、有名な伊達政宗の像があったのでそこで写真を撮った。 また、近くに資料館があったのでそこに行って映像作品や展示を観たりした。

伝承の宿 佐勘

鮑

本当はこの後大崎八幡宮に行く予定だったのだが、時間が足りないようだったのでパスしてそのままバスで仙台駅まで帰った。 そして宿泊予定のホテルのバスに乗りホテルに向かった。

佐勘はこのあたりでは有名な旅館らしい。 部屋に案内された後に大浴場に行き温泉を楽しみ、食事では鮑や仙台黒毛和牛のしゃぶしゃぶなど楽しんだ。 鮑のバター焼きは数年ぶりだ……。 豪華絢爛な食事内容に満足した。

散りゆく桜

出勤途中に川沿いの桜並木を見るのだが、今日は風が強かったせいかもう既に結構散ってしまっており、道路や川の上に桜の花びらが落ちていた。 それにしても風に乗ってまさに今散っている桜を見るのも美しい。 例年こんなに早かっただろうか、今年ももうこんな時期か、と思いながら桜吹雪を楽しんだ。 明日から旅行に行くので、帰ってきたら本当に桜の見頃を終えてしまっているだろう。

LINE モバイルの低速通信を体験

SIM フリーのタブレットでいつも LINE モバイルの 1 GB のプランを使用しているのだが、今日見てみたら遂に残量が切れてしまった。 LINE モバイルの低速通信は最大 200 kbps なのだが、実はこの速度は ISDN (最大 64 kbps) より 3 倍以上速い。 今時 ISDN は無いだろう、と思うかもしれないが、実はまだサービスを継続しているらしい……。

スマホ (楽天モバイル) の方のテザリングを使おうかとも思ったのだが、折角なので満員電車内で LINE モバイルの低速通信を試してみた。 将棋ウォーズやチェスなど進行速度が遅いゲームをプレイする分には全く問題ない。 MJ モバイルの場合データ通信量はさほどでもないが割と高速なレスポンスを期待されるので気になる。 Web サイトの読み込みは確かに遅いが、そもそも満員電車内だと高速通信時でも速度が落ちているので思ったより気にならなかった。 来月は 3 GB のプランに変更したのだが、これなら 1 GB のままでも良かったかもしれない。

愛車 BOLT の 2 回目の 1 年点検が完了したということで朝に取りに行ってきた。 今回はプラグのみの交換となった。 転倒でブレーキペダルに受けたダメージも修復されていた。 素晴らしい。 そして、エンジンオイルを交換した直後はとてもエンジンの調子が良く気持ちよく走れる。 総走行距離 28,500 km 弱。 来月か再来月には 3 万 km に達するだろう。

YSP のメンテナンスパック (半年・一年点検とエンジンオイル・フィルター交換のパッケージ) が今回で切れてしまったので、再度 2 年延長しておいた。 私はこういう車両周りのメンテナンスは全く自信がないのでプロに任せておくに限る、と思っているからだ。

それにしても今日は革ジャンで走っていると暖かいというよりもう暑い。 人間の快適に感じる温度帯はとても狭いというのを改めて感じさせられた。

iPad 2018

今年版の iPad が発表されたようだ。 アップルペンシルに対応したのが目玉のようだが SoC が A10 (iPhone 7 相当) になったのが大きい。 Android の一般的なハイエンドのタブレットと比べてもかなりの性能で、これがこれだけ安く手に入るのだから Android タブレットが売れないのも納得する。

いつも気になっているのが Wi-Fi モデルと LTE モデルの価格差が 1 万 5 千円と大きいことだ。 iPad 2018 も最小構成 (32 GB Wi-Fi) ならばとても安いが、その上の 128 GB にすると結構値段が上がってしまう。

HUAWEI P20

同じ時に HUAWEI の発表会があったようで P20 / P20 Pro が発表されていた。 Mate 10 Pro と同じ SoC である Kirin 970 を搭載しておりお馴染み LEICA 製のトリプルレンズカメラが売りのようだ。 個人的に今回残念だと思ったのは P10 の時のように「コンパクトな P10 と少し大きめな P10 Plus」という構成ではなく P20 の段階でも結構大きいスクリーンサイズとなっていることだ。 ノッチの分だけ実質的なスクリーンサイズが大きくなっており筐体サイズは実はそこまで変わらないのではないか、と一縷の望みを抱いて調べたのだが、やはり筐体サイズも P10 よりは大きくなってしまっていた (勿論 Mate 9 よりは小さい)。 多くの人はタブレットを持たずスマートフォン 1 台で運用するのでこの戦略で正しいのかもしれないが、私のようにコンパクトな端末を求めている人に対しても最適な端末を提供して欲しかったところだ。

P20 が発表されたことで今後前モデルである P10 の価格がどうなっていくのか興味がある。 結構安くなるようであれば P20 でなく P10 の方に買い替えを考えても良さそうだ。

あと賛否両論あるノッチに関してだが、個人的にはとても格好悪いと思っている。 だが、情報をよく見てみるとノッチを隠す設定もついており、隠す設定にするとノッチの部分はただの黒塗りとなり Android のステータスバーの機能 (時計や通知などの表示) の内容はそのノッチだった黒い部分に表示される (画像がないと説明が難しいので必要であれば検索してみることをお勧めする)。 これはとてもいい機能だと思うところで、これがあるのであれば P20 を購入することにそれほどの躊躇いを感じなくなった。

以前使っていたショルダーバッグはノート PC を持ち運んでいた時に使用していたものだったのだが、割とくたびれてきたのとサイズが大きすぎるというのでもう少し小さめのものを探していた。 いろいろ見た結果、今回はコーチの F54771 というモデルにしてみた。 コーチは富裕層向けに販売されているものと一般消費者向けにアウトレット専用で製造・販売されているものがあるらしく、これはアウトレット専用モデルだ。 型番に F が付くものがアウトレット専用品ということらしい。 しかしこのバッグ、メンズ用のようだがコーチのロゴが一面に入っているのでちょっとどうなのか、派手すぎないか、という不安はあった。 同じような形状でブランドロゴなしのモデルもあったのだが、今回は思い切ってこちらにしてみた。 ブランドロゴありとはいってもショルダーバッグのフタの部分の革にはロゴが印刷されておらず、フタに隠されていない部分を少しだけロゴを見せる格好となっているので問題ないと判断した。

以前のショルダーバッグよりも横が 7 cm 短くなりマチ (奥行き) が 4 cm 短くなった。 早速届いたバッグを使っているが、サイズが少し小さくなっただけで体感結構違う。 私が普段持ち歩くタブレット、折り畳み傘、買い物袋、常備薬、鍵、イヤホン、ティッシュがなかなかちょうど良く収まる。 何よりタブレット専用ポケットがついているのが嬉しい。 久々にいい買い物をしたと思った。

ちなみにボディバッグにすることも考えたのだが、やはり満員電車で使い勝手がいいのは圧倒的にショルダーバッグだと思うので思いとどまった。 ショルダーバッグは電車が混んできたら前に出すこともできるし、歩いている時は後ろに回しておくことが自在にできるのがいいと思う。 つり革の前に立ってバッグからおもむろにタブレットを取り出す時の動作も自然だ。

今週末に実家の父を迎えて家族で親子水入らずの旅行に行く予定なのだが、今回の家族旅行は新幹線で行くので現地ではバスや電車で移動しなければならず、かつ旅館までもバスでいかなければならないが送迎時間が決まっているので綿密な計画が必要となる。 今週末は今のところ天気が悪くはなさそうなので一安心だ。 現地には水族館もあるので屋内でも遊べることは遊べるが、やはり晴れが望ましい。

新幹線に乗れるというので子どもたちがとても楽しみにしているので、私としても計画に力が入るところだ。 それにしても新幹線はびっくりするくらい高い。 私一人の旅行であったならば絶対に新幹線は使わないだろう……。

来週に家族旅行を控えている上バイクも点検で預けてあって乗ることができないので、今日は普通に家でゆっくり過ごした。 花粉症の薬を飲んだら眠くなってしまいついうとうとと昼寝をしてしまった。 今日はとても暖かくなったし、外に買い物に行くと咲いている桜にカメラを向けている人もちらほらと見かけた。 完全に春の陽気という感じだ。

ラーツーは一応成功

まずは栃木県の大平山に行ってみた。 ここは適度にワインディングが楽しめるので大学生時代に何回か行ったところなのだが BOLT だと初なのでなかなか新鮮だった。 太平山神社に行って適当にお参りしたのだが、この山は思ったより標高が低かった (300 m 強)。 あと Wikipedia の「大平山」を見ると全国には 50 以上の大平山があるのを知った……。 しかも読み方もさまざまで「おおひらさん」「おおだいらやま」「たいへいざん」「だいひらやま」「おおひらやま」などあるのに驚いた。

そこから栃木県道 210 号線を走ってみた。 「フェスティカサーキット栃木」があるのみで他には何もない。 だからというわけでもないだろうが、この県道のワインディングをすべて通り抜けるまでに対向車と 1 台もすれ違わなかった。 これはすごい。

そのまま進んでいき県道 283 号線に行った。 途中駐車場があったのでここでラーメンを作ってみようかと思い駐車したのだが、どうも虫が飛び回っているのが気になって止めた。 283 号線の終点まで行って林道の中に入っても適当なスポットがなく、ちょっとした広場があっても林道内だとやはり虫が多い。 ラーメンを作る快適な環境を探すのは難しい。

川があったのだが河原に簡単に出られそうもなく、諦めて帰ろうかと思った時に人が全くいない公園を見つけた。 虫もいないようだしそこで作ることに決めた。 いつも通り小型ガスバーナーでお湯を沸かして家から持ってきたラ王を作って食べた。 もう 3 回目なので大分慣れてきたが、やはり場所を探すのに手間取ってしまうのが難点だ。

BOLT 1 年点検

そこから自宅まで帰ってきて BOLT を 1 年点検に預けるために YSP まで乗っていった。 今回は 1 ヶ月前の転倒の際のダメージと、ハンドルのグリップが回転してしまうのを見てもらえないかとお願いした。 水曜日には完了しているようなので、来週木曜日に取りに行くことにした。

YSP から BOLT の 2 回目の 1 年点検の連絡が来たので明日に予約をした。 早いものでリターンライダーになってから 2 年が経とうとしている。 今のところ総走行距離は 2 万 8 千 km 強といったところで、ペースは落ちてきたがそれでも 1 年で 1 万 km 以上は乗るペースを維持している。

流石にこれだけ走ってしまうと近場は既に行ったところばかりになってきた。 まあ既に走ったところをまた走っても楽しいのだが、マンネリ化しないように工夫してやっていきたいところだ。 4 月、5 月のバイクシーズンはまたどこか泊りがけで行ければいいと思っている。

他のフレームワークには用意されていたりする

テンプレートにおける表示用の加工処理は大体 Thymeleaf が標準で備えているが、例えば CakePHP における Helper だったり Django における独自のテンプレートタグの作成のようにテンプレートの機能だけでは賄いきれない、多くはプレゼンテーション層における HTML への変換のためのロジックを使いたい時がある。 こういう時に Spring Boot における Thymeleaf 上ではどうすればいいのだろうか、というのが今回のテーマである。

勿論 ControllerModel (Form) に対応する HTML タグへの変換処理を書いたりすることはできるが MVC において本来コントローラやモデルでプレゼンテーション層の処理を書くのは好ましくないので避けたいところだ。 いろいろ試行錯誤してみた結果、プレゼンテーション層のヘルパクラスをコンポーネントとして登録して Thymeleaf 側から呼び出すのが一番シンプルな気がした。

ヘルパークラス定義

今回は Markdown で書かれたテキストを HTML に変換したいとする。 以下のようなヘルパークラスを任意のパッケージに定義する:

/**
 * Thymeleaf テンプレート上で使用するヘルパ.
 */
@Component
class Helper {

    /**
     * Markdown を HTML に変換して返す.
     *
     * @param markdown Markdown
     * @return HTML に変換された Markdown
     */
    fun toHtml(markdown: String): String {
        val (parser, renderer) = Parser.builder().build() to HtmlRenderer.builder().build()
        return renderer.render(parser.parse(markdown))
    }
}

@Component アノテーションを付与することにより Spring 管理下のコンポーネントとして機能する。 これを Thymeleaf 上で使用するには以下のように ${@helper.toHtml(xxx)} といった @ を頭につけた記法となる:

<!-- HTML エスケープされないように th:utext を使用する -->
<div class="post-content" th:utext="${@helper.toHtml(post.markdown)}"></div>

後はこういう要件が出てくる度にこの Helper クラスにメソッドを追加していけばよい。

定数の参照はどうする

同じような悩みとして Thymeleaf 上から Kotlin の定数を参照したいというのがある。 一応 Thymeleaf 上で ${T(パッケージ.クラス名).static フィールド名} という記法で任意の static フィールドやメソッドを呼び出すことはできる。 ただ、例えば Kotlin で object を使用して static を表現したとする:

object Consts {
    val DAYS = arrayListOf('月', '火', '水', '木', '金', '土', '日')
}

上記の定数を Thymeleaf 側で参照するには ${T(com.kojion.etc.Consts).INSTANCE.DAYS} のようにしなければならない。 Kotlin の object が Java コード側から見るとシングルトンな INSTANCE という static フィールドを介してアクセスするようになっているので INSTANCE といちいち付けなければならず、あまり綺麗とは言えない。

この場合あえて Kotlin でなく Java で書いてみる:

public class Consts {
    public static final List<String> DAYS = Arrays.asList("月", "火", "水", "木", "金", "土", "日");
}

これで ${T(com.kojion.etc.Consts).DAYS} とアクセスできるので少しシンプルになった。 Thymeleaf 側からは Kotlin でなく Java として見なければならないのが少し辛いところだ。

パッケージ名を書くのも気になる場合は、先程のヘルパークラスと同様にコンポーネントとして登録して定数定義するのがいいのかもしれない:

@Component
object Consts {
    val DAYS = arrayListOf("月", "火", "水", "木", "金", "土", "日")
}

これで Thymeleaf 側から ${@consts.DAYS} でアクセスできるようになった。 object で定義しているので Kotlin 側から定数としてアクセスしたい場合も自然だ。

今日は春分の日だったが天気は悪く、外はみぞれになっていたようだ。 なので買い物を早々に済ませ、自室に籠もって Spring の勉強をしていた。 疲れてきたらチェスや麻雀をプレイする。

市販の中華麺を買ってきて久しぶりにジャージャー麺を作った。 私や家族は辛いものが好きなので、花椒を合わせて買ってすり鉢で擦って肉味噌の上にかける。 更にラー油をまぶして麻辣の組み合わせを堪能する。 こうするなら汁無し坦々麺の方がいいのかもしれない。

1 週間で R1550 から R1600 に上がったのは早かった。 オープニング (ルイ・ロペス) は結構速く組み上げることができるようになった。 チェスはほとんどの駒が将棋でいうところの大駒の動きをするのでパッと見ただけではどこに駒が効いているのかよく分からないところがあり「タダ取り」されないように常に気を使う必要がある。 逆にいうとそれが常に緊張感を持って駒を進めていくということで、それが面白いところではある。

今のところ CHESS HEROZ で実戦を繰り返して、空いた時間に Chess Tempo でタクティクス問題を解く、という進め方で成長できているので、この調子で継続したいところだ。

もう冬という感じではなくかなり暖かくなった気がする。 ただ、天気予報によると明日と明後日は真冬並みの気温に逆戻りするらしい。 その 2 日が過ぎれば本当に春の到来だろう。 明日は良いのだが明後日はせっかく春分の日で休みなのに残念だ。

普通の休日を過ごす

昨日はやや寒いという予報だったので控え、今日は曇りで少し風が強そうだということで先週、先々週も行ったので今回はいいかな、と見送った。 昨日はレイクタウンに行って適当にアウトレットを見てヴィレッジヴァンガードダイナーでクリスピーオニオン BBQ バーガーを食べた。 そして今日は普通に買い物に行っていつものように夕食の材料を買い揃えた。

「ホワイト餃子」を久々に家で作ろうと思って買いに行ったのだが、午後 3 時頃店舗に行ったら「本日の生餃子の販売は終了しました」という張り紙が出ていた……。 3 ヶ月くらい食べていなかったので少々残念だった。 本日は今が旬のアサリの味噌汁に焼き魚を加えた形にしようと思う。

左手の違和感がずっと抜けない

最近どうも左手に違和感があり、タイピングの練習を少なめにしていても全然症状が改善しない。 その上タイプウェルに関しては練習回数を少なくすると目に見えて下手になる。 恐らく「強いワード慣れ」に頼っているからだろう。 イータイピングの方はそれほど遅くなっているように感じない。

仕事に支障をきたすわけにはいかないので、タイプウェルはここで休止してこれからはイータイピングのみに専念しようと思う。

手軽に入れ物を作る場合便利

Kotlin にはデータクラスという JavaBeans のようにデータを入れることに特化したクラスを簡単に作成するための仕組みがある。 詳しくは公式リファレンスを参照すればよいが、簡単に書くと以下を自動で用意してくれる:

  • equals() / hashCode()
  • "User(name=John, age=42)" 形式の toString()
  • 宣言した順番でプロパティに対応する componentN() 関数
  • copy() 関数

尚、上記の恩恵を受けられるのはプライマリコンストラクタに指定したプロパティのみということに注意が必要である。 クラスの本文に書いたフィールドに関しては一切データクラスの影響を受けない。 以下それを検証する。

以下のような Kotlin というデータクラスが定義されているものとする:

// a, b, c, d の 4 つのプロパティを受け取るプライマリコンストラクタ
data class Kotlin(var a: Int, var b: Int = 0, val c: Int, val d: Int = 0) {
    var e: Int = 0  // ただのフィールド (再代入可)
    val f: Int = 0  // ただのフィールド (再代入不可)
}

この Kotlin クラスを Java 側から使ってみる:

public class Java {
    public static void main(String...args) {
        final Kotlin kotlin = new Kotlin(0, 0, 0, 0);
        kotlin.setA(1);
        kotlin.setB(1);
        kotlin.setE(1);

        // プライマリコンストラクタに指定してあるフィールドのみ toString() の対象になる
        System.out.println(kotlin);  // Kotlin(a=1, b=1, c=0, d=0)

        // プライマリコンストラクタに指定してあるフィールドのインスタンスの equals() がすべて true ならば true
        final Kotlin kotlin2 = new Kotlin(1, 1, 0, 0);
        kotlin2.setE(2);  // 関係ない値を違う値にする
        System.out.println(kotlin2);  // Kotlin(a=1, b=1, c=0, d=0)
        System.out.println(kotlin.equals(kotlin2));

        kotlin2.setA(2);  // 関係ある値を違う値にしてみる
        System.out.println(kotlin.equals(kotlin2));  // false

        // コピーしても関係ない値はコピーされない
        System.out.println(kotlin2.getE());  // 先ほど変更したので 2
        final Kotlin kotlin3 = kotlin2.copy(3, 3, 3, 3);  // Kotlin で呼ぶと任意のプロパティのみ変更可
        System.out.println(kotlin3.getE());  // 2 ではなく初期値の 0
    }
}

デフォルトコンストラクタの作成条件

前述の Kotlin クラスでは引数なしのコンストラクタ、いわゆるデフォルトコンストラクタが作成されない。 データクラスにおけるデフォルトコンストラクタの作成条件はプライマリコンストラクタのすべてのプロパティに初期値が存在することとなっている。 つまり前述の Kotlin クラスを以下のように書き換える:

// a, b, c, d すべてに初期値を設定
data class Kotlin(var a: Int = 0, var b: Int = 0, val c: Int = 0, val d: Int = 0) {
    var e: Int = 0
    val f: Int = 0
}

するとコンストラクタがデフォルトコンストラクタとプライマリコンストラクタの 2 種に増えている:

public class Java {
    public static void main(String...args) {
        final Kotlin kotlin = new Kotlin();  // デフォルトコンストラクタを呼ぶ
        final Kotlin kotlin2 = new Kotlin(1, 2, 3, 4);  // 従来のすべての引数ありコンストラクタも作成される
    }
}

尚、以下のようにデフォルトコンストラクタを明示的に宣言しても良い:

data class Kotlin(var a: Int, var b: Int = 0, val c: Int, val d: Int = 0) {
    constructor(): this(0, 0, 0, 0)  // 明示的なデフォルトコンストラクタ
    var e: Int = 0
    val f: Int = 0
}

JPA Entity ではデフォルトコンストラクタの定義が必要

ここからは Spring Boot での JPA の話である。 例えば以下のようにデータクラスで Entity を定義する:

@Entity
@Table(name = "posts")
data class Post(
        @Id @GeneratedValue
        var id: Int,
        var date: String,
        var name: String,
        var body: String,
        var enabled: Boolean,
        var created: Date,
        var modified: Date
)

これを PostRepository から DB アクセスを行うと以下のエラーが表示される:

org.hibernate.InstantiationException: No default constructor for entity:  : com.kojion.entity.Post

先ほどの教訓から解決法は明らかだ。 以下のようにすべてデフォルト値を指定してやれば良い:

@Entity
@Table(name = "posts")
data class Post(
        @Id @GeneratedValue
        var id: Int = 0,
        var date: String = "",
        var name: String = "",
        var body: String = "",
        var enabled: Boolean = false,
        var created: Date = Date(),
        var modified: Date = Date()
)

toString() が循環呼び出しされてしまう場合データクラスの対象外にする

例えば以下のように相互にアソシエーションを張っている場合に起きる:

// Post は複数の Tag を持つ
@Entity
@Table(name = "posts")
data class Post(
        @Id @GeneratedValue
        var id: Int = 0,
        @ManyToMany
        @JoinTable(name = "posts_tags", joinColumns = [JoinColumn(name="post_id")], inverseJoinColumns = [JoinColumn(name="tag_id")])
        var tags: List<Tag> = arrayListOf()
)

// Tag も複数の Post を持つ
@Entity
@Table(name = "tags")
data class Tag(
        @Id
        @GeneratedValue
        var id: Int = 0,
        @ManyToMany(mappedBy = "tags")
        var posts: List<Post> = arrayListOf()
)

この例だと PostTag が中間テーブル posts_tags を通して多対多のアソシエーションが張られている。 ここで同じように PostRepository から取得した Entity を出力しようとすると java.lang.StackOverflowError: null とクラッシュする。 PosttoString() しようとしてフィールドの List<Tag> に対しても toString() を試み、更に Tag にも子の List<Post> があり……というわけである。

この例の場合 Post が何の Tag を持つかは見たいが Tag が何の Post を持っているかはそこまで見たくない (必要ならば別途取ってくれば良い)。 そこで Tag 側の @ManyToMany 定義されているプロパティをプライマリコンストラクタの範囲から出すことで TagtoString() しようとした時に子の List<Post> を見に行かなくなる:

@Entity
@Table(name = "tags")
data class Tag(
        @Id
        @GeneratedValue
        var id: Int = 0
) {
    @ManyToMany(mappedBy = "tags")
    var posts: List<Post> = arrayListOf()
}

Flyway とは

DB の状態をバージョン管理する為のツールで Spring Boot で簡単に使用することができる。 IntelliJ IDEA で新規プロジェクトを作成する時に依存関係に Flyway を含めると以下が build.gradle に追加される:

dependencies {
    compile('org.flywaydb:flyway-core')
}

Spring Boot の場合 application.yaml に DB 設定を行うことが必須だ。 SQLite の場合以下のように定義しておく:

spring:
  datasource:
    url: jdbc:sqlite:./db.sqlite3
    driverClassName: org.sqlite.JDBC

Flyway のマイグレーションファイルは (クラスパス)/db/migration 以下に V(バージョン番号)__(説明).sql の形式で置く。 バージョン番号は公式的には単調増加自然数 (1, 2, ...) のようだが V1_0_2__(説明).sql のようにするといわゆるマイナーバージョンとリファクタリング番号を記録する (v1.0.2 的な) ことができるようだ。

Spring Boot の場合アプリケーションを実行すると (クラスパス)/db/migration 以下のマイグレーションが自動で走り、今までのマイグレーションの状態は DB の中に flyway_schema_history テーブルが作成されて管理される。 この flyway_schema_history テーブルの中を見てみるとどこまでマイグレーションが適用されているか簡単に確認できる。

Flyway の詳しい解説は Flyway使い方メモが分かりやすかった。 SQL ファイルだけでなく Java コードでマイグレーションを記述することができるらしい。 以下、上記ページで解説されていなかった「既存 DB に対するマイグレーション定義」に関して書く。

既存の DB を元にマイグレーションを行う場合

Flyway のマイグレーションはデフォルトでは DB 設計を一から行って新規で作成するアプリケーションの場合が想定されている。 つまり初めは空のデータベースが用意されており V1__Initial.sql などというファイルを用意し初期テーブルを定義し、その後 V2__Add_delete_flag などといった感じで初期テーブルに変更を加えていくイメージだ。

だが「既にテーブルが定義されている DB に対しマイグレーションを行った場合」 (まだ flyway_schema_history が定義されておらず初期投入扱い) は以下の様にエラーが表示される:

Caused by: org.flywaydb.core.api.FlywayException: Found non-empty schema(s) "main" without schema history table! Use baseline() or set baselineOnMigrate to true to initialize the schema history table.

これを回避するためにベースライン (初期状態のバージョン番号) を Flyway に教える必要がある。 application.yaml に以下を定義する:

spring:
  flyway:
    baseline-on-migrate: true
    baseline-version: 1
    baseline-description: Initial

spring.flyway.baseline-on-migratetrue の場合既存 DB にマイグレーションを行った場合に spring.flyway.baseline-version まで適用済みとみなしてくれる。 つまり前述の例の場合 V1__Initial.sql は実行されずに V2__Add_delete_flag のみが適用される。 spring.flyway.baseline-description に定義した文字列はマイグレーション実行後に flyway_schema_history.description のベースラインまで適用されたことを示す文字列として挿入される。 初期値 (未定義の場合) は << Flyway Baseline >> のようなのでこのままでも問題ないと思われる。

このあたりの設定の解説は Flyway 公式に書いてある (英語)。

自宅の Windows PC の Bluetooth が頻繁に無効になる。 これは MacBook の Boot Camp で運用していた時も同様の現象に見舞われていたので Windows PC ではあまり Bluetooth での接続を信用しないほうがいいのではと思っている。 なので Bluetooth マウスの他に有線マウスを横に置いておき Bluetooth マウスが無効になった時に緊急でそちらを使うという若干不便なことをしていた。

今回買ったのは会社でしばらく使って問題ないことを確認した Logicool の M220 というサイレントマウスだ。 これは Amazon だと現在 1,000 円ちょっとで買えるというとてもリーズナブルなマウスである。 1,000 円ちょっとと言ってもさすがに Logicool のマウスだけあって PC に初めからついてきたものよりは使い勝手がいい。 確かに安っぽいが私にとってはこれで十分だ。

それにしても使っているキーボードが 2 万や 3 万もするのにマウスが千円しかしないというのは不釣り合いな気がするが、あまりマウスにはこだわりがないのでこれでいいことにしている。 以前高いマウスを使っていた時もあったのだが、多機能になったり重くなったりするだけでそんなに便利だとは思えなかったというのもある。

ペアスロープは好きなブランドの 1 つ

キーホルダーが壊れたので、買い換えようと会社帰りにペアスロープ本店に寄ってきた。 今年の春夏モデルが置いてあったのだが、バイク用品は一度買ってしまうと駄目になるまで買い換えないので今年は特に買うものがない。 何となくハンチングをかぶったりメッシュジャケットやカバンを見たりしていたが、結局目的通りキーホルダーのみ購入した。

ペアスロープのスタジャン (S-30) と SGB ハンチングは 2 年ほど使っているが、やはり品質は素晴らしいの一言だ。 何ともいえない所有感を与えてくれる。 今年もようやく寒くなくなってきたので、これらのアイテムの稼働時期に入ってきたのが嬉しい。 バイクに乗るのも気持ちいい季節ということでこれからのツーリングが楽しみなところだ。

2 年点検の時期がやってきた

帰宅したら YSP から 12 ヶ月点検の案内が届いていた。 早いもので BOLT を購入してからもう 2 年になる。 そろそろバッテリーやブレーキパッドなどいろいろ交換する時期に入ってくるだろうか……。 後前々回のツーリングでの転倒のダメージを念の為見てもらいたいところだ。

今月初めに CHESS HEROZ を見つけたので少しずつ練習している。 最初は R1500 を割ってしまうなどなかなか苦戦していたのだが、最近調子よく勝ち進んで今日最高 R1550 に到達することができた。

CHESS HEROZ は将棋ウォーズと比べて無料で 1 日 5 戦できるのがなかなか良い。 ただ、公式の NEWS が 2016/6/30 から更新されていないようなので、今後サービスが停止しないか不安ではあるが……。 私でも R1550 に到達できてしまうところをみると chess.com などと比べてレベルが低めのようだ。 私は初心者なので、モチベーションを保つという意味ではこの方が良い。

初心者が上達するにはタクティクス (将棋でいうところの次の一手) をやり込むべきだというのを見たので、有名な Chess Tempo というサイトで空いた時間に少しずつ解くようにしている。 また、「ボビー・フィッシャーのチェス入門」も少しずつ読んでいる。

次の目標としては CHESS HEROZ の RAPID で R1600 を目指したいところだ。

先日電池交換に出していた Knot が帰ってきたのでまた身に着けて使っている。 普段腕時計を着けない癖をつけてしまっていると、時間確認が必要な時でもなかなか腕時計を見るというアクションを起こせない。 これはしばらく毎日着けていれば元に戻るだろうとは思う。

30 歳近辺の頃は腕時計といえばロレックスやオメガ、そしてその上のいわゆる雲上のブランドに憧れのようなものがあったのだが、今となっては薄くて軽くてシンプルなものであれば良いと思うようになってしまった。 そこをいくとこの Knot などは理想に近い。 他人に見せるのであればともかく、自分で使う分には道具としての機能が優れている方が好みだ。 「多機能」ではなく「使いやすい」という意味でだ。 機械式時計など確かに素晴らしいのかもしれないが、自分が使ったらそのうち面倒くさくなって時間を合わせなくなるに決まっている。

歳をとればとるほど年相応にいいもの、値段がそれなりにして他人にモノの価値を認めてもらえるものを身に着けたくなるものではないかと思っていたが、そんなことはないようだ。

バイクで銚子は初

今日は銚子方面にツーリングに出かけた。 交通費節約のため行き帰りとも下道を使った。 Google Map ナビによって利根川沿いの道路を案内されたが、この道路は信号がまったくなくほぼノンストップで走れて素晴らしい道だった。 行けども行けども風景が変わらないので単調なのがちょっと残念ではあるが、渋滞にはまるよりはずっと良い。

銚子は車では来たことがあるがバイクでは初だった。 最初に以前も行った犬吠埼灯台に向かった。 入場料 200 円で灯台に登れるのだが、私は高所恐怖症なので登ってすぐ降りてしまった。 一応周りに灯台に関する展示室があったのでそこを眺めて満足した。

Brown Sugar

Brown Sugar アボカドチーズバーガー

銚子にはグルメバーガーショップがあるのは前から知っていたのでそこを再訪した。 といっても以前の店名は Brown Sugar ではなく K2 Diner という店名だった。 5 年ぶりくらいの再訪だが、店の基本的な佇まいは変わっていなかった。

今回はアボカドバーガーにチーズトッピングをチョイス。 K2 Diner の時はアイコウシャやフランクリンアベニューのように具材が全てバラバラの状態で提供されたと思ったのだが、今回頼んだものはトマトとオニオンのみ別盛りになっているだけでちゃんとハンバーガーの形で提供された。 邪推だがグルメバーガーを食べ慣れない方の場合提供されたバラバラの食材を見て困ってしまう場合があったのかもしれない。

パティがとても大きくバンズからはみ出してしまっている。 そういえば K2 Diner の時もそうだったな、と思いだした。 パティは食べごたえがありゴツゴツとした食感、バンズはしっかりとしたグルメバーガーの品質のもの。 とても美味しかった。 ポテトにペッパーが振ってあるのが変わっていたが、これもなかなか合っていた。

最初は店内に他のお客さんがいたのだが、食べている間に私一人になったので店主がいろいろと話しかけてきてくれた。 カウンターでこうやって店主と他愛もない話をしていると、昔なじみの店でよく食べていたことを思い出す……。 「銚子にまた来た時は是非寄ってくださいね」とのことなので「遠いのでなかなか来れないかもしれませんが、また来ます」と返答した。 私はグルメバーガーが大好きだし、こういう細々と頑張っている郊外のグルメバーガーショップも大好きだからだ。

帰りに酒々井アウトレットに寄ってみた

銚子からの帰り道は今まで行ったことがなかった酒々井アウトレットに行ってみた。 見た感じやはり他のアウトレット施設と同じような感じで、近くだと阿見のアウトレットと同様の雰囲気だった。 入っているブランドもそんなに代り映えしないようだし、この手の施設は自分の家から近くのところに行けば十分だと思った。 女性ならまだしも、私のようないい歳した男が一人で行くようなところではないようだった。

人がいっぱいで回るのも厳しかったが、一通りぐるりと回ってから自動販売機でジュースを買って、満足して帰路についた。

最初に注意として JDK は今のところ必ず 1.8 (Java 8) を使用すること。 JDK 9.0 を使用してしまうと以下の手順の中で原因不明のエラーが表示されてしまう。 Kotlin 側のプラグインもまだ 1.8 までしか用意されていない。

まず PostgreSQL の例

Spring Boot やその他 Java EE プロジェクトでデータベースを扱う際は JPA を使用することが多いと思うが、まずここでは IntelliJ IDEA を使用し Spring Boot で PostgreSQL を使用できる環境を作る手順を示す。 新規プロジェクトの Spring Initializr の依存関係で以下を選択する:

  • Web (Spring MVC)
  • Thymeleaf
  • JPA
  • PostgreSQL

この状態でアプリケーションクラスを実行すると以下のエラーとなる:

Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified and no embedded datasource could be auto-configured.

エラーメッセージの通り application.yaml に以下を定義する:

spring:
  datasource:
    url: jdbc:postgresql://localhost/postgres
    driverClassName: org.postgresql.Driver
    username: postgres
    password: postgres

これで実行するとまたエラーとなる:

java.lang.reflect.InvocationTargetException: null
...(中略)...
Caused by: java.sql.SQLFeatureNotSupportedException: org.postgresql.jdbc.PgConnection.createClob() メソッドはまだ実装されていません。

これでは何だか分からないので Stack Overflow に聞いてみると application.yaml に以下の設定を追加すれば良いようだ:

spring:
  jpa:
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
        dialect: org.hibernate.dialect.PostgreSQLDialect

この状態で再度アプリケーションクラスを実行し、先ほどのエラーが表示されずに正しく Tomcat が起動する事を確認する。

SQLite の場合

SQLite の場合 PostgreSQLDialect に値する SQLiteDialect といった実装が最初から用意されていない。 ただ、これに関しては既に作成して Maven リポジトリに上げている方がいらっしゃるのでありがたく使用させていただくことにする。 それと SQLite の JDBC ドライバも必要なので build.gradle に以下を追加する:

dependencies {
    compile('com.enigmabridge:hibernate4-sqlite-dialect:0.1.2')
    runtime('org.xerial:sqlite-jdbc')
}

これに従い application.yaml を以下に書き換える:

spring:
  datasource:
    # SQLite のファイル位置を指定
    url: jdbc:sqlite:./db.sqlite3
    driverClassName: org.sqlite.JDBC
  jpa:
    properties:
      hibernate:
        temp:
          use_jdbc_metadata_defaults: false
        dialect: com.enigmabridge.hibernate.dialect.SQLiteDialect

アプリケーションクラスを実行し、正しく Tomcat が立ち上がることを確認する。

簡単にテストしてみる

JPA の詳細に関しては検索すればいくらでも出てくるのでここには記載しない。 まず該当するテーブルが SQLite 内にあるものとして以下のような感じで Entity クラスを用意する:

@Entity
@Table(name = "post")
data class Post(
        @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Int = 0,
        var date: String = "",
        var name: String = "",
        var text: String = "",
        var enabled: Boolean = false,
        var created: Date = Date(),
        var modified: Date = Date()
)

Kotlin だと data classEntity が用意できるのでとても便利だ。 toString() を実装しなくてもいい感じにクラス内のデータを出力してくれる。 var にして意味のない初期値を与えなければならないところがちょっと格好悪いが仕方がないところだろうか。

対応する PostRepository クラスを以下のように定義する:

interface PostRepository : JpaRepository<Post, String> {
    fun findById(id: Int): Post
}

定義したリポジトリを使用してコントローラから実行してみる (本来は Service から実行するのが筋だがここでは例のため簡単にする):

@Controller
class SampleController {

    @Autowired
    lateinit var postRepository: PostRepository

    @GetMapping("/")
    fun sample(model: Model): String {
        val post = postRepository.findById(1)  // ID をキーにして 1 件取得
        System.out.println(post)  // Post の中身が出力される
        return "sample"
    }
}

特に意味はないがテンプレートが必要なので /resources/templates/sample.html として以下を用意:

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Sample</title>
</head>
<body>
<p>Hello World!!</p>
</body>
</html>

実行して http://localhost:8080 にアクセスし、標準出力に正しく DB 取得結果が表示されることを確認する。

Spring Web の例は前回行ったので、今回は Spring Batch での Hello World を実施する。

要件とやりたいこと

  • IntelliJ IDEA + Kotlin + Spring Boot 2.0.0 + Gradle
  • 複数の Job を作成しコマンドライン引数で実行を分ける
  • ユニットテストでの実行を行えるようにする

初期設定

IntelliJ IDEA の新規プロジェクトから Spring Initializr を選択。 Gradle Project で言語を Kotlin にしパッケージングは jar を選択。

依存関係のところでは勿論 Batch を入れるのだが Spring Batch はデータベースが使用できるようになっていないと実行できない。 その為対応する JDBC ドライバが必要なのでこれも導入する。 ここでは PostgreSQL がローカルに既にインストールされているものとする。 生成されたプロジェクトの build.gradle の依存関係は以下のようになる:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-batch')
    compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    compile("org.jetbrains.kotlin:kotlin-reflect")
    runtime('org.postgresql:postgresql')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.batch:spring-batch-test')
}

この時点で Application クラスを実行すると以下のようにエラーが出力される:

Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified and no embedded datasource could be auto-configured.

DB の URL 設定がなされていないというわけで、プロパティファイルより YAML で書いたほうが便宜がいいということですでにある application.properties を削除し application.yaml として以下を定義する:

spring:
  datasource:
  url: jdbc:postgresql://localhost/postgres
  username: postgres
  password: postgres
  driverClassName: org.postgresql.Driver

この状態で同様に実行し、まだバッチの実装をしていないので何も起きないがとりあえず上記のエラーが出ずに正常にアプリケーションの実行が終了することを確認する。

Tasklet の実装

ネットの情報を見てみると CommandLineRunner で実装する方法と Tasklet 若しくは Reader, Processor, Writer で逐次処理をする方法があるようだが、ここでは Spring Batch 公式の Quick Start に記載してある通り Tasklet を使用する。 以下のような BatchConfiguration クラスを定義する:

@Configuration
@EnableBatchProcessing
class BatchConfiguration(val jobBuilderFactory: JobBuilderFactory, val stepBuilderFactory: StepBuilderFactory) {

    @Component
    class Tasklet1 : Tasklet {
        override fun execute(contribution: StepContribution?, chunkContext: ChunkContext?): RepeatStatus {
            System.out.println("Hello World!")
            return RepeatStatus.FINISHED
        }
    }

    @Component
    class Tasklet2 : Tasklet {
        override fun execute(contribution: StepContribution?, chunkContext: ChunkContext?): RepeatStatus {
            System.out.println("Hello World2!")
            return RepeatStatus.FINISHED
        }
    }

    @Bean
    fun step1(): Step? = stepBuilderFactory.get("step1")?.tasklet(Tasklet1())?.build()

    @Bean
    fun step2(): Step? = stepBuilderFactory.get("step2")?.tasklet(Tasklet2())?.build()

    @Bean
    fun job1(): Job? = jobBuilderFactory.get("job1")?.start(step1())?.build()

    @Bean
    fun job2(): Job? = jobBuilderFactory.get("job2")?.start(step2())?.build()
}

従来の Spring だと、この場合の JobBuilderFactoryStepBuilderFactory を DI する為に @Autowired アノテーションを付けていたようだが、最近のバージョンだと付けなくても注入してくれるようだ。 勿論 @Autowired を付けても良い。

この例だと job1()job2() という 2 つのジョブが定義されていることになる。 ジョブは更にステップという単位に分割され順々に実行することができるようだが、ここでは 1 ジョブ 1 ステップ構成ということでそれぞれ step1()step2() を定義している。 また Tasklet の定義はラムダ式でも良いのだが、後述するが Tasklet 単位でのユニットテストを行いたいのであえてクラスとして定義している。

この状態で実行してみると以下のエラーが出る:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
...(中略)...
Caused by: org.postgresql.util.PSQLException: ERROR: relation "batch_job_instance" does not exist

この batch_job_instance というテーブルは何なのだろうか。 調べてみると、どうも Spring Batch がジョブの実行状態を管理するために内部的に生成するメタデータらしい。 このメタデータのテーブルがまだ DB に無いため does not exist と言われてしまうわけだ。

このメタデータを生成したい場合は application.yaml に以下のように書けば良い:

spring:
  batch:
    initialize-schema: always

これで先ほどのように実行すれば問題なく動作するのだが、このメタデータで管理されているのが逆に煩わしく感じる。 多くのプロジェクトの場合ジョブの管理など不要で、実行したい時に実行できればそれでいいはずだ。 以下、このメタデータを使わなくてもバッチを実行できるようにする。

メタデータを使用しないでいい方法

これに関しては Spring Batchのメタデータテーブルを作らせない/使わせないが大変参考になった。 記事を参考に同様の実装を Kotlin で行う。 以下のような MyBatchConfigurer を定義する:

@Component
class MyBatchConfigurer : BatchConfigurer {

    private val transactionManager = ResourcelessTransactionManager()

    private val mapJobRepositoryFactoryBean = MapJobRepositoryFactoryBean(transactionManager).also { it.afterPropertiesSet() }

    private val jobRepository = mapJobRepositoryFactoryBean.`object`!!

    private val jobExplorer = MapJobExplorerFactoryBean(mapJobRepositoryFactoryBean).also { it.afterPropertiesSet() }.`object`!!

    private val jobLauncher = SimpleJobLauncher().also {
        it.setJobRepository(jobRepository)
        it.afterPropertiesSet()
    }

    override fun getJobRepository(): JobRepository = jobRepository

    override fun getJobLauncher(): JobLauncher = jobLauncher

    override fun getJobExplorer(): JobExplorer = jobExplorer

    override fun getTransactionManager(): PlatformTransactionManager = transactionManager
}

この状態で再度実行すると Tasklet1Tasklet2 の実装が呼び出され Hello World! と Hello World2! が出力される。

Tasklet のユニットテストを行う

以下のように @Autowired を使用して注入した Tasklet インスタンスに対して普通に実行してみる:

@RunWith(SpringRunner::class)
@SpringBootTest
class TestApplicationTests() {

    @Autowired
    lateinit var tasklet1: BatchConfiguration.Tasklet1

    @Autowired
    lateinit var tasklet2: BatchConfiguration.Tasklet2

    @Test
    fun tasklet1() {
        tasklet1.execute(null, null)
    }

    @Test
    fun tasklet2() {
        tasklet2.execute(null, null)
    }
}

こうすると以下のように 2 重に実行されてしまう:

Hello World!
Hello World2!
Hello World!
Hello World2!

Spring Boot Batch のデフォルトの挙動としてアプリケーションの main() が実行された時点ですべてのジョブを実行するので、その実行の後にこのテストケースでの各 Tasklet が実行されてしまう。 やりたいのは各 Tasklet の実行だけであり、アプリケーション起動時の全ジョブの実行は不要だ。 この実行を無効化するには application.yaml に以下のように書く:

spring:
  batch:
    job:
      enabled: false  # main() での全ジョブ実行を行わない

再度ユニットテストを実行し Hello World! が 2 重に表示されないことを確認する。

JAR から実行するジョブを分けたい

このアプリを JAR ファイルにする。 IntelliJ IDEA のサイドメニューの Gradle から Tasks -> build -> bootJar を選択すると JAR ファイルがビルドされプロジェクトの build/libs 下に置かれる。 これをコマンドラインから job1() だけ実行したい場合は以下のように呼び出す:

java -jar (JAR ファイルパス) --spring.batch.job.names=job1 --spring.batch.job.enabled=true

コマンドライン引数で application.yaml の設定を一時的に上書きすることができるので、もし先ほどの設定で spring.batch.job.enabled=false が指定されている場合は JAR ファイルを実行してもバッチが実行されないので spring.batch.job.enabled=true を明示的に渡すことで実行できる。

1 ヶ月以上前に私が使用している Knot の腕時計の電池が切れてしまったので Knot カスタマーサポートセンターに送付したのだが、そのまま音沙汰がなく 1 ヶ月が過ぎてしまった。 本当に届いて対応されているのか不安になったので連絡してみたところ、今日になってようやくメールで連絡が来た。 やはりただの電池切れのようで、受け取り時に恐らく代引きで 2,160 円を支払うことになる。

しかし 1 ヶ月以上も腕時計が使えないのは痛すぎる。 Knot の Web サイトには「一般の時計店でも電池交換対応は可能であるが Knot カスタマーサポートセンターに送付をお勧めする」と書いてあったのでそれに従ったのだが、これだと一般の時計店に持ち込み即対応してもらったほうがいいと思った。 Knot の製品自体は値段なりではあるがまずまず満足している。 実用的な観点でみると薄くて軽いのは使いやすいし、一応サファイアガラスを使用していると書いてあり傷に強そうで安心だ。

HUAWEI の現時点での最高性能を誇るファブレットである Mate 10 Pro を購入した。 とはいっても使うのは私ではなく妻が P10lite の代わりに使用する。 P10lite 購入時に物足りなくなる可能性があるとアドバイスはしたのだが、結局買って使ってみてから不満が出てきたようだったので今回の買い替えに至ったわけだ。 「通話・LINE やちょっとだけネットが使えればいい」といった方は P10lite で十分だろうが、四六時中スマホを触っているような依存度が高い方やスマホゲームがメインの方はハイエンド機種のほうがいいと思う。

少しだけ触らせてもらったが、やはり狭額縁のデザインが格好良いし SoC が最新の Kirin 970 でキビキビ動く。 そして Mate 9 とも見比べてみたが有機 EL なので画面の色合いが結構違う。 IP67 相当の防水機能はバイクのナビに使用するには欲しいところだ。

尚 Mate 10 Pro は出た時より少し安くなっているようだし、楽天スーパーセールで購入してポイントが相当付いたのでまずまず悪くない買い物だったと思う。 ちなみに私の Mate 9 はまだまだ戦えるし不満を感じたことはないので HUAWEI P20 が出ても買い替えないかもしれない。

花粉症の薬としてアレグラ FX があるが、確かに効果があることはあるが鼻水やくしゃみをピタッと止めるまではいかない。 その代わり眠くなったり喉が渇くといった副作用が少ないのはいい。 以前パブロン鼻炎カプセル Sα という薬を使用していたが、こちらはものすごく効果があり面白いくらいに鼻水やくしゃみが止まる。 その代わり副作用がひどく、頻繁に喉が乾くようになり眠気が襲ってくる。 これでは怖くて運転するときには飲めない。

そういえば花粉症の新薬としてビラノアやデザレックスというのが出てきたらしいが、どうもまだ市販されていないようだ。 医師に処方してもらえば使える感じだろうか。 今のところはアレグラ FX にマスクを装着することでなんとか凌げそうなので、今年はこれでいくことにする。

朝起きた瞬間から鼻水やくしゃみが止まらないので花粉症の薬を飲んだ。 何とかそれで凌いだが辛い時期だ。 午後からは雨模様になったので助かった。

今日はとても暖かかった。 先日までの寒い日々と比べるとかなりの違いで、とにかく極端だと思った。 春秋用のアウターで買い物に出かけたがそれでも少し汗ばむ気がした。 尚、花粉も猛威を振るっている様子で、マスクをつけていても鼻水が止まらなかった。 昨日バイクに乗ったので今日は乗らなかったのだが、せっかく暖かいのに花粉のせいで快適に乗れないのも悲しいところだ。

NAVITIME ツーリングサポート

今年初の秩父ツーリングに出かけた。 まず、今日は NAVITIME が出しているツーリングサポートというバイク乗り向けのナビアプリを試してみた。 これは Google Play の場合月 400 円で利用できるのだが、最初の 1 ヶ月は無料でお試しができる。 まずこのアプリに関する感想を書き記しておく。

秩父までナビで行ってみたのだが Google Map ナビに比べて国道などの幹線道路に誘導される割合が高い。 その結果渋滞に巻き込まれてしまった。 Google Map ナビの場合は細道や裏道のような道路でも構わず案内するので、車だと若干扱いにくいところがあるのだがバイクだと全く問題ない。 帰りに秩父から自宅までもルート検索したのだが、Google Map ナビよりも明らかに所要時間が長い。 その為しびれを切らして帰りは Google Map ナビを使ってしまった……。

ナビゲーションの案内や音声に関しては Google Map ナビよりもカーナビに近い感じになっている。 交差点近くなったら拡大図が出るし、割と前の方から曲がる合図をする。 この辺りは優れているような気がするのだが Google Map ナビに慣れてしまっているからかそんなに良さを感じなかった。

ツーリングサポートはゼンリンの地図を使用しているようだが、地図上に表示されているスポット名がどうにも少ない。 この辺りは Google Map の方が一歩上手のようだ。 しかし、ゼンリンの地図の方はコンビニやガソリンスタンド、バイク駐車場が地図上にアイコンとして表示されているのが分かりやすい。 Google Map ではガソリンスタンドが表示されないので、スタンドを探している時は検索して位置を把握している。 慣れの問題もあるかもしれないが一長一短な気がする。

あと私がゼンリンの地図で致命的だと思ったのは、地図上のスポットをタップしても何も起こらないところだ。 Google Map の場合どんなスポットでも詳細情報 (住所、営業時間、評価など) が出てきて、更にそこに対する経路検索をシームレスに行うことができる。 私は初めから回る場所をすべて決めてツーリングに出かけることは少なく、むしろ行く場所を決めて行った後に面白そうな県道・林道やスポットを Google Map 上から探して行くというやり方が多い。 しかしそれがゼンリンの地図だとできない。

バイク専用ならではの機能として「ツーリングロード機能」がある。 全国のツーリングロードが登録されており、これを選択するとそのツーリングロードをトレースする為のナビを行ってくれる。 これは便利そうではある。 ただ、月 400 円払ってまで使いたいかというと微妙なところ。

結論としては Google Map ナビのままでいいかなという感じだった。 初めて使ったナビアプリがこちらだったらまた感想も変わったのだろう。

袋麺でのラーツー

初袋麺ラーツー

現地のセブンイレブンで五目野菜セット (袋入りのカット野菜) を買ってからラーメンを作れるスポットを探しに向かった。 食べる前に 1 年前行けなかった満願の湯で温泉に入った。 3 時間 900 円で内湯と露天風呂がついており、食堂と休憩室がある。 正直可もなく不可もなしといったところで、近くに行った時に温泉に浸かりたかったらいいかな、といった感じに思えた。 ただ、秩父には他にも温泉施設がいっぱいあるので、そちらも試してみたいところではある。

埼玉県道 284 号の天空のおやきあたりにある東屋で作った。 カット野菜はすべてクッカーに入らなかったので 1/3 くらい残してしまった……。 風がほとんど吹いていなかったのでウインドスクリーンを使用しなかったのだが、前回お湯を沸かしたときよりかなり時間がかかってしまった。 見た目上そんなに役に立っていないようでかなり効果があったようだ。 勉強になった。

袋麺で作った感想としては美味しいことは美味しいが所詮は袋麺ということだった。 現地で手間ひまかけて作ったのだからさぞかし美味しいのだろう、と過度な期待をしていたのがいけなかった。 これなら正直カップラーメンでも良かったかもしれないし、もっと言えば現地のコンビニでおにぎりを買って持ってきて食べても美味しいのだろう。

あと、ラーツーをやっているとどうにも「ツーリングが不自由になる」気がしてならなかった。 出かける前から「今日はラーツーをやろう」と機材の準備をして積み込み、現地では「ラーメンを快適に食べられる場所を探す」ようなツーリングになってしまう。 それが楽しいという人もいるかもしれないが、私は 1 人で自由気ままに好きなところに行って好きなタイミングで適当な店に入る若しくはコンビニのおにぎりを齧るようなツーリングの方が好きだ。

ということで、今後のラーツーは頻度を低くして続けていこうと思った。 自分でやってみて初めて見えてくることがあるという事を改めて感じさせられた。

CHESS HEROZ で対人戦をやってみた。 海外の方が対戦相手ということで余裕で負けるのだろうな、と思っていたが何とか勝ててしまった。 タクティクスなど全く分からないのだが、何となく将棋の経験が生きているような気がする。 あとは駒をただ取りされないように注意深く動かしていけば何とかなった。 麻雀、将棋に続く第 3 のボードゲームの趣味としてしばらくやってみようと思う。

今日将棋ウォーズをプレイしていたら上の方に「囲碁ウォーズ」の広告が出てきた。 私は囲碁は打てないのだが、囲碁ウォーズがあるのなら「チェスウォーズ」があるのではないか、と興味本位で Google Play を覗いてみた。 そうしたら同じ会社 (HEROZ) の CHESS HEROZ を見つけた。 早速プレイしてみたが、アプリ内の言語はすべて英語で書かれており世界中のチェスプレイヤーとマッチングできるようだった。 将棋ウォーズと同様に無料会員では 1 日のオンライン対戦回数が限られているが、それでも 5 回プレイできるようで十分だと思った。 将棋ウォーズには「棋神」という 5 手だけ最強 AI が代わりに指してくれるという機能があるのだが、この CHESS HEROZ にも CAISSA という同じような最強チェス AI に 3 手だけ代わりに指してもらえる機能があった。 あと CHESS HEROZ はアプリ内のデザインがとてもクールだし音楽も上品でとても好感が持てた。

そういえば昔チェスをやってみようと本を買って勉強したことがあったのだが、スマホでこのような良質なオンライン対戦ができないためどうしてもコンピューターと戦い続けることになってしまいモチベーションが保てない原因になっていた。 今はこの CHESS HEROZ があるし、将棋クエストならぬ「チェスクエスト」があるのではないかと探してみたらこれも存在した。 日本国内で自由に対戦して腕を磨きたいチェスプレイヤーの皆様にはとてもいい環境が整ったのではないだろうか。

というわけで将棋、麻雀に加えてチェスもやってみようということで昔買った本「ボビー・フィッシャーのチェス入門」を読み返そうとしているところだ。 オープニング (将棋でいうところの序盤の定跡) など何もわからないが、何となくやってみようと思った。