scramble cadenza

技術ネタのガラクタ置き場

serverless + webpack + babel で AWS Lambda をナウく書く

イントロ

最近 AWS Lamdba を仕事で触っています。
serverless やら Apex なり色々あるわけですが、沢山ありすぎてよくわかりません。

今までは Apex の手軽さに甘えていましたが、そろそろ serverless が本気を出してきたという噂を聞き、serverless を使い始めています。

どうせ serverless を使うなら、もう全部使い倒してやろうということで、babel も意識高く使っていきたいわけです。

この記事では serverless + babel を使うところまでをまとめました。

環境

  • serverless@1.1.0
  • serverless-webpack@1.0.0-rc.2
  • webpack@1.13.3

serverless

まずは雛形作成

# template 指定は必須
# `sls` は `serverless` コマンドの alias 
$ sls create -t aws-nodejs

serverless.yml というファイルが設定ファイル。
これを早速いじっていく。コメントや公式のドキュメントがわかりやすい。

# serverless.yml を編集していく
$ emacs serverless.yml
$ git diff serverless.yml
 provider:
   name: aws
   runtime: nodejs4.3
+  region: ${env:AWS_REGION}
 functions:
-  hello:
-    handler: handler.hello
+  imageResizer:
+    handler: index.handle
# service の deploy
$ sls deploy

# AWS Lambda 関数の deploy
$ sls deploy function -f imageResizer

# AWS Lambda 関数の実行
$ sls invoke -f imageResizer

# Log の tail
$ sls logs -f imageResizer -t

まず最初に sls deploy で Service の deploy を行う。
この Service というのは serverless 用語の一つで、AWS Lambda を実行するための AWS インフラ全体を指す名称。

この Service を管理するために serverless は CloudFormation を暗黙的に使用している。
うっかり region 指定を忘れると、sls deploy を実行した時、serverless デフォルトの region である us-east-1 に、 CloudFormation の Stack が出来上がってしまうので要注意。

region はコマンドラインの option で渡すか serverless.ymlprovider で指定する。

serverless-webpack

serverless を使って deploy はできるようになった。後は書いたコードを babel を使っていく。
serverless-webpack プラグインがあって、これを使えば OK。

elastic-coders/serverless-webpack: Serverless plugin to bundle your lambdas with Webpack

外部の npm パッケージを使う場合

serverless-webpack を使用している場合、任意の package を handler の中で require しようとすると、 webpack コマンドで失敗してしまう。

$ git diff
--- a/index.js
+++ b/index.js
@@ -1,5 +1,6 @@
 'use strict';

+import gm from 'gm';

# sls コマンドは serverless コマンドの alias
$ sls webpack —out .webpack
...
ERROR in ./~/thread-sleep/~/node-pre-gyp/package.json
Module parse failed: /Users/argerich/dev/serverless-image-resizer/node_modules/thread-sleep/node_modules/node-pre-gyp/package.json Unexpected token (2:8)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (2:8)
    at Parser.pp$4.raise (/Users/argerich/dev/serverless-image-resizer/node_modules/acorn/dist/acorn.js:2221:15)

この場合、webpack-node-externals を使って、設定ファイルを調整する必要がある。
具体的には以下のような修正をする。

# webpack.config.js
+const nodeExternals = require('webpack-node-externals');
+
 module.exports = {
   entry: './index.js',
   target: 'node',
@@ -5,5 +7,6 @@ module.exports = {
     libraryTarget: 'commonjs',
     path: '.webpack',
     filename: 'index.js'
-  }
+  },
+  externals: [nodeExternals()]
 };
# serverless.yml

 custom:
   webpack: webpack.config.js
+  webpackIncludeModules: true

webpack を通した deploy は serverless deploy function -f では行われない。
serverless deploy で行う。

$ sls deploy

Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for CloudFormation to explicitly create them for you.
Serverless: Bundling with Webpack...
Time: 578ms
   Asset     Size  Chunks             Chunk Names
index.js  2.17 kB       0  [emitted]  main
Serverless: Packing external modules: babel-polyfill@^6.16.0, gm@^1.23.0
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…
Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
.....
Serverless: Stack update finished…
Serverless: Removing old service versions…

Service Information
service: serverless-image-resizer
stage: dev
region: ap-northeast-1
api keys:
  None
endpoints:
  None
functions:
  serverless-image-resizer-dev-imageResizer: arn:aws:lambda:ap-northeast-1:xxxxxx:function:serverless-image-resizer-dev-imageResizer

✨  Done in 27.52s.

まとめ

  • region のデフォルトは us-east-1 なので、指定を忘れないこと。
    • serverless.yml に region を書いておく(ドキュメントの隅っこ にサラッと書いてある)
  • serverless-webpack で外部 package 使う場合は、webpack-node-externals を使う
  • deploy は serverless deploy function ではなく、serverless deploy を使う

という話でした。

Apex と Serverless 両方使ってみての感想

個人的には Apex のほうが学習コスト少なくて、機能もシンプルなので好き。

Serverless はもれなく CloudFormation が付いてきて、管理運用の面で色々思うところがある。AWS Lamda だけ触りたいんや! と思ってる人にとって、いきなり CloudFormation が出てくるとびっくりする。

けどその分 serverless は設定ファイルを柔軟に書けるし、プラグインは充実している。Apex で微妙だと思った環境変数周りを、うまくカバーしているところは好印象。

一方で Apex でしか出来ないことは Terraform との連携だけ。この機能にどれだけ期待するかが、一つの採用基準でもあると思う。

しかし、ただ単に AWS Lambda を使いたい、というだけであれば Apex で十分だし、落とし穴は少ない。 Apex で不満に思うことが増えてきたら serverless を検討する、で良いのかな。

と、当たり前の結論に至りました。

参考になる

おまけ

$ sls webpack —out .

とやると、現在のディレクトリが全て消されて途方にくれるので、 良い子のみんなは絶対に真似をしないでください

ファイル名で使えない文字

イントロ

web アプリケーションで、ユーザーにファイルをダウンロードさせる機能を追加したいとする。
例えばダウンロードさせるファイルの名前をアプリ側で自動決定する場合、その名前にはどのような制限があるのだろうか。

何回か調べてる気がするので、まとめてみる。

前提

そもそも、ファイル名の制限は、OS ではなく、基本的にはファイルシステムで決まる。

ファイルシステム上の限界

ファイルシステムの限界はどこまでなのかというと Comparison of file systems - Wikipedia, the free encyclopedia にまとめられている。

古いファイルシステムでは、ファイル名は 8 文字、拡張子が 3 文字、ドットも含めて合計 12 文字までしかダメ、という時代もあったらしい。8.3形式 と呼ばれているのがそれだ。

有名どころのファイルシステム(NTFS, FAT系, ext4, xfs)では

  • ファイル名の長さは 255 バイト or 文字まで
  • NUL だけがファイル名として使えない(一部のファイルシステムでは / も禁止されている)

という仕様が多いようだ。

Windows で禁止されている予約語

ただし Windows は例外がある。
ファイルシステムだけでなく、OS レベルで予約されている文字があり、これらはファイル名として使えない。

Naming Files, Paths, and Namespaces (Windows) によると、以下の文字が予約されている。

< (less than)
> (greater than)
: (colon)
" (double quote)
/ (forward slash)
\ (backslash)
| (vertical bar or pipe)
? (question mark)
* (asterisk)

手元に win 機が無かったので、残念ながら検証はしていない。
本当にダメなのか試してみたい。

結論

対応するファイルシステムのカバー範囲により、ファイル名に使える文字は異なる。
だが、現実的な落とし所として

  • windows を視野にいれるのであれば、windows で禁止されている予約語は使わないほうが良いと思う。
  • NULL文字も使えないファイルシステムが多いため、止めたほうが無難。
  • 長過ぎるファイル名は NG なので、そのあたりも調整する。
    • マルチバイト文字のときが面倒くさいが、より小さい方に合わせ 255 byte まで のほうががいいのかな。

ぐらいを考えておけば良さそうだろうか。

おまけ

手元の MacファイルシステムHFS+ だった。

f:id:mgi:20161010132555p:plain

https://en.wikipedia.org/wiki/Comparison_of_file_systems によると、制限は以下の通り

File system Maximum filename length Allowable characters in directory entries Maximum pathname length Maximum file size Maximum volume size
HFS Plus 255 UTF-16 characters Any valid Unicode Unlimited slightly less than 8 EiB slightly less than 8 EiB
# 制御文字のファイル名は作れる
`touch "\a"`
=> ""

# null 文字はダメだった
`touch "\x0"`
#  ArgumentError: string contains null byte
#        from (irb):1:in ``'
#        from (irb):1
#        from /Users/argerich/.rbenv/versions/2.3.0/bin/irb:11:in `<main>'

# 256 byte は長すぎる
`touch #{"a" * 256}`
touch: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: File name too long

# 255 byte なら OK
`touch #{"a" * 255}`
=> ""

# 日本語で 255 文字はアウト
`touch #{"" * 255}`
touch: あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああ: File name too long

>> "".bytesize
=> 3

# 3 * 85 = 255 なので、85 文字まで OK
>> `touch #{"" * 85}`
=> ""

# 86 文字目からアウト
>> `touch #{"" * 85 + "a"}`
touch: あああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああああa: File name too long

# UTF-16 が OK ならば、絵文字でもいけるはず。
`touch #{"\u{26C4}" * 85}`
=> ""

f:id:mgi:20161010132646p:plain

絵文字ファイル名で作れているっぽい。ls すると ? になるけど。

f:id:mgi:20161010132710p:plain

それにしてもWikipedia 先生。ファイル名の長さ、255 byte までなのでは?
Any valid Unicode の中に null 文字は入ってないのかな?

一部の文字は互換性維持のため、OS の API で制御しているとの噂もあるけど… うーむ。
情報が少なすぎて、これ以上調べるのは難しそうだった。一次情報どこにあるの。

試してみたものの、Wikipedia を引用してまとめたので、すごい微妙になってきた…
この記事は用法用量を守って、正しくお使いください。

参考

AppStore でアプリのアップデートができなくなった件

イントロ

「この製品のディストリビューションファイルを検証できませんでした。
破損しているか、署名されていない可能性があります」

f:id:mgi:20160919201738p:plain

と出て、update できない。

状況

  • 上記のようなメッセージが出て、更新通知があるアプリを更新できない
  • 何故か更新できるアプリもある
  • キャッシュを消しても出る
    • 試したのは % sudo rm -rf ~/Library/Caches/* /Library/Caches/*
    • 乱暴なので、良い子は真似しないように。
  • /var/log/system.log に以下のようなメッセージが出力される
    • Assertion failure in -[CheckPreflightOperation verifyDistributionAtURL:allowsDevSign:allowsUnsigned:osVersionToBeInstalled:error:distributionController
# /var/log/system.log
Sep  2 11:54:11 --- last message repeated 1 time ---
Sep  2 11:54:11 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.003149032592773438
Sep  2 11:54:15 argerich-no-Mac-mini storedownloadd[583]: *** Assertion failure in -[CheckPreflightOperation verifyDistributionAtURL:allowsDevSign:allowsUnsigned:osVersionToBeInstalled:error:distributionController:], /Library/Caches/com.apple.xbs/Sources/Commerce/Commerce-463.9/CommerceKit/CheckPreflightOperation.m:291
Sep  2 11:54:23 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.004208028316497803
Sep  2 11:54:27 argerich-no-Mac-mini iTerm2[348]: Time to encode state for window <PseudoTerminal: 0x7f8544b54630 tabs=1 window=<PTYWindow: 0x7f8542d2b920 frame=NSRect: {{0, 74}, {1918, 972}} title=1. Horowitz (tmux) alpha=1.000000 isMain=0 isKey=0 isVisible=1 delegate=0x7f8544b54630>>: 0.004567980766296387

解消法

ゲストユーザーでログインし、アップデートしたら成功。
恒久対応ではない気がする。

アップデートを完了できませんでした。 | 公式 Apple サポートコミュニティ

Java 歴 23 分の Ruby エンジニアが Effective Java を読んで感動した話

イントロ

例外処理を書くことはよくやっているのだけれど、その時の主軸となる考え方について、今までなんとなくで行っていた部分が多かった。

毎回考えるポイントは例えば以下のような疑問。

  • どこのレイヤーで、どこまで例外処理を行えばよいのだろうか?
  • どの例外をキャッチし、どの例外を伝搬させればよいだろうか?
  • 前提条件をチェックし、失敗した場合、例外を出したほうがよいか、nil, false を返すほうがよいか?
  • 例外をどういう単位でラップさせるのが良いだろうか?
  • 例外をチェインし過ぎると却って煩雑になる気がする。どうすれば良いのだろうか。

しかし、この辺りの話って、API の設計だったり、仕様の影響もあるので、都度対応が異なってしまうもの。
したがって抽象化して理解することが難しく感じた。

とてもよく使ってるし、とても大事な事なことなのに。

そんな今更な事で悩んでいた時に、Effective Java という良い本を紹介してもらったので、例外処理の章を読んでまとめてみました。
(5 章まで読んで力尽きていたが、勇気を振り絞り、また読み進めてみた)

EFFECTIVE JAVA 第2版 (The Java Series)

EFFECTIVE JAVA 第2版 (The Java Series)

読書メモ

項目.57 例外状態の時だけ例外にする

// 行ってはならない
try { 
  int i = 0;
  while(true) 
    range[i++].climb();
} catch(ArrayIndexOutOfBoundsException e) {
}
  • 例外に基づくイディオムはパフォーマンスが悪い
    • そのため、例外の目的がわからない場合は使ってはいけない
  • 例外は例外的条件の時のみに使用する。通常の制御フローに対しては使ってはいけない
    • コードの保守性を落とすだけでなく、パフォーマンスも悪くなるので
  • 通常の制御に例外を使用することをクライアントに強制してはならない
    • 例えば、予測不能な状態でのみ呼び出されるメソッドは「状態依存 」である
      • e.g) Iterator#next()
    • 一般には状態を判別する状態検査メソッドを持つべき
      • e.g) Itereator#hasNext()
    • 状態検査メソッドが呼ばれること無く、状態依存のメソッドが呼ばれた場合は、そうとわかるような特別な値を返す

状態検査メソッドを使うか、区別できる戻り値を使うか

区別できる戻り値を使う場合

以下の条件を満たすとき。

  • 並行してアクセスされる場合
  • 外部要因で状態遷移する場合

状態検査メソッドと、その変更メソッドを呼び出す間に、状態が変更になる場合があるから。

状態検査メソッドを使う場合

上記以外のとき。

  • 若干読みやすい
  • 状態検査メソッドを呼び出すことを忘れると、例外が投げられるため、バグに気づきやすい。

から。

項目.58 回復可能な状態にはチェックされる例外を、プログラミングエラーには実行時例外を使用する

チェックされる例外(checked exception)

  • 回復可能な状態であるときに発生する例外
  • 回復に役立つ情報を提供するメソッドを持つと良い

実行時例外(runtime exception)

  • 回復可能でない状態である場合に発生する例外 = チェックされない例外
  • 実行を続けても役にたたない、むしろ害になるような場合に発生する例外
    • RuntimeException をサブクラス化する
  • 「回復可能かそうでないか」を判別できない場合も「実行時例外」として扱うべき

エラー(error)

  • 基本的には使わない
    • API のユーザーの混乱を招くだけだから
    • 得られるものは殆ど無いから

項目.59 チェックされる例外を不必要に使用するのを避ける

チェックされる例外は、API の使用者に例外処理を強制させ、信頼性を上げる

しかし、チェックされる例外を過剰に増やすと逆に API を使いにくくする。 API を使う側は、以下の2つのどれかを強いられるため、多少の負荷がかかるから。

  • 少なくとも1つ以上の catch が必要
  • 例外を外側に伝搬させる

チェックされる例外を使う場合

  • API の適切な使用では例外処理を防ぐことができない
  • API を使用しているプログラマが例外に直面したときに、何らかの有効な処理を行える

この2つの条件をみたす場合に、上記の負荷が正当化される。(ただし CloneNotSupportedException は例外) それ以外の場合はチェックされない例外のほうが適切。

チェックされる例外を、チェックされない例外に変更する方法

上記で述べたように、チェックされる例外を使用し過ぎると、負荷が高くなる。 その方法が「例外を投げるメソッドを分割して、状態検査メソッドと、それ以外の処理に分割する」というもの

ただし、以下の場合は NG。

  • 外部同期なしに平行してアクセスされる場合
  • 外部要因により、状態遷移する場合
  • actionPermitted メソッドが action メソッドの処理を行う必要がある場合
    • (パフォーマンス上の関係で、この分割を行う必要がないので不要となる)
// チェックされる例外を使用する場合の呼び出し
try {
  obj.action(args);
} catch(TheCheckedException e) {
  // 例外処理
}
// 状態検査メソッドとチェックされない例外を使用する場合の呼び出し
if (obj.actionPermitted(args)) {
   obj.action(args);
} else {
  // 例外処理
}

項目.60 標準例外を使用する

既存の例外を再利用する

  • プログラマが熟知している確立された慣例と一致するため、使用が容易になるから
  • 見慣れない例外でごちゃごちゃしないので、API を使用するプログラムが読みやすい
    • 例外クラスが少ないことは、クラスのロードに費やされる時間が少ないことを意味する

よくある具体例

  • IllegalArgumentException
    • 不適切な引数を渡した時に発生する例外
  • IllegalStateException
    • レシーバーのオブジェクトが不正な状態ならば発生する例外
  • NullPointerException
    • null が禁止されているパラメータに対して、null を渡した場合など
  • IndexOutOfBoundsException
    • index を表すパラメータで、範囲外の値を呼び出した場合など
  • ConcurrentModificationException
    • 並行して変更されようとしている
  • UnsupportedOperationException
    • 行おうとした操作をオブジェクトがサポートしていない場合

また、エラーに関連する情報を付与したい場合は、自由に例外をサブクラス化すること。 再利用する時の例外をどう選択するか、は必ずしも厳密でない場合がある

項目.61 抽象概念に適した例外をスローする

例外翻訳とは

「上位レイヤは下位レベルの例外をキャッチし、上位レベルの抽象概念の観点から説明可能な例外をスローする」こと。例えば以下の様な例を指す。

// イディオム
try {
} catch (LowerLevelException e) {
  throw new HighLevelException(...)
}
// AbstractSequentialList からの一例
public E get(int index) {
   ListIterator<E> i = listIterator(index);
  try {
    return i.next();
  } catch(NoSuchElementException e) {
    throw new IndexOutOfBoundsException("Index: " + index);
  }
}

上位レベルの例外から、下位レベルの問題をデバッグするためには、例外連鎖を用いる

  • 上位レベルから下位レベルの例外を取り出すためのアクセサを用いる
  • 具体的には、スーパークラスの連鎖可能なコンストラクタに引き渡す
    • 殆どの標準例外は連鎖可能なコンストラクタを持っている
try {
  // Do something
} catch (LowerLevelException cause) {
  throw new HighLevelException(cause);
}

class HighLevelException extends Exception {
   HighLevelException(Throwable cause) {
    super(cause);
  }
}

例外翻訳は何も考えないで例外を伝搬させるよりは優れてるが、乱用すべきではない

  • 可能であれば、下位レベルのメソッドを呼び出す前に、それが成功することを保証する
    • (状態検査メソッドのこと?)
    • 引数を引き渡す前に、正当性を検査する
  • 不可能であれば、上位レイヤに黙って処理させる
    • 下位レベルの問題から、上位レベルのメソッド呼び出し元を隔離する事が次善策
  • 呼び出し元を隔離出来ない場合は、例外連鎖を使う

項目.62 各メソッドがスローする全ての例外を文章化する

例外がスローされる条件を Javadoc@throw タグで正確に文章化する

  • スローする例外に気をつけているプログラマの手引にならないから
  • クラスやインターフェイスを効果的に使用することが困難だったり、あるいは不可能だったりするから

チェックされない例外についても、注意深く文章化するほうが懸命

  • ただし、強制はしない
  • 書くことで、メソッドが首尾よく実行されるための事前条件を記述できる
  • これは必ずしも達成可能であるとは限らない
  • throw タグを使わない
    • チェックされない例外と、チェックされる例外を視覚的に区別するため

項目.63 詳細メッセージにエラー記録情報を含める

例外の toString メソッドが例外の文字列表現である

  • 文字列表現 = クラス名 + 詳細メッセージ
  • エラーの原因を表す文字列で、できるだけ多くの情報をかえす事が重要

例外の原因となった全てのパラメータとフィールドの値を含んでいるべき

  • 例) IndexOutOfBoundsException は下限範囲、上限範囲、範囲内の収まらなかった実際の index を返す。これは診断にとても役立つ情報

例外の文字列表現と、エンドユーザーに対してわかりやすいメッセージを混同すべきではない

  • プログラマにとっては、エラーを解析する情報の内容のほうがはるかに重要
  • 以下の様にコンストラクタを作成することを推奨
    • プログラマがエラーを記録することが容易になるし、逆に記録しないことが困難になるから
    • 高品質な文字列表現を生成するコードを一箇所にまとめる事ができるから
    • アクセサを提供することで、エラーからの回復に役立てることができるかもしれないから
/*
IndexOutOfBoundsException を生成する
@params lowerBound 最も小さな正当なインデックス値
@params upperBound 最も大きな正当なインデックス値に 1 を足した値
@params index 実際のインデックス値 
*/
public IndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
  super("Lower bound: " + lowerbound +  ", Upper bound: " + upperBound + ", Index: " + index);
  this.lowerbound = lowerBound;
  this.upperBound = upperBound;
  this.index = index;
}

項目. 64 エラーアトミック性に努める

エラーアトミックとは

  • 「失敗したメソッド呼び出しは、オブジェクトをそのメソッド呼び出しの前の状態にしておく」という状態
    • なるべくエラーアトミックに保つことを意識する

エラーアトミックを保つための方法

  • 不変オブジェクトを設計する
    • 変更できないので、エラーが出ても同じ状態であるから
  • パラメータの正当性を検査する
    • オブジェクトの変更を行う前に例外をスローすることができるから
  • 失敗するかもしれない部分を、オブジェクトを変更する部分よりも前に行う
  • 操作の途中で発生する例外を捉えて、状態を戻す「回復コード」を書く
    • 一般的ではない
    • 主に永続的なデータ構造に対して使用される

ただし、エラーアトミック性は必ずしも達成できるとは限らない。例えば複数スレッドが同期なしに、同一オブジェクトを同時に変更する場合など。

メソッドがエラーをスローする場合は、一般的には回復不可能であり、エラーアトミック性を保持しなくてよい

  • たとえ回復可能でも、必ずしも望ましいとは限らない
  • 例外は回復可能である場合がある。対比に注意
  • メソッドの仕様の一部である例外は、エラーアトミック性を保持するべき
// 最初のサイズ検査が無くても例外を投げる
// しかし、それだと size フィールドを不正号な状態にしてしまい
// オブジェクトに対するその後の呼び出しを失敗させてしまう
public Object pop() {
  if (size == 0)
    throw new EmptyStackException();
  Object result = elements[—size];
  elements[size] = null; // 廃れた参照を取り除く
  return result;
}

項目.65 例外を無視しない

例外を無視してはいけない理由

  • 例外の目的が達成されないから
    • 例外の目的とは、例外的状態を処理させることを強制する、こと
    • 火災報知機を無視して、警報を切ってしまうのと同じ
    • 無視する場合はコメントに含んでいるべき
  • エラーをも無視してしまうから
    • 原因とは何も関係のないところでエラーになる可能性がある
    • 例外は、外に伝搬させるだけでも有益な情報を残してくれる
// ダメ、ぜったい
try {
} catch (SomeException e) {
}

感想

普段は ruby を書いているが、早速活かせそうな話ばかりだったので、試してみたい。

例外処理を書く時の指針が揃っていたし、その理由も納得できるものだった。特に「チェックされる例外」と「チェックされない例外」という考え方は自分が特に迷っていた部分だったので、参考になった。 例外連鎖を使うときにも指針ができた。

Java は型あるので、擬似コードが理解し易くていいですね。

こういうプログラミング全般?の話というか、プログラミングテクニックというか、そういう本って他にもあるのかな。
基礎力が無いのでもっと読んだほうが良い気がする。Effective Java も更に読み込んでいきたい。

2017/11/22 追記

かなりスペースを取ってしまってますが、冒頭に目次を追加しました。
忙しい人でも目次だけ読んで、気になったところだけ読めるようにする意図を込めてます。

rails 5 を今更触ってみた話

イントロ

rails5 の主要機能は色々と知られている通りだけど、業務に投入するとなると、やっぱり互換性だったり、細かいコードの違いが気になるもの。

今回 authlogic を使ったログイン機能を雑に作ったので、その限られた狭い範囲であるものの、ハマったところや違いをまとめてみた。

なんで authlogic ?

  • 昔業務で使ってたから。そしてまた使うかもしれない。
    • なお devise は初心者の頃使って見事に爆死し、それ以降使ってない。
  • そこそこ古く、歴史ある gem
  • シンプルな機能だけを提供してくれている
    • だから好き。
    • (ただし、中身はかなり癖のある実装)
  • 昔のおさらい。復習を兼ねて。

環境

  • rails (5.0.0.rc2)
  • authlogic (3.4.6)
  • rspec-core (3.1.7)

気づいたこと

authlogic は rails5 対応中

結論から言うと、今は使えなかった。
しかし対応 PR や issue も立っているので、しばらく傍観モード。

全くメンテナンスされていないわけではなさそうなので、少し安心した。

サンプルを作成する時には、vendor/bundle 以下に install されている authlogic を直接修正して、無理やり動かすことになってしまった。

トップページが….

初めて rails s して localhost:3000 にアクセスするとこんな感じ。かっこいい。

f:id:mgi:20160626180707p:plain

ログを見ると以下のようになっている。

Started GET "/" for 127.0.0.1 at 2016-06-25 22:55:12 +0900
  ActiveRecord::SchemaMigration Load (0.6ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by Rails::WelcomeController#index as HTML
  Parameters: {"internal"=>true}
  Rendering vendor/bundle/gems/railties-5.0.0.rc2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered vendor/bundle/gems/railties-5.0.0.rc2/lib/rails/templates/rails/welcome/index.html.erb (4.5ms)
Completed 200 OK in 24ms (Views: 11.3ms | ActiveRecord: 0.0ms)

Rails::WelcomeController#index というのが呼ばれている。 書き換えたいときは今までどおり、root を設定すればいいっぽい。

controller のテストが rails-controller-testing に切り出されている

assigns が使えなくて、scaffold で作ったテストが NoMethodError で落ちる。 assigns を使いたければ rails/rails-controller-testing を各自で install して使う仕組みになったようだ。(対応したのはもっと昔なのだろうけど、知らなかった)

rspecminitest とは違って、全部まるごと入ってるのが嬉しいから、個人的にはちょっと残念。今後の方針がどうなるかは知らないけど、テストのために色々な gem を探したり install するのは大変だし、面倒だと思う。その面倒さが無いのが一番の利点だと思ってたので。*1

prepend_before_filter が deprecated

authlogic の内部で書かれている。 なので bundler で環境を読み込むたび出てくるので鬱陶しい。

$ rails c
DEPRECATION WARNING: prepend_before_filter is deprecated and will be removed in Rails 5.1. Use prepend_before_action instead. (called from require at /Users/argerich/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib/bundler/runtime.rb:77)
Loading development environment (Rails 5.0.0.rc2)
>>

メッセージにも書いてあるとおり、prepend_before_action を使えば警告を解消できそう。

Fix deprecation warning: prepend_before_filter in Rails 5 by bparanj · Pull Request #491 · binarylogic/authlogic

use_transactional_fixtures が deprecated

rspec-railsuse_transactional_fixtures が deprecated になっている模様。 代わりに use_transactional_tests= を使えとあるけど、rspec-3.1.7 では対応されておらず、NoMethodError で弾き返される。

$ rspec
…
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/top_controller_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/user_sessions_controller_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/users_controller_spec.rb:21)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/models/user_spec.rb:3)
DEPRECATION WARNING: use_transactional_fixtures= is deprecated and will be removed from Rails 5.1 (use use_transactional_tests= instead) (called from <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/requests/users_spec.rb:3)
…

Deprecation warning: use_transactional_fixtures= is deprecated · Issue #1549 · rspec/rspec-rails を見ると、master branch では対応しているっぽい。

ActionController::TestCase

get url, parameter, session でお馴染みの、リクエストを送るメソッドの引数が、HTTP method だけになる模様。

get :show, params: { id: 1 }, session: { user_id: 1 }
process :update, method: :post, params: { id: 1 }
 (called from block (3 levels) in <top (required)> at /Users/argerich/dev/authlogic-rails5-example/spec/controllers/users_controller_spec.rb:57)
DEPRECATION WARNING: ActionController::TestCase HTTP request methods will accept only
keyword arguments in future Rails versions.

具体的には、以下の様な修正を行えばおk。
引数に名前がついて、わかりやすくなった。地味に嬉しい。

しかし、これは多分運用しているテストを直さないといけなくなるのかなー。

--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe UsersController, :type => :controller do

   describe "GET new" do
     it "assigns a new user as @user" do
-      get :new, {}, valid_session
+      get :new, params: {}, session: valid_session
       expect(assigns(:user)).to be_a_new(User)
     end
   end

ログが綺麗になってる

前はもっと asset 周りのログが沢山流れていたが、今は出ない。
すっきり。

Started GET "/" for 127.0.0.1 at 2016-06-26 00:09:06 +0900
Processing by TopController#index as HTML
  Rendering top/index.html.erb within layouts/application
  Rendered top/index.html.erb within layouts/application (11.4ms)
Completed 200 OK in 40ms (Views: 34.7ms | ActiveRecord: 3.1ms)


Started GET "/login" for 127.0.0.1 at 2016-06-26 00:09:12 +0900
Processing by UserSessionsController#new as HTML
  Rendering user_sessions/new.html.erb within layouts/application
  Rendered user_sessions/new.html.erb within layouts/application (3.9ms)
Completed 200 OK in 66ms (Views: 64.3ms | ActiveRecord: 0.0ms)

last_comment が deprecated

Rake.application.last_comment が deprecated らしい。 rspec-core3.1.7 では、ここRake.application.last_comment が使われていて、警告が出る。

 % rake
DEPRECATION WARNING: prepend_before_filter is deprecated and will be removed in Rails 5.1. Use prepend_before_action instead. (called from require at /Users/argerich/.rbenv/versions/2.3.0/lib/ruby/gems/2.3.0/gems/bundler-1.11.2/lib/bundler/runtime.rb:77)
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.
[DEPRECATION] `last_comment` is deprecated.  Please use `last_description` instead.

last_description に書き換えると警告が止まる。 確認はしてないが、master なら治ってそう。

まとめ

休日の空いてる時間で、雑に作ったものですが、できあがったものはこちらです。

mgi166/authlogic-rails5-example: authlogic meets rails 5

*1:もっとも、最近では controller のテストではなく request のテストを書く事が多いだろうと思われるが

webpack-dev-server を使う

イントロ

webpack dev server

見ればだいたい書いてある。
webpack は少し触らないと忘れてしまうので、メモしておく。
それと何故か webpack の公式 Document は読みづらい(私だけ?)というのも理由の一つ。

webpack-dev-server って?

開発用の web サーバー。Express で作られてる。 おおまかに2つのことができる

  • Hot Module Replacement
    • module を変更したら変更を検知して読み込み直す
  • Automatic Refresh
    • react とかで、component を変更したら、自動で reload する

これだけできれば、わざわざ変更のたびにサーバーを再起動しなおさなくて済むので、とりあえずの開発には困らなくなる。

導入

まずは npm install

$ npm i --save-dev webpack-dev-server

webpack.config.js を編集。

module.exports = {
   context: path.join(process.env.PWD, 'frontend'),
-  entry: "./index.js",
+  entry: [
+    "webpack-dev-server/client?http://localhost:8080",
+    "webpack/hot/dev-server",
+    "./index.js"
+  ],
   output: {
     path: path.join(__dirname, 'dist'),
-    filename: 'webpack.bundle.js'
+    filename: 'webpack.bundle.js',
+    publicPath: "/assets/"
   },
@@ -14,7 +18,8 @@ module.exports = {
   plugins: [
     new webpack.DefinePlugin({
       'process.env': {
         NODE_ENV: JSON.stringify(process.env.NODE_ENV)
       }
-    })
+    }),[f:id:mgi:20160416232151g:plain]
+    new webpack.HotModuleReplacementPlugin()
   ],

やってることは 3 つ

  • entry を増やす
    • webpack-dev-server/client?http://localhost:8080, webpack/hot/dev-server の2つ
  • plugins に webpack.HotModuleReplacementPlugin を足す
  • output.publicPath: を設定

後は option つけてコマンドラインで起動すれば良い

$ webpack-dev-server —hot —inline

まとめ

f:id:mgi:20160416232151g:plain

簡単に導入できて、凝ったことしない限りは十分なので非常に助かる。

magit と elscreen を使いやすく

イントロ

magitelscreen は結構相性悪いと感じることが多く、今まで以下の様な点で困っていた。

  • magit-mode を終了しても magit 用の buffer が消えない
    • なんとなく気持ち悪い
    • 1 つならまだしも、2 ~ 3個残る。
  • elscreen 上で magit buffer を閉じた時、どの buffer が開かれるか、学習できなかった
    • 本当は最後に作業していた buffer を開きたい。
      • 条件によっては、何故か magit 用 buffer がスクリーンに残ったり 、わけがわからない
    • 複数の elscreen 上で magit 動かすと、最後の buffer を全く覚えておらず、不思議な挙動をしているように感じてしまう。

何言っているかわからないと思うが、俺も何が起こってるかわからなかった。

magit + elscreen で運用している人が少ないのか、あまりインターネッツに情報がない。
これはなんとなく magit 使いづらいと感じていた部分を少しだけ直した話になります。

やったこと

以下のような関数作って、フックに追加する。
これで二週間くらい運用して見た限りでは、だいぶストレスがなくなりました。

しかし、magit-status 毎に buffer を新しく生成しているので、PC リソース(というかメモリ)を上手に使えていないはず。

(defun elscreen-kill-all-scratch-screen ()
  "Delete all scratch screen"
  (interactive)
  (dolist (screen-and-buffer (elscreen-get-screen-to-name-alist))
    (when (and (string-match "*scratch*" (cdr screen-and-buffer))
               (> (length (elscreen-get-screen-list)) 1))
      (elscreen-kill (car screen-and-buffer)))))

(defun delete-all-magit-buffers ()
  "Delete all *magit-xxx* buffer"
  (interactive)
  (dolist (buffer (buffer-list))
    (when (string-match "*magit" (buffer-name buffer))
      (kill-buffer buffer))))

(defun magit-mode-quit-window ()
  "Restores the previous window configuration and kills the magit buffer"
  (interactive)
  (delete-all-magit-buffers)
  (elscreen-kill-all-scratch-screen)
  (jump-to-register :magit-fullscreen))

(define-key magit-status-mode-map (kbd "q") 'magit-mode-quit-window)

(defun with-editor-post-finish-hook-1 ()
  (delete-all-magit-buffers)
  (elscreen-kill-all-scratch-screen))

(defun with-editor-post-cancel-hook-1 ()
  (delete-all-magit-buffers)
  (elscreen-kill-all-scratch-screen))

VPC についてまとめ

イントロ

VPC 周りの知識が曖昧なままここまで来てしまったので、整理する。
なんとなく知っていた用語や単語について、一般的な概要、及び AWS ではどういう特徴をもっているか、を大雑把にまとめたもの。

VPC

概要

Virtual Private Cloud の略。Vrtial Private Cloud とは、クラウド環境に構築された仮想的なプライベートネットワークのこと。
VPC 自体が 1 つの IP アドレスを持っていて、VPC は沢山の private ip を持っている。
そんなインターネット上のでっかい領域を指す。

AWSでは

  • VPC は CIDR = x.x.x.x/16 ~ x.x.x.x/28 の間で作成可能
  • デフォルトの VPC というものが存在する
  • CIDR は一度作成したら変更不可
    • 後からレンジを変えられないので注意
  • VPC 削除のタイミングで、関連のAWS リソースは削除されてしまう
    • subnet とか Internet Gateway とか色々。
    • 削除して作りなおすと、全て作り直しになってしまうので、CIDR は余裕を持たせるのが基本

VPC の作成方法

VPC とサブネット - Amazon Virtual Private Cloud

インターネットゲートウェイ

インターネットゲートウェイ - Amazon Virtual Private Cloud

  • VPC のルーティングを各サブネットのルーティングテーブルに追加できる
  • NAT 変換を行い、public IP を持つ instance がインターネットに接続する

という機能を持つ。

AWS では

  • デフォルトの VPC というものが存在する。
  • インターネットゲートウェイがデフォルトの VPC にアタッチされている
  • デフォルトのサブネットの中で起動したインスタンスは、public IP が付与される
    • public の IP が付与されると、インターネットゲートウェイが NAT 変換を行える。
    • つまりインターネットに接続できる

というわけで、何も考えずに EC2 インスタンスをポチると、外部と通信することができるようになるのは、AWS の素晴らしい挙動によるもの。

インターネットゲートウェイの作成

インターネットゲートウェイ - Amazon Virtual Private Cloud

ルートテーブル

ルートテーブル - Amazon Virtual Private Cloud

インターネットトラフィックがどこからどう流れていくか、を記した経路のこと。
どこからどこにトラフィックを流す、という対応表で表現される。

AWS では

ルートテーブル - Amazon Virtual Private Cloud

にまとめられてる。全部重要だけど最低限の機能としては次の通り。

  • VPC を作成すると、ルートテーブルが自動で割り振られて、インターネットゲートウェイへのルートが追加されている。これを メインルートテーブル と呼ぶ
  • サブネットを特定のルートテーブルに紐付けないと、メインルートテーブルに紐付けられる。
    • つまり作成したサブネットはデフォルトでインターネットに通信できる。
  • 新たに追加する場合、ルートは CIDR で指定する

ルートテーブルを操作する

ルートテーブル - Amazon Virtual Private Cloud

サブネット

VPC の中で作成するグループのこと。 VPC が持つ IP address の範囲を超えない程度に、自由にグループ化可能。

VPC都道府県とするなら、サブネットは市区町村みたいな立ち位置。

サブネットの性質上、ルートテーブルに関連付けることにより、初めてサブネット内にトラフィックを流すことができる。

{public,private} サブネット

public サブネット

インターネットゲートウェイにルーティングされているサブネットのこと。 public サブネット内に作成された EC2 インスタンスが public ip もしくは elastic ip を持っている場合、インターネットに接続することができる。

private サブネット

インターネットゲートウェイに routing されていないサブネットのこと。 このサブネット内に EC2 インスタンスを作成しても、通常はインターネットに接続できない。 インターネットに接続するためには、NAT gateway を構築したり、インターネットゲートウェイをルートテーブルに追加する必要がある。

予約された ip address

  • 10.0.0.0
    • ネットワークアドレス。ネットワーク自身を表すアドレス
  • 10.0.0.255
    • ネットワークブロードキャストアドレス。ローカルネットワークのホスト全てに通信するためのアドレス

これ以外にも AWS が独自に予約しているアドレスが存在する。

AWS では

  • AZ(Availability zone) にまたがってサブネットを構築することはできない

AWS が予約している ip address

参考

まとめ

AWS のドキュメントは入門書として優秀。 勿論周辺情報を理解するためには、別の文献や本で知識を補わないとダメだけど、AWS なら実際に手を動かしながら試せる環境があるし、概要の把握という意味では理解が進みやすい。

本当はもっと書きたかったのだけど、一旦ここまで。

ActiveRecord::Base.connection#table_exists? と ActiveRecord::Base#table_exists? の違い

イントロ

#

似ているメソッドで、微妙に挙動が異なり、ちょっとハマったのでメモ

違い

引数、結果をキャッシュするかどうかが違う

引数

  • ActiveRecord::Base.connection#table_exists?
    • 引数を一つ取る
ActiveRecord::Base.connection.table_exists?("users")
#=> 引数で受け取ったテーブルが存在していれば true, なかったら false
  • ActiveRecord::Base.table_exists?
    • 引数を取らない
class User < ActiveRecord::Base
end

user = User.first
user.class.table_exists?
#=> モデルに対応したテーブルが存在していれば true, なかったら false

キャッシュするかどうか

  • ActiveRecord::Base.connection#table_exists?
    • キャッシュしない
    • 毎回 database とやり取りして、テーブルが存在するかどうかを確認するメソッド
ActiveRecord::Base.connection.table_exists?('users')
#=> true

ActiveRecord::Base.connection.drop_table('users')

ActiveRecord::Base.connection.table_exists?('users')
#=> false
  • ActiveRecord::Base.table_exists?
    • 一度呼びだされたらキャッシュされる
      • キャッシュ先は ActiveRecord::Base.connection
user = User.first

user.class.table_exists?
#=> true

ActiveRecord::Base.connection.drop_table('users')

user.class.table_exists?
# => true
# DROP TABLE `users` したのに true が返る!

ActiveRecord::Base.connection.schema_cache.table_exists?('users')
# => true
#
# * `ActiveRecord::ConnectionAdapters::SchemaCache` によってメソッド呼び出し結果が保存されている。
# * `ActiveRecord::ConnectionAdapters::SchemaCache` インスタンスは `ActiveRecord::Base.connection` のインスタンスが保持している

結論

上にも書いたとおり、引数と、メソッド呼び出しの結果をキャッシュするかどうかが違う。

  • ActiveRecord::Base.table_exists? は通常 ActiveRecord::Base 継承したモデル(例だとUser)が対象となるので、引数を取らない
  • また、table の存在判定は、一般的には変更されにくい値であるので、一度 table_exits? を呼び出したらキャッシュされる

と覚えれば良い。

私は動的テーブルを作ったり消したりしていてハマってしまいました。
ActiveRecord を使って、頻繁にテーブルを作成したり削除したりする場合は注意が必要。かも。

# 表記に迷ったのですが ActiveRecord::Base.connection の戻り値は、ActiveRecord::ConnectionAdapters::XxxAdapterインスタンスが返るので、 ActiveRecord::Base.connection#table_exists? という表記は正確ではありません。可読性を考えて敢えてそのままにしてます。(rails 触ったことある人に馴染み深そうな表記のほうがわかりやすいのでは、と考えてのこと)

Mac で apery を compile する

イントロ

最近、職場の後輩に将棋で負けたことをきっかけに、将棋がマイブーム。

apery って?

将棋プログラムの一種。2014 年の世界大会で優勝したとかなんとか。
一部界隈では、あの ponanza 先生をも凌ぐとかなんとか言われている?らしく、とにかくすごく強いソフト。

Apery - Wikipedia

で、そんな世界大会で優勝した将棋エンジンが github 公開されているのを知ったので、compile してみました。

HiraokaTakuya/apery

そのままやると...

すさまじい警告が出て、失敗する

$ make
24 warnings generated.
g++ -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native  -o ../obj/common.o -c common.cpp
g++ -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native  -o ../obj/pieceScore.o -c pieceScore.cpp
g++ -o apery ../obj/main.o ../obj/bitboard.o ../obj/init.o ../obj/mt64bit.o ../obj/position.o ../obj/evalList.o ../obj/move.o ../obj/movePicker.o ../obj/square.o ../obj/usi.o ../obj/generateMoves.o ../obj/evaluate.o ../obj/search.o ../obj/hand.o ../obj/tt.o ../obj/timeManager.o ../obj/book.o ../obj/benchmark.o ../obj/thread.o ../obj/common.o ../obj/pieceScore.o -lpthread  -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native
ld: library not found for -lgomp
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [apery] Error 1

gomp というライブラリが無いと言われているようだ。

gomp とは

GOMP — An OpenMP implementation for GCC - GNU Project - Free Software Foundation (FSF)

openMP で実装している GNU project らしい。

openMP とは

複数CPU を効率的に計算するライブラリのこと。所謂スレッド。
http://www.cc.u-tokyo.ac.jp/support/kosyu/03/kosyu-openmp_c.pdf

計算を早く行うためにマルチスレッドで処理しているのだろう、と想像。

それと URI protocol の仕様として、計算中でも入力を受け取れるようにしなければならないので、そういうところでもスレッドの出番はありそう。

Mac でどう compile するか

g++-4.9 で compile する

環境

  • OSX 10.11.2
$ brew install gcc49
$ g++-4.9 --version
g++-4.9 (Homebrew gcc49 4.9.3) 4.9.3
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
--- a/src/Makefile
+++ b/src/Makefile
@@ -1,4 +1,4 @@
-COMPILER = g++
+COMPILER = g++-4.9
 #COMPILER = mpicxx
 CFLAGS   = -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp
 CFLAGS   += -march=native

結果

$ cd src
$ make
g++-4.9 -o apery ../obj/main.o ../obj/bitboard.o ../obj/init.o ../obj/mt64bit.o ../obj/position.o ../obj/evalList.o ../obj/move.o ../obj/movePicker.o ../obj/square.o ../obj/usi.o ../obj/generateMoves.o ../obj/evaluate.o ../obj/search.o ../obj/hand.o ../obj/tt.o ../obj/timeManager.o ../obj/book.o ../obj/benchmark.o ../obj/thread.o ../obj/common.o ../obj/pieceScore.o -lpthread  -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -march=native

というわけでうまくいった。

うまくいったけど、どういうことなのか

HiraokaTakuya/apery の README.md より。

Linux のディストリビューションによっては、Makefile に記述されている '-lpthread' を '-pthread' にしなければ、
実行時にエラーになってしまう場合があります。
Linux, Windows で G++ 4.8 以上のバージョンで動作確認をしています。
Clang では正しくビルド出来ているか確認出来ていません。
Visual Studio でビルドすることは現状では出来ません。
Windows でビルドする場合は、MinGW64 をお使い下さい

というわけで、g++4.9 を使うとうまくいく、と。
mac のデフォルトでは、gcc コマンドが clang コマンドの alias になるという罠があり、明示的に 4.9 以上の gcc を指定する必要があった、というオチ。
普段の生活では gcc に馴染みがないので、少しハマった。

起動を早くする

初回起動時は評価関数生成のため、起動が非常に遅い。
ので、以下を行って評価関数のみ生成しておく。

linux,macでのコンパイルと使い方 · HiraokaTakuya/apery Wiki

$ git submodule init
$ git submodule update --depth=1

# コンパイル済みの apery を bin 以下に移動させる
$ cp ../src/apery . 

$ ls bin
20151105                  Readme.txt                apery                     book                      make_synthesized_eval.bat make_synthesized_eval.sh

# bin の directory 上で `make_synthesized_eval.sh` を実行する。
$ cd bin
./make_synthesized_eval.sh
info string start setting eval table
info string end setting eval table

で成功。
make_synthesized_eval.sh が、bin 以下で実行されること前提の実装になっていたのが難しかった。

動かしてみる

# 入力待ちになる
$ ./apery

# 標準入力で通信開始の合図を入れる
usi

# apery が標準出力でレスポンスを返してくれる
id name Apery Debug Build
id author Hiraoka Takuya

option name Best_Book_Move type check default false
option name Book_File type string default book/20150503/book.bin
option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
option name Clear_Hash type button
option name Emergency_Base_Time type spin default 200 min 0 max 30000
option name Emergency_Move_Horizon type spin default 40 min 0 max 50
option name Emergency_Move_Time type spin default 70 min 0 max 5000
option name Eval_Dir type string default 20151105
option name Max_Book_Ply type spin default 32767 min 0 max 32767
option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
option name Max_Random_Score_Diff_Ply type spin default 40 min 0 max 32767
option name Max_Threads_per_Split_Point type spin default 5 min 4 max 8
option name Min_Book_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Score type spin default -180 min -32601 max 32601
option name Minimum_Thinking_Time type spin default 1500 min 0 max 2147483647
option name MultiPV type spin default 1 min 1 max 594
option name OwnBook type check default true
option name Skill_Level type spin default 20 min 0 max 20
option name Slow_Mover type spin default 100 min 10 max 1000
option name Threads type spin default 4 min 1 max 64
option name Use_Sleeping_Threads type check default false
option name USI_Hash type spin default 256 min 1 max 65536
option name USI_Ponder type check default true
option name Write_Synthesized_Eval type check default false
usiok

というわけで無事動かせた。
これで世界レベルの将棋ソフトが動かせる!!!

参考