bussorenre Laboratory

hoge piyo foo bar

ISUCON9 に参加し惨敗しました

お久しぶりです。 @bussorenre です。

前々から存在は知っていたものの、なかなかプライベートの予定が合わず参加出来なかったISUCON にようやく初参加出来ました!! 結果は、最終スコア2910 (初期スコア 2100くらい)で、まぁものの見事に惨敗です 😇

やったことの振り返りと、次回参加とかに向けての反省をば

チーム構成

会社のメンバーと組みました。と言っても普段業務で一緒に仕事をしているメンバーではなく、全く別のプロダクト開発に従事ししているメンバー、つまりほぼほぼ「はじめまして」のメンバーでチームを組みました。 メンバー全員にruby 知見があるので、使用言語はruby に即決。それぞれの担当としては以下の通りです。

  • @Yoshimaru46
  • @Miyamizu
    • アプリケーションの改善
  • @bussorenre
    • 計測環境の整備やデプロイの自動化。ミドルウェアのチューニング等

@Miyamizu 氏が9月入社のメンバーで、@Yoshimaru46 氏がそのメンターということもあり、二人のペアプロ体制が最初に出来上がり、そこに私が乗っかった形のチーム構成です。

事前準備

チームが決まったのが8月最終日くらいだったので、事前準備にあまり時間を割けなかったのですが、就業後の時間等を利用し、物理的に集まって作戦会議等をしていました。

作戦会議で決めたことは以下のとおりです。

  • 目標
    • isucon に挑戦する(0次予選を突破する = 起床して課題と向き合う)
    • 0点を回避する(必ずスコアが出る状態で終了する)
    • 出来ることを確実にやる

初挑戦から優勝を狙いに行く強者もいますが、我々の実力的に(準備期間的にも)まずそれは難しいだろうということで、「わからないことに挑戦すること」を最も重要な目的としました。 雑に「絶対優勝すっぞ!!」とか言うと、圧があってかえって挑戦するマインドを萎縮させかねないので、「まぁ楽しもうぜ」ということを、共有しました。

一方で、出来ることは必ずやるようにしようという目標も設定しました。具体的には以下の事を必ずやるようにしようと事前に決めました。

  • アプリケーション系
    • index をしっかり貼る
    • N+1問題の解消
    • SQLの並列化
    • 画像をバイナリでDBに持っているのをやめる
    • 可読性低いコードをやめる
  • 計測系
    • スロウクエリの自動検出
    • デプロイの自動化
    • nginx のログをalp で統計する
  • ミドルウェア
    • nginx じゃないhttpサーバーが来たらnginx にする(わからないから)
    • DBロックの調査
    • Cache Control
    • タイムアウト/キャッシュのの設定を適切に
    • Worker数を適切な数に

また、alibaba Cloud という普段使い慣れないクラウドが舞台なので、確実にインスタンスを立ててssh 出来るようにする練習を行いました。

予選開始前から、解くべき課題を見つけるくらいまで

予選当日は9時過ぎくらいに会社に集合し、設営を行い、9:50分ごろから事前ブリーフィングを行いました。

まずは午前中にやることを共有し、私は以下のことを担当することになりました。

  • 起動しているサービスを確認する service --status-all
  • 全ての情報をgithub に乗せる
    • application (全実装)
    • conf(nginx や mysql など)
  • ↑で載せた情報をシンボリックリンクにする
  • デプロイ自動化
  • alp の構築
  • 全体的に基本的なnginx.conf の見直し

私がこれらの基本的な設定に取り組んでいる間に、他二人のチームメンバーには課題を読み込んでもらい、N+1等、解決できそうな問題を発見してもらうことにしました。

諸々の準備が整ったのが11:52分。その頃には「どうやら /users/transactions.json あたりがN+1でヤバそう」という情報がアプリケーションコードからも、alp の統計からもう読み解くことが出来ました。

昼食。とりあえず寿司を発注する

メインイベント このあたりで、絶対集中力が尽きるだろうと思っていたのと、前日のチームが寿司を発注しているのをTLで観測したのを受け、「これはもう寿司頼むしかねーな!」という感じで頼みました。 発注から1時間足らずで届いたのにはびっくり。銀のさら様様です。

f:id:bussorenre:20190908130418j:plain

昼食を取りながら、お互い次に取り組むことを決めますが、どうもアプリケーション側にボトルネックが多いと判断し、私も午後からアプリケーション側を見ることになりました。 予定ではnginx の最適化とか、せっかくインスタンス三台使えるんだからDBとapp 分けるかとか考えていたのですが、アプリケーションを改善しないとインスタンス分離してもそもそもスコアが出ないだろうという判断の元決定しました。

午後:地獄のN+1潰し

このあたりで普段の業務を如何にライブラリや既存の仕組みにに乗っかかってるだけか反省することが多かったです…… 😇 join を使ってN+1を解消するのに結構時間がかかりました。普段如何にORMに頼っているかわかりますね。

あーでもないこーでもないと3人で言いながら /users/tranzaction.json/new_items のアクセスを早くした頃にはもう16 時でした……。

「N+1潰したからベンチ上がるやろ!」と思い意気揚々とベンチを回すも、上述の「2910」までしかスコアが出ず 😇 改めてalpを確認すると、たしかにそこはかなり改善しているのですが、今度は /buy のレスポンスが遅く、更に殆どのリクエストが4xx系のエラーを返していることに気が付きます。

このとき今更「そういえば、今回のスコアは商品の売上合計だったよな」ということに気が付き、よりたくさん商品を売りさばくチューニングをしなければ行けなかったことに気が付きます。問題の趣旨の理解が出来たのが16:30頃

必死にbuy の改善に取り組むも、「外部APIの仕様わからん」「なんでこんな403ばっかり返しているのかわからん」等と言ってるうちに18時となりゲームセット。 (※ 最後はポータルの不具合で18:10 まで時間が延長になりましたが…)

最終スコアは2910でした。

反省

大きく2つで、

  • 焦らずもっと課題とドキュメントを読み込む事
  • 環境構築系のスクリプトをもっと事前に用意しておけばよかった事

が課題でした。特に後者は、「本当に解くべき課題がなにか」を理解するのに遅れたという痛恨のミスです。

作問担当がメルカリということで、メルカリかメルペイっぽいアプリが来るだろうなーという予想を立てていたんだから、メルカリっぽいサービスを適当に作ってみるとかをしても良かったかなと思います。 この辺は実装経験が差をつけるかなと。

後者に関しては↑の課題理解をより早く行うためにある課題だと思っていて、構築系の自動化・高速化はもうちょっと備えていきたいかなという所です。

今回、チームメンバー全員がisucon 初挑戦という非常にフレッシュなチームで、個人的にはかなり楽しくさせていただきました。 チームメンバーの Yoshimaru46氏 と Miyamizu氏には改めて感謝を。そしてまたぜひ来年一緒に挑戦して予選突破しましょう!!笑

最後になりましたが、isucon 運営の皆様、非常に楽しい大会を提供していただきありがとうございました!!

tmux 2.9 にバージョンアップしたときのconf の変更等

普段使っているMacbrew を更新し、いくつかのパッケージを最新にしたら、tmux から以下のような警告が出るようになった。

.tmux.conf:65: invalid option: status-attr
.tmux.conf:68: invalid option: window-status-fg
.tmux.conf:69: invalid option: window-status-bg
.tmux.conf:73: invalid option: window-status-current-fg
.tmux.conf:74: invalid option: window-status-current-bg
.tmux.conf:78: invalid option: pane-border-fg
.tmux.conf:79: invalid option: pane-active-border-fg
.tmux.conf:82: invalid option: message-bg
.tmux.conf:83: invalid option: message-fg

調べてみると、tmux 2.9 から option の指定が一部変更になったらしい。 以下のように変更すると治った

 # default statusbar colors
-set-option -g status-bg colour236 #base02
-set-option -g status-fg colour136 #yellow
-set-option -g status-attr default
+set-option -g status-style bg=colour236,fg=colour136
+#set-option -g status-style default

 # default window title colors
-set-window-option -g window-status-fg colour244 #base0
-set-window-option -g window-status-bg default
+set-window-option -g window-status-style fg=colour244,bg=default
 #set-window-option -g window-status-attr dim

 # active window title colors
-set-window-option -g window-status-current-fg colour166 #orange
-set-window-option -g window-status-current-bg default
+set-window-option -g window-status-current-style fg=colour166,bg=default
 #set-window-option -g window-status-current-attr bright

 # pane border
-set-option -g pane-border-fg colour235 #base02
-set-option -g pane-active-border-fg colour240 #base01
+set-option -g pane-border-style fg=colour235
+set-option -g pane-active-border-style fg=colour240 #base01

 # message text
-set-option -g message-bg colour235 #base02
-set-option -g message-fg colour166 #orange
+set-option -g message-style bg=colour235,fg=colour166

個人の思考の整理に良いと思い、再び Scrapbox を使い始めた

「この記事は個人の見解であり、所属する組織の公式見解ではありません」

最近、個人でScrapbox を使うようになったので、僕もユースケースを紹介します。

Scrapbox の特徴としては、

  • リアルタイムでの複数人編集
  • タグ付けによるリンクの自動生成
  • 独自記法によるリッチテキストエディットが可能

と言った感じでしょうか。

当初は Scrapbox がそこまで良いと思っていなかった

去年くらいに、社内で @mactkg 氏にコレが良いぞと紹介してもらったのですが、そのときはあまり自分には刺さりませんでした。

刺さらなかった理由としては

  • scrapbox記法という独自記法を覚えるコスト高くね??
  • markdown で書けるDropbox pager のほうが良くね??
  • プロジェクト管理用のwikiなら Confluence でよくね?
  • 情報発信(アウトプット)なら、Qiita or ブログでよくね?
  • 作業メモとか雑なやつは手元のメモ帳(当時はSublime Textを使ってました)を使えばよくね?

などなど、当時イメージしていた使い方だと代替手段があり、良いユースケースが思いつかないままでした。

先人の取り組みを見て行ける気がした

しかし、1年間社内や社外で様々な使い方を見ているうちに、「思考の整理ツールとして使えるな。」と思い、使い直すことにしました。

参考にしたのは下記リンクです。

4つめの記事は、「個人の思考の整理」ではなく、「チームの思考の整理」として使えている例な気がします。

中途半端なアウトプットを中途半端なまま出力できる

自分の「知識の獲得や、アウトプット」と行った活動を見直した際、今まで私が取っていた行動として

  • 物理的な紙(ノートなど)に書き留める
  • ブログやqiita にまとめて公開する

の大きく2つの方法があったのですが、前者は手がしんどい。そもそも字が汚い。オンライン共有できない。とデメリットが多いです。

ブログやqiita にまとめるのは、整形にすごい手間がかかり、雑な事を書くとブラド・ツェペシュもビックリするくらい串刺しにされる。という理由で、アウトプットに時間がかかり、結果としてアウトプット頻度が減っていく傾向にありました。

そこで、「Wiki」がいいんじゃね?と思って個人のAWS上にnginx を立てて、さてエンジン何にしようと考えてたところ、「あ、そういやScrapbox いいんじゃね?」ってことで思い直して今に至ります。

もともと、同等の機能として、DropboxPaper を使っていたのですが、諸々の理由でDropbox を捨てることにしたので、Scrapbox が残ることになりました。

Scrapbox の最も良い点としては、やはり雑な状態で雑にアウトプット出来る。という点でしょうか。 今回のこの記事も、ある程度雑な状態で二ヶ月くらい寝かせておき、まとまってきた段階で、「よし、整理して公開するか」となったので、アウトプットが腐らずに済みます。

実際のscrapbox はココにあります。 れんれぼっくす

f:id:bussorenre:20181221190306p:plain
れんれぼっくす

今はまだ私のれんれボックスは、自分と、もうひとりにしか編集権限を渡していないのですが、少し整備が進むと、編集権限はクローズドだけど情報はオープンみたいなwiki が作れるような気がしています。

結論:Scrapbox 良いぞ

Scrapbox に対する不満がないわけではありません。特に、markdown 互換が無いのがやはり厳しいです。 しかし、それを補ってあまりある力を今のところは感じており、アウトプットに悩んでいる人には良いツールだと思ってます。

何かの参考になればぜひぜひ。

コンテキストスイッチに立ち向かう - 複数案件を抱える中で生産性を高めるために

この記事はRecruit Engineers Advent Calendar 2018 の1日目の記事です。
2日目は→ Go言語を使って一年間が経った

こんばんは。 @bussorenre です。初日からアドベントカレンダー脱稿なんて事にならずにホッとしています。(大汗

この記事は半年ほど前に書いた コンテキストスイッチにどうやって立ち向かうか の続編になります。自分なりにあれこれ最適解を見つけてきたので共有します。

コンテキストスイッチとはなにか?

コンテキストスイッチとは、元々はCPUのマルチタスク処理のために考案された仕組みの事で、複数のプロセスが1つのCPUを共有できるように、CPUの状態(コンテキストと呼びます)をメモリ等に退避させたり、退避させた情報を元にCPUの状態を復元したりを切り替えながら、あたかも複数の処理を並列に実行しているかのように見せる処理の事を示します。

この記事はCPUのコンテキストスイッチングに関する解説ではないので詳細は wikipedia とか コンテキストスイッチの仕組み - 日本OSS推進フォーラム などを参考にしてください。

この記事において「コンテキストスイッチ」と呼ぶのは、 人間が行っている作業を何らかの要因により中断させ、別の作業に切り替えること を示します。また、切り替えの際に生じる作業を中断しなければ発生しなかったであろう余計な作業 の事を「コンテキストスイッチコスト」と呼びます。

エンジニアにおけるコンテキストスイッチ問題

基本的に、エンジニアリング業務は非常に大きな集中力を求められます。 新しい機能の設計や不具合の原因調査等には、熟考が求められることが多く、途中で全く関係の無い話題を振られたりすると、「あれ?オレ今どこまで何を考えていたっけ?」となりがちです。

すると、先程の思考を取り戻すのにメモをみたり、コードを見直したり、仕様書を読み直したりしますし、最悪の場合、先程の思考は帰ってこないかもしれません。

私の話

実際の業務中にあった話ですが、4月から6月くらいまで、私は4つの業務を並行して担当していました。具体的には以下の4つです。

  • サービスAの新規機能開発
  • サービスAの営業行動の最適化
  • サービスBの定常保守
  • オフィスの引越(Wi-fi等のオフィスインフラの要件定義と発注とか)

この中で、最も重要な仕事は、「サービスAの新規機能開発」だったのですが、引っ越しは日程が差し迫ってて緊急度が高く、営業は物理的に声が大きいのでよく割り込みタスクを持ってくる。突然サービスBの案件が降ってくる等、コンテキストが目まぐるしく動いている状態でした。

結果、最も重要な「サービスAの新規機能開発」が滞る。という事態が発生しました。

世の中にはコンテキストスイッチ耐性が高い人というのが一定数居て、どんな状況からでも瞬時に頭を切り替えれる素晴らしい脳を持った人が居るのですが、残念ながら私の脳はシングルコアなので、コンテキストスイ ッチ耐性が低いです。

あまりにもスイッチが発生すると、自分が何を考えているのかわからなくなって混乱し、業務に手が付かない。整理にすごく時間がかかることがあります。

コンテキストスイッチはいつでもどこでも発生する

オフィスの引越終わったし。サービスBの案件が降って着やすい時期も抜けたし、「サービスCの新規機能開発」に業務を絞る調整を上長にしてもらい、このときは解決しました。 (ちなみに、どこからCが湧いてきたんだっていう話はココでは一旦ツッコまないでくれ……)

しかし、9月頃から、再び私の集中を妨げる事態が発生し始めました。

具体的には以下の要素です。

  • 新規大型開発案件が始まる
    • どうしても、認識のすり合わせや仕様/設計/スケジュールの調整等で会議・会話が増えてしまう
  • 突然のCS対応。
  • クライアントに訪問して調査しなければならない案件の設計・実施
  • (プライベート要因ですが)引っ越し
    • 急遽決まった。その調整とか準備とかで、昼間にガス会社から開栓手続きの電話が来るとかがあった。
  • 同僚の雑談が普通に面白い
    • ついつい雑談に乗ってしまう

期の変わり目・繁忙期等はどうしてもコンテキストスイッチが発生しがちです。また、私はまだ独り身ですが、今後、結婚するとか、子供が生まれるとかがあるかもしれないし、親の介護とかが発生したりすることを考えると、コンテキストスイッチが発生しないことはありえません。むしろ増えそう。

そこで、真剣にコンテキストスイッチ問題に立ち向かうことにしました。

コンテキストスイッチの発生源を特定する。

まずは、原因分析から始めます。 原因分析には、 Jins MEME の力を借りました。

f:id:bussorenre:20181201143904p:plain
Jins MEME ES

一週間ほど、この眼鏡を掛けて業務を行い、「集中力が続いている日」と「集中力が断続的になっている日」をピックアップします。

f:id:bussorenre:20181201143655p:plain
jins MEME による計測結果

それぞれピックアップ出来たら、ユーザーストーリーマップの要領で、一日の自分の行動を詳細に可視化します。
可視化する際には、github のコミットログ、Slack のチャット履歴、メールの送受信履歴 などを参考にします。

これらを元に、コンテキストスイッチへの対処を考えます。

私の場合、大きく5つの要因があることがわかりました。

  • Slack の投稿/閲覧 頻度
    • 最もコンテキストスイッチを生んでいる媒体
    • 文字でレスポンスする頻度は低いが、わりとポンポン :yosasou: みたいなリアクションを取っていることが多い。
    • 頻繁にリアクションしているということはしょっちゅう Slack にコンテキストスイッチさせられてる可能性が高い
  • メール
    • しれっと重要なメールが飛んでくることがあるので、読まないわけには行かない。
    • しかし中には、どうでもいい広告とか、通知系のメールも多い。
  • 会議
    • 基本的に多い。
    • デイリースクラム等の些細なものから、部定例等参加者の大きいものまで、結構ウェイトが大きい。
  • 相談、雑談等
  • 並行業務
    • 午前中Aをやっていて、突然Bを対応してくれ的なのが発生した。等

これらに対し、それぞれ対策を実行していきます。

コンテキストスイッチの発生頻度を下げる

Slack

私は一見でも未読があるとすごい気になってしまうタイプなので、ちょっとでも光ったらすぐSlack を見に行く癖があります。

基本的に、以下の方針で、見なくていい情報を徹底的に排除する方針で動きました。

  • 重要なチャンネルは★をつける
    • development 等の、メンションが無くても読むべきチャンネルは★に入れます。
  • 重要じゃないチャンネルは見ない。
    • general/random チャンネル等
    • 基本的に @channel@here がつかない限り見に行きません。
    • 後から(通勤時)とかに纏めて読みます。
  • 見なくていいチャンネルは /mute してしまって見ない
    • じゃぁ /leave すればええやんってなるかもだけど、大事なときには通知を飛ばしてほしいときに使う
    • alert や notifications チャンネル的なので有効

この方法により、slack を読む頻度はかなり下がってる気がします。

何度も聞かれる質問はBOTで答えれるようにする。

オフィスの無線LAN などを管理している関係上、中途入社の方や異動で来た新任の方に、質問をされるケースが何度かあります。 社内のルールなど、答えが決まりきっているものは、BOT に聴いたら応答できるように整えます。

f:id:bussorenre:20181201152834p:plain
Dialog UI を使ってよくある質問を自動で返すようにしている。

メール

やることは非常にシンプルで、

  • フィルターでフォルダ分けをして、見なくていいメールは全部別フォルダに入れます。
  • わけわからん広告は全部配信設定をオフに。
  • オフに出来ないやつは迷惑メールにぶっこむ

の3つだけです。これだけで、読むべきメールしか残らないので、かなり生産性が上がります。

物理的な逃げ場所を作る

強い集中を求めたい場合は、他人の声が入りにくい場所に移動して仕事をします。 多くの人はカフェとかコワーキングスペースとか、サテライトオフィスとかがそれに相当するのかな……? リモートワークが100%許可されているからこそ可能な芸当。

私の場合はサーバールームが、良い隠れ家になってます。

エンジニアでもあまり入ってこようとは思わない空間で、まず人が来ることはありません。
社内のサーバールームはオフィスインフラのトラフィックをさばく機械しかないので、問題はありません。

ファンの音しか聞こえない空間にかなり癒やされる。

1 on 1 を上手く使う

現在は上長との 1 on 1 が隔週に一回あるので、その場で、今自分が何をやっているか(公私共に)、何を感じているかを可能な限り伝えます。 具体的な方法については。もうちょっとうまいやり方を模索しているんですが、自分が何をやってるかを公私共に伝えるという方向性は有効だと感じてます。

会議のリモート化

基本的に、スプリントレビュー等の大事な会議以外は、可能な限りリモート参加OKにし、発言が求めらてたタイミングだけ発言する等をします。 たとえオフィス内に居たとしても、30分の会議に常時コミットする必要はなく、自分が関わる部分だけをピックアップして参加し時間を作ることが出来ます。

コンテキストスイッチが発生したときのスイッチングコストを最小限にする

コレまでは、コンテキストスイッチが発生しないようにする工夫でしたが、ここからは、コンテキストスイッチが発生した時のコストを最小限に収める努力を紹介します。

scrapbox に逐一メモを書きながら作業する

作業メモなどを逐一残して、後から引し出しやすいようにします。

かつては、Sublime Text (後にvisual studio codeに変更)のような、不意のQmd + Q にも耐えられるバッファを使っていたのですが、最近 scrapbox が有効だと感じています。

何を考えていたか後から引っ張りやすいので、一時的なメモ置き場として最適なだけでなく、中途半端なアウトプットを纏めておく事で、後から練度の高いアウトプットに昇華させやすくなる(ブログへの投稿等)と思ってます。

チームで使用しているscrapbox, 個人で使っている公開scrapbox, 個人で使っている非公開scrapbox の3箱を並行して運用しています。

Scrapbox についてはまた別で記事書きます。

追記:書きました →  bussorenre.hatenablog.jp

タスク / チケットを可能な限り分解し、誰からも見えるようにする

チーム開発において、誰が何を担当しているかの可視化(JIRAでチケットを明確に切るなど)チケットのスコープの明確化、当然のごとく行います。

「あ、そのチケット終わってます」みたいな会話をしょっちゅうPMとするのはコミュニケーションの無駄なので、意識してJIRAのチケットの状態を最新に保つようにします。

また、JIRAとは別に、個人のTrello ボード を持っています。

f:id:bussorenre:20181201152614p:plain
自分のTrello

複数の案件を持っていたり、複数チームにまたがって仕事をしているときに有効で、基本的にチーム内の人が見れる場所においておき、「こいつ今何やってるんだ…?」というのがひと目で分かるようにラベリングしています。「Aという案件で忙しいのにBという重い案件が急に降ってきた!ギャー」という事を防ぎやすくなります

これらの努力により、「聞くな、ココに書いてある、見ろ」というコミュニケーションが成立するので、作業を中断するような会話が発生しにくくなります。

タスク管理は特に発生しやすいので特筆しましたが、基本的にチーム人数が増えれば増えるほど、ドキュメント化は正義だと思っており、上手くドキュメント化をすればするほど、コンテキストスイッチコストが下がると思ってます。

コード上に大量にメモを残す

コミットしてプルリクエストになるときには消えているんだけど、作業中には残しておくと便利なコメントをとにかく書き残します。 仕様が固まっていなかったり、自分が知らない実装に挑戦するときなどは非常に有効です。

また、コードレビュー中も、github 上でザクッとPRのコードを見るのではなく、 ローカルに落としてきて、ローカルブランチ上で大量にメモコメントを書きながら見るようにします。

ローカルで feature/hogehoge_review みたいなレビュー専用ブランチを切って残して置くと、 後々見返したいときに参照できて便利です。

メールの定型文を大量にIME に登録する

社外の人や、グループ内の別企業の方とのやり取りといったことが割とあるので、とにかくメールにかける時間を短縮します。

最もシンプルで効果があるのは、定型文をIMEに登録することです。

変換前 変換後 備考
th 大変お世話になっております。
rmpm リクルートマーケティングパートナーズ 松本です 相手によって肩書を変えたい場合など他の短縮語がある
gm ご迷惑をおかけし、大変申し訳ありません。 謝罪メール多いな…
おt お手数をおかけしますが、何卒宜しくお願い致します。 お疲れ様です。と勘違いしやすい

これだけでメールの返事に書ける時間が相当短縮できます。

試したけどあまりうまく行かなかったこと

do not disturb を使う

Slack には do not disturb という、一定の時間の間一切の通知をオフにする機能が存在します。

f:id:bussorenre:20181201154337p:plain
do not disturb

コンテキストスイッチを発生させないという意味では非常に有効だったのですが、重要な連絡(本番環境でエラーが頻発している等、コンテキストスイッチさせてでも対応しなきゃならない事)を逃して対応が遅れるという事があったので、やめました。

リモートワーク環境を超絶整える

f:id:bussorenre:20181201144005p:plain
bussorenre のリモートワーク環境2018

「突然話しかけられて、あれこれ対処しなきゃならない」。みたいなケースはダントツで減るのですが、オフィスに居ないコミュニケーションロスをSlack で補うことになり、結果としてSlack を見ていないと気が落ち着かない。という状態になりました。

ただ、あくまで「コンテキストスイッチに立ち向かう」という意味であんまり効果がなかっただけで、リモートワーク自体は非常に生産性を上げるのに有効です。

「ちょっと今日調子が悪いけど、休む程ではない」ときにリモートワークを活用すると、通勤コストを支払わない分、体力的に非常に効果的だったりします。

また、明らかにやることが明確な時などにリモートワークを実行すると、まとまった時間が取れるのでかなり捗ります。

Jins MEME で定常的に自分の状態を監視する

最初の課題設定フェーズにおいては非常に役に立ってくれたのですが、常用するには、「額の違和感により集中力が下がる」という壁を超えることが出来ませんでした。

端的に申し上げると、つけ心地が悪く気持ち悪い。というのが慣れだけでは解消できませんでした…。

Jins MEME 3くらいで改良してくれることを期待。

副業を止める

あまりにも目まぐるしくて参っていたときに取った手段です。効果は何よりも絶大だったのですが、デメリットが大きすぎました。具体的には、「収入が下がる」 「社外でのアウトプット機会が減る」の2つです。

しかし、当時を振り返って改めて冷静に考えると、「こんなに厳しいコンテキストスイッチングに晒されたら転職すればよくね?」と思ったので、次回厳しい環境に身をおくことになったら転職活動という手法を試すのが良いのかも? :thinking_face:

最後に

みんな当たり前にやってることばっかりの事を紹介しただけの記事になったかもしれませんが、まぁ個人の試行錯誤の結果ということで記事にしました。

また、これらの思考錯誤を行う上で、 エンジニアの知的生産術 - 効率的に学び、整理し、アウトプットする が非常に助けになりました。いい本です。おすすめです。

「こういうツールあるよ」とか「こういう方法を取っている」等のご意見コメントや、「お前そこ全然出来てへんやん」みたいな、耳の痛い話がありましたらぜひ頂けると嬉しいです。

ご精読ありがとうございました。

Scala における末尾再帰

背景

S-99: Ninety-Nine Scala Problems 等の例題を説いていると、よく再帰による実装を見かけます。

確かに、 Scala関数型デザイン&プログラミング―Scalazコントリビューターによる関数型徹底ガイド | Paul Chiusano, Rúnar Bjarnason, 株式会社クイープ | 工学 | Kindleストア 等の書籍にも、可能な限り再帰的な考え方で実装しろと書かれています。

しかし、「再帰でばかり実装すると、スタック領域を食いつぶしてしまうんじゃないのか?」という不安があります。 基本的に、関数呼び出しの際は、現在実行している関数の情報(レジスタ情報や引数・戻り先のポインタ)を、メモリ上のスタック領域と呼ばれるところに押し込んでいくので、再帰はスタック階層が深くなり、かの有名なスタックオーバーフローエラーが出ることになります。

普通の再帰

階乗(n!)を実装します。 階乗とは、例えば n = 5 の時、 5! = 5 x 4 x 3 x 2 x 1 = 120 となります。

scala で実装すると以下のようになります。

def fact(n: Int): BigInt =
  n match {
    case 0 => 1
    case _ => n * fact(n - 1)
  }

さて、n の値が小さいうちは普通に計算してくれますが、10000 とかぶっこむと StackOverflowError が出ます。

java.lang.StackOverflowError
  at scala.math.BigInt$.apply(BigInt.scala:38)
  at scala.math.BigInt$.int2bigInt(BigInt.scala:96)
  at .fact(<console>:14)
  at .fact(<console>:14)
  at .fact(<console>:14)
  at .fact(<console>:14)
  at .fact(<console>:14)
  at .fact(<console>:14)
  at .fact(<console>:14)
# 以下略

延々とfact関数を呼び出しており、エラーログ的にも優しくありません。

末尾再帰

Scala では、関数の最後の処理として自分自身を呼び出す再帰関数(これを末尾再帰と言う)を検知すると、パラメーターを新しい値に更新した後、再帰呼び出しを関数の冒頭にジャンプするコードに書き換えるしくみがあるみたいです。 末尾再帰を検知すると、内部的にはwhile文に変換している。と捉えても大きな違いはなさそう。

実際にfact関数を末尾再帰にしてみる。

末尾再帰を妨げているのは、n * fact(n - 1) の部分で、この処理を別の関数として置き換え、その関数を再帰的に呼び出すように修正します。

def fact(n: Int): BigInt = {
  def innerFact(n: Int, f: BigInt): BigInt = 
    n match {
      case 0 => f
      case _ => innerFact(n - 1, n * f)
    }
  innerFact(n, 1)
}

fact の中に、内部関数としてinnerFact を定義しました。実装を見てもらえるとわかるように、関数の末尾は innerFact を呼び出すだけになっている。これにより、Scala の末尾再帰検出機構が働き、fact(10000) などもうまく実行してくれるようになります。

本当に書いた関数が末尾再帰になっているかを確認するには、 @tailrec アノテーションを利用します。

import scala.annotation.tailrec

@tailrec
def fact(n: Int): BigInt =
  n match {
    case 0 => 1
    case _ => n * fact(n - 1)
  }

<console>:19: error: could not optimize @tailrec annotated method fact: it contains a recursive call not in tail position
           case _ => n * fact(n - 1)

def fact(n: Int): BigInt = {
  @tailrec
  def innerFact(n: Int, f: BigInt): BigInt = 
    n match {
      case 0 => f
      case _ => innerFact(n - 1, n * f)
    }
  innerFact(n, 1)
}

// 何もエラーが発生しない

参考にしました

Scala におけるパターンマッチ

今日もScala。明日もScala。明後日もScala

// Scala におけるパターンマッチ
<セレクター式> match {
    case 選択肢1
    case 選択肢2
    ......
 }

上から順番に与えられた選択肢を評価し、最初にマッチしたもののみを実行する。break 文などは特に不要。

例1, Option をパターンマッチする

val element = Some(100) // or None
element match {
  case Some(e) => println(s"some: ${e}")
  case _ => println("none")
}

この時、e は変数パターンと呼ばれ、ワイルドカードのように機能し、右側の式で値を参照することが出来る。上の例だと、Some の場合、Some の中身 e を変数束縛してその値を参照することが出来る。

_ は変数名束縛の無いワイルドカードで、全てのパターンにマッチするものの右側の式で参照できない。

それ以外は定数パターンで、特定の値のみを受け取る。

マッチするパターンがなければ MatchError が返される。(コンパイル時にwarning が帰ってくるので、事前に気がつくことは出来る。) ので、想定していない値が来たときのデフォルをの挙動を case _ => sys.error("想定されていない動作") 等を組んでおくことが無難か

if とかと同様に、match も なので、必ず値を返す。

def hoge(n: Int) = n + 100

val element = Some(100) // or None

val result = element match {
  case Some(e) => Some(hoge(e))
  case _ => None
}

println(result)
// Some(200) or None

上の例では、element がsome ならその中身に関数hogeを適用して返し、None ならNone を返すというロジックを組んでいる。

以下追記:

case _ => None のところは以下のようにも書ける

case n @ _ => n

アットマークで、マッチしたパターン全体に対する変数束縛することが出来る。このケースの場合は、その他をしめすパターン _ を変数束縛させることが可能になっている。