プロジェクトの振り返りに LeanCoffee を試してみた

back check 事業部開発チームの匠平@show60です。

プロジェクトの振り返りに LeanCoffee を用いてみたので、今回はこちらを振り返ってみたいと思います。

振り返りを行った経緯

back check チームではスプリントの終わりに KPT を用いた振り返りを行っていますが、今回これとは別にプロジェクト単位での振り返りを行いました。

チームではこの1ヶ月ほど新機能の開発を進めていたのですが、進行の障害となる事象が起こり始め、うまくいっていないと感じるようになりました。 何が原因で "うまくいってない" のかを洗い出すため、各自が課題と感じていることを収集し議論する場を設けることにしました。

LeanCoffee の実施方法

LeanCoffee とは

何が課題であるかを探るところから始めるため、振り返り手法には LeanCoffee を用いることにしました。

アジェンダのないミーティング方法です

参加者が集まり、アジェンダを作り、議論を始めます

Lean coffee

メリットとしては以下のことが挙げられています。

  • 広い範囲のトピックを扱うことができる
  • 事前・事後の準備が (ほぼ) 不要
  • ネクストアクションを出すこともできる

LeanCoffee の進め方

準備と進行方法は、こちらのスライドを参考に実施しました。

www.slideshare.net

振り返り会場は miro で作成し、事前に以下のような枠組みだけ用意しました。

f:id:show-hei:20210706205703j:plain

話したいトピックを黄緑色の付箋に書いて左の Ready の枠に入れていきます。出揃った付箋のなかから話す順番を決めるため、緑の●を各自2票ずつ投票します。

話すトピックをピックアップして Doing に移します。トピックについて話す時間を7分間とし議論します。

7分経ったら、このトピックの議論を続けるかを投票します。手のジェスチャーで意思表示しますが、今回 miro で行ったためカーソルアイコンを手の絵文字に乗せることで投票としました。

「議論を継続」に4票以上投票されていたら議論時間を4分追加します。これを繰り返し、4票未満になるまで議論を行っていきます。

やってみた結果どうだったか

良かったこと

ミーティング時間が延々と長引くことがない

議論が白熱したり、煮詰まるとついつい長引いてしまうことがあります。LeanCoffee では投票時間を設けてあるため、ミーティング時間を区切る機会が多く、コントロールがしやすいと思います。

広いトピックを扱うことができる

投票によって議論するトピックが決まります。選出理由に制限はなく、多くのメンバーの興味を引いたトピックが選出されます。個人で抱えている課題も場に出すことができるため、相互理解にも効果があると感じました。

良くなかったこと

トピックが大きい場合、別途切り出すなど工夫したほうがいい

LeanCoffee に議論の制限時間を設けているのは、トピックを速く処理するための仕掛けだと思っています。その特性上、取り扱う課題が大きいトピックや、前後関係の深いトピックの場合は扱いが難しいと感じました。

これらのネクストアクションまで話し合うには、ファシリテーションを含め慣れや訓練が必要そうです。

「◯回の延長が行われた場合、別のミーティングを設ける」など独自ルールを決めてもいいかもしれません。

他の手法を上回るメリットが出てこなかった

課題の収集という点ではタイムライン振り返りのほうが抜け落ちが少ないため、LeanCoffee を課題収集・理解の目的と割り切るには難しいように思います。

また課題を選出して議論するという手法は KPT も同じですが、ネクストアクションを必ず出すという点においては KPT のほうが強力です。

KPT での振り返りに慣れているチームとしては、上記で挙げている「良かったこと」を KPT で享受できており、今回試してみた限りでは大きく勝るメリットを感じられませんでした。

準備の手軽さという点は大きなメリットなので、これから振り返りを導入しようというチームにとっては選択肢としてありなのではと思います。

同じように、普段とは違う部署やメンバーとミーティングするときなんかも上手く機能するのではないかと感じました。

おまけ: 振り返り方法の選定

振り返り方法として LeanCoffee 以外にも KPT とタイムライン振り返りも検討しましたので、不採用理由もそれぞれ記述します。

結果として、今回の目的であればタイムライン振り返りを採用してもよかったなと思っていますので、ぜひ次回チャレンジしてみます。

KPT

スプリントの終わりの振り返りには KPT を用いています。 KPT はチームの課題 (Problem) に対して改善のためのネクストアクション (Try) を決めるという強いメリットを持つフレームワークです。

議論したいトピックを投票で選出することで短時間でネクストアクションを考えられる反面、選出されなかったトピックは議論されないまま、また日の目を見ることを期待してそっと過去に消えていきます。

今回の目的は各自が課題に思っていることを洗い出し、できる限り多くを議論の場に持っていくことが目的であったため KPT の採用は見送りました。

タイムライン振り返り

プロジェクトの始まりから終わりまでを時系列に沿って書き出す、データの収集を目的としたフレームワークです。 今回 LeanCoffee を試してみたかったこともあり採用を見送ったのですが、振り返り材料の収集という点では効果のある手法です。

タイムラインで収集した振り返り材料を KPT に持っていく、という組み合わせでぜひチャレンジしてみたいです。

エンジニアのインプットと読書術

この記事は 個人Qiita と同じ内容です

qiita.com/sekiyaeiji

まえおき

フルリモート時代においてエンジニアの情報インプットのスタイルも様変わりしているように感じます。

2019年までのリアル社会では、競争率の高い勉強会とか海外カンファレンスの招待枠が取れる/取れないのような価値観が、たしかに存在していた気がしますが、物理の制約を解かれたセミナーイベントにはもはや人数制限はなく国境も超え無償になり、エンジニアの情報インプットにおいては劇的に恵まれた時代に突入したと言えます。

また、オンラインが主流になることでイベント内の登壇発表部分には収録動画の配信を活用するケースも増えてきています。

私が新しい技術情報をインプットするメディアの割合は、

  • オンライン文字 > オンライン動画 > 書籍

ではありますが、まさにいまこうして文章を書くための情報インプットなどでは、よくまとまってて精査されている書籍の文章も非常に頼りになります。

オンラインイベントが自由を手に入れ、生まれ変わりつつある一方で、日本の活字媒体の事情も変化しつつあるようです。

活字メディアの動向

新型コロナ影響下の書籍の動向

2020年、日本のコミックの売上が、過去最高記録を打ち出したのをご存知でしょうか。

25年ぶりのコミック売上記録更新という奇跡的な回復には、新型コロナの巣ごもり需要が大きく影響しているそうです。

同様に、新書、ビジネス書、専門書(技術書はこれに含まれる)の売上も前年比でプラスに転じていることから、巣ごもりでできた時間を自己研鑽に投じる意識の現れかもしれません。

自費メディアの浸透

活字メディアの動向で気になっているもう1つのトピックは、技術書典技書博のような自費出版カルチャーの浸透です。

技術書典は、3年前にはすでに盛り上がっていたように感じますが、新型コロナ影響によりリアルイベント中止の代替手段としてオンライン販売を全面展開してから、結果的により身近になり、より多くの読者が参加・購入できるようになりました。

盛り上がりに比例して出品ラインナップの玉石混交感も増しており w 書籍の選択には注意が必要ですが、良書を見つけると、ネット記事と本格的な技術書の中間ぐらいの、知りたいことをピンポイントで知るのにちょうどいいボリュームの良書に出会うことができます。

活字メディアによるインプット

さて、そんな私の活字メディアによるインプット方法ですが、まず、できれば電子書籍よりもリアル本を利用します。

学びを結果に変えるアウトプット大全/樺沢紫苑』や『大人のための読書の全技術/齋藤 孝』で共通する内容で、書籍に線を書き込むという手の運動が脳を刺激し、読書時の記憶を促進するという趣旨の記載があります。

書き込んだり折り目をつけたりメモしたりしながら、本を自分色に染めながら読む習慣のために、紙の本がある場合はなるべく入手します。

また、とくに印象に残したい書籍についてはなるべく読書記録を残すようにしています。

これも『学びを結果に変えるアウトプット大全/樺沢紫苑』にある、自己成長の量はインプットの量ではなくアウトプットの量で決まるという提言に従い励行しています。

インプット速度を上げる

さて、そんな私が昨今課題に感じていたのが、インプット速度の向上です。

限られた時間により多くの情報を扱いたくなったときに、ヒントになる内容が『大人のための読書の全技術/齋藤 孝』の中で触れられていたので、具体的にいくつかの方法を引用してご紹介します。

ちなみに、本書における"速読"とは、◯◯協会が提唱する速読トレーニングみたいな内容ではなく、社会人が書籍の内容を、限られた時間内で素早く把握するための具体的な方法について説明されています。

読書速度を上げるテクニック

大人のための読書の全技術/齋藤 孝』の「第2章 : 読書の量を増やす − 速読の全技術」の中で紹介されている、読書の速度と量を上げるテクニックが実施しやすく役に立ったので、以下の通り7つ、引用してみます。

1.目的と締切を同時に設定する

  • 目次を読む
    • 概要を知り、大事な箇所をはっきりさせる
    • どこをしっかり読めばいいかをあらかじめ把握する
  • 本をさばく
    • 1冊につき20分ぐらいかけて本の内容を人に話せるぐらいまで把握する作業
      • 次に開いたときにすぐ読める
    • 買ったその日に、その本に一目惚れしたテンションのまま一気に中身を把握しておく
    • タイトル、帯、カバー袖、目次、小見出しで全体の趣旨をつかむ
    • 練習すれば新書1冊5分も可能

2.逆算読書法

  • 結論から読み始める
  • 内容を要約できればいい
    • 大事なところから読めばいい

3.二割読書法

  • 全体の二割を読んで全体をつかむ
  • 覚える対象になりそうな箇所だけ精読する
  • 目次から全体把握が大切

4.書店で鍛える

  • 購入に値するか、真剣に見極めるトレーニン

5.サーチライト方式

  • 読む前に大事なキーワード5〜6個を決めておく
  • そのキーワードを見つけたら次々とボールペンで丸をつける
  • 大事だと思う箇所のページの上端を折る
  • まあまあ大事な箇所はページの下端を折る

6.同時並行読書術

  • 複数冊を並行して読めると読書量が増やせる
  • 1冊ごとの読破に重きを置かない
  • そのときの気分によって手にとった本の続きから読む

7.TPOに応じて読む

  • TPOに応じて読み分ける
  • 読みかけの本を自宅のあちこちに散らばらせておく
  • 暇を見ては本を読む習慣が身につく

実践する際の注意点

以上は、各テクニックのポイントを簡潔に箇条書きで引用しましたので、より詳細を知りたい方はぜひ書籍を購入してください。

これらの読み方のテクニックはもちろん、ビジネス書や専門書の読み方として紹介しておりまして、小説の読み方として適さないことは言うまでもありません。

私は上記をビジネス書や技術書の内容を把握する場合に利用していますが、たしかに始まりから終わりまで精読するよりは遥かに時短になり、一度トライしたら辞められない読書法になってしまいました。

最初は筆者に対する罪悪感のようなものを感じなくもなかったのですが、実用書は活用することが目的の書物と割り切って一度試してみると、筆者の言いたいことをよりよく理解しようと努力している、むしろ良いことをしてるかも、という感覚にもなれるかもしれません。

まとめ

紙の本を使い倒すことによる効率的な情報インプットについて、情報を集めてご紹介してみました。

技術書もそうですが、変化に対応し、さらなる変化を求める市場において、ビジネスやテクノロジーに関するさまざまなアイデアが雨後の筍のようにアウトプットされる現在、情報を効率よく取り込むテクニックは、備えていて損はなさそうです。

積ん読(つんどく)本とのつき合い方が変わるかもしれません。

輪読会の読み順をランダムで決める chrome 拡張機能を作る for Google Meet [React + TypeScript]

この記事は個人ブログと同じ内容です

輪読会の読み順をランダムで決める chrome 拡張機能を作る for Google Meet [React + TypeScript]

最近弊社の開発メンバーでブログを書こうという運動があります。 ネタ探しをしていたらよさげな記事を見つけたのでアイデアをお借りします。 shohei さんありがとう🙏 ※決してパ○リではありません https://techblog.roxx.co.jp/entry/2021/04/23/064837

毎週チーム内での読書会や、部署をまたいだエンジニアでの輪読会を行っているのですが、都度読み順を決めるのが面倒なので、参加メンバーをシャッフルしてリストで返す chrome 拡張機能を作ってみました。 なお、弊社の読書会は Google Meet で行うことが多いため必然的に Google Meet 用の拡張機能となっておりますのでご理解ください。

作ったもの

現在 Meet に参加しているメンバーをランダムに並び替えて一覧表示する拡張機能を作りました ※store で公開はしていない

image

こちらで公開しているのでぜひ覗いてみてください github.com

技術選定

UI の構築を含むので書き慣れたフレームワークを使いたかったので React + TypeScript を採用しました。

chrome extension をつくるにあたって、以下のテンプレートを使えば React + TypeScript が入った状態でサクッと始められそうだったので使わせていただきました。 https://github.com/chibat/chrome-extension-typescript-starter

パッケージ類

Chrome 拡張機能の型定義を読み込むために @types/chrome を使いました。

image

popup.tsx と content_script.tsx

  • popup.tsx
  • content_script.tsx
    • 開いているタブで実行できる script

上記の 2 箇所でデータをやりとりするためには以下の chromeAPI を活用します

chrome.tabs.sendMessage() chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {} )

https://developer.chrome.com/docs/extensions/reference/tabs/ https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage

popup 側の実装

ユーザー一覧 drawer の表示

ユーザー一覧 drawer が表示されないと参加メンバー名を持った DOM が生成されないため、 初回レンダリング時に sendMessage でcontent_script 側でイベントを発火させます

参加メンバー一覧の取得処理

一覧取得ボタンをoukajini getMemberList() が走り、 sendMessage を送信することで content_script 側で script を実行し、参加メンバー一覧を文字列で取得します それをよしなに加工して view 側で表示させてあげることで参加メンバー一覧を表示しています

  const currentChromeTab = (callback: (tabId: number) => void) => {
    chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
      // 現在表示しているタブを取得
      const tab = tabs[0];
      if (tab.id) {
        callback(tab.id);
      }
    });
  };

  const getMemberList = () => {
    currentChromeTab((tabId) => {
      chrome.tabs.sendMessage(
        tabId,
        undefined, // message は不要なため undefined とする
        (msg) => {
          if (typeof msg === "string") {
            const shuffledMembers = shuffle(msg.split(","));
            setCurrentTime(new Date());
            setMembers(shuffledMembers);
          }
        }
      );
    });
  };

  // init
  useEffect(() => {
    currentChromeTab((tabId) => {
      // ユーザー一覧 drawer を表示させる
      chrome.tabs.sendMessage(tabId, undefined);
    });
  }, []);

content_script 側の実装

ユーザー一覧 drawer の表示判定

初回表示時にユーザー一覧 drawer の表示判定を行い、未表示だった場合は自動で開くようにしました

参加メンバーの名前一覧を取得

cylMye というクラス名を持った DOM の子孫に参加メンバー名が格納されていたので取得します

const openAllUserDrawer = () => {
  /*
   * 各ボタンの aria-label 属性にラベルに表示するボタン名が格納されている
   * 全てのボタンを DOM から取得して全員を表示ボタンを探す
   */
  const ariaLabelElems = document.querySelectorAll("[aria-label]");
  for (let i = 0; i < ariaLabelElems.length; i++) {
    if (
      ariaLabelElems[i].getAttribute("aria-label") === ALL_USER_BUTTON_LABEL
    ) {
      const chatOpenButton = ariaLabelElems[i] as HTMLButtonElement;
      chatOpenButton.click();
    }
  }
};

const getUserNameList = (sendResponse: (response?: any) => void) => {
  let names: string[] = [];
  const elems = document.querySelectorAll(".cylMye");
  if (!elems.length) {
    // .cylMye が存在しない = ユーザー一覧 drawer が表示されていない
    openAllUserDrawer();
    return;
  }
  for (let i = 0; i < elems.length; i++) {
    names = [...names, findUserNameFromElm(elems[i])];
  }
  // 重複した名前を省く
  names = Array.from(new Set(names));
  sendResponse(names.join(","));
};

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  getUserNameList(sendResponse);
});

メンバーをシャッフルする関数

今回はランダムの偏りをなくすためフィッシャーイェーツのシャッフル という方法をアルゴリズムに採用しました

const shuffle = (value: string[]) => {
  for (let i = value.length - 1; i >= 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [value[i], value[j]] = [value[j], value[i]];
  }
  return value;
};

export default shuffle;

.zip ファイルを生成する

Github Actions で master ブランチへ push 時に build を実行 + 実行結果を .zip に圧縮しリリースに含めるように build.yml に記載

      - name: yarn install & build
        run: |
          yarn
          yarn build --if-present
      - name: zip output
        run: |
          cd dist
          zip release *.*
      ・
      ・
      ・
      - name: upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./dist/release.zip
          asset_name: chrome-extention.zip
          asset_content_type: application/zip

まとめ

参加してるメンバーを集計する目的でも利用できるのでわりと便利かと思うのでぜひ使ってみてください。 今後、公開まで試してみたい。

GASでテスト書くときのちょうどいい塩梅を探る

この記事は個人ブログの転載です

kotamat.com

GASは気軽にコードが書けるというのもあり、テストを書かないケースが多いのかなとは思っているのですが、とはいえある程度の規模になったらテストも書きたくなってくると思うので、どのへんをライン引きとしてテストを書くか、またそのテストの環境はどう設計するかを考えてみたいと思います。

Level 0: Webコンソールでいじる場合

Webコンソール上で直接触るケースはおそらく非エンジニアも触る環境ないしは、すぐに捨てるコードであることが多いかなと思います。 そういったケースの場合、テスト環境どころか開発環境に対してのカスタマイズする余地が殆どないか、その環境を作る過程で実装が終わってしまうレベルの案件となってしまい、費用対効果が出せない状態になるかと思います。

こういったときは潔くテストは書かず、ただつらつらとコードを書き、サクッとデプロイ・実行するというのが良いかなと思います。

Level 1: 保守が見込める場合

今後ある程度の期間使われ、かつ保守メンテが必要になるであろうスクリプトの場合、コード自体はバージョン管理ツールに載せた上で機能開発をしていく必要があるかと思います。

その場合は clasp を用い、ローカルでの開発をしていくことになるのですが、後述のLevel2に満たないレベルだとしても TypeScript 化して最低限の型担保はしておかないと逆に開発生産性が下がってしまうため、TS化をこの範囲での保守性担保のラインとします。

導入方法

導入方法は簡単で、

yarn add -D clasp @types/google-apps-script

を実行した上で、tsconfig.jsonに下記を記載します。

{
  "compilerOptions": {
    "lib": ["esnext"],
    "experimentalDecorators": true
  }
}

その後、下記コマンドを実行し、スクリプトを作成します

npx clasp create --type standalone --rootDir src

あとはsrcディレクトリにて .ts ファイルを作成し、コードを書いていくだけです。

リリースはclasp pushコマンドで反映でき、clasp openで当該GASをWebコンソールで確認することができます。

メリット

今回依存に追加している@types/google-apps-script はGASでよく使うAPIのインターフェースが予め実装されているため、

  • あれ、Spreadsheetから取得したsheetってどういうAPI持ってるんだっけ?
  • getRangeの引数ってrowが先だったっけ?columnが先だったっけ?

という細かな仕様確認を、型情報をみるだけでわかるようになるため、不必要なtypoや、間違ったコードを書きづらくなります。

当然この状態ではテストコードレベルの品質は担保できないため、API上は問題ないけど意図しない挙動が発生するリスクはあります。 そのため、そのあたりの担保もしたい場合はLevel2を検討する必要が出てきます

Level 2: コードの実行結果が実行してみないとわからない場合

スプレッドシートのデータを高度なロジックで置換する場合や、複数のリソースに対して参照し計算結果を算出する場合、時間発火のロジックを書く場合は、コードの実行結果が実行してみないとわからない状態になるかと思います。

ここまで複雑になってくると、テストコードなしでの開発そのものが生産性悪くなるため、外部APIとの通信をしているところと処理ロジック部分を分離した上で、処理ロジック部分のユニットテストを書く必要が出てきます。

考慮点

GASの場合下記を考慮する必要が出てきます。

  1. GAS本体にテスト実行基盤があるわけではないため、別途テストツールを導入する必要がある
  2. テストコードはGAS上では不要なファイルになるため、rootDir外に必要がある
  3. ファイルを切り出してテスタビリティを向上する場合、ファイルの命名規則が大事になる。

1.に関しては今回はjestを使ってみます。こちらもTSベースでコードを書くため、下記の依存をインストールします。

yarn add -D jest "@types/jest" ts-jest 

2.に関しては、Level1でも紹介したようにデプロイ対象のファイルを src/ディレクトリに入れ、テストコードを同階層の__tests__ディレクトリに設置することで解消します。

3.が躓いたポイントなのですが、GASそのものはexport/importの機能を有していないため、GASでTSを書くとexport/importの記述が消された上で反映されます。 GASはファイル名が若い順にコードを読み込むため、エントリーポイントとなるファイルよりもファイル名が後ろのファイルは読み込まれず、実行時エラーが発生します。 現在はエントリーポイントをindex.ts、依存系ファイルを_utils.tsのように_をつけて先頭で読み込まれるようにしています。ここイケてない感じがすごいので、いい方法あれば教えてほしいです。

実際の書き方

テストを書くとはいえ、このレベル感であればソースコードとしてはそれほど肥大化しないことが想定されます。 雑にUtilsというクラスを用意し、そこに諸々のロジックを詰め込んでいくことを考えてみます。

UtilsはGASがない環境でユニットテストを実行する必要があるため、GASのAPIには依存しないインターフェースにする必要があることだけ考慮し設計していきます。

export class Utils {
  constructor(private now: Date = new Date) {}

  public isDateInMinute(date: Date, minute: number = 5): boolean {
      const minutesAfter: Date = new Date(this.now.getTime())
      minutesAfter.setTime(minutesAfter.getTime() + (1000 * 60 * minute))
      return this.now.getTime() < date.getTime() && date.getTime() <= minutesAfter.getTime()
  }
}

コンストラクタに何を入れるかと言うのは議論の余地はありそうですが、GASで頻出する表現は時間発火の概念なので、第一引数に現在を示す変数を入れていきます。

ここで雑にisDateInMinuteという関数を考えてみます。これは引数に指定した日時が現在時刻と比較して所定の分数の間にあるかどうかを判定する関数です。GASは毎分起動するということを設定できるため、スプレッドシートに記載した時間に発火するとかができるようになります。

これをベースにテストを書いてみます。

import { Utils } from "../src/_utils";

const data_isDateInMinute = [
    {
        now: "2021-06-07 10:00:00",
        target: "2021-06-07 10:01:00",
        inMinutes: 5,
        result: true
    },
    {
        now: "2021-06-07 10:00:00",
        target: "2021-06-07 10:06:00",
        inMinutes: 5,
        result: false
    },
    {
        now: "2021-06-07 10:00:00",
        target: "2021-06-07 10:05:00",
        inMinutes: 5,
        result: true
    },
    {
        now: "2021-06-07 10:00:00",
        target: "2021-06-07 10:00:00",
        inMinutes: 5,
        result: false
    },
]
describe.each(data_isDateInMinute)('Utils.isDateInMinute', (data) => {
    it(`${data.now} to ${data.target} in ${data.inMinutes} minutes?`, () => {
        const util = new Utils(new Date(data.now))
        const targetDate = new Date(data.target)
        expect(
            util.isDateInMinute(targetDate, data.inMinutes)
        ).toBe(data.result)
    })
})

このテストコードでは境界値を確認するテストを入れてみました。jestのdescribe.eachを使うことで、複数のシナリオを同時に実行できます。 大事なのはit()のところで new Utils(new Date(data.now))としているところかなと思います。時間に依存している処理を、コンストラクタでDIすることにより、実行時間に依存しないテストにすることができています。

僕はめんどくさがりなので(?)テーブルドリブンなテストケースの作成にjsonを書くとフラストレーションがたまります。 その場合は下記のようにcsvにテストケースを書き、csvを読み込んでシナリオを構築するのでもいいでしょう。いくらここで依存ファイルをimportしたところでGASのアウトプットには一切入ってこないので。

import { Utils } from "../src/_utils";
import csv from "csv-parse/lib/sync";
import fs from "fs";
import path from "path";

// CSVから同期的に読み込む
const data_isDateInMinute = csv(fs.readFileSync( path.join(__dirname, './data/isDateInMinute.csv')))
describe.each(data_isDateInMinute)('Utils.isDateInMinute', (data) => {
    it(`${data.now} to ${data.target} in ${data.inMinutes} minutes?`, () => {
        const util = new Utils(new Date(data.now))
        const targetDate = new Date(data.target)
        expect(
            util.isDateInMinute(targetDate, data.inMinutes)
        ).toBe(data.result)
    })
})

メリット

当然ここまで書けば、テストしたい粒度のものをUtilにぶちこんであげるだけで簡単にテストをかけるようになってきます。殆どのケースであればここまでやっておけばいいでしょう。

Level 3: Webアプリケーションを作る

もはやここまで来ると、GASが動く環境をサーバーとした、Webアプリケーションを作るレベルになるかと思います。 この領域になるとWebアプリケーションとしての設計やテストを求められるようになるため、webpackでまとめたり、テストの分割の仕方も一筋縄ではいかなくなるかと思います。 infrastructure層をGASの外部APIとしたような設計を行い、ユニットテストだけではなくフィーチャーテストも書くようになるかもしれません。

正直ここの領域までGASでやろうと思ったことはないので未知数ですが、やる機会があったらチャレンジしてみようと思います。

まとめ

GASの保守性担保の方針を考えてみました。もしもっとこういうのやってみるといいかもとかあればTwitterまでいただけるとうれしいです

AWS App Runner でアプリケーションをデプロイする

この記事は個人ブログと同じ内容です

www.ritolab.com


2021 年 5 月下旬 に AWS から App Runner というサービスがローンチされました。

今回は App Runner を使ってアプリケーションをデプロイしてみます。

AWS App Runner

AWS App Runner は、インフラストラクチャを管理せずに AWS にアプリケーションをデプロイするサービスです。

ソースコードまたはコンテナイメージを指定するだけで、App Runner がアプリケーションを自動的にビルドおよびデプロイし、ネットワークトラフィックの負荷を分散し、自動的にスケールアップまたはスケールダウンし、アプリケーションの状態を監視し、暗号化を提供します。

aws.amazon.com

  • ソース(Github ソースコードまたはコンテナイメージ)が update されると自動でデプロイしてくれる(自動・手動は選択可)
  • 自動でスケーリングが行われ、設定したしきい値に従って自動でスケールアップ・ダウンされる
  • 公開されたアプリケーションはロードバランシングされており自動でトラフィックを分散してくれる
  • 証明書(TLS)も管理されデフォルトで付与される URL は HTTPS でアクセス可能。更新も自動で行われる

aws.amazon.com

docs.aws.amazon.com

App Runner では 1 つのデプロイを「サービス」と呼び、サービスを作成するだけでアプリケーションを公開できます。

アプリケーションについて

App Runner では、アプリケーションソースを「Githubソースコード」または「コンテナイメージ」かのどちらかを選択する事ができます。

今回は ECR にコンテナイメージを設置して、それをデプロイしていこうと思います。

アプリケーションについては、以下 App Runner のワークショップで使われていた node のソースとコンテナを使用します。

www.apprunnerworkshop.com

App Runner を構築する

ここから実際に App Runner を使ってデプロイができるまで進めていきます。

いつもであれば構成は terraform で管理するのですが、App Runner は「サービス作成=デプロイ」となるため、App Runner の構成を terraform で管理する必要性がありませんでした。(terraform で予めサービスを作成しておいてそれを何かで起動したり... といった概念ではない)

なので、最低限だけ terraform で作成して、ECR への image の push と、App Runner のサービス作成は Github Actions で行っていきます。

先述のアプリケーションと併せて、最終的には以下のファイル構成になります。

root/
├── .github
│   └── workflows
│       └── deploy.yml
├── Dockerfile
├── index.js
├── package.json
├── source-configuration.json.template
└── terraform
    ├── main.tf
    └── terraform.tfvars

ECR Repository 作成

コンテナイメージを設置する ECR Repository を作成します。

main.tf

# variables
variable "aws_id" {}
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "aws_region" {}

provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = var.aws_region
}

# ECR Repository
resource "aws_ecr_repository" "app" {
  name                 = "sample_node_for_app_runner"
  image_tag_mutability = "MUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }
}

ECR リポジトリを作成しているだけです。当然空っぽなので、コンテナイメージについては後で Github Actions で push していきます。

ポイントとしては自動デプロイを有効にしたい場合、イメージのタグは固定する必要があるため、image_tag_mutability は MUTABLE である必要がありました。(サービス作成時にイメージのタグも指定するため)

App Runner の IAM Role 作成

App Runner に付与する IAM Role を作成します。

App Runner が ECR にアクセスできるようにするロールです。

main.tf

# IAM Role for AppRunner
## AWS管理ポリシー
data "aws_iam_policy" "AWSAppRunnerServicePolicyForECRAccess" {
  arn = "arn:aws:iam::aws:policy/service-role/AWSAppRunnerServicePolicyForECRAccess"
}

## IAM Role - for AppRunner
resource "aws_iam_role" "for_app_runner" {
  name        = "tf-AppRunnerECRAccessRole"
  description = "This role gives App Runner permission to access ECR"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Service = [
            "build.apprunner.amazonaws.com"
          ]
        }
      },
    ]
  })
}

## Attach Policy to Role
resource "aws_iam_role_policy_attachment" "app_runner" {
  role       = aws_iam_role.for_app_runner.name
  policy_arn = data.aws_iam_policy.AWSAppRunnerServicePolicyForECRAccess.arn
}

ポリシー自体は AWS で管理しているものを使用しています。

デプロイ用 IAM User 作成

Github Actions から ECR への image の push と App Runner のサービス作成を行なうために、デプロイ用の IAM User を作成します。

main.tf

# deploy user
## IAM User
resource "aws_iam_user" "deploy_app_runner" {
  name = "deploy_app_runner"
}

## IAM Policy
resource "aws_iam_policy" "for_deploy_app_runner" {
  name        = "deploy-app-runner-policy"
  description = "ECR push and App Runner operations Policy."
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:InitiateLayerUpload",
          "ecr:UploadLayerPart",
          "ecr:CompleteLayerUpload",
          "ecr:PutImage",
          "apprunner:ListServices",
          "apprunner:CreateService",
          "iam:PassRole",
          "iam:CreateServiceLinkedRole",
        ]
        Resource = "*"
      }
    ]
  })
}

## attach Policy
resource "aws_iam_user_policy_attachment" "deploy_app_runner" {
  user       = aws_iam_user.deploy_app_runner.name
  policy_arn = aws_iam_policy.for_deploy_app_runner.arn
}

ポリシーは「ECR への push」と「App Runner のサービス作成」のためのミニマムの権限を付与しています。

terraform で IAM User を作成しましたが、アクセスキーは AWS コンソール画面から手動で生成します。

ECR への image 登録と App Runner サービス作成

ここからは Github Actions で ECR への image 登録と App Runner サービス作成を行っていきます。

deploy.yml

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Amazon ECR "Login" Action for GitHub Actions
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPOSITORY }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:latest .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest

      # App Runner サービス作成
      - name: Create App Runner Service if no exist.
        env:
          AWS_ID: ${{ secrets.AWS_ID }}
          AWS_REGION: ${{ secrets.AWS_REGION }}
          SERVICE_NAME: sample_node
        run: |
          SERVICE=`aws apprunner list-services --query "length(ServiceSummaryList[?ServiceName=='$SERVICE_NAME'])"`
          if [ $SERVICE -eq 0 ]; then
              sed -e "s/<AWS_ID>/${AWS_ID}/" -e "s/<AWS_REGION>/${AWS_REGION}/" ./source-configuration.json.template > ./source-configuration.json
              aws apprunner create-service --cli-input-json file://source-configuration.json
          fi

App Runner に関しては、既にサービスが作成されているかチェックし、存在しなければサービスを作成する。という処理にしています。

実際のところ、AWS CLI を使えばコマンド一発でサービスは作成できます。

docs.aws.amazon.com

今回でいうとここです

aws apprunner create-service --cli-input-json file://source-configuration.json

サービスを作成する際に、設定項目を渡す必要があるので、それは Github Actions の中で json ファイルを作成しています。

アプリケーションのソースに json ファイルのテンプレートを作成しておいて、 Github Actions で必要な設定値を入れているだけです。

source-configuration.json.template

{
    "ServiceName": "sample_node",
    "SourceConfiguration": {
        "ImageRepository": {
            "ImageIdentifier": "<AWS_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/sample_node_for_app_runner:latest",
            "ImageConfiguration": {
                "Port": "3000"
            },
            "ImageRepositoryType": "ECR"
        },
        "AutoDeploymentsEnabled": true,
        "AuthenticationConfiguration": {
            "AccessRoleArn": "arn:aws:iam::<AWS_ID>:role/tf-AppRunnerECRAccessRole"
        }
    },
    "HealthCheckConfiguration": {
        "Protocol": "TCP",
        "Interval": 10,
        "Timeout": 5,
        "HealthyThreshold": 1,
        "UnhealthyThreshold": 5
    },
    "AutoScalingConfigurationArn": "arn:aws:apprunner:<AWS_REGION>:<AWS_ID>:autoscalingconfiguration/minimum_setting/1/xxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

この辺の項目は AWS コンソール画面でのサービス作成時に出てくるものとほぼ一緒なので、最初はコンソール画面からサービス登録してみるとイメージが掴みやすいと思います。

ちなみに今回は、スケール設定に関してはミニマムの設定にしておきたかったので、デフォルトのものではなくて予め作成した設定を指定しています(minimum_setting)

動作確認

一通りの設定は完了したので、terraform で AWS のリソースを作成したら Github Actions からデプロイすると、App Runner のサービスが作成されます。

f:id:ro9rito:20210614191344p:plain

デフォルトドメインに表示されている URL にブラウザからアクセスすると、アプリケーションが公開されていることが確認できます。

f:id:ro9rito:20210614191411p:plain

また、コンテナイメージが更新される(アプリケーションのソースコードを更新してイメージを再 pushする)と、自動的にデプロイが走ります。

f:id:ro9rito:20210614191423p:plain

ソースが更新されるとサービスも更新されることが確認できました。

f:id:ro9rito:20210614191440p:plain

まとめ

簡単にデプロイできて、ネットワーク周りやスケーリングを気にしなくて良いのは便利だなと思いました。

ただし、コンテナイメージでも github リポジトリでも、1つのソース(イメージ・リポジトリ)しか選択できないので、フロントエンドとバックエンドがソースとして別れている場合はすべてを 1 つのサービスで一撃構築!みたいな用途では使えない。(nginx と php-fpm 2コンテナ兄弟みたいなやつも同じくダメ)

ネットワークからスケーリングまでフルマネージドである特性上、全部のせアプリケーションでないと成立しないっていうのには納得。(片側だけ動かすとかなら良いかも)

Github リポジトリを使う場合も、AWS コンソール画面から App Runner のサービス作成をやってみましたが、サービス作成画面で AWSGithubリポジトリを連携させるだけなので操作は簡単でした。

ちなみにサービス作成してから構築完了までは約 5 分ほど、削除に関してはおよそ 1 分以内程度かかりました。

AWS からは便利なサービスがどんどん出てきますが、特性を知って必要な時に選択肢の一つとして出せるようになっておきたいですね。

Autify を使ってみた所感と Puppeteer と Playwright

この記事は個人ブログと同じ内容です

http://ratatatat30.hatenablog.jp/entry/2021/06/13/114921

sekitats です。

さて、back check では E2Eテストツール Autify が導入されました。

f:id:sekilberg:20210613194119p:plain

1日触ってみたので所感を書きたいと思います。

よかった点

まず、海外発サービスかと思いきや、日本人が作った感のある馴染みやすいUIが良い。

シナリオ作成では、ブラウザでの操作をそのままレコーディングしてテストシナリオとして保存できるのが画期的だ。すぐに使い方を覚えることができた。 これであれば誰でもテストシナリオを書くことができるだろう。

そして、何よりクロスブラウザテストをサポートしているところが嬉しい。これだけで導入するだけの価値はある。

また、AI技術を使って変更の差分を検出するなど、きめ細かなサポートが素晴らしい。

ダミーのメールアドレスを生成してそれを使いまわせるのが便利だし、開発者にとっては JavaScript でコードを実行できるのも柔軟さがあっていい。

気になった点

使い心地で不便を感じたのは、シナリオ作成途中で修正が発生した場合、基本的にブラウザでレコーディングをする以外の方法がないことだ。

それから、例えば、コードであれば当然あるシナリオをコメントアウトやスキップするなりして一部のシナリオを実行させないことができる。一部シナリオを無効化するなどできると嬉しいと感じた。

細かくシナリオを区切ってパーツ化したとしても、最終的に一つのテストプランに書き出すマスタリングのような工程が必要なため、つぎはぎのままではテストを実行できない。

それから、ローカルでの設定、ステージングでの設定など、設定を簡単に切り替えられるのもコードなら簡単にできる。

テストが異常終了した場合もコンソール上であれば分かりやすい。

それなら Autify である必要がないのはもっともだ。

結局、細かいところの制御はコードには勝てない。

要は用途によって使い分けるべきということでしょうか。

しかし、E2Eテストをここまで進化させた Autify を応援したい気持ちでいっぱいです。Autify の更なる進化に期待せざるを得ません。

Master of Puppeteer

f:id:sekilberg:20210613203131p:plain

私は Puppeteer でテストシナリオを書き始めた。Puppeteer は E2E テストフレームワークではないが、Autify で不便に感じたところを解消できそうな気がしたのである。

Puppeteer と聞くと、Metallica“Master of Puppets” が思い浮かぶ。 Metallica の代表的な曲であり海外では有名な曲なので聞いたことない人は一度聞いてみて損はない。(ギターリフを全て異常な速さのダウンピッキングで弾いていることでも有名)

話が脱線しましたが Puppeteer の tips を共有。

iframe 内の要素にアクセス

const frame = await page.frame().find(f => f.name() === 'preview-html')
const button = await frame.$('.button')

タブの切り替え

const pages = await browser.pages() // 全タブを取得
const newTab = pages[pages.length - 1] // 開いたタブを取得

新規タブが開いたあと画面がローディング中のまま進まないことがあった。 一度他のタブに切り替え、新規タブに切り替え直すとローディングから先に進むようになった。

const pages = await browser.pages() // 全タブを取得
...
const prevTab = pages[pages.length - 2] // 一個前のタブ
await prevTab.bringToFront() // 一個前のタブを前面に
await prevTab.waitForTimeout(2000) // 2秒待機
await newTab.bringToFront() // 開いたタブをもう一度選択する

クリップボードにコピー

const documentHandle = await page.evaluateHandle('document');
await page.evaluate((document) => {
    // documentHandle を渡すと evaluate 内でdocumentにアクセスできる
    const oneTimePass = document.getElementById('password').innerText
    navigator.clipboard.writeText(password)
}, documentHandle)
await documentHandle.dispose() // 使い終わったら破棄

クリップボードの中身を取得

const password = await page.evaluate(async () => {
    return await navigator.clipboard.readText()
})

ちなみにクリップボードのアクセスを許可しておかなければならない。

const browser = await puppeteer.launch()
await browser
  .defaultBrowserContext()
  .overridePermissions(' host名 ', ['clipboard-read'])

おまけ playwright も触ってみた

f:id:sekilberg:20210614131150p:plain

playwright とは

Playwrightとは、Microsoftが提供しているWebDriverやpuppeterと同じ種類の自動テスト用のライブラリです。2020年2月1日に、オープンソースで公開されました。ChromiumFirefoxWebKitといったブラウザの動作をプログラムで再現し、自動的にテストをすることができます。

ざっくりいうと puppeteer の制作チームが Microsoft に行って Chromium だけでなく Firefox, WebKit にも対応した新しいライブラリを作ろうってことで puppeteer の改良版 playwright を作ることになったてな感じでしょうか。

今回、最初から playwright で書けば良いのではと聞こえてきそうだが、“Master of Puppeteer” ってどうしても言いたかったのでよいのである。

puppeteer と Playwright との詳細な違いは他の記事に任せるとして、puppeteer とのざっくり変更点。browser インスタンス生成のところだけ変えただけでだいたい動いた。

const { chromium, webkit } = require('playwright');

const browser = await chromium.launch({ ...config })
const context = await this.browser.newContext()
await context.grantPermissions(['clipboard-read'], ' host名 ')

playwright になってタブの操作方法が変わった(以前の書き方でもできそうだが、context ってのを使うっぽい)

const [newPage] = await Promise.all([
  context.waitForEvent('page'),
  page.click('a[target="_blank"]') // Opens a new tab
])
await newPage.waitForLoadState();
console.log(await newPage.title());

(新規タブが開いたあと画面がローディング中のまま進まないところは以前として解決できない。これは他の原因がありそう。)

playwright について言えば、なんとユーザー操作からコードを生成するなんてことができるようだ。機会があれば使ってみて記事を書いてみたい。

まとめ

Autify は素晴らしいツールだ。人力でやっている非効率な作業から解放され、プロダクトの価値向上のためによりリソースを使える。こういったサービスがさらに進化し続け、世の中の負の解消になれば良いと思う。

他のE2Eテストフレームワークもどんどんと進化を続けている。 Karma によるブラウザの自動操作を初めて見たときは衝撃を受けた。もちろん Selenium もあったし、ヘッドレスブラウザに関しては、PhantomJS, CasperJS, Nightwatch.jsNightmare もあった。IEやレガシーEdge がなくなった世界になれば、E2Eテストはさらに楽になる。願ってやまない。

それから今回同じく E2Eテストフレームワークcypress も触ってみた。しかし、cypress は別タブ遷移ができないので痒いところに手が届かなそう、ということで断念。

結局何が言いたかったかというと、Metallica も E2Eテストでも何でも進化を止めてはいけない。という雑なまとめで締めたいと思う。

スカウトメールに 3割 返信もらったはなし

この記事は個人Qiitaと同じ内容です

qiita.com


スカウトメールに 3割 返信もらえたはなし

まえおき

エンジニア採用担当のみなさま、スカウト送信、やってますか?

ビズリーチ、転職ドラフト、Forkwell、キャリトレ、LAPRAS SCOUT等々、スカウト型転職サイトが、特にエンジニア採用において注目されていると思います。

企業にとって採用とは従来、応募や紹介など待ちのスタイルだったものが、スカウト型転職サイトにおけるスカウトの利用によって、必要な時に必要な分だけ企業側から行動を起こせる攻めのスタイルが選択できるという点で、採用の可能性が広がる仕組みと言えます。

ただし、経験ある方ならわかると思いますが、スカウト送信って、1件1件かなり時間がかかるものです。

私は業務の合間に対応して、残業時間も適度に利用して、1日10件送信が限度でした。

それだけ時間をかけて送るスカウトですから、返信率はなるべく高いほうがいい。

スカウト送信の目的

転職サイトへのスカウト送信作業の直接的なゴールは、 良質な面接 数を獲得することかな、と思います。

スカウトの先にあるのは採用面接ですので、企業と求職者の双方が入社してほしい/入社したいと感じるのが真の目標達成ですが、お互いの状況次第でそうはならない場合もあります。

スカウト送信における良質な面接とは

面接後に、企業側がぜひ入社してほしいと思えた方との面接や、または内定水準にはぎりぎり達しなかったけど、実際に会ってみなければわからなかった内容だったから面接できてよかったと腹落ちできる面接は、良質な面接と言ってよさそうです。

なので一つ注意点としては、検索範囲を闇雲に拡げて内定水準にマッチしないことがおおよそわかっている返信が増えてもそれは良質な面接にはつながらないため、自社のニーズにおいて妥協のない検索条件に対してヒットした求職者を送信対象にする、というのは大前提になります。

では以上を踏まえて、今回はスカウトの送信内容にフォーカスして、具体的に行ったことを見返してみましょう。

スカウトテンプレート作成のポイント

まず送信されたすべての求職者に届く定形部分になる、メールテンプレートにおいて気をつけた点があります。

応募ハードルを下げる

まず、応募のハードルを下げるために以下の2点を実施しました。

  • 必要技術スタックはMUST、WANT含め「習得できる技術スタック」として表現し応募ハードルを下げる
  • フルスタックと説明したいところは、表現を和らげて「マルチスタック」と表記する

具体的な技術スタックを、応募資格や応募条件のように表現すると、その技術スタックすべての既修得者だけを募集しているように見えるため、入社後の修得も可能であることを理解してもらうため、習得可能な技術として表現しました。

また、チームのEng.の目標はフルスタックになることではあるのですが、募集に「フルスタック」を書きすぎると気後れする求職者もいるかも知れないと考え、「マルチスタック」と柔らかい表現を使用し、"オラつき感"を抑えました。

伝えたい魅力を伝える

エンジニアとして、こういう項目に喜びを感じる方に入社してほしいという観点から、以下2件を魅力として訴求しました。

  • プロダクトの市場におけるチャレンジングな側面を説明する
  • ユーザー顧客と直接的に関われることを説明する

事業自体がチャレンジングであることと、ユーザーの声と近いことは、大切な価値であると共感いただける方と働きたいと思っています。

カスタマイズ文言作成のポイント

定形テンプレートの次に、最も力を注ぐべき、求職者ごとのカスタマイズ文言において気をつけた点をあげだしてみます。

まず、プロフィール自体と、併せて掲載されているリンクからGitHub、Qiita、スライド、ポートフォリオ、個人ホームページなどに目を通します。

ここで読み取るポイントは以下です。

  • 何を伝えたがっているのかを理解する
  • 他者と異なる点(個性、特性、属性)を探す
  • 求職者がやりたがっていることとこちらがお願いしたいことが一致するポイントを探す
  • 求職者が重視しているらしい環境や労働条件、役割、課題のうち、弊社なら提供、解決できそう、と伝えられそうなポイントを探す
  • その他、特徴的で同調できる取り組みや実績を探す

つまり、論点を抽出すると以下のどれかの切り口になります。

  • あなたが必要である
  • あなたの現状や希望を解決できる
  • あなたの体験に深く同調できる

これらを1つ以上、可能であれば複数掲載できると、より読んでもらいやすいスカウトメッセージになると予想しました。

そしてこれらの観点で読み取った内容を、例文にしてみると以下のようになります。 これらは実際に送信した文章の抜粋です。

  • ◯◯◯ を重視して開発されるスタイルについて理解
  • ご指摘の課題について当社スクラムチームでは継続的に解決しながら開発を進めている
  • お力添えをいただきたい内容が多くぜひともに働きたい
  • アーキテクトを包括的にリードしていただける役割を担っていただけないかと思う
  • スタートアップ企業においてすべて見渡せる規模の環境で xxx 様の技術力を存分に発揮してほしい
  • 技術スタックにおいて当社が導入を検討したい項目が見受けられ、力添えをいただきたい
  • 網羅的に対応し積極的に技術課題を解決されておりオールマイティに広く深い技術知見をお持ちとお見受けできる
  • 当社はでは ◯◯◯ についても対話が可能
  • ご希望の ◯◯◯ につながるキャリアパスも描きやすい環境
  • 技術者として好感の持てるプロセスを実直に遂行されている
  • xxx 様が記載されているフローやアプローチは、当社においても重要な観点
  • ◯◯◯ というフレーズが新鮮で、目を留めてしまった
  • ドキュメントの書き方から、エンジニアとしての誠実さを感じ取ることができた
  • ユーザー目線を大切にされていること、業務改善に高い意識で取り組まれていることが伝わる
  • 仲間やご家族を大切に思う気持ちが伝わる

スカウトされる側の心得...

少しテーマからそれますが、以上の内容は求職者(スカウトされる側)のプロフィールの書き方の参考にもなるかと思います。

求職者さんがスカウトをたくさん送られたい場合は、以下を気にかけると、スカウトを送る側にとって送りやすいプロフィールになるのかもしれません...

  • 企業に必要と思われるスキルや実績のアピール
  • 現業で実現できない、やりたいことや解決したいこと
  • 開発や事業に対する個人の想いや目標

私自身がスカウトされようとした実体験がないので、あくまで仮説の域を出ておりません。 的外れな発言だったらごめんなさい。

まとめ

以上のスカウト送信のポイントを実行して、私の今回のトライアルでは、母数はあまり多くないですが、20件送信して6件の返信、ちょうど3割の返信を獲得し、返信いただいたみなさんとカジュアル面接を実施することができました。

最後に、スカウトに限らず採用においては、私は以前から以下のことは大原則として心得るようにしています。

  • 採用プロセスはごく丁寧な対応に努めること
    • 採用候補者は企業のために時間を割いていただいているため
    • 採用候補者はプロダクトのユーザー、あるいは将来ユーザーになる可能性があるため

スカウトも、求職者の人生に少なからず関わろうとする行為であることを考慮しますと、丁寧にできることをやりきってから連絡するのが最低限のマナーなのかな、と感じました。