Diary of a Perpetual Student

Perpetual Student: A person who remains at university far beyond the normal period

アグリコラ:泥沼からの出発 リバイズドエディション の誤訳指摘

遅くなってしまいましたが、 アグリコラ:泥沼からの出発 リバイズドエディション(日本語版)を開封しましたので、誤訳の指摘や補足情報の提示をします。ゆっくりやればよいと思っていたのですが、お盆休みで多くのプレイヤーが泥沼で遊んでいそうなのを観測したので取り組みました。

hobbyjapan.games

id:arthur-1 は2018年夏に海外で先行販売されたドイツ語プレプリント版を所有しており、そちらを訳したものとの比較になります。現時点ではエラッタが入っており、日本語版に反映されたカードもあるかもしれませんが把握はしていません。

なお、それとは関係なくこちらの情報も間違っているかも知れないので、間違いあれば指摘してください。

本記事はメンテナンス終了しました

公式の訂正情報が出たようなので、基本的には本記事のメンテナンスを終了します。以下を参照してください。

hobbyjapan.co.jp

[M125] 資材倉庫 など、私が気づいていなかったものも掲載されていましたので、ぜひご確認ください。

(逆に言うと、こちらでは指摘していますが掲載されていないものもありました。)

更新情報

  • 2022-10-14 15:20 公式の正誤表が公開された情報を追加
  • 2022-08-22 13:00 [M068] 教会のアイコン誤記に関する情報を追加
  • 2022-08-12 10:45 [M006] 高熱窯、 [M007] タイル窯は窯である旨に訂正

ルールブック

軽く目を通しましたがドイツ語版との致命的な差異はなかったと思います。強いて言えば、「つい最近まで、泥炭を掘るというということは…」という文章がありますが、つい最近なんてことはなくて、前世紀ぐらいかな〜と思いました。

カード

[M006] 高熱窯

  • 日本語版のカード名に窯と書いてあるが、ドイツ語版・英語版を見る限りは窯ではない
    • 例えば、[C075] 薪 のような、窯を参照するカードの対象にはならない
  • 2022-08-12 追記) こちらのミスで、窯としての扱いで問題ないです
    • 旧版のカード名と同じだと思っていたら変わっていた
    • そもそもルールブックに Brenn- und Heizofen gelten entsprechend ihrem Namen als Öfen. って書いてあった

[M007] タイル窯

  • [M006] 高熱窯と同様に窯ではない
  • 2022-08-12 追記) こちらのミスで、窯としての扱いで問題ないです

[M022] ビオトープ

  • 「自分だけ小麦 そして/または 野菜を植えていれば食料1を得る」のそして/またはの使い方がよく分からない。小麦を植えていれば食料1、野菜を植えていれば食料1の意味が正解
    • 両方達成していれば食料2もらえる
  • 「森タイル そして/または 泥地タイル」のところも同様に、森タイル最多なら食料1、泥地タイル最多なら食料1
  • さらに、森タイル/泥地タイルのカウントについては、単独で最多である必要がある。誰かとタイならもらえない

[M023] 森のはずれ

  • (補足) 柵のスペースとは、タイルとタイルの間にある柵を置ける空間のことを指す。
    • 隣接している、という表現が若干わかりにくいが、タイルとタイルの間にある柵置き場の数を数えてね、と言う意味

[M031] 有用動物市場

  • 前提の「動物5」は「家畜5」

[M032] 泥炭塀の小屋

  • 食糧供給フェイズ -> 食料供給フェイズ

[M039] 特別牧場

  • 「柵で囲んだスペースは、タイルがなくなると牧場になる。」の一文が丸々不要。[M038] 自然保護区域の文章からコピーしてきたのだろう

[M044] 沼地化

  • 沼地タイル -> 泥地タイル

[M049] 不動産地図

  • 前提の「2ラウンド以前に出す」は「ラウンド2以前に出す」の意味

[M055] 道具小屋

  • 『 特別アクション「焼畑」または「泥炭を掘る」を行う前か後、すぐにもう1つの特別アクションも実行できる』とあるが、もう1つのとは同じカードに描かれているはずの2つのアクションのもう一方という意味である
    • 焼畑」と「労働市場」を同時に、と言う芸当ができるわけではない
    • 「もう一方の」という言葉を使うとこのニュアンスが表現できているかもしれない

[M065] 消防署

  • 森タイル5枚以上であればボーナス4点得られる
    • 日本語版のテキストでもそう解釈できはするのだが、すぐ後ろの[M066] 耕作区画のテキストのように「2/3/4/5枚以上 ある場合」、とした方が分かりやすいのではないだろうか

[M066] 耕作区画

  • 注釈として(未使用スペースによるマイナス点に追加される。)とあるが、未使用スペースのマイナス点は変わらずあるよ、という意味
    • このカードで得られる点はボーナス点扱いのまま

[M068] 教会

  • ※誤訳ではないし、日本語版の問題でもない
  • 左下に「泥炭を掘る」マークが描かれているが、カード効果的に不要と思われる
    • 独語版でも同様に印刷されていたので、言語に依存しないミスだろう

[M069] 革の鞍

  • 「馬が3以上になるたび」というよりは「馬が3以上ならば」の方がわかりやすいだろう

[M070] 泥炭考古学

  • 「空いた農場スペース」というのは、ただの未使用スペースということではない。「泥炭を掘る」を行ったことによってちょうど今空いたスペースを指している

[M073] 繁殖手当

  • 旧版の日本語版で勝利点という表記が確かにあったような気もするが、ボーナス点に統一したい
  • 独語版では(以降同様)とは書かれていない。最大でも3倍までしかボーナス点はもらえないと思う

[M074] 管理部門

  • 前提の「手札の進歩* 4枚以上」は「4枚以下」の間違い
    • 旧版の泥沼拡張も同じ誤訳があった

[M078] 小舟

  • 燃料1と木材1を交互に置くのではなく、燃料1と食料1を交互に置く
    • そして、(先に木材1を置く)とあるが、燃料から置くのが正解

[M081] 泥炭船

  • (ここから、建設資材1種類だけを得ることはできない。)という注釈は間違い。建設資材1つだけを得ることができない、が正しい。
    • 燃料2で葦1を買うのはダメだよ、という意図の注釈

[M084] 炭坑場

  • ルール上問題はないが、カード名は炭坑場ではなく炭坑馬の方が適切だと思う

[M092] 乾燥地

  • 「空の農場スペース」とあるが、[M070] 泥炭考古学 での指摘同様に、「泥炭を掘る」を行ったことによってちょうど今空いたスペースを指している

[M096] 休閑地

  • [M070] 泥炭考古学・[M092] 乾燥地 での指摘同様に、「空いている農場スペースに食料1ずつ置く」のではなく、特別アクションによってちょうど今空いたスペース1か所に食料1を置く
    • 先に挙げた2つは正しく解釈できなくもないが、こちらは「食料1ずつ」と書かれており明らかに誤訳

[M099] 泥薬

  • 『人物がベッドに横たわっている「病院」のアクションスペースを使うたび』という表現は不適切。ベッドに寝ている主体は病院ではなく人物であり、「ベッドに横たわっている人物が」とするのがよさそう

[M101] 肉処理台

  • 4、7、9、11、13、14ラウンド -> ラウンド4、7、9、11、13、14

以上です。良い泥沼ライフを!

ダークモードをおまけ感覚で提供するのは厳しい

スマートフォンを先日発売された Google Pixel 6a に乗り換えました。 もともとは Pixel 3a を利用していたのですが、セキュリティアップデートの提供保証が切れたことが買い替えの主な理由です。

store.google.com

機種変更に伴って、ワンタイムパスワードを用いた2要素認証の設定をし直しました。

GitHub の Web 上で表示された2次元コードをスキャンしたのですが、なぜか読み込むことができませんでした。

2次元コードはダミーです

原因はダークモードなのに2次元コードの周囲に白い余白が十分用意されていなかったことでした。 試しに、開発者ツールからスタイルを変更して、以下のように余白を白くすると読み込むことができました。

.qr-code-img {
  background: white !important;
}

近年は、CSS でカスタムプロパティが使えるようになり、変数の値をまとめて変えるだけでフロントエンドのデザインテーマを切り替えられるようになりました。 しかし、ライトなテーマの延長として色を変えるだけで実装すると、このように操作性の悪い UI が生まれてしまうことがあります。

今回の件以外にも、プロパティの設定値によってコントラストの差が小さくなり、視認性が悪くなるという問題が起こることも考えられます。

実装コストとの兼ね合いを考えると気づきベースでなんとかしていくしかないような気もします。しかし、ダークモードが市民権を得た今、アクセシビリティテストを CI で動かすなどの手法(思いつきです、できるかどうかは分からない)により、ダークモードを安心して世に出せる仕組みが整備されたいと思いました。

株式会社はてなに入社しました(本物)

株式会社はてなに入社しました

株式会社はてなに入社しました - hitode909の日記


といういつものやつではなくて、これは本当の入社エントリ(にしては遅すぎないか?)です。

株式会社はてなにWebアプリケーションエンジニアとして入社して4か月が経とうとしています。 はじめての査定結果が通知され会計年度が終わろうとしているこのタイミングで振り返っていきます。

自己紹介

id:arthur-1 です。お理工だけじゃダメなんだ大学の学部に7年間通った後に、はてなに新卒入社しました。 3留なのでどこにも雇ってもらえないと思っていたけどなんとかなりました。

学生時代は学園祭実行委員会で参加団体向けの Web 申請システムやお客さん向けの優良企画投票システムを作ったり、ボードゲームに関する Web サービスを個人開発・運用したりしていました。

db.buratsuki.page

neo.buratsuki.page

また、専攻はコンピュータサイエンスだったのですが、研究では比較的得意だったプログラミング系から距離を置き、数理最適化×信号処理という分野をターゲットとしていました。

会社のこと

新卒研修

新卒エンジニア研修として、他の新卒エンジニアと一緒にとある社内向けサービスを開発しました。 この時の話だけで 1 エントリ書けてしまうので、今回は触れないでおきます。

技術グループのPodcast である Backyard Hatena でも少しだけ紹介されています。

developer.hatenastaff.com

所属チームのこと

5日間のエンジニア研修が終わったのちに、Mackerel という監視 SaaS の開発チームに配属されました。

ja.mackerel.io

Mackerel 開発チームへの onboarding

「Mackerel アプリケーションエンジニア入門」として、各領域に設定された実績を解除していきました。 チームとしてのタスク(スプリントバックログアイテムの消化)をメインとしてこなしつつ、余った時間で以下のような内容について学習しました。

幸いなことに、Mackerel で使っている技術スタックと自分が持っていたスキルがある程度合致していた(Scala・TypeScript+React)ので、業務に必要なプログラミングの習得には時間がかかりませんでした。 join して 3日目には最初の Pull Request をマージすることができました。 しかし、インフラ周り(特にクラウドサービス)の知識はほとんどなかったため、最初は話についていくのが大変でした。

メンターを始めとするチームメンバーの方々から絶妙なタイミングで新しいことにチャレンジする声掛けをしていただきました。 そのおかげで、毎日少しずつ背伸びをしながら、できる業務の幅を少しずつ広げていくことができました。

また、SRE と同じチームで一緒に仕事をしていることもあり、入社時と比べて格段にインフラ周りの知識が身についています。 これは、留学するとめちゃくちゃ英語できるようになる、みたいな話と近いかもしれません。

業務の上で心がけたこと

dogfooding

Mackerel を自分で使っているうちに見つけた要改善ポイントを、積極的にプロダクトバックログアイテムとして起票しています。 誰のどんな課題をどうやって解決するのかをきちんと言語化するのは大変ですが、時間をかけても良い大事な作業だと考えています。 これによってプロダクトオーナーを始めとするチームメンバーに、課題そのものや、課題の価値によってプロダクトの価値を上げられるということを共有することができます。 結果、優先度が高いと判断されたアイテムは、素早くスプリントに載せられ、チームとして取り組むことになります。

大規模なシステム開発だと、上から降ってきたタスクをただこなしているだけの人間になってしまいそうと思っていましたが、現実はそうではありませんでした。 Mackerel チームのスクラム開発では、 dogfooding を通じて明らかになった課題を、プロダクトの価値を高めるために解決していく仕組みが整っています。 自分がプロダクトに貢献できているという実感を持って取り組む仕事はとても楽しいです。

また、dogfooding から始めたこの取り組みによって、他のプロダクトバックログアイテムについても、利用者のことを考えた進め方を提案して進めることができています。

先ずテストより始めよ

チームに join してしばらくの間は、アプリケーションの仕様の理解に時間が掛かります。 先にテストを書いたり修正したりして、不安な場合にはこの状態で他のエンジニアに見てもらうことで、仕様への理解を深めました。 その上で、テストが通るようにアプリケーションコードを改修しました。

闇雲にタスクに取り組むのではなくテストコードから始めることにより、手戻りを減らしつつ、自信を持って Pull Request を作ることができました。

最後に

特に入社エントリとしてのまとめとしての話は用意していないのですが、このエントリで楽しく仕事をしていますという雰囲気が伝われば、エントリの目的は果たしていそうです。

これだけだと締まらないので、将来の話を少しだけします。

8月から始まる来半期は、自分の成長だけを考えていた状態からチーム・プロダクトの成長も合わせて考えられるようにシフトしていきたいと考えています。 チームとしてカバレッジが足りていない領域や、プロダクトが抱えている問題に合わせて、自分の成長の指針となる目標を設定しようと、メンターやディレクターと相談しています。

こんな形で今後も public な振り返りを定期的に書いていきたいですね。

ISUCON 12: はじめての ISUCON 体験記

ISUCON 12 予選にチーム「横浜の缶詰」*1として参戦し、最終スコア 9368 点を残して敗退しました。参考値ではありますが 104 位(661 チーム中?)ですね。

isucon.net

結果としては残念な感じですが、初めての ISUCON で今後の成長に繋がるやり方を貫き通せたところが良かったと思っています。次回以降も前向きにチャレンジしていきます。

さて、事前準備や当日の様子・学びについて振り返っていきます。

事前準備

まず、3人で作戦会議をしました。アプリケーション担当 2人 + インフラ担当 1人という形で分担することにしました。(自分はアプリ担当。)

また、事前演習として、チームで ISUCON 10 予選問題を解きました。計測すると明らかに「なぞって検索」が支配的に重いことが分かるのですが、ここの改善は MySQL の GEOMETRY 型(初見)が登場してとても難しいです。なんとか改修することができたのですが、演習時間のほとんどをこれ1本に注いで終わってしまいました。

演習後、チームで KPT 法による振り返りを行いました。以下はその抜粋です。

  • K: deploy 周りのスクリプト用意されていてよかった
  • K: log の grep テク大事
  • K: 計測して改善のムーブを大事にできた
  • P: 今何をしているか共有できなかった
  • P: 重いタスクを2つに分割したつもりだったけど完全に分割できていなかった。2人で同じところにハマってしまった
    • T: リソース効率よりもフロー効率を重視して、基本ペアプロで進めていく

加えて、チームでの事前演習とは別に社内の ISUCON 練習会に参加しました。朝まで飲んでいたら寝坊したので、本番前日はたとえ飲みに行っても終電で帰るという強い誓いを立てました。

予選当日

このセクションでは、アプリケーション担当の id:arthur-1 目線で競技中のタイムラインを記述する。(インフラ担当の頑張りはあまり記述していない。)

9:30 起床

起床チャレンジ成功。ゴミ捨てチャレンジには失敗した。

10:00 マニュアル・ドキュメント読み合わせ開始

インフラ担当が準備している間にアプリ担当2人でマニュアルを読んだりブラウザで動作確認したりしていた。

10:30 リポジトリ準備完了

インフラ担当がソースコードGitHubリポジトリとして共有した。ソースコードをまじまじと眺めて、テナントごと SQLite のファイルがあるのに気づき戦慄する。

動画で CSV によるスコア入稿が重いという話があったので、 func competitionScoreHandler を読んでいた。明らかに n+1 なので、bulk insert にしたり一括で select するようにしたい。

11:00 対戦環境が整う

Discord に alp や slow-query の結果を投稿してくれる君が動くようになった。alp の結果を正規化し、ベンチを回して解析結果を眺めようやくスタートラインに立つ。

GET /api/player/competition/:competition_id/ranking エンドポイントを 2人がかりで改善していくことに決定。

11:30 competitionRankingHandler をターゲットに

visit_history という謎テーブルはどうやら課金額の計算に使用しているらしいことが分かる。以下の修正方針を立てる。

  • player_score テーブルに index 張りたい
  • retrievePlayer を for文で回しているところが n+1 なので直したい

改善したいクエリって slow_query に載ってるのか?という痛い指摘を受けたけれど、残念ながら SQLite なので改善すべきクエリなのかはわからない。

11:50 player_score に index 張った

他のクエリに影響がでないよう player_score 関連のクエリを洗い出して index 設計。SQLite だと CREATE TABLE と同時に INDEX 作れないことを知る。

12:20 デプロイ成功 & index の効果をベンチで確認

自分達で build したバイナリのデプロイ成功。スコアは誤差レベルでほとんど変わっていない。

12:40 SQLite 脱却の決意

GET /api/player/competition/:competition_id/ranking の n+1 クエリ脱却に成功。デプロイしたが、スコアは向上せず、むしろ下がってしまった。pprof や alp の結果を見ると明らかに良くなっているのだけれど。

これは SQLite なのが悪いと断定した。また、以下の点から出題者が MySQL への移行を推奨しているのではないかと思った。

  • テナントごと DB が分かれているにもかかわらず、tenant_id カラムが用意されていること
  • SQLite の db ファイルを SQL のクエリ列に変換するスクリプトが用意されていること

SQLite のままだと3台フルに生かした複数台構成にしにくいこともあり、脱却を決意した。

13:50 アプリケーションコードの SQLite 脱却完了

昼休みを交互に取りながら、SQLite 脱却作業を行う。

tenantDB にアクセスしに行っているコードを adminDB に書き換える作業が完了した。残すは初期データの生成のみ。

14:10 tenantDB の初期データを MySQL に作り替え始める

tenantDB 初期データの mysql 向けの dump ファイルを作りたい。sqlite3-to-sql スクリプトの出力結果を MySQL に食わせているけど永遠に終わる気配がない。これはでかい釣り針に釣られてしまったのではないかと焦り始める。

15:15 bulk insert に変換する

sqlite3-to-sql スクリプトの出力結果を bulk insert に変換するスクリプトができたので、初期データを MySQL に移すのを試してみる。

max_allowed_packet に邪魔されて実行できない。

15:30 MySQL 移行諦める

事前練習でなぞって検索に全振りしてしまった反省から、 MySQL 移管を損切りすることに決める。

これができないと App 2+ DB 1 のような複数台構成を目指すのがかなり無理になってしまい、インフラ担当が今後できる改善ポイントが減ってしまうので、やり遂げる技術力がなくてただただ残念に思っていた。

作戦会議をして、全体としては GET /api/organizer/billing の改善に pivot することに決めた。

また、僕は動画で紹介されていた CSV 周りの処理が気になっていたので、 POST /api/organizer/competition/:competition_id/score の改善に取り組むことにした。

15:50 competitionScoreHandler の n+1 を直す

POST /api/organizer/competition/:competition_id/score の処理が n+1 になっていたので直した。元から NamedExecContext を使っていたので bulk insert に書き換えるのが簡単だった。優しさを感じる。

この頃インフラ担当が App + DB の 2台構成を試していたのでスコアの比較はできなかったが、少しは改善しているように見えた。

16:45 billingHandler の改善を進める

visit_history を記録しているけど、課金に必要なのは最初の visit の情報だけなので、 min_visited_at を記録するテーブルとして分割することにした。コードの改修が終わってベンチを回すも整合性チェックに fail する。

どうやら、初期化時にテーブルの生成ができていないようだ。webapp/sql/admin/10_schema.sql に書けば良いと思っていたけど、このファイルはアプリケーションでは使っていないことに気づく。init.sql に移し替える。

16:55 最高スコア記録

インフラ担当の頑張りによって、最高スコア 9777 点を記録する。確か player_id の謎の生成方法をやめて uuid に変えていた。

17:05 min_visited_at 初期データ作り

500 エラーは発生しなくなったが、引き続き整合性チェックに引っかかる。そもそも、初期から visit_history にレコードがあるので、ここから初期データの min_visited_at を作らなければならないことに気づく。

ここを直すと大幅にスコアが伸びると期待されていたので、焦りながらも黙々と進める。

17:30 初期データ作り失敗

min_visited_at の初期データを作るクエリを組み立てるのに試行錯誤していた。成功するも整合性チェックに通らず。どうやら課金額 50 円ほど少なく計算されてしまっているようだ。課金計算という言葉に拒否感を覚え始める。

インフラ担当が店じまい(デバッグログ出力の停止など)を始める。

18:00 競技終了

最終スコアは 9368 点。結局 GET /api/organizer/billing の改善を完遂することはできなかった。最後のデバッグログ停止などはあまりスコアに響かなかったようだ。

チームで振り返りをした後解散。夕食を食べて酒を買い出しに行き、社内のオンライン反省会に参加した。PHP 大好き〜〜と語っていた。

スコアの変遷

感想

個人的には、以下の点について反省しています。

仕様の理解をおろそかにしていて、改善アイデアが参照実装コードに縛られていた

コードがユーザにどんな機能を提供しているのかの理解をおろそかにしていました。コードを見れば、「あっ、ここは n+1 なので直せるな」といったことはすぐに分かります。しかし、このような一目でわかる小手先の改善だけでは戦っていけません。その関数が結果として何を返しているかをあまり考えずにいたので、「visit_history テーブルってそもそも必要なくない?」などの抜本的な改善のアイデアに辿りつくことができませんでした。

ブラウザ上でアプリケーションを動かすのをもっと丁寧にやると良いのかもしれません。

コーディング速度が遅かった

単純に、改善ポイントを見つけてから改善するまでの時間をもっと短くしたいと思いました。チームでは Go 言語を選択したのですが、僕は Go を読み書きし始めて日が浅く、流暢にプログラムを書くことができませんでした。

ISUCON に関する圧倒的な情報量・pprof などの使いやすい解析ツールの有無を考えると、Go 言語を選ばないとハンディキャップになってしまいます。仕事でも Go 言語を使う機会がかなりあるので、半年以内には素早く書ける言語にしていきたいです。

とは言ったものの、最初の練習から比べるとかなり書けるようになったので、その分は自分を褒めたいです。NamedExecContext を見て「おっ!」と反応するのは golang 初見ではできなかったでしょう。

最後に

ISUCON めちゃくちゃ楽しい!!!本当に isuports(esports) そのものだと思いました。ISUCON に取り組むことで得た技術力やマネジメント力はきっと業務に活かせると確信しています。来年も絶対出るぞ!!!!

*1:チーム名は ISUCON チーム名ジェネレータ で決めました。たまたま自分は横浜市民でした

最近聴いてる曲: Jul. 2022

社内でおすすめの新譜を載せていくチャンネルが生えていてめちゃくちゃ良いな〜と思った。

全然新譜ではないけれど最近聞いてる曲を淡々と載せていく。

我愛你 / Cody・Lee(李)

www.youtube.com

ドラを鳴らす曲を作りたかったので参考に。

ただ選択があった / フロクロ

www.youtube.com

IV△7→♭VII7 が最高。

24 night / matsudamiki

www.youtube.com

近所のラーメン屋さんでずっとこのアーティストの曲を流しているので聴いている。*1

負け犬 / RuLu

www.youtube.com

缶缶さんめちゃくちゃカッコ良い〜〜〜良い。アルバムとしても結構好きな曲が多いので良く聴いている。

唇の凍傷 / ワルキューレ

www.youtube.com

華やかな転調、そして転調。

君たちキウイ・パパイア・マンゴーだね。 / 古幡愛

www.youtube.com

名曲のカバー。みんな言ってるけど PV が良い。

まるで霧雨のような / はるふり

www.youtube.com

ギターの音色、シンセソロ、好きな要素しかない。

感想

仕事始めてから高校の軽音部に定期的に遊びに行くことがなくなってしまったので、若者のトレンドについて行けていない感じがする。

*1:と思ったけど、この前行った時には Just the Two of Us が流れていて、あれ?ってなった

悪しき日にレジリエンスを考える

この4月にブログを新しく作り直したのですが、前のブログの記事の移転がまだできていません。

それはそうと、今日は自分が昔書いたエントリを掘り返して、令和最新版にリファインメントしていこうと思います。

東日本大震災

急に思い出した話を書きます。

7年弱前*1に起きた東日本大震災。 (もうこんなに昔なんですね)

当時僕は中学2年生で、学校の体育館で3年生を送る会を行っていました。 突然の長い揺れ。ガラスの割れる音。

急いで教室に戻りテレビをつけると、大津波警報の文字が。 震源から数百km離れた沼津でも、保護者引き渡しとなりました。 家に帰るための国道は津波警報のため通行止めになりました。

中学の体育館は、新しいものができるまで立ち入り禁止になりました。 仙台の親戚の家は水たまりを残して跡形もなくなりました。 電池などの物資が不足し、大阪の友人が送ってくれました。

震災から1週間経ったある日、 国語の授業のはじめに先生がこんなことを言いました。

「大震災の報道を見て、 非日常に興奮してしまっている自分がいるんじゃないか。 みんなも自問自答してみてほしい。」

この発言は非常に衝撃的でした。 さして当事者でもない僕の気持ちを表現するに どれほど的を射た発言だったでしょうか。

多くの人が亡くなって、 生き残った人も過酷な避難生活を過ごして、 大規模な原発事故も起きて、

そんな状況に自分は興奮しているとするならば、 どれだけ残酷なことだろう、 と子どもながらショックを受けました。

こんなことをメディアで言ったら それこそ津波のような勢いで叩かれるでしょう。 しかしながら、それを的確な言葉で伝えた先生。 この立場だからこそできることだと思います。

不謹慎という言葉だけで片付けられるでしょうか。

ニュースや週刊誌に載っているのは他人の不幸ばかり。

これは人間の真理なのかもしれません。

最後の締めにそれっぽいことを言っていますが、結局何が言いたいのか分からない文章ですね。これは、当時の自分が「じゃあ人々はどうしたら良いのか」という問いに対する回答を明確に持ち合わせていなかったことによります。書いた当人(自分です)は満足しているかもしれませんが、これではただのポエムです。

4年半経った今、自分の思っていることを、解像度を上げて書いていきます。

客観視し、自分を受け入れる(抽象)

僕は医者や心理学者ではないので断定はできませんが、大きなインパクトのある事故や事件に対して、落ち着きのなさを感じるのは心理的反応としておおむね自然なものでしょう。無理して「いつも通りに生きよう」という気持ちにはなれない、もしなれたつもりでも本当にそうなのかは分からないと思います。

レジリエンスという言葉があります。心理学の分野では、自己に不利な状況に自身のライフタスクを対応させる能力を指すそうです。

ja.wikipedia.org

アメリカ心理学会が提唱する「レジリエンスを築く10の方法」のうちに、「変えられない状況を受容する」ことがあります。動揺し興奮した自分を否定せず、生理的現象・事実として許容することが、レジリエンスの面で有効ではないかと考えています。

仕事の話に例える(具体)

Web アプリケーションエンジニアをしていると、多かれ少なかれシステム障害に遭遇することがあります。対応中、当然多くのエンジニアは焦燥感を覚えるでしょう。焦った結果、ミスコミュニケーションが発生したり、判断ミスでより良くない結果をもたらしたり、ストレスを感じ engagement の低下に繋がったりすることになります。

しかし、「焦らないようにしよう」「落ち着いて対応しよう」という TRY が提示されても、それを実現するのは難しいと思います。もちろん、こういった問題への対応は、個人の努力だけに依存せず、仕組みで解決する方向に持っていきたいですね。ただ、理論的にはそうでも、個人の心情問題なので難しいところがあります。

1つの策として、事象を受け入れられない自分や他者を客観視し、受け入れるという方法があるのではないかと思います。これは個人的な経験に基づくものでしかないのですが、自分の感情を一歩外から引いて見つめ、「まあそういうこともあるよね」「人間として自然だね」と事実として受け止めることで、感情の保持者という意識が薄れ落ち着くことができます。「落ち着け」と言って押さえ込むのは、ポジティブフィードバック*2になる可能性があり、制御の観点上望ましくありません。外圧を受け入れしなやかに適応していこうとすることが、無理せず維持できる、あるべき心の持ちようなのではないでしょうか。

最後に

いつかこの話をしようとずっと思っていたのですが、自分の身の回りのあらゆる様子が乱れている今日この頃に、言葉としてまとめておきたかったのでした。そして、この行動自体もまた防衛機制の表れなのかもしれません。

*1:本エントリ執筆は2017年12月20日です

*2:前向きな内容を相手に伝えるという意味ではありません: ポジティブフィードバック - Wikipedia

LambdaでSlack Botを開発するTips

本エントリは、バックエンドにAWS Lambdaを用いたSlack Botを開発するときのTipsを雑多に垂れ流していくものです。普段の記事と異なり、エントリとしての主張のまとまりに関してはほとんど考慮していません。

Slackで使えるインタフェースを把握しよう

Slack Appを作るとき、我々はSlackの仕様に縛られることになります。Slack側が用意してくれているインタフェースを使ってユーザはAppとやりとりすることになります。

ただ、Slack Appでは思ったよりもリッチなアプリケーションを作れます。作りたいものが決まって余裕があるなら、Slackが提供しているユーザインタフェースのうちどれを使うと良い体験になるかを考えてみると良いでしょう。(そもそも、こんなUIが実現できるんだという存在を認識するところがスタートです。)

  • メッセージ
  • スラッシュコマンド
  • モーダル(アクション)
  • ホームタブ
  • ショートカット
  • ワークフロー

もちろん、UIがリッチになっていくほど実装は大変になります。作り始めから特定のUIに依存しない形でサービスロジックのコーディングができていると、他のUI経由にしたいときも実装を変えやすいです。

Slack Appの設定はコード化しておこう

複数人で開発すると、どうしてもSlack Appの設定(エンドポイントURLや必要な権限など)の情報を各人で共有することが難しくなります。開発環境と本番環境など、同じような設定を複数のSlack Appにしたいならば尚更です。

インフラ構成をIaC (Infrastructure as Code) で管理したくなるのと同様に、Slack Appの設定はmanifestファイルとして管理することができます。

api.slack.com

個人でお遊びのものを作る分には良いのですが、業務で使うようなAppの開発は引き継ぎできるかも大事なポイントの1つです。設定をコード化して属人化を防ぎましょう

レスポンスは3秒以内にSlackに届けよう

スラッシュコマンド実行時やModalでの送信時に、SlackからSlack Appに対してリクエストが送られます。API Gateway経由で起動したLambdaでそのリクエストを処理してレスポンスをSlack側に返してあげる必要があります。

ここで、リクエスト送信からレスポンス到着までの時間が3秒以上かかってしまうと、Slack上では「何らかのエラーが発生しました」と表示されてしまいます。ある程度時間がかかってもレスポンスさえ返せばSlack上での画面変化が正常に行われるのですが、エラーと表示されてしまうとユーザとしては不安ですよね。

アルゴリズムの改善や実行に必要なファイル群のサイズを減らすなどによって実行時間を減らすことができますが、これらの細かい工夫だけで解決ができるかは保証できません。何より、他のAPIにアクセスして結果をもらうという典型的な処理においては、そのアクセスのレイテンシも乗っかってくるので、自分たちがいくら努力しようともどうしようもないことがあるでしょう。

以下の記事で紹介されている通り、レスポンスを受け取ったらすぐに空の200を返してしまい、その後で黙々と処理をするのが良さそうです。

qiita.com

また、金銭的余裕があるなら、Lambdaのコールドスタートをできる限り回避するためにプロビジョニング済み同時実行を使うなどの工夫も考えられます。(それだとLambdaの良さ台無しでは?というところではありますが。)

なお、Slack Appレベルの規模の場合、ランタイムの選択によって3秒問題を解決できるほど大きく実行時間が変わるということはないと考えています。JavaScriptでもGoでも好きな言語を選ぶと良さそうです。(ただしJavaは微妙かも。)

acro-engineer.hatenablog.com

dev.classmethod.jp

Slackの再送による多重実行を回避しよう

「レスポンスは3秒以内にSlackに届けよう」にも関連するのですが、SlackがSlack Appのエンドポイントにリクエストを送った結果エラーとなった場合、Slackは自動でリクエストを再送します。これは、3秒以内にレスポンスが届かなかったとき(http_timeout)にも起こるので、上の問題と絡み合って不具合の原因特定と解消が困難になってしまいがちです。

Slackのお節介な仕様が必要でなければ、再送された場合のリクエストに対しては処理せず200を返すだけにしてしまいましょう。概ね、以下の記事の「パターン3, そもそも再送を無視する方法」に紹介されている通り、ヘッダを見て再送しているリクエストかどうかを判別するのが良さそうです。

dev.classmethod.jp

AppからSlackにメッセージを投稿する場合、エラーハンドリングの方針を考えよう

Slackを通じて何らかのアクションを行う場合、きっとSlackのメッセージで結果を返したくなるでしょう。もし、バックエンドのメインロジックの処理が成功したのに、その後でSlackのエラーが発生してレスポンスがエラーになると、一見処理は正しくできているのにSlack上では「エラーが発生しました」と表示されてしまいます。

で、Slackのエラー(もっと具体的に言うと、LambdaからSlackのAPIを叩くときのクライアントエラー)なんて起こるのかというと、意外と起こる可能性があります。例えば、SlackのAPIは短い時間でたくさん呼ぶとthrottlingエラーを返します。1回のアクションに対して複数回postするような場合には注意が必要です。

補助的にSlackのメッセージを送りたいのであれば、あえて、Slackの投稿に失敗してもLambdaの実行失敗としないようにエラーを隠してしまうのも一つの手だと思います。

逆にSlackにメッセージを送るのがMustな要件の場合は、キューサービス(AWS SQSなど)を挟んで投稿専用Lambdaを作るとよいでしょう。投稿用Lambdaは投稿に失敗したら異常終了するようにし、Queueに再送することでthrottlingなどによる投稿失敗を防ぐことができます。何回も失敗する場合には何かがおかしいので、Dead Letter Queueに入れて元のキューが詰まらないようにし、DLQのメッセージ数をモニタリングしてアラートを出すと良さそうです。

リンク集