JavaScriptでちょっと複雑なcliを作るのに便利なEnquirer

LAPRAS アウトプットリレー の...何日目だっけ?3/25の記事です! こんにちは!LAPRAS エンジニアの @rockymanobi です!

最近Node.jsでCLIを作る機会があり、その時に触ったEnquirerというライブラリが便利だったので、軽く紹介してみようというものです。ツールそのものについて軽くふれつつ、制作過程で出てきた「こんなことしたいけど、どう実現すれば良いんだろう」と試行錯誤して分かった使い方などを共有できればなと思います。

Enquirerとは

Enquirerは CLIアプリケーションにおける対話的インターフェイスの実装を楽にしてくれるライブラリです。単純なテキスト入力の受付はもちろん、リストからの選択、チェックボックス、パスワード、入力補完、など、様々な入力方式を手軽に組み込むことができます。Node.js製です。JavaScript(TypeScript)万歳!

公式サイトによるとこんなのもできるそうです(凄い!! いつ使うんだろう

類似のツールとしては先発の Inquirer.js などがあります。公式サイトに「Inquirerより速いぜ!」とあったり、Inquirerとほぼ同じような記述方式をサポートしていることから、かなり意識して作っているように見えます。どちらも利用者が多く、メンテも続いているのでどちらを使っても良いでしょう。あえて比較をするならば、Enquirerの方が多少全体や中身を把握しやすかったのと、少し凝ったことをやろうとしたときに素直そうな印象でした(個人の感想です)。

基本的な使い方

例えばこんなものを作りたい場合は...

f:id:rocky_manobi:20200326022503g:plain:w400

const Enquirer = require('enquirer');

(async ()=> {
  const question = {
    type: 'select',
    name: 'favorite',
    message: '好きな乗り物は?',
    choices: ['パトカー', '救急車', '消防車'],
  };
  const answer = await Enquirer.prompt(question);
  console.log(`僕も${answer.favorite}が好きだよ`);
})();

このようにpromptメソッドにオプションを渡してやると、それに応じた方式で入力を受け付ける描画をしたのち、ユーザの入力が完了したときにPromiseresolveしてくれます。結果はオプションnameに指定したキーにぶら下がってきます。

この要領で公式ドキュメントを見ながら使えば、大抵のことは実現できると思います(少し違う使い方もありますが後で触れます)

少し複雑なケースの実装

ここからは少しだけ複雑な要件に対応してみます。 複雑な入力と言われて最初に思い浮かぶのはポケモンバトルの選出画面です。故にここからは「ポケモンバトルの選出画面をCLIで実装するとしたら」をテーマに少しづつ進めていきたいと思います。

リストから要素を複数選択(これは単純

ポケモンの対戦は、基本的に以下のような流れで進行します。

1. ポケモン6匹でパーティを構築する
2. 対戦前にお互いにパーティを見せ合う
3. 6匹のうち3匹を選出し、3vs3で対戦

今回の対応範囲である「選出」というのはこの3番めにある「6匹のうち3匹を選ぶ」作業のことを指します。

以上を踏まえると、選出画面のCLI版は以下のようなものになりそうです。

f:id:rocky_manobi:20200326022638g:plain:w400

実装は以下のようになります。

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に反映させてみます。

f:id:rocky_manobi:20200326022748g:plain:w400

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クラスにおけるtypemultiple:trueなど)が不要になっています(TypeScriptなら型チェックも効いて快適)。

この方式ではPromptクラスのrun()メソッドを実行したときにユーザの入力を受け付けるようになり、同cansel()メソッドを呼んでやることで、強制的に入力を終了させることが可能です。

PromptクラスはEventEmitterを継承しており、上記コードでは入力受付開始時のrunイベントに反応してタイマーを作動させ、一定時間経過後にキャンセルするようにしています。入力終了時(キャンセル/タイムアウト含む)に発生するcloseイベントが発生したタイミングでは、タイマーを止める処理を実行するようにしています。

最後に、プロンプトをキャンセルしたときにはPromiserejectされるので、catch節でエラーを拾って、「時間切れの場合は先頭の3匹強制選出」を示す結果を返すようにしています(ちなみにcatch節のコールバックには何も引数が入って来ません)

カウントダウンを表示する

制限時間を超過すると時間切れになるようにはできましたが、選出中に「後何秒?」が分からないのは辛いものがあります。これも対応しましょう。

f:id:rocky_manobi:20200326022819g:plain:w400

(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(',')} を選出しました`);
})();

タイマー処理をsetTimeoutsetIntervalに変えて、1秒毎にカウントダウンするように変更しつつ、メッセージに「残り n 秒」を表示させるために、以下の修正を施しています。

  • messageオプションに残り秒数を表示する文字列を返す関数を渡す
  • 1秒毎に prompt.render()メソッドを実行する

これにより、プロンプトの内容が毎秒再描画され、残り時間がカウントダウンされていく様子を表示することができました。カウントダウン部分のみを他のライブラリや独自実装などで代替しようとすると、カーソルの状態が衝突して表示がおかしくなったりするので、このあたりをサポートしてくれているのは有り難い限りです。

複数の質問をする & 前の回答を考慮して選択肢を変更する

これで完成かと思いましたが、そうはいきません。確かに選出は6匹から3匹を選び出す作業ですが、同時に「誰を先発させるか」を決める作業でもあることを忘れていました。

最初に選択する要素には特別な意味をもたせる必要がありそうなので、最初に先発を聞いて選んでもらった後に、控えの二匹を選出してもらうようにしてみます。

f:id:rocky_manobi:20200326022904g:plain:w400

(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? ほら、esa.ioさんもだいたいできたらWIPでもいいから公開しようっておっしゃっているではありませんか。

オンボーディングとは

オンボーディングとは、新たにサービスを導入した人や新たに組織に参加した人などに対して早く慣れることができるようにサポートすることです。

この記事の文脈は後者。 注意することやTIPSを書いていきたい。自分にも多少刺さりまくることがあるが、過去はもっと酷かったところが若干改善したと思っているので、勇気を持って書くことにする。

いきなり目立った成果を出そうとして消耗しない

まずは危険性を認識する

  • 新しいチームに入ってまず最初に戦わないといけないのは「自分、役に立ってるの?必要なの?」「自分は何者なの?」という不安感
  • どのような人でも、環境が変わればまずは学びのフェーズから始まる(エンジニアで、ある程度システムが大きくなっている場合は特に)
  • チームに入りたての学びのフェーズでは貢献は愚か、自己認識としてはマイナスの存在とも感じてしまう
  • 以前の職場でのパフォーマンスが高かったり、重要なポジションを担っていたりするほどギャップが激しく、精神的負荷が高い
  • 早くその状態を脱出しようとして焦り、目に付くインパクトの大きそうな課題やできそうなことを片っ端からやろうとするが、成果が出ずに自己肯定感が下がって消耗する
  • この過程に耐えられず、特に(想像上の)他者の認識から身を守ろうとして、他責的な振る舞いになって信頼を失う。これは危ない。
  • 地に足をつけて着実に積み上げていく方が長期的にチームにも貢献できるはず

以下、この危険性に対処するための方法を紹介する

目標とスケジュールを設定して共有する

  • 理想と現状のギャップに対して焦らないようにするためには、目標とスケジュールを決めるのが効果的
  • スケジュールがあれば「まだゴールには遠いけど、オンスケだから今日休める!寝るぞー」と思うことができる
  • この認識をチームと共有することで、自分も今の所大丈夫だと思えるし、周囲にもそう思ってもらえていると安心できる

ちなみにLAPRASでは入社前から以下のような期待値を提示されていた(内容はエンジニア向け、とは少しズレるが参考までに。純えんじにあ職ならばより技術的な貢献ができるような内容が並ぶはずだ)

* 1ヶ月
  * スクラムの開発チームに馴染んで(技術・内部仕様理解以外の点で)普段の業務に障害がなくなること(=普通に仕事がお願いできる状態)
* 3ヶ月 : 
  * 開発チームをまとめ、リードしていくことができる
* 6ヶ月
  * 予測不能(全社的に変化が激しいので、いまは明確にはできない
  • 入社直後に上記目標について改めて合意をし、合わせて必要なサポート(後述)についても認識を合わせる機会があった
  • 一開発者としてだけではなく、チーム全体への貢献を求められるポジション枠で入社していたということもあり、「まずは普通の開発者として一人分の仕事ができるようになることにフォーカス」と思えたことは学習フェーズの不安との戦いに大きく貢献した。(仮に僕が開発サイドオンリーで戦える強者であるならばとにかく開発集中でよいと思うが、残念ながら自分の技術レベルはそのレベルには達していない)
  • このような機会が無い場合はリクエストしよう

進捗を共有して必要であればスケジュールを変更する

達成できない目標は精神衛生上悪影響があり過ぎるし、達成できたと思っているのが自分だけだというのもまた悲しい

フィードバックをもらって現状を正しく認識する
  • 自己認識でうまくいっていると思っていても、他者からはそう見えていないこともあるし
  • チームに貢献するという文脈において、このギャップは解消すべき
  • 逆に、自分ではダメだと思っていても、他者からはよく見えているところもある
  • 本来なら無用な悩みになるので、このギャップも解消すべき
  • 「●●の目標に対して、自分としてはこの点とこの点はできていると思っている。ここは足りないのでこうしようと思っている。認識はあっていますか?」という質問ができると良い
ギャップがあれば解消する方法を考える
  • 世の中うまくいかないこともある
  • どうすれば修正できるのか、どのようなサポートが必要なのかを改めて対策を考えたり、助言を求めたりする
  • 目標やスケジュールの達成見込みが絶望的ならば、期待値かスケジュールを調整する

LAPRASでは主に毎週(慣れてきたら隔週)の1on1と、月一の振り返りで上記のような作業を行なった。同じく、このような機会がなければリクエストしよう。

(おまけ)結果

今は入社4ヶ月になるが、概ねギリギリ大丈夫と言えるレベルだと思っている。次のステップに向けて新しい課題に四苦八苦しながら対処しているところでもある。

受ける側の話とはずれるが、先輩社員に「焦って結果出そうとして苦しんで潰れていく人が多いから焦らないで一つずつやっていった方がよいですよ」と言葉をかけてもらったことをすごく覚えている。いくら自分がそのつもりで、目標を共有していたとしても、焦る気持ちはゼロにはならないので、言葉にしてもらえるて死ぬほど楽になったのを覚えている。

現状の仕組みを作り上げたことに対する敬意と感謝を忘れない

改めて言語化しようと思ったらTweetがあった。連投しているので気になる方にはみてもらえると嬉しい。大事なのは「味方感」。本当の意味で味方になることだ。

ちなみに受け入れ側として注意したいのは以下のような力学(そしてLAPRASのチームは信じられないくらいこの手のネガな部分が無くて好きだ)

  • 課題があるのは当たり前、むしろ課題が無いなら自分がいる意味がない。自分は被害者ではない
  • そもそも自分で今の状態を一から作れるかかんがえる(多分NOだから起業せずに入社したんだろう)
  • まずはそのすごさを認めて敬意を表し、チーム全体を自分にとって善の存在として認識する
  • その意識を忘れなければ細かい表現は気にしなくていいはず
  • 味方として一緒に解決に取り組めば良い

実務上のサポート

エンジニア向けと書いたので、実務についての工夫も書いておく。 先ほど書いた「必要なサポート」の具体的な内容はおもにこちらにリンクする。ガンガンリクエストしていこう。

システムを触る

  • 自分たちが何を作っているのか理解せずに物を作るのはまずい。機会がなければ確実にリクエストしよう。

システム全体像のインプット

  • 最初に全体像を説明をしないところはないと思うが、されなかったらリクエストしよう
  • 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日目の記事です

f:id:rocky_manobi:20191210010511p:plain

概要

  • LAPRASに入る前、業務の内容をDMでたくさん受け取って辛いという課題があった
  • DMを受け取るとパブリックチャネルに優しく誘導してくれるDM警察というBOTを作ったがLAPRASに入ったら需要がなかった
  • なんでや?じゃあLAPRAS関係ないやろ!?
  • まとめ

課題 - 業務の内容をDMでたくさん受け取るのは辛い

このような内容をDMで受け取ると少し困ります。

f:id:rocky_manobi:20191210010546p:plain

パブリックチャネルの発言であれば、僕がこの質問に答えられない場合でも「@知っていそうな人 さん、わかりますか?」とメンションするだけで事が足ります。たまたま会話を見ていて知っている人が能動的に答えてくれる事もあるでしょう。また、後々一連の会話をシェアしたくなったときでも、この会話へのリンクを貼れば経緯を伝えることができます。DMではいずれも叶いません。

本当に秘匿したい情報のやりとりを除いた殆ど全ての場合において、情報はパブリックでやりとりする方が良いはずです。良いはずですが、この手のやりとりをDMで行う人の割合は決して少なくありません。

以前の僕も、そういったDMを受け取るたびに上記の説明をしつつ、なるべくパブリックチャネルにポストしてもらうよう促していました。そして、これはおそらく日本のあちこちで行われている事でしょう。

DMを送る方には悪意がない

一方でDMを送る方としては、単純にパブリックチャネルに発言するメリットを理解していなかったり、使い方を知らなかったり、オープンな場所に投稿することをためらってしまうような空気があったりと、必ずしも悪意があるわけではありません。そこが悩ましいところです。

丁寧に啓蒙していくしかないのですが、やはり続くと辛い。そして言い続けているうちに、段々と「うるさいやつ認定」されていくのもまたつらい。

DM禁止は筋悪(だと思っている

一律禁止はしたくない、というスタンスです。有用なシチュエーションでは普通に使うべきだと思います。

なにより、大事なのはDMをしない事ではなく、パブリックに情報をやりとりすることです。その大切さを伝える方向で解決したいところです。

DM警察を作って公開しました

というわけで、諸問題を解決するBOTを作りました。ざっと特徴は以下の通りです。

  • DMを受け取ると、BOTがパブリックチャネルに誘導してくれる
    • コミュニケーションは職質風。パブリックチャネルに投稿する意義を啓蒙してくれる
    • ゴネれば10分黙ってくれる
  • 簡単に使える
    • Slackワークスペース公式サイト からインストールできる
    • Botにメンションする事で、DMの監視を依頼できる(監視してほしい人だけが使うことができる)
  • 権限が気になる人は、Herokuボタンで独自環境を構築することができる

DMを受け取ると、BOTがパブリックチャネルに誘導

DMを受け取ると、DM警察が現れてDM送信者に職質をかけてくれます。無視をしてもDMを受け取るたびに起動するので、あまりのウザさに流石に無視はされません。

f:id:rocky_manobi:20191210010511p:plain

拒否すると、パブリックチャネルに投稿する意義などを教えてくれます。「我々も仕事でやっているのですみません」という感じで、とてもウザい感じに仕上がっています。

f:id:rocky_manobi:20191210010836p:plain

ゴネ続けると、最後には諦めて許してくれます。10分離脱してくれるので、その間に会話を済ませることもできます。

f:id:rocky_manobi:20191210010417p:plain

Slackワークスペースに公式サイトからインストールできる

こちらが公式サイト です。動画やマニュアル、注意事項などが書いてあります。

「dm警察をslackにインストールする」ボタンを押すとSlack Appがインストールされます。インストール時にAppのWebhookを送るチャネルを聞いてくるので、あらかじめ「#DM警察」などを作っておくと良いでしょう。

また、誤ったワークスペースにインストールしないようにご注意ください。

Botにメンションする事で、DMの監視を依頼できる(監視してほしい人だけが使うことができる)

こんなふうに「パトロールよろしく」とメンションすると、パトロールしてくれます。

f:id:rocky_manobi:20191210010341p:plain

Herokuボタンで独自環境を構築することができる

DM警察のサーバはDMの内容を記録していませんが、DMを読めるという強めの権限を渡すこと自体がネックになるという方もいらっしゃると思い、ソースコードを公開してHerokuボタンも設置しました。コチラを参考に独自の環境を構築する事ができると思います。やってみてね。

あと、リポジトリにスターつけて頂けるとメンテ続ける意欲が強化されるので、気に入った方は是非ぜひ。

github.com

運用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警察は稼働していませんが、ご興味ありましたらこちらをご覧ください。

t.co

こういったものを作っています

lapras.com

こんな組織を目指しています

www.wantedly.com

LAPRAS株式会社に入社しました

私ごとですが、2019年8月にLAPRAS株式会社に入社しました。

転職エントリ的なものを書くつもりはありませんでしたが、転職以後も以前の立場としてのお仕事のお話を頂いたり、何よりしれっと立場だけが変わっていると「何があった!!!!?」となってしまうよなとも思い、ここでも告知をさせていただきます。事実中心エモ成分少なめで書いていきます。

(あと、一旦バチっと所属を明らかにしないと記事シェアとかしたくてもステマになってしまうという心のブレーキが辛かったというのもあります)

以前の所属/立場との変更点

自分を知っている方としては「入社した?ってことはNextremerは?」というのが当然の流れだと思いますので、まずは変更点から。主要なものは以下の通りで、いずれも2019.04 ~ 2019.09の間の変化です。(注:改めて字面だけを見ると衝撃的な内容ですが、Nextremerとしてのお仕事も引き続き継続しています)

  • 株式会社dataremer代表取締役を辞任しました (株式会社Nextremerの100%子会社)
  • フリーランスエンジニアとしてのお仕事を開始しました
  • 株式会社Nextremerを退職しました
  • 株式会社Nextremerとアドバイザリー契約を締結しました
  • LAPRAS株式会社に入社しました
  • (生活拠点を関東に移しました(埼玉です))

変更理由

事実成分重めと言っても、背景ゼロで辞任や退職をお伝えするのは少々重すぎるので、簡単に記述しますと...

会社全体のフェーズや方向性の変化に伴って、以前とは異なるスキルや性質が求められるようになったという背景があり、対処方法を検討した結果、Nextremer/dataremerの事業や組織運営についてはより今のフェーズにおいて必要な能力のある方に担っていただき、自身は強みを活かせる領域に集中することが、お互いにとってプラスになると判断した、というのが主な経緯です。

この路線をベースに、必要な関わり方や稼働の量、僕自身の嗜好性、制度や運用など諸々を相談した結果、正社員という立場とは異なる形で業務に携わることになりました。

もちろん、厳密にはもっと複雑で細かいお話や、葛藤、お気持ちなどたくさんありますが、文章として公開するつもりはありませんので、興味のある方はご飯でも食べに行きましょう。

現状のお仕事状況

ここからは、前述の変化の結果どうなったのか、いま何をやっているのかについて書いていきます。

LAPRAS株式会社

corp.lapras.com

改めて、2019年8月にLAPRAS株式会社に入社しました。稼働割合としてはもっとも大きなもの(80%以上)となるので、bioなどもこちらを主として更新しています。動機などについてはまたの機会に書きますが、意気込みとしては「喜んでもらえるサービスを作ることに貢献しつつ、みんなで勝ちたい!」という気持ちでいっぱいです。

VPoEという肩書きをつけていますが、2019.11.01時点ではスクラムチームの開発者の一員として、必死でコードを書きつつプロダクトの技術的/ビジネス的/文化的な歴史等諸々を追いかけています。プロセスの改善や組織を強くすることなどなどについては、色々と思案していたものをそろそろ動かしていくぞ、という段階です。成果が出せた暁には何かしら新しいセオリー的なものを生み出せるような予感がしているので、まずはそちらを乞うご期待といった具合です。

WebAppチームのエンジニアの募集もしていますので、興味ある方はぜひ!

株式会社Nextremer/dataremer

立場的には株式会社Nextremerの社員ではなくなりますが、前述の通り引き続きお仕事をさせていただいています。アドバイザリー契約という高尚な響きのする契約ではありますが、実際の業務としては引き合い案件の技術的な要素検討、プロトタイプの実開発、SaaSプロダクトの内部情報提供やドキュメント化など、稼働は減りこそするものの、実務レベルでは業務の形はさほど変わりません。高知AIラボについても、私自身は高知AIラボ代表という立場ではなくなりますが、引き続き運営を継続しています。

現状の体制等については会社Webサイトをご覧ください。

また、個人としても高知でのお仕事はさせていただいており、時々足を運んでいますので、高知関係で僕の貢献できそうな事であればお声かけいただければと思います。

株式会社エナーバンク

twitter.com

電力のリバースオークションサービス「エネオク」を中心に、電力領域で事業を展開しているスタートアップです。開発マネジメントや技術的意思決定、優先順位の決定、組織づくりなどのサポートを目的として、知見提供や壁打ちやハンズオンをしたり、たまに手を動かしてコードを書く...などなどもしています。

事業分野も非常にホットで、絶賛立ち上げフェーズで勢いもあります。興味のある方は連絡いただくか、CEOの@enetech5 の発信をチェックしたりすると良いと思います(直接絡んでもいいかも?)

フリーランス開発者

フリーランスエンジニアとして開発案件やアドバイザ業/コンサルティングなどを請け負っています。現在はNode.js(TypeScript)製のIot関連プロダクトの立ち上げ開発をメインに進めており、アーキテクチャ設計~機能実装を担当しています。

おわりに

業務は完全に途切れていないとはいえ、2015年5月の入社以来ずっと全力でやってきたNextremer/dataremerのお仕事がメインではなくなるというのは、やはり一区切りした感があります。 本当に多くの方に助けて頂いたおかげで、得難い経験をさせていただき、かつ一定の貢献をすることができたと考えています。関わってくださった皆様、本当にありがとうございました。これまでと少し形は変わりますが、恩が返せるよう頑張って参りますので、引き続きよろしくお願いします。

社内制度や規則を整備しなきゃ!というフェーズで考えていたこと - アニメ SYCHO-PASSと制度設計

組織の人数が20~30人あたりになったタイミングで、制度とかルールを整備しようと社内が動きだしていたときに「こういう考えで進められたらいいよね!」と社内に向けて書いたエントリをpublicにしました。エンジニアのリーダーという立場から、制度設計等をメインで進めるチームと、ステークホルダーであるメンバーに向けて書いたものです(改めて見直すと全然社内限定の話ではない)

最近、本業以外で色々なチームのお話を聞いたり壁打ち相手になったりしていることや、最近こういう記事がよく目につくので、自分も考えをアウトプットして置こうと思ったのが主な動機です。 2年前(2017年2月) に書いた記事のため、今よりも幼稚な思考ではあるものの、芯として大事にしたい部分は変わっていません。

これを書いた時期は研究開発受託が事業の主軸であったことと、その時の立場、精神年齢などに起因して、少々偏って要ることを念頭においていただけると助かります。 特に エンジニア => 社員orメンバー と読み替えていただいた方が良いと思います

【本編】社内制度や規則について思ったこと

2017.02.21

社内制度や規則を整備するにあたって、なんとなくこれ大事なんじゃないかな〜と思うことをつらつらと描いていきます。歴史についても少し。

人々はシステムの末端を通して システムを認識し、理解する。システムの信頼性とは、 いかに末端が厳格に機能しているかで判断される (アニメ PSYCO-PASSより)

いきなりアニメからの引用で痛々しい限りですが、核心をついていると思うのでここから始めます。

例えば僕たち一般人は、某携帯会社さんを「携帯端末」「代理店」「契約内容」などの末端のインターフェイスを通して認識し、その質をもって全体を評価します。その評価対象に、創業者の人柄や想いは通常含まれません。某飲食チェーン店なら、「店」「料理」、強いて言えば「店員」のみが認識/評価対象となるでしょう。

社員に対する会社の想いや価値観もこれと同様です。 働いている社員をユーザとするならば、会社の「想い」や「価値観」は何を通してユーザに認識、評価されるでしょうか。それは、トップの人柄でもメッセージでもなく、運用されている「しくみ」「制度」「規則」、もう少しぼかして言えば、「具体的な行動」です。

これまでのお話

Nextremerにジョインする半年前、つまり、高知AIラボが立ち上がる半年ほど前、向井(CEO)さんから「エンジニアが働きやすいためにはなにすればいいの?」と仕切りに聞かれました。ジョインしたあと、そのときに答えたこと+@ がなんらかの形で実行されていて、とても驚いたのを覚えています。

技術書買える。マシンは希望SPECのものを買える。技術的な実験に必要なものは購入できる。経営者がエンジニアを信じ、エンジニアが正しいと思うことをできるように動く。ノリのサイドプロジェクトやエンジニアの意思による開発を推奨する。 ぱっと見クールを重視する。技術的にイケてるを大事にする。利用ツールを先端(エンジニア)に合わせる。などなど、アドホックではあっても「エンジニアが働きやすい場所を作る」という想いにそった「具体的な行動」がそこにあり、「この会社は本当にエンジニアのことを考えている」と感じたのを覚えています。

これからのお話

2015年7月から、ご存知の通り急激に社員が増えて行くことになります。 人数が増えれば当然、創業者、経営陣と社員が触れ合う時間も減っていきます。社員の視点からは、これまで行われていたアドホックなアクションが見えにくくなり、代わりに制度や規則が視界に入るようになっていきます。

この段階に入ると、制度や規則は会社のメッセージの代弁者となり、そのフィルターをとおして社員は会社の想いや価値観を認識し、評価するようになります。だからこそ、制度や規則は、会社や創業者の想いや価値観、文化が正しく反映されている必要があります。

現在、これまでアドホックに行われていたアクションを具体的な制度や規則に落とし込んでいる段階ですが、 「イケてる会社で働いている」(ワードチョイス自体がオッさんかもしれませんが)という感覚を持ち続けられるかどうかは、この作業の質に強く依存します。技術でお客様に価値を提供し続けることができる組織であるために、検討できることは全て検討し、進められたらと思います。

大きくなると

Nextremerは次のステージに向かおうとしています(内部情報過ぎたので表現ぼかしてます)。必然的に外部による監査や助言、指摘、口出しなどを受けるシーンも増えるわけですが、やはり、スタートアップとしていかに効率よく働けるかを意識し、一般的な価値観とぶつかってでも譲れないところは譲らない姿勢を貫きたいと思います。少なくとも、現段階では「攻めすぎ」くらいにはいきたいですね。

「そもそもなんでリスクなのそれが」は常に問い続けたいものです。

制度にはストーリーを

新しい制度、新しい福利厚生を始める時、必ずストーリーや想いをお伝えしたいと思っています。どうして、何を狙ってこれをやるのか。その大義名分を説明することは、発表内容のポジティブ/ネガティブに関わらず常に重要です。このことは完全ガイドに描かれていても良いかもしれません。

  • ネガティブな場合は「協力/理解を得るため」
  • ポジティブな場合は「当たり前ではなく、努力の結果勝ち得た権利だということを共有し、忘れないため」

反対に、以下の二つは望ましくないアプローチといえます。

  • 本当の理由を隠し、破綻した論理であたかも「正しいこと」かのように取り繕う
  • 演出の意図が透けて見える演出をする

Nextremerの社員はとても優秀です。エンジニアとして自信を失うくらい優秀な人がたくさんいます。だからこそ、小手先の言葉では騙されません。正直に情報を開示し、協力を求めるべきところは求める姿勢が大切だと考えています。

自分の環境は自分で作る。空気は読まずに一緒に作る。

制度や規則についての提案や制定は、限られた人の権利ではありません。働く人全員がユーザであり、ステークホルダーです。 思うことはどんどん声をあげ、会社を働きやすい環境にHackしていくのが健全だと思います。

幸いなことに現時点ではそれが許される、むしろ推奨される環境です(続いていくことを望みます)。無関心に流れに身を任せて他にうつるよりも、流れを変えることが比較的容易な社内を変えてみるのも良いのではないでしょうか。

それでもコンサバに倒すこともある

「いつも攻めまくって限界を探してるウチがコンサバな制度をつくったのだから、きっとよっぽどの理由があるに違いない。」

こんな風に思える状態ができると良いと思います。 様々な理由で、攻めの制度を作れない場合はいくらでもあります。そんなとき、普段から信頼を勝ち得ている組織であれば、正確な説明さえすれば納得してもらえる可能性が高いです。  逆に普段から攻める姿勢を持てていなければ「本当にこの可能性は検討したの?」「そもそも大丈夫なの?」という状態になり、進行が難しくなってしまいます。この点、とても大事です。

制度設計の想いを外部に発信したり、LT大会などで発表してみるのも良いでしょう。

こういう話ではない

言葉に意味がないということを言っているわけではありません。 理念やビジョンはとても大事で、言語化できていないのは課題です。ただし、言葉だけでは不十分です。 経営を立て直そうと外部コンサルに頼んで理念とビジョンを、会社全体、各部署にも設置して、、、というテコ入れの現場をなんども見てきましたが、実際の行動の伴わない動きには誰もついてきません。

おわりに

理想論ばかりで現実を見ていないと批判をうけそうですが、最初から理想を諦めるのと、追求した先に落とし所を見つけるのは違います。ただ落としやすいところに落とすのはもったいないと思うので、エンジニア個人としての見解をつらつら書いて見ました。

本編終わり

改めて今読むと..

  • こういうものは「合議」で決めるのが正しいとは限らない、ということに言及すべきだった気がする
  • 「エンジニアが働きやすい」というよりも「メンバーが働きやすい」だ
  • 「働きやすい」というより「パフォーマンス高く...」の文脈の方が労働者発信だとよかったかも
  • 言葉も大事だよ(言葉ばっかりで話が終わりそうな空気があったので言葉だけじゃダメだよって強めに描き過ぎてたのかな)

割と今も同じ様な考えに近いですが、追記したいことも山ほどあるので後日2019年verとしてリライトすることにします! あと、PSYCHO-PASSというアニメはめちゃくちゃに金言が詰め込まれているので10週以上見てもまだ新鮮に見られる素晴らしいアニメですので、dアニメとかで見てみると良いと思います。