左右分離型キーボード導入記録 - キーバインドとか使用感とか
2021.11.18に販売が開始された日本語配列の左右分離型キーボード「Mistel BAROCCO MD770 JP」を購入したので、新しいキーバインドになれる目的もかねて、使用感やキーバインドについてまとめました。
分離キーボードを検討している方、日本語配列派だけど自作までは...という方、Windows + WSLで開発環境を作っている方、などは多少参考にできるかもしれません。使用感はまだ買ったばかりなので一か月くらい使ったらまた書こうかなと思います。
日本語配列の分離型は完成品がほとんどなく、現状(2021.11.18)時点で唯一の選択肢といっていいと思います。軸は静音赤軸、非RGBバックライトモデルを選びました。英字配列キーボードに移行しない理由はここでは割愛します。
動機
購入動機はノリです。大したものではありません。
- 右スペースキーにいろいろバインドするとはかどる、と聞いた
- デスク周りに何か変化がほしかった
- 何となくガチ勢っぽい感じがした
- 肩こりが解消する、と聞いた(特に悩まされてはいないけれど)
- 日本語配列だと自作するしかない?と3日ほど悩んでいたときにちょうど販売開始された
気になる方はYoutubeででも「キーボード 分離(分割)」で調べてみるとよいと思います。
施した設定/キーマッピング
主な購入動機は右手スペースに何かをバインドさせていろいろやりたい、でしたので初期配置では使いません。
MD770のキーマクロ機能とMicoroft Powertyosのキーマッピング変更機能を使ってカスタマイズします。MacやLinuxの場合はそれに準ずるもので対処が可能です。
話はそれますが、Windowsのキー設定をいじるうえでPowerTyosは導入が楽なのが良いです。自分のWindowsの使い方は「Windowsデバイスドライバ付のUbuntu」なので、WindowsでPython依存のツールを使ったりコマンドラインを開いたりすることは苦痛でしかありません。この工程が存在しないだけでもかなり偉いです。
ただし、 右CTRLと左CTRL動詞押しのバインドをサポートしていない ので、左右CTRL同時押し@1というようなマッピングは設定できないので注意です。
上下左右矢印キーの配置を他のキーボード風に変更
買った後に気づきましたが、日本語版の矢印キーの配置はこんな感じです。
ないわーと思いましたが「Windows + 右スペース + kana」の同時押しで 右Shift を 上キーとした配置に変更できます。速攻やりました。
ややこしいのでキーキャップも付け替えるとよいと思います。
ホームポジションを動かずに上下左右移動、Enter、Backspaceを押したい
自分はVimユーザですが、これまでは普通にVim以外での上下移動は上下左右キーを使っていました。なので今回も矢印キーを普通に使うかなと思い矢印キーの配置変更までしたのですが、分離キーボードにするとなぜかホームポジションから動きたくなくなりました。なぜかは分かりません。 慣れていないので動きづらいのか、単純に右スペースキーをうまく使えば副作用なく実現できそうな気分になったからなのか、いろいろあると思います。わかったら書きます。
MD770のマクロでのマッピング
やりたい事 | マッピング | 心 |
---|---|---|
右スペース | 右CTRL | 本購買のコンセプト。右手親指ですぐに押せるところに配置するとはかどりそう |
CapsLock | 左CTRL | みんなやってますよね。これまではレジストリで設定してました |
やりたい事 | マッピング | 心 |
---|---|---|
右CTRL + o | Enter | 薬指で押していたと思うので合わせてみました |
右CTRL + ; | Backspace | HJKL以外ならどこでも良かったです |
右CTRL + HJKL | Vim風上下左右移動 | なんかやりたくなりました |
前提: 変換/無変換でIMEのON/OFFをするMac風の設定での運用
[没] 左CTRL + 変換 -> Backspace
推しやすさは良かったですが、思いのほか使用頻度が高く、親指を屈伸した状態でキーを押し続けた結果この記事の半分も書ききらずに親指が悲鳴を上げました。
[没] 右スペース -> Enter
同じく押しやすさは最高で、親指の屈伸具合も上記よりはましですが、この記事を3/4書ききるくらいには痛さを感じるようになりました。
また、右CTRL
が便利に使えないため必然的にHJKL
移動は左CTRLを使うことになります。そうすると、Chromeで最も利用するショートカットの一つであるCTRL+L
を潰してしまうため、HJKL移動の装飾キーは右CTRL
であるべきであり、低コストで利用するには右スペース
の位置にマッピングする必要がありました。
使用感
注意:まだ届いて3日程度なので気が変わるかもしれません。
全般 - 違和感なし
今のところ全く違和感なく使えています。もともとホームポジションを守った教科書通りのタイピングをしていたのもあって、左右分割による文字タイピングへの影響はありませんでした。すべて中学時代の我が家に訪れたタイピングゲームブームのおかげです。癖のある使い方をされている人は慣れるのに時間がかかるかもしれません。
また前述の通り「なんかホームポジションから動きたくないな」力学が強めに働く気がします。
新しいキーバインドの使用感
慣れたら最高で、ホームポジションから動かずにカーソル移動と改行、削除ができるのはとても良い体験です。CTRL+zなど左CTRLだと若干指の形が複雑になるようなものも右CTRLを使うことでストレスが減りました。
一方で右スペースにEnter
やBackspace
をバインドしたときほどではありませんが、そこそこに右手の親指を酷使するので若干の痛みは感じています。神経は既に慣れましたが、痛みについては指が負荷に慣れる必要がありそうで、もう少し時間がかかりそうです。
一方で、ちょっと使用感がVimっぽくなってしまったがゆえにx
やe
やb
や数字
+j
的なものを使いたくなってしまうのがたまに傷かもしれません。
あと、慣れるまでにあらゆる操作をミスったおかげでSlack等の未開拓のショートカットをいくつか発見しました。CTRL
+ SHIFT
+ h
でHuddleを開いたり、CTRL
+>
でスレッド表示をToggleしたり。
姿勢 - 変化はあるけど体感はまだない
肩が開く(というと野球だと悪い意味に聞こえる)ことは確かにないですが、もともと肩こりもそんなにひどい方ではないので「改善した!」という実感はありません。来月あたりに不思議と体調が良い気分になることを期待しています。
キーボードとしての感想(打鍵音/打鍵感覚)
打鍵音は十分静がで、金属音は気にすれば気になるレベルです。
静音赤軸は十分に静か。開封直後は底打ちの金属音が気になって仕方がありませんでしたが、少し使っていたら気にならなくなりました。開封の儀の時は打鍵音に神経を研ぎ澄ましていたのだと思います。Web会議のマイクでも拾われていないようなので今のところ問題はないと思っています。とはいえ静音Realforceと比べてはいけません。あの静音性能は化け物です。
打鍵間はごく一般的な静音赤軸の打鍵感だと思います。
その他やったこと
新しいキーバインドになれるためのハードモード設定
Enter
やBackspace
などは手になじみすぎているのでこれまでの手癖で入力してしまいがちなので、以下のマッピングを追加して使えなくしました。いずれもMD770のマクロでリマップしています。
- Enter -> Escape
- Backspace -> Escape
これで強制的に新しいキーバインドを利用するしかなくなります。
WSL-Ubuntu側で起動したGUIアプリで変換キーを認識してくれない問題の解決
以前のキーボードでは問題なくUbuntu側で起動したGUIアプリでも変換キーを認識してくれていましたが、MD770-jpをつなぐと変換キーを認識してくれません。無変換は認識してくれるのに。
キーについていろいろ調べましたが解決には至らなかったため、INSERTキー
を犠牲にすることで対処しました。
- kanaキーをINSERTに変更(MD770側)
- PowerToysでINSERTを変換と認識するように変更
- MD770側
※自分の環境ではVSCodeをUbuntu上で起動しています。Linterなどの実行のためにPython等の環境が必要なのですが、Windows上で起動するとWindows上にPython実行環境を作る必要があるなど非常に面倒くさいのが理由です。
むすび
このエントリを書くことで大分なれることができました。 しばらくこれで運用してみて、新しいことがわかったりしたらまた何か書くと思います。
(多分普段の執筆はどのみちVimで書くことになりそうだなぁーと思いました)
スマホでSwitchを操作できるようにして、隙間時間でポケモンマスターを目指す
LAPRAS夏の自由研究リレー - 8/24枠の記事です。 自由研究リレーとはその名の通り、LAPRASのメンバーが自由研究したことを次々にアウトプットしていくというもので、すでに沢山の記事が公開されています。ぜひご覧ください。
ちなみに当エントリは自由研究と言いつつも「趣味で作った作品の開発ログ + 申し訳程度の技術Tips」という構成になっています。小学生や中学生の自由研究においても、特に何かを研究をするわけでもなくただ単に工作だけしてお茶を濁すというのはよくある話です。あなたのお友達にもいたはずです。このエントリもそんな工作枠として受け入れられることを祈っています。
それでは本編です。
背景
2019.11.15にポケモン最新作「ソード&シールド」が発売された。自分は金銀以降まったくプレイしていなかったのだが、弊社CTOの @showwin がこの日のために休みをとっていたのを見て試しに買ってみたら、見事に対戦沼にはまってしまった。やるからには当然勝ちたいという欲望が湧いてくるのだが、ここ20年でポケモンは数もシステムも様変わりしており、もう自分の知っているポケモンではなくなっていた。このギャップを埋めるためにはとにかく多く対戦経験を積む必要があった。
Switchよりも手軽に、コッソリとポケモンバトルができる状態を作る
対戦経験を積むといっても時間は無限にあるわけではない。仕事もあれば子供もいる。 隙間時間でササッとポケモンができれば、ながら対戦ができれば、もっと効率よく場数を踏めると考えた。
そのために以下の課題を解決する必要があった。
- スマホは余裕でいじれるがSwitch本体の操作は無理というシチュエーションが多い
- 最近の子供の寝かしつけはほぼ隣で横たわっているだけで、光さえ枕で遮断すればスマホは問題ない
- Switchをやっていると完全にゲームをやっているとバレて無邪気な攻撃をうける
- Switchをやっていないときでも、無秩序に攻撃が飛んでくることがある
- 攻撃をうけると一時的に画面を見られない状態になる
- 子供のオムツ関連事故が発生したときにも、一時的に画面を見られない時間が生じる
- ながらでやったとしても、選出や立ち回り、ダメージ感覚はあとから振り返りたい
30過ぎたおじさんが満員電車でSwitchでポケモンをやっていたら物理的にも精神的にも迷惑をかけそうだった(これは通勤自体が消滅したので解決した電車の中で電波が不安定になった時に対戦切断扱いになるのが怖かった(これは通勤自体が消滅したので解決した
Switchよりも手軽に、コッソリとポケモンバトルができる状態を作ることでこれを解決していくことにする。
作ったもの
というわけで出来上がったのが「ポケモン剣盾のランクマッチにスマホで潜れる装置」だ。これを使えば子供の寝かしつけ時や、キッズランド等での付き添い時、電車での移動中、駐車場で買い物待機をしている間にもコッソリとポケモンの修行ができる。 普通にポケモン対戦をすることができる機能に加え、テキスト読み上げやメッセージログ表示など、ながら作業をしていても情報を逃すことがないようなアシスト機能を備えている。AirPods等のイヤホンをつけて使うことを想定している。
特徴
- 手軽
- スマホのサイズと操作性
- 操作手順の削減
- ずっと集中していなくても重要な情報を落とさない情報表示
- コッソリ
- スマホは多用途なのでバレない
- イヤホンをつけて戦況を音声読み上げで把握するなども可能
- その他
- あとで振り返ることができるための、選出、使用した技、入ったダメージ等のロギング
仕組み
仕組みを簡単に説明すると以下のようになる。詳しいやり方は「作り方」を見ていただきたい。
- スマホで操作をするとインターネット経由でPCに繋げているSwitchにコマンドが送られ、実行される
- Switchの映像をキャプチャボードを使ってPC側で読み取り、しかるべき解析をしたのちに要所の画像をスマホに送って表示する
基本機能
基本的な画面構成は、選出、行動選択、その他の3つ。順に紹介していく。
選出
「画面上で選出したいポケモンにチェックを入れる」->「確定」の操作をすると、あとはそのとおりにSwitchにコマンドを送ってくれる。初めはSwitchの各種ボタンを一つ一つ押す操作をすれば問題ないだろうと考えていたが、操作に時間がかかり過ぎて選出を時間内に完了できないケースが多発したため、このような形になった。動きはこのGIFアニメのようなイメージ(右側にあるのがスマホの録画)
右下のスマホ画面で「確定」を選んだ後にSwitch側で選出操作が行われているのがわかる。 このときアイコンの画像認識で相手のポケモンの一覧も画像から判定しており、対戦中の動画解析結果と合わせることで自分と相手それぞれの選出のログを残せるようになっている。
行動選択
こちらもSwitchの操作一つ一つを入力する方式ではなく、利用できる技や入れ替えたいポケモンを直接選択できるようにしている。ダイマックスもできる。
その他
非戦闘中における操作は特別なUIを設けず、Switchのボタンを一つ一つWeb上に用意することにした。行動選択や選出のUIで不備がでた場合のバックアップとしても使っている(それから「降参」のための操作も実装していないので、降参したい場合はこれを使う)。以下の画像で一瞬でイメージは湧くと思う。
対戦ログ
自分の選出、相手の選出、使った技、誰にダイマックスを切ったかなどをログに残しくれて、あとから振り返ることができる。 正直これは開発をしていたら面白くなってしまって、あれもできるならこれも...とやっていった中で出来上がったものなので、解決したい課題に対して特にクリティカルには効いていない。(PC向けのバトルアシストツールを作っているのでそちらには応用する気満々)
## 自分 - ドラパルト[先発] - ドラゴンアロー,ゴーストダイブ - ドリュウズ[dymax] - ダイスチル - バンギラス - ステルスロック,でんじは,がんせきふうじ, ## 相手 - ドリュウズ[先発] - じしん, - カバルドン - ステルスロック,ほえる,あくび, - ラプラス[dymax] - キョダイセンリツ,ダイストリーム,なみのり,
ダメージログ
2ターン目 -[自分の] ドラパルトがカバルドンにドラゴンアロー - 57% のダメージ 3ターン目 -[相手の] カバルドンがバンギラスにステルスロック - 4ターン目 -[自分の] バンギラスがカバルドンにステルスロック - -[相手の] カバルドンがバンギラスにほえる - 5ターン目 -[自分の] ドラパルトがカバルドンにゴーストダイブ - -[相手の] カバルドンがドラパルトにあくび - 6ターン目 -[自分の] ドラパルトがカバルドンにゴーストダイブ - 8%(以上) のダメージ 7ターン目 -[自分の] ドラパルトがラプラスにゴーストダイブ - -[相手の] ラプラスがドラパルトにキョダイセンリツ - 8ターン目 -[自分の] ドラパルトがラプラスにゴーストダイブ - 35% のダメージ -[相手の] ラプラスがドラパルトにキョダイセンリツ - 88%(以上) のダメージ 9ターン目 -[自分の] バンギラスがラプラスにでんじは - -[相手の] ラプラスがバンギラスにダイストリーム - 59% のダメージ 10ターン目(9) -[自分の] バンギラスがラプラスにがんせきふうじ - 26% のダメージ -[相手の] ラプラスがパ のンギラスになみのり - 24%(以上) のダメージ
ながら対戦アシスト機能
操作可能時サウンドアラート
操作する必要があるシチュエーションになったら「ピコ!」と音を鳴らしてくれる。具体的には、対戦相手がみつかって選出画面に遷移したとき、新しいターンに入って行動を選択できるようになったとき、ポケモンが倒されてポケモンを変えなければならないとき、の3つのシチュエーションで音がなる。ながらでやっているので必須。そもそも普通に公式にも実装して欲しい機能でもある。
ちなみに ON/OFFが可能。
バトルメッセージログ表示
公式に実装して欲しい機能その2。戦闘中に表示されたメッセージのログを見ることができる。 ながらで対戦をしているので、画面をずっと見ているわけにはいかない。見ていない間に何が起こったのかが分からないと行動を選択することができないので必須(そもそも普通に対戦していても相手の特性を見逃していて焦ったりする)
テキスト読み上げ
バトルメッセージをText2Speechで音声読み上げしてくれる機能。ON/OFFが可能(流石に)
いかにバトルメッセージのログを見られるとはいえ、行動決定に許された時間はわずか45秒。できればターンが回ってきたそのときには現状を把握している状態が望ましい。ながら対戦では画面をずっと見ておくことはできないので、音声読み上げでこれを補う。子供の寝かしつけのとき、公園で遊んでいるときなどなど、非常に重宝した。
作り方 / 開発記録
ここからは全体の構造や利用している技術、開発時に課題となり工夫を要した箇所などについて触れていく。
PCでSwitchを操作できるようにする
多くのサイトで紹介されているように Arduino LEONARDでSwitchを操作できるようにするとともに、FT232RL USBシリアル変換モジュールを組み合わせて、そのArduinoに対してPCからUSBシリアル通信で命令を送れるようにしている。
Switchの映像をPCに取り込む
普通にキャプチャボードを使ってHDMI経由で映像をPCに取り込む。 ちなみにキャプチャボードはこれを買った。
今回は下心としてWebで画像解析の真似事などをしてみたいという欲求もあったので、Webで実装した。以下のように、Webカメラの映像を取り込む時となんら変わりない方法で映像を取得できる。
全体構成
これらに、サーバサイド画像解析等々を加えるとこんな感じになる。
- CLOUD
- Appサーバ (Node.js / Socket.io)
- Arduino LEONARDO + USBシリアルモジュール
- シリアル通信で受け取った命令をSwitchにバイパスする
- PC
- Switch操作アプリ(Node.js)
- Socket.ioメッセージ <=> USBシリアル通信変換app
- 動画解析機(Webフロント)
- 画像解析 / OCR等踏み台 サーバ(Node.js)
- Switch操作アプリ(Node.js)
- スマホ
- スマホコントローラ(Webフロント)
各プレイヤーのやり取りは、動画解析機と画像解析サーバ間を除いて全てSocket.ioで行なっている。 書く前から感じていたが、これは図にしないとだめ。
戦況解析
先に紹介したUIを実現するには、どの画面が表示されているか、技やポケモンの選択肢、ダイマックス可否などを映像から判定する必要がある。これらの大半はフロントエンドで判定をしている。OpenCV.js等を使うと重さに耐えられなかった(バトルメッセージの表示速度を鑑みると解析頻度は10FPSは欲しい)のと、興味もあったので生のCanvas、特にUint8ClampedArray
などのナマモノををかなり触った。
開発方法 - 機械学習の中の人を人力でやる
最初に一気にテストコードを書いて、期待の判定になるようにルールを書いていった。 テストデータは何戦か録画をして、そこから判定したいシチュエーションの画像を抽出して得た。
こんな画像を用意して
こんなテストコードを書いて
describe("getHp", () => { it("画面から相手と自分のポケモンのHPを読み取る(%)", () => { testCases.forEach((testCase) => { const ctx = getDrawedCanvas(testCase.file); const result = getHp(ctx); expect(result.myHp).toEqual(30) expect(result.opposingHp).toEqual(74) }); }); });
あとはひたすらにロジックの中身を書き続ける。 ちなみにHPの判定はHPバーにしめる緑色/黄色/赤色ではない色の割合でパーセンテージを出している。
辛いところ
いくら直接HDMIから取り込んでいるからといっても、リアルタイムにエンコードしている関係上、画像の数値は同じ画面でも、同じメッセージ表示でも完全にピクセルの数値が一致することはない。黒い部分でも白い部分との境界近くではグレーに近づいたりなどもする。
画面の判定
(本当はもっとあるが)画面の種類としては主に以下のどれが表示されているかを判定する必要があった。これに関しても各画面で特徴的な部分のルールを書いて対処している(例えばバトルメッセージ表示中の判定は、適当に2値下して、下の方は黒だが、全体は黒ではなく、メッセージが表示される領域がxx%以上黒ドットではない、のような泥臭いロジックを書いている)。これもあらかじめ全ケースをテストコードを書いておくことで、精度を上げるために他の画面と誤検知した〜などの事故を起こさずに実装することができた。(そろそろ限界だけど)
OCR
ポケモンの名前、技の名前など、文字の検出にはGoogle Cloud VisionのOCRを使っている。 お金がかかるので、少しでもリクエスト回数を減らすためにローカルの画像処理で可能な限り重複リクエストを避けるようにしたり、複数の言語が混じっている場合(相手のポケモンが海外産だと名前も英語になる)でも精度を下げないような前処理を入れたりするなどの工夫をしている。今はおおよそ1マッチ100リクエスト程度(多分10円以下)。この辺りについては機会があればまとめておきたい。
複数の言語が混じっている場合への対処だけに触れると
こういうのを...
このように、スペースが空いているところを改行に見えるように画像を加工してからAPIを叩いてあげると飛躍的に精度が向上したりする。 こうすることで、戦況ログを保存するにあたっては十分な制度が得られた(逆にうまくいかなければおそらく作れなかった)
その他機能
音/テキスト読み上げ
音声の再生はWeb Audio API、テキスト読み上げはChromeのWeb Speech APIを使っている。 特筆することは特にないが、iOSではテキスト読み上げ機能はChromeのみで使用できる。ゆえにHome画面にショートカットを置いて起動した場合などには使えない。
本編終了
おしまい
おわりに
以上でスマホでSwitchを操作してポケモンランクマッチに潜るための装置の紹介を一旦終わります。読んでいただいた方、ありがとうございました。 予告した通り、開発記録+申し訳程度の開発Tipsだったとは思いますが、割と興味本位で無茶に無駄に工数をかけてしまっていることだけは分かっていただけると思います。
ちなみにこいつ自体は結構重宝して動いてくれてはしましたが、まだポケモンの方に結果がでていないので効果のほどはなんとも言えません。 (明確な開発成果といえば、過去2度試みて失敗しているVim->VSCodeの移行に成功したということくらい)
とりあえずこの先、ゲームをやるべきかツールをつくるべきかの天秤で悩んだりしつつあれもやりたいこれもやりたい試したいととっちらかってきているので、もし使ってみたかったり、一緒に作ってみたいという方がいらっしゃればTwitter DMででも連絡をいただければ幸いです。
おまけ - これから
新しいポケモンもさらに増えて困ったことになりそうな11月に向けて、次は直接的に戦闘をアシストしてくれるようなツールを作ってみようと思っています(作っている)。 戦況を自動で判別しつつ、相手の育成パターンによってこちらの技がどの程度入るのかをざっくりみられたり、逆にこちらが打った技がn%はいったのでおそらく耐久に振っている、、など、経験が浅くてダメージ感覚のない人でも初見ポケモンに殺されないようなものができると嬉しいです。
戦況解析からのダメージ計算の実現はとても複雑なドメインで、モデリングの勉強にとてもなることがわかりました。そのあたりを考えるのが楽しくてここ最近まったく進捗がでていないのですが、完成したらモデリング文脈でもなにかアウトプットできるといいなと思っています。
(あとすばやさ関係もいい感じのUIで表現したい)
ポケモンの選出画面をキャプチャして、相手のパーティとの素早さ関係や、相手のポケモンそれぞれに対する有効打点とダメージ計算結果を表示するやつができつつある pic.twitter.com/gHRgMfcZhE
— rocky=興梠@エンジニアリング (@rocky_manobi) May 2, 2020
それでは、次の自由研究に乞うご期待です。
JavaScriptでちょっと複雑なcliを作るのに便利なEnquirer
LAPRAS アウトプットリレー の...何日目だっけ?3/25の記事です! こんにちは!LAPRAS エンジニアの @rockymanobi です!
最近Node.jsでCLIを作る機会があり、その時に触ったEnquirerというライブラリが便利だったので、軽く紹介してみようというものです。ツールそのものについて軽くふれつつ、制作過程で出てきた「こんなことしたいけど、どう実現すれば良いんだろう」と試行錯誤して分かった使い方などを共有できればなと思います。
Enquirerとは
Enquirerは CLIアプリケーションにおける対話的インターフェイスの実装を楽にしてくれるライブラリです。単純なテキスト入力の受付はもちろん、リストからの選択、チェックボックス、パスワード、入力補完、など、様々な入力方式を手軽に組み込むことができます。Node.js製です。JavaScript(TypeScript)万歳!
公式サイトによるとこんなのもできるそうです(凄い!! いつ使うんだろう)
類似のツールとしては先発の Inquirer.js などがあります。公式サイトに「Inquirerより速いぜ!」とあったり、Inquirerとほぼ同じような記述方式をサポートしていることから、かなり意識して作っているように見えます。どちらも利用者が多く、メンテも続いているのでどちらを使っても良いでしょう。あえて比較をするならば、Enquirerの方が多少全体や中身を把握しやすかったのと、少し凝ったことをやろうとしたときに素直そうな印象でした(個人の感想です)。
基本的な使い方
例えばこんなものを作りたい場合は...
const Enquirer = require('enquirer'); (async ()=> { const question = { type: 'select', name: 'favorite', message: '好きな乗り物は?', choices: ['パトカー', '救急車', '消防車'], }; const answer = await Enquirer.prompt(question); console.log(`僕も${answer.favorite}が好きだよ`); })();
このようにprompt
メソッドにオプションを渡してやると、それに応じた方式で入力を受け付ける描画をしたのち、ユーザの入力が完了したときにPromise
をresolve
してくれます。結果はオプションname
に指定したキーにぶら下がってきます。
この要領で公式ドキュメントを見ながら使えば、大抵のことは実現できると思います(少し違う使い方もありますが後で触れます)
少し複雑なケースの実装
ここからは少しだけ複雑な要件に対応してみます。 複雑な入力と言われて最初に思い浮かぶのはポケモンバトルの選出画面です。故にここからは「ポケモンバトルの選出画面をCLIで実装するとしたら」をテーマに少しづつ進めていきたいと思います。
リストから要素を複数選択(これは単純
ポケモンの対戦は、基本的に以下のような流れで進行します。
1. ポケモン6匹でパーティを構築する 2. 対戦前にお互いにパーティを見せ合う 3. 6匹のうち3匹を選出し、3vs3で対戦
今回の対応範囲である「選出」というのはこの3番めにある「6匹のうち3匹を選ぶ」作業のことを指します。
以上を踏まえると、選出画面のCLI版は以下のようなものになりそうです。
実装は以下のようになります。
const Enquirer = require('enquirer'); const myPokemonNames = [ 'フシギバナ', 'リザードン', 'カメックス', 'ゴリランダー', 'エースバーン', 'インテレオン', ]; (async ()=> { const question = { name: 'selections', type: 'select', multiple: true, message: '誰を出す?', choices: myPokemonNames, validate: (selectedItems) => { if(selectedItems.length === 3){ // true/falseを返すとOK/NGのみを表現 return true; } // 文字列を返すとエラーメッセージになる return '3匹選んでください'; }, }; const answer = await Enquirer.prompt(question); console.log(`${answer.selections.join(',')} を選出しました`); })();
オプションmultiple: true
を渡すことで、複数選択可能なチェックボックス式の入力を受け付けます。
また、validate
にバリデーション用の関数を渡すことで、ユーザ入力を検証して、不適合な入力を弾き、入力画面をキープすることができます。エラーメッセージを独自のものにしたい場合は、true/false
ではなく文字列を返すようにすることで、判定をNGとしたうえで、関数が返した文字列をエラーメッセージとして表示してくれます(errorMessageってオプションあったほうがわかりやすいきがしますが)。
タイマーで入力をキャンセルする
ここまでは公式Readmeにもしっかり書いてあるので難なく対応できました。が、要件を一つ忘れていたのでここで追加します。
ポケモンの対戦では遅延行為を防ぐため、あらゆる行動に制限時間が設けられています。もちろん選出も例外ではなく、すべてのプレイヤーは1分30秒(記事執筆時点)以内で3匹のポケモンを選び出す必要があります。これに間に合わない場合は、強制的に上から順に3匹のポケモンが選出されます。
この要件をCLIに反映させてみます。
const Enquirer = require('enquirer'); // (信じられないことに)配列 myPokemonNamesをchoicesオプションとして渡すと破壊的に配列が変更されるので、 // 違うArrayインスタンスを返すようにFunctionに包んでいる const myPokemonNames = () =>{ return [ 'フシギバナ', 'リザードン', 'カメックス', 'ゴリランダー', 'エースバーン', 'インテレオン', ]; }; (async ()=> { const prompt = new Enquirer.MultiSelect({ name: 'selections', message: '誰を出す?', choices: myPokemonNames(), validate: (selectedItems) => { if(selectedItems.length === 3){ return true; } return '3匹選んでください'; }, }) let timer; prompt.once('run', ()=>{ timer = setTimeout(()=>{ prompt.cancel() }, 5000) }) prompt.once('close', ()=>{ clearTimeout(timer); }); const answer = await prompt.run().catch(() => { // 時間切れです return myPokemonNames().slice(0,3); }); console.log(`${answer.join(',')} を選出しました`); })();
これまでとは少し実装方法を変えています。これまではInquirer.js風の実装をしていましたが、ここではコチラのように、ライブラリにビルトインで入っているPrompt
の子クラスを用いた実装にしています。
コンストラクタの形式はこれまでprompt
メソッドに渡していたものに似ていますが、各クラスごとに自明なものMultipleSelect
クラスにおけるtype
やmultiple:true
など)が不要になっています(TypeScriptなら型チェックも効いて快適)。
この方式ではPrompt
クラスのrun()
メソッドを実行したときにユーザの入力を受け付けるようになり、同cansel()
メソッドを呼んでやることで、強制的に入力を終了させることが可能です。
Prompt
クラスはEventEmitter
を継承しており、上記コードでは入力受付開始時のrun
イベントに反応してタイマーを作動させ、一定時間経過後にキャンセルするようにしています。入力終了時(キャンセル/タイムアウト含む)に発生するclose
イベントが発生したタイミングでは、タイマーを止める処理を実行するようにしています。
最後に、プロンプトをキャンセルしたときにはPromise
がreject
されるので、catch節でエラーを拾って、「時間切れの場合は先頭の3匹強制選出」を示す結果を返すようにしています(ちなみにcatch節のコールバックには何も引数が入って来ません)
カウントダウンを表示する
制限時間を超過すると時間切れになるようにはできましたが、選出中に「後何秒?」が分からないのは辛いものがあります。これも対応しましょう。
(async ()=> { let timeRemaining = 10; const prompt = new Enquirer.MultiSelect({ name: 'selections', message: () => { return `誰を出す? 残り ${timeRemaining} 秒` }, choices: myPokemonNames(), validate: (selectedItems) => { if(selectedItems.length === 3){ return true; } return '3匹選んでください'; }, }) let interval; prompt.once('run', ()=>{ interval = setInterval(()=>{ timeRemaining -= 1; if(timeRemaining <= 0){ prompt.cancel() } else { prompt.render() } }, 1000) }) prompt.once('close', ()=>{ clearInterval(interval); }); const answer = await prompt.run().catch(()=>{ console.log('時間切れです'); return myPokemonNames().slice(0,3); }); console.log(`${answer.join(',')} を選出しました`); })();
タイマー処理をsetTimeout
をsetInterval
に変えて、1秒毎にカウントダウンするように変更しつつ、メッセージに「残り n 秒」を表示させるために、以下の修正を施しています。
message
オプションに残り秒数を表示する文字列を返す関数を渡す- 1秒毎に
prompt.render()
メソッドを実行する
これにより、プロンプトの内容が毎秒再描画され、残り時間がカウントダウンされていく様子を表示することができました。カウントダウン部分のみを他のライブラリや独自実装などで代替しようとすると、カーソルの状態が衝突して表示がおかしくなったりするので、このあたりをサポートしてくれているのは有り難い限りです。
複数の質問をする & 前の回答を考慮して選択肢を変更する
これで完成かと思いましたが、そうはいきません。確かに選出は6匹から3匹を選び出す作業ですが、同時に「誰を先発させるか」を決める作業でもあることを忘れていました。
最初に選択する要素には特別な意味をもたせる必要がありそうなので、最初に先発を聞いて選んでもらった後に、控えの二匹を選出してもらうようにしてみます。
(async ()=> { let timeRemaining = 10; let currentPrompt; let interval; // Enquirerインスタンスの参照が欲しいのでstaticメソッドのpromptではなく // new Enquirer()して、そいつのpromptメソッドを呼ぶようにする const enquirer = new Enquirer(); enquirer.on('prompt', (prompt) => { currentPrompt = prompt; prompt.once('run', ()=>{ interval = setInterval(()=>{ timeRemaining -= 1; if(timeRemaining <= 0){ currentPrompt.cancel() } else { currentPrompt.render() } }, 1000) }); prompt.once('close', ()=>{ clearInterval(interval); }); }) const answer = await enquirer.prompt([ { type: 'select', name: 'starter', message: () => { return `先発は誰にする? 残り ${timeRemaining} 秒` }, choices: myPokemonNames(), }, { type: 'select', multiple: true, name: 'reserves', message: () => { return `控えは誰にする? 残り ${timeRemaining} 秒` }, choices() { return myPokemonNames().filter((name) => { return this.state.answers.starter !== name; }) }, validate: (selectedItems) => { if(selectedItems.length === 2){ return true; } return '2匹選んでください'; }, } ]).catch(console.error); if (answer) { const selected = [answer.starter].concat(answer.reserves); console.log(`${selected.join(',')} を選出しました`); } else { console.log('時間切れです') console.log(`${myPokemonNames().slice(0,3).join(',')} を選出しました`); } })();
公式ドキュメント によると、Enquirerは複数の質問を連続して表示することに対応しているようですが、Enquirer.prompt
メソッドにオプションの配列を渡してあげる形式にする必要があります。このままだとタイマー処理によってキャンセルすることができないので、どうにかしてPrompt
クラスのインスタンスを参照する必要があります。
ということをやろうとしているのが上の方にあるこの処理です。
// Enquirerインスタンスの参照が欲しいのでstaticメソッドのpromptではなく // new Enquirer()して、そいつのpromptメソッドを呼ぶようにする const enquirer = new Enquirer(); enquirer.on('prompt', (prompt) => {
Enquirerクラスは内部的に保持しているPrompt
インスタンスを処理するタイミングでprompt
イベントを発火しつつPrompt
インスタンスを渡してくれるので、そこでこれまでのケースと同じようにイベントハンドルを仕掛けています。
先発で選んだポケモンを控えの選択肢に出さない
message
オプションなどと同様にchoices
オプションにも関数を指定することが可能です。そして、この関数内部でthis.state.answers
を参照することで前の質問に対する入力の値を得ることができます。コレを利用して、控えポケモンの選択肢から、先発に選んだポケモンを除外しています。
choices() { return myPokemonNames().filter((name) => { return this.state.answers.starter !== name; }) },
その他
今回の例ではchoices
にはString配列を渡していましたが、{ name: '興梠', value: 'rocky' }
のようなオブジェクトの配列を渡すことで、見た目上はname
に指定した値を表示しつつ、実際にanswer
で得られるのはvalue
に指定した値にする、ということも可能です(多分大体そうする)。
加えて、このようにオブジェクトを渡す方式にしている場合は、最後の例を実現するにあたってchoices
をフィルタする代わりに、各choice
のオブジェクトにdisabled: true
などを渡すことで選択不能な状態にすることができるみたいです。
(というか複数聞きたいなら複数回prompt
呼んでしまえばいいじゃないのって思ったけど違うのかな)
まとめ
無事、ポケモン選出画面の要件を満たすことができました。 技術的には以下のあたりがリポジトリ検索したり調べたりコード見てみたりしないと見えてこなかった印象があるので、実現したい方は参考にしてみると良いでしょう。(PullRequestチャンスでもある)
- タイマーでプロンプトを終了させるためには
Prompt#cancel
- Promptクラスの参照は、最初から
Prompt
クラスを直接使った方法で実装するか、Enquirer
クラスのprompt
イベントをリスニングして降ってくるのを拾う
最後に
この謎チュートリアルはノンフィクションです(実際に勢いでポケモン対戦できるCLIを書いているときの展開をほぼそのまま再現しました)
新しいチームに加わるエンジニアのための "被" オンボーディングガイド v0.1.0
この記事はLAPRAS Advent Calendar 2019の13日目の記事です
自分も最近体験したということもあり、LAPRASのWebAppチームのオンボーディングについて紹介しようと思いましたが、最近自社推しが過ぎる気もしたので視点をオンボーディングを受ける側に移し、「こんな風に立ち回ろう!」というスタンスで書くことにしました。
v0.1.0 ということで走り書きの勢いでそのままお届け致します。
オンボーディングとは
オンボーディングとは、新たにサービスを導入した人や新たに組織に参加した人などに対して早く慣れることができるようにサポートすることです。
この記事の文脈は後者。 注意することやTIPSを書いていきたい。自分にも多少刺さる点もあるが、過去よりも改善はされていると思っているので勇気を持って書くことにする。
いきなり目立った成果を出そうとして消耗しない
まずは危険性を認識する
- 新しいチームに入ってまず最初に戦わないといけないのは「自分、役に立ってるの?必要なの?」「自分は何者なの?」という不安感
- どのような人でも、環境が変わればまずは学びのフェーズから始まる(エンジニアで、ある程度システムが大きくなっている場合は特に)
- チームに入りたての学びのフェーズでは貢献は愚か、自己認識としてはマイナスの存在とも感じてしまう
- 以前の職場でのパフォーマンスが高かったり、重要なポジションを担っていたりするほどギャップが激しく、精神的負荷が高い
- 早くその状態を脱出しようとして焦り、目に付くインパクトの大きそうな課題やできそうなことを片っ端からやろうとするが、成果が出ずに自己肯定感が下がって消耗する
- この過程に耐えられず、(想像上の)他者の認識から身を守ろうとして、他責的な振る舞いになって信頼を失う。これは危ない。
- 地に足をつけて着実に積み上げていく方が長期的にチームにも貢献できるはず
以下、この危険性に対処するための方法を紹介する
目標とスケジュールを設定して共有する
- 理想と現状のギャップに対して焦らないようにするためには、目標とスケジュールを決めるのが効果的
- スケジュールがあれば「まだゴールには遠いけど、オンスケだから今日休める!寝るぞー」と思うことができる
- この認識をチームと共有することで、自分も今の所大丈夫だと思えるし、周囲にもそう思ってもらえていると安心できる
ちなみにLAPRASでは入社前から以下のような期待値を提示されていた(内容はエンジニア向け、とは少しズレるが参考までに。純えんじにあ職ならばより技術的な貢献ができるような内容が並ぶはずだ)
* 1ヶ月 * スクラムの開発チームに馴染んで(技術・内部仕様理解以外の点で)普段の業務に障害がなくなること(=普通に仕事がお願いできる状態) * 3ヶ月 : * 開発チームをまとめ、リードしていくことができる * 6ヶ月 * 予測不能(全社的に変化が激しいので、いまは明確にはできない
- 入社直後に上記目標について改めて合意をし、合わせて必要なサポート(後述)についても認識を合わせる機会があった
- 一開発者としてだけではなく、チーム全体への貢献を求められるポジション枠で入社していたということもあり、「まずは普通の開発者として一人分の仕事ができるようになることにフォーカス」と思えたことは学習フェーズの不安との戦いに大きく貢献した。(仮に僕が開発サイドオンリーで戦える強者であるならばとにかく開発集中でよいと思うが、残念ながら自分の技術レベルはそのレベルには達していない)
- このような機会が無い場合はリクエストしよう
進捗を共有して必要であればスケジュールを変更する
達成できない目標は精神衛生上悪影響があり過ぎるし、達成できたと思っているのが自分だけだというのもまた悲しい
フィードバックをもらって現状を正しく認識する
自己認識でうまくいっていると思っていても、他者からはそう見えていないこともある。チームに貢献するという文脈において、このギャップは解消すべきだ。逆に、自分ではダメだと思っていても、他者からはよく見えているところもある。これは本来なら無用な悩みになるので、このギャップも解消すべきだ「●●の目標に対して、自分としてはこの点とこの点はできていると思っている。ここは足りないのでこうしようと思っている。認識はあっていますか?」という質問ができると良い。
ギャップがあれば解消する方法を考える
- 世の中うまくいかないこともある
- どうすれば修正できるのか、どのようなサポートが必要なのかを改めて対策を考えたり、助言を求めたりする
- 目標やスケジュールの達成見込みが絶望的ならば、期待値かスケジュールを調整する
LAPRASでは主に毎週(慣れてきたら隔週)の1on1と、月一の振り返りで上記のような作業を行なった。同じく、このような機会がなければリクエストしよう。
(おまけ)結果
今は入社4ヶ月になるが、概ねギリギリ大丈夫と言えるレベルだと思っている。次のステップに向けて新しい課題に四苦八苦しながら対処しているところでもある。
受ける側の話とはずれるが、先輩社員に「焦って結果出そうとして苦しんで潰れていく人が多いから焦らないで一つずつやっていった方がよいですよ」と言葉をかけてもらったことをすごく覚えている。いくら自分がそのつもりで、目標を共有していたとしても、焦る気持ちはゼロにはならないので、言葉にしてもらえるて死ぬほど楽になったのを覚えている。
現状の仕組みを作り上げたことに対する敬意と感謝を忘れない
改めて言語化しようと思ったらTweetがあった。連投しているので気になる方にはみてもらえると嬉しい。大事なのは「味方感」。本当の意味で味方になることだ。
逆に新しく入る側としては
— rocky=興梠@エンジニアリング (@rocky_manobi) 2019年8月12日
● 理想状態を語ることと実現することは全然違う
● 全てをいい感じにするのは無理ゲー
● ここまで作り上げたことに敬意を払う
この三つを忘れず、その上で全てをいい感じに近づける為に出来る方をしたり言ったりするのが大事 https://t.co/gfUYhuAO3t
- 課題があるのは当たり前、むしろ課題が無いなら自分がいる意味がない。自分は被害者ではない。
- そもそも自分で今の状態を一から作れるかかんがえる(多分NOだから起業せずに入社したんだろう)
- まずはそのすごさを認めて敬意を表し、チーム全体を自分にとって善の存在として認識する
- その意識を忘れなければ細かい表現は気にしなくていいはず
- 味方として一緒に解決に取り組めば良い
ちなみに受け入れ側として注意したいのは以下のような力学(そしてLAPRASのチームは信じられないくらいこの手のネガな部分が無くて好きだ)
新しく強い人をチームに迎えられたとして、その人の強みを活かせるかどうかは、これまで自分のやってきたことが自分以外の人の手で改善される事を良しとできるかがとても大きい。無意識に自己の評価を下げる事象と(誤)認識して、口出したりマウントとったりして手放せないとかの負の影響はかなりある
— rocky=興梠@エンジニアリング (@rocky_manobi) 2019年8月11日
実務上のサポート
エンジニア向けと書いたので、実務についての工夫も書いておく。 先ほど書いた「必要なサポート」の具体的な内容はおもにこちらにリンクする。ガンガンリクエストしていこう。
システムを触る
- 自分たちが何を作っているのか理解せずに物を作るのはまずい。機会がなければ確実にリクエストしよう。
システム全体像のインプット
- 最初に全体像を説明をしないところはないと思うが、されなかったらリクエストしよう
- DB定義、依存ライブラリ/サービス、全体の設計思想、インフラの大まかな構成 などを中心に聞く
- 一度に理解する必要はない。実務をやりながら理解を深めていくイメージで良い。
ペアプロ
- コード理解において「(目的もなく)コード読んでおいて」はワークしないので、「とりあえずコード読んでおきます!」という宣言はしない
- 任せるタスクが無い、余裕が無いのであればとりあえずペアプロをした方が効率が良い
- 触っている箇所だけではなく、実装の背景など、気になったところは質問してドキュメントに落としきれていない知識を吸収することに務める
- LAPRASでは最初の2週間の開発タスクは全てペアプロで実施、3週目からは自己申告で「これはソロ狩りしたい」といったものだけソロで対処するスタイルをとった
1ヶ月で開発タスクをこなせるようになる、をクリアすることに大きく貢献した
もし現場でペアプロがあまり推奨されていない場合は、せめて対面でのコードレビューを求めよう(コードをみながらの会話は必ず必要だ)
ペアOpsもといペアワーク
- 現場で実務をこなす際に必要なのはコードを書くことだけでは無い
- 障害対応や、システム面の質問に答えるための調査をしたり、データ周りの調査などもそれに含まれる
- これらの作業はやってみないと振られないので、自分からリクエストしにいく
- とはいえいきなりは任せられないので「自分のやったことのない仕事やるときは声かけてください隣でみさせてください」と宣言する
- shellの設定や使っている便利ツール、システム運用時に利用しているサービス、などなど、入社後の説明では得られなかった情報を発見することがある
- 周囲のエンジニアのレベル感もわかる(この点自分は大いに焦りを覚えたのだけど)
自分の関わっていないPullRequestを見る
- 最初はどうしても自分のタスクだけを追いがちになるが、最低限チームが何をやっているのかは把握したいところ
- Issueは「これからすること」が書いてあるが、PRはその結果までついているので、初期はPRから見る方が効率が良い
- 文字通り直近書かれたコードを見ることができるので「昔はこういう書き方をしていたけど今は...」のような事故が防げる
- レビュー指摘の傾向などからチームの「大事にしているところ」や「雰囲気」も感じ取ることができるはず
- (スクラムをやっているならば、計画MTGの設計で話していた内容が、どうコードに落としこまれるのかを意識して読むと良い)
- みてはダメだと言われることはないが、時間をとれないようであればリクエストしてみよう
スプリント計画をちゃんとやるスクラムチームはEasyモード
- 本筋とは少しずれる
- もし新たに入るチームがスクラムを採用している場合、事前にスプリント計画の様子を聞くと良い
- スプリント計画ミーティングにおいて、作るものと設計について詳細に議論して合意しているチームは、当然ながら新しく入った人のキャッチアップも早い
まとめ
- ざっとみると、チームに入る側としては周囲に労力をかけてもらうようなリクエストが多いように見える
- とはいえ新しくきた人をワークさせるのに労力がかかるのは当然なのでためらってはいけない
- ためらって、結果としてパフォーマンスが下がればその方が損失が大きい
- 多少ずうずうしく、学ぶために周りを巻き込んでいく姿勢が大事
- 組織としては、その辺に気を配れるかが大事
その他
- こまかいメンタル的なところなどはキャラ的に敏感ではないので書かなかった
最後に
偉そうに書いたが完全に自分の出来栄えは棚に上げた理想論だと思って読んでほしい。
多少の貢献はできているかもしれないが、まだまだチームの力になるにはどうすれば良いか割と必死だ。気を抜いたらただのお荷物やで自分!という感覚とは普通に戦っている。チームの皆様にはその辺は生暖かく見守りつつ、サポートも頂いているのでそれに救われている。徐々にやれる範囲を広げてきたのもあって抱えているものが増えてきたので、次は仕組みにして手放していき、パフォーマンスをスケールさせたい。
このように全然余裕な雰囲気はない。やっていきしかない。
というわけで、v0.1.0よろしくプロットのような描きっぷりになってしまったが、少しは有益だと思うのでその辺は容赦してほしい。(万が一)リライトしたらTwitterに書くので、とりあえずその時までみんな頑張ろう。
SlackでDMを受け取るとパブリックチャネルに優しく誘導してくれるDM警察というBOTを作って公開しました
この記事はLAPRAS Advent Calendar 2019の9日目の記事です
概要
- LAPRASに入る前、業務の内容をDMでたくさん受け取って辛いという課題があった
- DMを受け取るとパブリックチャネルに優しく誘導してくれるDM警察というBOTを作ったがLAPRASに入ったら需要がなかった
- なんでや?じゃあLAPRAS関係ないやろ!?
- まとめ
課題 - 業務の内容をDMでたくさん受け取るのは辛い
このような内容をDMで受け取ると少し困ります。
パブリックチャネルの発言であれば、僕がこの質問に答えられない場合でも「@知っていそうな人
さん、わかりますか?」とメンションするだけで事が足ります。たまたま会話を見ていて知っている人が能動的に答えてくれる事もあるでしょう。また、後々一連の会話をシェアしたくなったときでも、この会話へのリンクを貼れば経緯を伝えることができます。DMではいずれも叶いません。
本当に秘匿したい情報のやりとりを除いた殆ど全ての場合において、情報はパブリックでやりとりする方が良いはずです。良いはずですが、この手のやりとりをDMで行う人の割合は決して少なくありません。
以前の僕も、そういったDMを受け取るたびに上記の説明をしつつ、なるべくパブリックチャネルにポストしてもらうよう促していました。そして、これはおそらく日本のあちこちで行われている事でしょう。
DMを送る方には悪意がない
一方でDMを送る方としては、単純にパブリックチャネルに発言するメリットを理解していなかったり、使い方を知らなかったり、オープンな場所に投稿することをためらってしまうような空気があったりと、必ずしも悪意があるわけではありません。そこが悩ましいところです。
丁寧に啓蒙していくしかないのですが、やはり続くと辛い。そして言い続けているうちに、段々と「うるさいやつ認定」されていくのもまたつらい。
DM禁止は筋悪(だと思っている
一律禁止はしたくない、というスタンスです。有用なシチュエーションでは普通に使うべきだと思います。
なにより、大事なのはDMをしない事ではなく、パブリックに情報をやりとりすることです。その大切さを伝える方向で解決したいところです。
DM警察を作って公開しました
というわけで、諸問題を解決するBOTを作りました。ざっと特徴は以下の通りです。
- DMを受け取ると、BOTがパブリックチャネルに誘導してくれる
- コミュニケーションは職質風。パブリックチャネルに投稿する意義を啓蒙してくれる
- ゴネれば10分黙ってくれる
- 簡単に使える
- 権限が気になる人は、Herokuボタンで独自環境を構築することができる
DMを受け取ると、BOTがパブリックチャネルに誘導
DMを受け取ると、DM警察が現れてDM送信者に職質をかけてくれます。無視をしてもDMを受け取るたびに起動するので、あまりのウザさに流石に無視はされません。
拒否すると、パブリックチャネルに投稿する意義などを教えてくれます。「我々も仕事でやっているのですみません」という感じで、とてもウザい感じに仕上がっています。
ゴネ続けると、最後には諦めて許してくれます。10分離脱してくれるので、その間に会話を済ませることもできます。
Slackワークスペースに公式サイトからインストールできる
こちらが公式サイト です。動画やマニュアル、注意事項などが書いてあります。
「dm警察をslackにインストールする」ボタンを押すとSlack Appがインストールされます。インストール時にAppのWebhookを送るチャネルを聞いてくるので、あらかじめ「#DM警察」などを作っておくと良いでしょう。
また、誤ったワークスペースにインストールしないようにご注意ください。
Botにメンションする事で、DMの監視を依頼できる(監視してほしい人だけが使うことができる)
こんなふうに「パトロールよろしく」とメンションすると、パトロールしてくれます。
Herokuボタンで独自環境を構築することができる
DM警察のサーバはDMの内容を記録していませんが、DMを読めるという強めの権限を渡すこと自体がネックになるという方もいらっしゃると思い、ソースコードを公開してHerokuボタンも設置しました。コチラを参考に独自の環境を構築する事ができると思います。やってみてね。
あと、リポジトリにスターつけて頂けるとメンテ続ける意欲が強化されるので、気に入った方は是非ぜひ。
運用TIPS: 口うるさいのは人ではなくDM警察
DM警察がきたからには、もう口うるさく啓蒙する必要はありません。警察を呼んだのです。任せましょう。
たまにBOTを無視して会話する猛者も現れますが、そのときは「BOTがうるさいので、パブリックチャネルいきましょうよ」「DMめちゃ送ってくる人がいて(あなたなのだけど)、その人対策でDM警察いれたんです。すみません警察がうるさくて」などといって、DM警察という共通の敵を作りながら、パブリックチャネルに誘導してしまいましょう。
言いにくいことは機械に言わせる。社会性フィルタを通した言い回しも、一度考えればそれで終わりです。繰り返していいんです。DM警察は機械ですから。
と、このように運用をした結果、以前よりDMを受け取ることが減りました。
なんでや?LAPRAS関係ないやろ!?
そうおっしゃらずに、まずはこちらをご覧ください。
ご覧の通り、弊社LAPRAS株式会社のSlackは発言の殆どがパブリックチャネルにポストされており、全社的に非常にオープンな状態であるといえます。業務をしていてDMを受け取ることは稀で、その内容もパスワードなどの秘匿すべき情報や、休日の遊び関係の会話(子供どうしで遊ばせる会のために集合場所を決めたりetc)などであり、なんら違和感を覚えることはありません。
もうお気づきかと思いますが、このような環境ではDM警察に活躍の場はありません。オープンな組織では、DM警察は生きていけないのです。4ヶ月前にLAPRASに入社した僕はそのことに気づき、そして...DM警察のDynoを「0」に設定しました。当時のDM警察は僕だげが使えるレベルにしか整備がされていなかったため、その日がDM警察の命日となりました。
それから...
DM警察を止めてから数ヶ月が経ったある日、平和なSlack生活を送っていた僕はTwitterであるものを目にしました。それは「Slackで業務連絡がDMで送られてきて辛い」というツイートでした。多くの共感を集めたそのツイートは拡散され、議論の火種となり、一定の周期で僕のTLに「Slack DM辛いネタ」を届けるようになりました。
「僕は解放されたけど、世の中にはまだまだ苦しんでいる人がいるんだ。解決策を持っているのに、自分が救われたからといって電源を落として満足している、こんなことで良いのか?DM警察にも申し訳ないのでは?」TLをみるたびにそんな気持ちになりました。そしてこの「うしろめたさ」こそがDM警察を公開した動機です。
もしLAPRASに入らず、あのままDM警察が稼働していたら、きっと僕はある程度の満足感を持って過ごしていて、DM警察を公開することはなかったでしょう。DM警察を一度はリストラしたという後ろめたい気持ちが、制作の原動力になったのです。その意味で、DM警察を公開するにあたってLAPRASは大いに関係していると言えるでしょう。
まとめ
かくして、色々なところで運用しながら育ててきたDM警察でしたが、LAPRAS入社と同時に晴れてお役御免となりました。これからは僕のためにではなく、世の中の同じ悩みを持つ人のため、組織のために働き、LAPRASのようなオープンな文化の組織が増えていくことに貢献してほしいと願っています(若干自社を褒めすぎたかとも思いましたが、そこはまだ入社4ヶ月で、作り出したものよりも既存の枠組みに乗っている割合の方が多い今だからまだ許されることでしょう。とはいえそろそろ「これは俺がやったったで!ドヤァ」という成果を出したいものですね)。
それでは、快適なSlack Public Channel ライフを!
そして最後に
最後に 「それから...」の下りは全くの嘘です 。DM警察を公開したのは、MushupAwardsもといヒーローズリーグ2019に応募するネタを考えていたら「そういえばDM警察があったやん!あれでいこう!もったいないから公開しよう!」と思ったからです。嘘をついた上にLAPRAS本当に関係なく、誠に申し訳ありませんでした。
つまるところ「オープンな文化はいいですよね。LAPRASのそういうところが僕は好きです」というエントリだったと受け取って頂ければと思います。はい。申し訳ありませんでした。
それでは、快適なSlack Public Channel ライフを!
宣伝
反響いただいたのと、ちょくちょくご利用いただいているっぽいので、少しは役得を...ということで、宣伝を!させてください! LAPRAS株式会社は現在 WebAppエンジニアを絶賛募集中でございます。
DM警察は稼働していませんが、ご興味ありましたらこちらをご覧ください。
こういったものを作っています
こんな組織を目指しています