その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

Kyoto.なんか #3で「そして物語は更に何度目かのアプリ内通知再実装を迎える」というタイトルで話してきました

kyoto-nanka.connpass.com

「そして物語は更に何度目かのアプリ内通知再実装を迎える」というタイトルで話してきました.スライドは以下です.

speakerdeck.com

前回開催のKyoto.なんか #2では「そして物語は何度目かのアプリ内通知再実装を迎える」というタイトルで話したのですが,今回はその後日談 (?) の話をしました.

とにかくストレージ周りで苦労をしたくない,そしてできるだけ生に近い状態でデータを取り扱いたい,というモチベーションから,バックエンドのストレージにS3を採用し,RSS (Atom) ファイルをPUT (or PATCH) することで,低い労力でスケーラビリティを確保するというアーキテクチャを提案しました.
実際のS3をバックエンドとしたアプリ通知実装は,個人の趣味アプリでPoC的に実装した程度なので,実際に高い負荷が与えられた時にどうなるのかというのは身をもって体感していませんが,まあある程度はいけるんじゃないでしょうか……という見解でいます (とはいえこれは実際にやってみないとなんとも言えないでしょう).


スライドの後半に出てくる,「onetime tokenを用いたS3からのfetch」については,発表中に「Pre-Signed URLsを用いれば似たようなことができる」という指摘を頂戴しました.ありがとうございます.セキュリティの要件にも依りそうですが,これを使うと対応できそうですね.
またスライドの方のブコメにも付いていましたが,「Amazon STSを使う」という方法でも対処できるようです.知りませんでした.


発表後の懇親会で議論した内容としては,

  • ユーザ数が多いとPUTやGETする為の金銭的コストがばかにならないのではないか
  • 複数の通知を1つに束ねる場合にRSSだと難しいのではないか

というものがあり,確かに……となりました.

前者の「金銭的コスト」については,確かに配膳する対象のユーザが増えれば増えるほどかさんでしまうので,実サービスに投入した時にうっかり破産,という風にならないような工夫が必要だなあと思いました.ここらへんは既存ストレージの管理コストや開発コスト等とのトレードオフになりそうな印象を抱きました.

そして後者の「複数の通知を1つに束ねる」というのはなかなかの難題だと感じました.例えば「Aさんがいいねをしました」という通知と「Bさんがいいねをしました」という通知をそれぞれ独立した1通ずつの通知にするのであれば,素朴に各々を1つのエントリにして,片一方を追記すれば良いので実装は単純ですが,仮に「Aさんがいいねをしました」という通知が来た後に「Bさんがいいねをしました」という通知が発火した際は「AさんとBさんがいいねをしました」という新たな通知文言となって届く,というような要件を満足したい時はそこそこ複雑な実装が求められることとなるでしょう.
こういう場合は追記するだけでは機能を実装できないので,過去の通知を舐めて,同種の通知をまとめて1つの新しい通知エントリにした後に不要なエントリを除去するというような実装になりそうですが,そうすると保持する通知件数に齟齬が生じる (例えば,フィードは10件の通知項目を保持しておいてほしいのにもかかわらず,その10件が同種の通知であった場合は1件にまとめられてしまい,通知画面が寂しくなる) 可能性があり,もう少しひねった実装にする必要がありそうです.なんらか工夫をする必要がありそうですね.大変だ.


という感じでまだまだアプリ内通知の設計・実装についての悩みは尽きないわけですが,ひとまず現状のステータスを更新したという次第です.今後も頑張ります.
なお,自分個人としての思いは「OSに通知機能があるなら,自前実装せずにそれに乗っかったほうが良い」というものです.しかしそうも言ってはおれん時もある.人生のようです.よろしくお願いします.

builderscon tokyo 2017に参加してきた

builderscon.io

参加してきました.
見た中で印象に残ったトークについて感想を少し.

詳細な内容を何も書けないんですがはちゃめちゃに面白かった

ブラウザ拡張のクロスブラウザ対応についての話.いろいろな便利ツールを自作して利用していて格好良かった.とにかくクロスブラウザ対応は大変そうという印象 (特にedge).なんとかなってほしい……

複雑なUIを持つブラウザアプリケーションをどう設計・実装するかという話.毎度のことながら実例や実コードを交えてわかりやすく説明していてよかった.Validation周りのアーキテクチャについてはあまり触れられていない分野だと思っていて,そこに言及されていたのは大変参考になった.
最後に言っていた「いくらきれいな設計に見えても,チーム内で合意が取れていない設計は間違えた設計」は本当に至言だと思う.

分割QRコードは完全に初耳で,その存在を知れただけでも収穫だった.大きなQRコードだと破損確率が上がるから,それを分割して1QRコードあたりの情報量を下げることで破損確率を下げるというのは考えられているな〜と思った.便利.

CIに時間がかかってしまうと開発のテンポが悪くなり結果的にスピードが落ちる.とにかくCIが遅いとつらい.そこで大量の並列数でゴリゴリぶん回して高速化を図るというのは合理的な考え方だと思った.そうした開発の足周りについてしっかりR&D的な活動をしているのは素晴らしい.

ファブレスな環境でキーボードを大量生産するという話.去年のbuildersconではcho45さんがキーボード製作のテクニカルな話で登壇されていたけれど,このセッションはどちらかと言うと製品製造の話で,製品設計や工場選定,部品選定,QAに至るまでのフルスタックなプロセスについて話されていて,とてつもなく濃密なセッションだった.有象無象の工場がどんどん違法行為を働いている様もうっすら明かされており迫力があった.普通にこういうコンサルタントで金を取れそう.しかしファブレスで製品を作るのはとにかく大変そうだ……

PHPの話かと思いきやHHVMの話だった.かつてPHP5で関数 (風) プログラミングスタイルで書かれていたSlackのサーバサイドソフトウェアをHHVMに徐々に移行して,今となってはHHVM + hacklangでPHPコンポーネントの全てが書かれているというのはなかなか先鋭的で面白かった.
「なぜHHVMを使っているのか」という質問に対して「SlackにはHHVMのコントリビュータがいて,知見を持っている人がたくさんいたから」と回答していて,技術選択のポリシーが確立されていてとても良いと思った.
ところで本筋とは関係無いんですが「Surprised type conversion」を「びっくり型変換」と通訳の人が翻訳されていて,非常に良い翻訳だと感心しました (本来は「Unexpected type conversion」とかになるはずなので,元の言葉のニュアンスを踏まえた結果なんでしょうが).

雑感

会場の日吉は程よく都心から遠く,カンファレンスに参加しているという感じがして良い.
慶応日吉キャンパスはHUBがあるので便利な一方,しかしついつい酒を飲みすぎてしまい一長一短あると感じている.毎日体調が悪かった.
それはそれとして今回も前回のbuilderscon同様様々な分野の話を聞けて良かった.次回も参加したい.

YAPC::Fukuoka Hakata 2017にてWeb Application Good Error Messageというタイトルで話してきました

表題のとおりです.話しました.
これは僕が普段の開発中にエラーメッセージと触れあう時に気にしていたり,考えていることを上手いこと言語化したいという試みから始まったものです.

speakerdeck.com

発表中にdan kogaiさんから「『間違えたことを言っているエラーメッセージ』も悪いエラーメッセージじゃないのか」というフィードバックを頂いて,確かに!! と思いました.すっかり抜けていました.おっしゃる通りです.

発表後頂いた質問としては「(セキュリティ的な観点から) エンドユーザ (非開発者) に詳細なエラーメッセージを表示しては駄目な場合とかがあると思うんだけど,そこらへんどうしてるのか」というものがあったんですが,回答としましては:

  • 技術的に詳細なエラーメッセージはエンドユーザに提供しない
    • エラーが出ている場合,エンドユーザに技術的詳細を提供しても基本的に対処不可能 (多くの場合サーバのエラーなので)
    • 非技術的な方法で対処できる場合はその手順をエラーメッセージとして示す (e.g. 「ネットワーク状態の良いところで試してください」)
    • 仮に「エンドユーザの操作・手順がおかしいからエラーが出ている」という場合があったとしても,それはアプリケーション側のバグ (ユースケースを想定してない,あるいは限定しきれていない) ということなので,「ユーザに正しい使い方を提示する」といったエラーメッセージは (基本的に) 提示しない
    • Tracking IDみたいなものを併記すると問い合わせとかに使えて良さそう
  • 何らかの統一的なエラーに統一する,みたいな手法もある
    • 「アプリケーションがおかしいので後でやり直してみてください」みたいなのに全部のエラーを隠蔽するとか……
    • これに関しては賛否あると思いますが
  • 技術的詳細が書かれた「解決」の為のエラーメッセージは開発者向けには出す

という感じでした.このへんは発表時間の都合上,省略してしまったので情報が薄くなってしまいました.反省しています.

あと「moznionを呼ぶボタン」はどういう実装になってるのかという質問もあったんですが,あれはボタンを押すとikachanが発火して僕にチャットでメンションが飛ぶという素朴な仕組みになっています.


今回のスライドに書かれていることは個人の思想が強いので,他にも様々な意見等あることと思います.エラーメッセージに関してはもっと色々な考えや,トピックや,思想があると思うんですがあまり表立ったものが無い気がするので,色々議論したい感じがしています.しましょう!

Exit statusのセマンティクス

*nixのexit statusのセマンティクスについてかつて質問して,答えていてもらっていたことを思い出したので記します.

moznion   [5:46 PM] signal受け取ってexitする時,そのsignalの値をそのままexit codeに使う,みたいなお作法みたいなのってあるんでしたっけ
takesako  [5:50 PM] ないと思いますー
moznion   [5:50 PM] 特に無いんですねえ,ありがとうございます
songmu    [5:51 PM] なんか、ラッパースクリプトとか書くときは、ものによるけど維持するように気をつけることとかある。
          [5:51 PM] horensoとかは維持するようにしてたはず。
moznion   [5:52 PM] なんかそこら辺はお行儀みたいな感じですかねえ
songmu    [5:53 PM] 上位でどのシグナルで殺されたかとか判断したいかどうか、とかかなぁ。
xaicron   [5:53 PM] eixt code はアプリ自体で定義しているものを出すってことに決めればいいと思ってる派
          [5:53 PM] コマンドラインだったら成功か失敗かぐらいしかほとんどユースケース無い気がする。あとはログを出そう
moznion   [5:55 PM] まあですよねえ
          [5:55 PM] 利用者がexit codeで挙動変えるような使い方をしてるかも知れないから,ラッパーであればcodeを維持するみたいな理念だと察しました
songmu    [5:55 PM] そですね
moznion   [5:57 PM] ログを出しておいたばかりにそれを利用者側に正規表現で引っ掛けられて挙動を分岐させられるのは起こりえそうですが知ったことではない
hirose31  [6:09 PM] exit code、意味あるで
tokuhirom [6:12 PM] どこがダジャレになってるのか気になっている
hirose31  [6:12 PM] w
          [6:12 PM] http://www.unix.com/man-page/all/3/sysexits/ とか /usr/include/sysexits.h とか。
          [6:13 PM] sendmailとかは、aliasesで呼ばれてるフィルタプログラムが exit 75 (EX_TEMPFAIL) すると再実行したりするで。
cho45     [6:15 PM] 絶妙に中途半端な数字だ
hirose31  [6:16 PM] 「いそのー EX_UNAVAILABLE しようぜー」
kazuho    [6:31 PM] execすればexitコード一致問題なくなるで
yappo     [6:52 PM] ひどいw
moznion   [7:31 PM] exit code割と無自覚に使ってたもんで,なんか紳士協定とかがあるのか気になったという次第でした
mattn     [7:32 PM] http://linuxjm.osdn.jp/html/LDP_man-pages/man3/exit.3.html
          [7:33 PM] https://ja.wikipedia.org/wiki/%E7%B5%82%E4%BA%86%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9
          [7:33 PM] こっちか
          [7:33 PM] 普段 0/1 だけど usage 出す時に変えたりしますね
moznion   [7:34 PM] fsckって論理和でステータス変わるのか……
          [7:34 PM] ですね,僕もそういう感じで使ってました

JSON::XSでデシリアライズする時にtrueとfalseの扱いを変更する

Perl5の話題です.
JSON::XSを用いて,JSON StringをPerlのHashRefにデシリアライズする時にtrueとfalseの扱いを変えたいという話です.

デフォルト状態でJSON::XSを用いてデシリアライズすると, trueTypes::Serialiser::true すなわち JSON::PP::Boolean の真値として, falseTypes::Serialiser::false すなわち JSON::PP::Boolean の偽値として扱われます.
普通の処理であればこれで問題ないかもしれませんが,例えば「JSON StringをPerlのHashRefにしてそれを更に何らかのシリアライザに通す」といったような処理をしようとすると,JSON::PP::Boolean のオブジェクトだと取り回しが悪い場合があります.そういった時に,true/falseを任意の値にマッピングするにはどうすれば良いか.

https://metacpan.org/pod/JSON::XS#true,-false
Types::Serialiser - simple data types for common serialisation formats - metacpan.org

やり方としては上記のドキュメントのまわりにあるように, $Types::Serialiser::true 及び $Types::Serialiser::false を任意の値 (reference) に書き換えてやると良い.これらの変数はourで宣言されており,外から操作することができる.

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = \1;
    $Types::Serialiser::false = \0;
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);

例えばこのようにすると true\1 に,false\0マッピングされるようになります.

あるいは以下のように所望のオブジェクトにマッピングすることもできます;

package MyBool {
    sub new {
        my ($class, $val) = @_;

        return bless {
            val => $val,
        }, $class;
    }
}

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = MyBool->new(1);
    $Types::Serialiser::false = MyBool->new(0);
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);

注意としては, $Types::Serialiser::true 及び $Types::Serialiser::false はreferenceを要求しているという点です.仮にここにreferenceではないscalar値なんかを突っ込むとなにが起きるかというと

use Types::Serialiser;
local $Types::Serialiser::true;
local $Types::Serialiser::false;
BEGIN {
    $Types::Serialiser::true = 1;
    $Types::Serialiser::false = 0;
}

use JSON::XS qw/decode_json/;

my $json = decode_json('{"true": true, "false": false}');
use Data::Dumper; warn Dumper($json);
$ perl json.pl
Segmentation fault: 11

SEGVします. JSON::XSはxsの処理の中身で Types::Serialiser::trueTypes::Serialiser::false の中身を SV * つまりreference相当に割り当てているためです.ここで Types::Serialiser::true|false にscalar valueすなわち SV を割り当てると当然SEGVしてしまいます.

このへん
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L95
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L1974
https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L136

<追記>
誤りの指摘を頂いたので修正しました.SvROKで事前チェックすれば良い気もするんですがどうなんでしょうね.

karupanerura [17:59]
リファレンスでもスカラ値でも `SV *` だから、ここでSvRVしているときにリファレンスじゃないとNULLが返ってくるのでSEGVしているというのが濃厚なきがする https://metacpan.org/source/MLEHMANN/JSON-XS-3.03/XS.xs#L139
[18:01]
リファレンスは `SvRV` というSVで、数値も `SvIV` というSVで差異はなくて、XSでSVを扱うときは基本的に `SV *` として扱うので、<追記ここまで>

きをつけましょう.

List::Haystack - A immutable list utility to find and count element


Perl5の話題です.List::Haystackというものを書きました.CPANにもアップロードしています.
経緯としては以下.


というものが欲しかったのです.List内の要素検索について,Immutableに処理をしたい + lazilyに処理したい,みたいなモチベーションです.

個別の使い方としてはpodを読んでもらうとして,例えば以下のように書くだけでシェークスピアのワードカウントなんてのを手っ取り早く書けるわけです.皆さん大学とかでやりましたでしょう.僕はやりませんでした.

use LWP::UserAgent ();
use List::Haystack;
use Data::Dumper;

my $response = LWP::UserAgent->new->get('https://ocw.mit.edu/ans7870/6/6.006/s08/lecturenotes/files/t8.shakespeare.txt');
my $txt = $response->decoded_content;
my $words = [grep { $_ } split /[^a-zA-Z0-9]/, $txt]; # XXX: Sloppy word separation!!!
my $haystack = List::Haystack->new($words);
print Dumper($haystack->haystack);

まあこういうのってperlだとHashとかで簡単に書けるんで別にモジュールにしなくても……と言う感じですが,アドホックに書くと同じような処理が随所に散らばってしまいがちで精神衛生上良くなかったので一丁モジュールにしたためた次第です.あとlazilyなconstructionが欲しかったので.

ご利用くださいませ.

Perl5のサブルーチン呼び出し時に二項演算子を期待していたら単項演算子の引数として認識されてしまう

sub func {
    return 100;
}

みたいなサブルーチンがあった時に,

say(func - 99);

みたいな感じで呼び出すと,パッと見 1 が出力されそうに見えるが,実際は 100 が出力される.
これは - 99func の引数として解釈されて食われるためである. B::Deparse するとわかりやすい.

$ perl -MO=Deparse func.pl
sub func {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func(-99);
func.pl syntax OK

ここから,二項演算子としての振る舞いを期待していた -99 と結合して単項演算子として扱われ, -99 が引数として扱われているのが読み取れる.

もちろん,サブルーチン呼び出し時にかっこを付けて

say(func() - 99);

という感じで呼び出してやれば当初の期待値である 1 が表示されるようになる.もしくはプロトタイプを利用する方法もある.

sub func () {
    return 100;
}

このように引数を取らないことをプロトタイプで明示すると,サブルーチン呼び出し時にかっこを付けなくても後続の値が引数として解釈されることはなくなる.その場合の B::Deparse の結果は以下のようになる.明解.

$ perl -MO=Deparse func.pl
sub func () {
    use warnings;
    use strict;
    use feature 'say';
    return 100;
}
use warnings;
use strict;
use feature 'say';
say func - 99;
func.pl syntax OK

これらは基本的な挙動ではあるのだけれど,日々を生きているとたまにハマりがち.例えば Time:PieceTime::Seconds をあわせて使ったときなんかに

use Time::Piece;
use Time::Seconds qw/ONE_HOUR/;
my $t = localtime - ONE_HOUR;
say $t;

などと書こうものなら,結果が Thu Jan 1 08:00:00 1970 というふうになり破滅する.localtime(-ONE_HOUR) となるからだ.
普段,perlの組み込み関数の時は (基本的に) かっこを付けずに記述するスタイルで書いていたので,組み込み関数である localtime をその感覚で使っていたところ綺麗にハマってしまった. *1


いままでは割と野生の勘みたいな感じでこのへんの諸問題を回避していて,「そういう掟なのじゃ」という感じで生活していたんですが真面目に B::Deparse とかで調べてみると色々学びがあるものですね.

*1:Time::Pieceはlocaltimeをoverrideするから最早組み込み関数ではないんでは? という意見はあると思いますが