コンテキストスイッチに立ち向かう - 複数案件を抱える中で生産性を高めるために
この記事は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 の力を借りました。
一週間ほど、この眼鏡を掛けて業務を行い、「集中力が続いている日」と「集中力が断続的になっている日」をピックアップします。
それぞれピックアップ出来たら、ユーザーストーリーマップの要領で、一日の自分の行動を詳細に可視化します。
可視化する際には、github のコミットログ、Slack のチャット履歴、メールの送受信履歴 などを参考にします。
これらを元に、コンテキストスイッチへの対処を考えます。
私の場合、大きく5つの要因があることがわかりました。
- Slack の投稿/閲覧 頻度
- 最もコンテキストスイッチを生んでいる媒体
- 文字でレスポンスする頻度は低いが、わりとポンポン :yosasou: みたいなリアクションを取っていることが多い。
- 頻繁にリアクションしているということはしょっちゅう Slack にコンテキストスイッチさせられてる可能性が高い
- メール
- しれっと重要なメールが飛んでくることがあるので、読まないわけには行かない。
- しかし中には、どうでもいい広告とか、通知系のメールも多い。
- 会議
- 基本的に多い。
- デイリースクラム等の些細なものから、部定例等参加者の大きいものまで、結構ウェイトが大きい。
- 相談、雑談等
- 並行業務
- 午前中Aをやっていて、突然Bを対応してくれ的なのが発生した。等
これらに対し、それぞれ対策を実行していきます。
コンテキストスイッチの発生頻度を下げる
Slack
私は一見でも未読があるとすごい気になってしまうタイプなので、ちょっとでも光ったらすぐSlack を見に行く癖があります。
基本的に、以下の方針で、見なくていい情報を徹底的に排除する方針で動きました。
- 重要なチャンネルは★をつける
- development 等の、メンションが無くても読むべきチャンネルは★に入れます。
- 重要じゃないチャンネルは見ない。
- general/random チャンネル等
- 基本的に
@channel
や@here
がつかない限り見に行きません。 - 後から(通勤時)とかに纏めて読みます。
- 見なくていいチャンネルは /mute してしまって見ない
- じゃぁ /leave すればええやんってなるかもだけど、大事なときには通知を飛ばしてほしいときに使う
- alert や notifications チャンネル的なので有効
この方法により、slack を読む頻度はかなり下がってる気がします。
何度も聞かれる質問はBOTで答えれるようにする。
オフィスの無線LAN などを管理している関係上、中途入社の方や異動で来た新任の方に、質問をされるケースが何度かあります。 社内のルールなど、答えが決まりきっているものは、BOT に聴いたら応答できるように整えます。
メール
やることは非常にシンプルで、
- フィルターでフォルダ分けをして、見なくていいメールは全部別フォルダに入れます。
- わけわからん広告は全部配信設定をオフに。
- オフに出来ないやつは迷惑メールにぶっこむ
の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 ボード を持っています。
複数の案件を持っていたり、複数チームにまたがって仕事をしているときに有効で、基本的にチーム内の人が見れる場所においておき、「こいつ今何やってるんだ…?」というのがひと目で分かるようにラベリングしています。「Aという案件で忙しいのにBという重い案件が急に降ってきた!ギャー」という事を防ぎやすくなります
これらの努力により、「聞くな、ココに書いてある、見ろ」というコミュニケーションが成立するので、作業を中断するような会話が発生しにくくなります。
タスク管理は特に発生しやすいので特筆しましたが、基本的にチーム人数が増えれば増えるほど、ドキュメント化は正義だと思っており、上手くドキュメント化をすればするほど、コンテキストスイッチコストが下がると思ってます。
コード上に大量にメモを残す
コミットしてプルリクエストになるときには消えているんだけど、作業中には残しておくと便利なコメントをとにかく書き残します。 仕様が固まっていなかったり、自分が知らない実装に挑戦するときなどは非常に有効です。
また、コードレビュー中も、github 上でザクッとPRのコードを見るのではなく、 ローカルに落としてきて、ローカルブランチ上で大量にメモコメントを書きながら見るようにします。
ローカルで feature/hogehoge_review みたいなレビュー専用ブランチを切って残して置くと、 後々見返したいときに参照できて便利です。
メールの定型文を大量にIME に登録する
社外の人や、グループ内の別企業の方とのやり取りといったことが割とあるので、とにかくメールにかける時間を短縮します。
最もシンプルで効果があるのは、定型文をIMEに登録することです。
例
変換前 | 変換後 | 備考 |
---|---|---|
th | 大変お世話になっております。 | |
rmpm | リクルートマーケティングパートナーズ 松本です | 相手によって肩書を変えたい場合など他の短縮語がある |
gm | ご迷惑をおかけし、大変申し訳ありません。 | 謝罪メール多いな… |
おt | お手数をおかけしますが、何卒宜しくお願い致します。 | お疲れ様です。と勘違いしやすい |
これだけでメールの返事に書ける時間が相当短縮できます。
試したけどあまりうまく行かなかったこと
do not disturb を使う
Slack には do not disturb という、一定の時間の間一切の通知をオフにする機能が存在します。
コンテキストスイッチを発生させないという意味では非常に有効だったのですが、重要な連絡(本番環境でエラーが頻発している等、コンテキストスイッチさせてでも対応しなきゃならない事)を逃して対応が遅れるという事があったので、やめました。
リモートワーク環境を超絶整える
「突然話しかけられて、あれこれ対処しなきゃならない」。みたいなケースはダントツで減るのですが、オフィスに居ないコミュニケーションロスを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 におけるパターンマッチ <セレクター式> 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
アットマークで、マッチしたパターン全体に対する変数束縛することが出来る。このケースの場合は、その他をしめすパターン _
を変数束縛させることが可能になっている。
Scala におけるList の操作
Programming in Scala 第三版16章 リストの操作
言語によっても仕様が異なり混乱しやすいリスト。 Scala における 配列とリストの違いは
- リストはイミュータブルオブジェクトで、リストの要素は代入によって置き換えられない
- 内部的にはLinked List
配列と同じ部分でいうと、Scala におけるArray, List は[T]で示される型のみを格納できる。
// String 型しか格納できない val fruits: List[String] = List("apple", "banana", "cherry") // nums 型しか格納できない。型を省略した場合、型推論でなんとかしてくれる val nums = List(1,2,3,4,5)
中間演算子によるこういう書き方も可能。
この場合 ::
は右結合の演算子なので、() を省略しない場合こういう順番になる
val nums = 1 :: 2 :: 3 :: 4 :: Nil val nums = 1 :: ( 2 :: (3 :: (4 :: Nil)))
リスト同士の連結は
val nums = List(1,2,3,4) ::: List(5,6,7)
この時、もちろん 両項の List[T] 型は一致していなければならない。 この演算子も右結合
パターンマッチによるリストの要素の分解が出来る。
scala > val List(a, b, c) = fruit a: String = apple b: String = banana c:String = cherry
上記構文では、List の要素数が3の型に限られてしまうので、要素数が可変の場合は、 (リストの要素数がわからない場合は)先程の中間演算子を用いるともっとあっさり解決する
scala> val a :: b :: remining = fruit a: String = apple b: String = banana remining:List[String] = List(cherry)
Scala を書く時に他言語と混乱しないための文法備忘
Scala 初めてまだ数日勢。毎日ずっと書いていないと、元々書いていた他の言語に邪魔されて思考が途切れてしまう。 特にSwift と混合しやすい。次点で Go, javascript と混合してしまいやすい。
苦しんで自分にscala を叩き込む
宣言
val, var を使う。
val hoge = new Object() var i = 100
特にval。よく間違えてlet とか const とか書いて怒られている。さすがにauto と書くことはない。
メソッド宣言と呼び出し
まずは宣言
def hogepiyo(arg1: type1, arg2: type2): ReturnType = { // write something } def foobar(arg1: Type1, arg2: Type2): ReturnType = foo * bar
呼び出しが少しややこしい。
からの抜粋になるが、
class Hoge { //引数リストがないメソッド def f1 = 1 //引数がないメソッド def f2() = 1 //引数が1つのメソッド def f3(x: Int) = x * 2 //引数が複数のメソッド def f4(x: Int, y: Int) = x * y } //引数リストがないメソッド ////hoge.f1() ////hoge f1() hoge.f1 //> res0: Int = 1 hoge f1 //> res1: Int = 1 //空行は伊達じゃないよ //引数がないメソッド hoge.f2() //> res2: Int = 1 hoge.f2 //> res3: Int = 1 hoge f2() //> res4: Int = 1 hoge f2 //> res5: Int = 1 //空行は伊達じゃないよ //引数が1つのメソッド hoge.f3(1) //> res6: Int = 2 hoge f3(1) //> res7: Int = 2 hoge f3 1 //> res8: Int = 2 hoge.f3 { 1 } //> res9: Int = 2 hoge f3 { 1 } //> res10: Int = 2 ////hoge.f3 1 //引数が複数のメソッド hoge.f4(1, 2) //> res11: Int = 2 hoge f4(1, 2) //> res12: Int = 2 ////hoge.f4 1, 2 ////hoge f4 1, 2
基本的にはjava と同じ書き方をすれば動くっちゃ動く。が、関数型っぽくないらしい。引数がない時に()を省略できるのはまぁruby とかでもやってきたから慣れれば出来る。
後置記法というらしいが、
hoge piyo
みたいな感じで、.
も()
も省略されている場合はその次の行は空行である必要があるみたい。
hoge piyo foo
みたいな感じで、引数が一つの場合は後ろを空行にしなくてもいいらしい。この差異の意図がよくわかってない。 例文として、0 to 3 のような演算子のように使うメソッドは省略することが多い的なことが書いてあった。なんとなくわからんでもない。
あとは、副作用があるかないかでも変わる。副作用がある場合は必ずhoge.piyo(foo)
みたいに書く。
コンストラクタ宣言
class Hoge(a: Type1, b:Type2) { require(a != b) // do something // auxiliary constructor def this(c: Type3) = this(c, c*c) }
ruby / javascriptに近いかも。インスタンスが生成される時、上から順に実行されている。
requireでバリデーションが出来る。くっそ便利。バリデーションに失敗したら例外が帰る。
Scala ではコンストラクタはただ一つのプライマリコンストラクタを持つことが出来る。それ以外のコンストラクタは補助コンストラクタ(Auxiliary Constructor) と呼ばれ、必ずプライマリコンストラクタ(Primary Constructor)を呼ぶ必要がある。
ここはSwift で苦しんだのでわりとあっさり理解した。
制御構文
厳密にはscala には制御構文はない(本当に?) if 等は全部 if 文ではなく if 式である。 式 = statement なので、必ず結果を返す。
if式
要するに三項演算子だと思えば理解が早い。
val a = if (a or b) functionA else functionB
さて、else が必要ないときはelse を省略することが出来るが、() というUnit 型が返される(何もしないという副作用だけを返す)
if (a > 0) { println("hoge") } else { println("piyo") }
このようなif文のような事も普通に出来る。この時、計算結果を返さないので副作用のみを扱う関数2つを定義し、条件式でどっちを実行するか決定している。みたいなイメージになる。
if 式 というかif 関数と思えば扱いやすいのかな。3値(条件式, 関数1, 関数2)を元に演算結果を返す。みたいな。
for 式
あとで別記事で書く
まだWIP
Intelli J Idea でScalaを書く環境を整えてく(自分用)
業務用ということで、会社から Inteli J Idea を支給してもらった。この機会にぜひとも乗りこなしたい。
Emacs バインドをある程度使えるようにする
Emacs使いのための IntelliJ IDEAキーマップ チートシート を参考にする。
Emacs+ Patched というプラグインを入れ、 Preferences -> Keymap で Emacs+ を選択し、Duplicate... でコピーを作成
Preferences -> Appearance & Behavior -> Presentation Assistant で、Main keymap に作成したコピーを指定
変更 キーバインド アクション 追加 Ctrl+X, Ctrl+B Switcher
PlantUML
PlantUML でドキュメントを書いているので、同じくプラグインをぶっこむ。
初めて入れてみたけどくっそ便利すぎて今までの人生は何だったのか感