Developers Summit 2024 Summer に参加した & いくつかセッション感想

7月23〜24日のデブサミに参加して、とても刺激を受けることができました。 リアル参加はかなり久しぶりで、2018年9月のデブサミ関西以来。 参加費無料で素晴らしいイベントを開催いただいた翔泳社と、出張させてくれた弊社に感謝です。

どのセッションも学びがありましたが、特に心に残ったセッションの感想をざっと書き残しておきます。

Classi 鈴木さん:オーナーシップは誰のものか プロダクトマネージャーに頼らないプロダクトマネジメントへの挑戦

event.shoeisha.jp

speakerdeck.com

Classiさんの最近の組織・プロセス改編、特に「プロダクトボード」の運用を開始し、プロダクトマネジメントを強化したという話。

Classiさんのここ数年の開発組織再編の動き、今回初めて知ったのですが(もっと早くキャッチしておきたかった...)、チームトポロジーを参考にしたチーム再編、その後のレポートライン整理からの今回の話、どれも、自分のチームと問題意識や動きともに近く、興味深く聞かせていただきました。 変化によってハレーションが起こる、などの部分は自分の経験からも理解・共感でき、それを織り込みつつ、変化を決断している所がすごいなと思いました。

別の観点で、Classiさんは開発組織について、節目節目で状況をまとめて発信しておられます。

これは対外的な認知だけでなく、自身やチーム/組織にとっても良いまとめになるはず。 自分やチームも組織改善には取り組んでいるので、見習いたいという刺激をもらいました。

DMM 石垣さん、ミノ駆動さん:技術的負債~困難を乗り越える道筋~

event.shoeisha.jp

speakerdeck.com

技術的負債に関する分析・考察と、その解消の取り組みの紹介。 前半は石垣さんの「技術負債による事業の失敗はなぜ起こるのか」、後半がミノ駆動さんの「組織で技術的負債を解消する場合のボトルネックと解決の切り口」というリレーセッションでした。

前半、見積もりのズレから技術的課題を検出する、という視点はとてもわかりやすく参考になりました。 後半では、技術的負債を改善していく具体的施策が多く紹介されていました。 後半、資料がおそらく公開されていないので手元のメモ頼りになりますが、これまでの対策としては

  • 改善のための横断チーム
  • 解析ツールや開発環境の整備(SonarCloud導入やモノレポ化)
  • レビューのレビュー

で効果が限定的だった、そこで今後の取り組みとして

をやっていく、という内容でした。

自分のチームでも、負債解消をリードしているメンバーがいますが、紹介されていたこれまで/今後どちらの対策とも重なる部分があり、勇気づけられたり、見直すきっかけになる部分があるなと感じました。地道な活動の部分が知れて良かった。

NTTデータグループ 菅原さん:エンジニアのための処世術 ~ 推しの技術を採用させるメソッド

event.shoeisha.jp

エンジニアが、自分が使いたい「推し」の技術を採用させるためのアプローチについてのセッション。

エンジニアは手段/シーズ志向で語りやすいが、経営・判断側はビジネス/顧客/ニーズ志向で考える、というギャップがありがち、組織の方針説明はツマンナイと思わずネタの宝庫と思おうなど、超あるあるなお話。 個人的には、新人からベテラン、ジェネラリストからスペシャリストまで、全エンジニアのMUSTで持つべき観点かというと例外はあると思うけど、持つに越したことはないし、そういった観点を持って提案できるエンジニアは一定組織が大きくなると強く求められると思います。 今回は「技術を採用させる」というシナリオだったけど、エンジニアからの提案一般(例えばアーキテクチャ刷新、仕様変更、プロセス改善など)で応用できる話だなと思いました。

リンカーズ 大河原さん:エンジニアリングマネージャーはどう学んでいくのか

event.shoeisha.jp

speakerdeck.com

  1. エンジニアリングマネージャーは「非定型問題」を多く扱う、
  2. 非定型問題の解決には「推論」が大事
  3. 推論の精度を挙げるには、幅広く学び、深く定着させられるという仮説があるが、時間は有限→学びの効率大事
  4. 効率の良い学び方と、登壇者の実践内容

という、エンジニアリングマネージャーの「学び」に参考になる内容。 内容もさることながら(資料を是非ご覧ください)、↑の流れがすっと頭に入る発表で、発表のまとめ方が上手で参考になるなと思いました。 おそらく、系統だった良い学び方をされているからこそ、良い発信もできるんだろうなと思います。

(7/28 22:10追記:)p.111「本の内容によって精読法を変える」は、本は読むがうまく活用できない人にはすごくわかりやすい指針になる素晴らしいスライドだと思いました。

おわりに

自身はマネージャーとして仕事していることもあり、気づけばマネジメント関連ばかり挙げてしまいますね。 今度はがっつり技術寄りのカンファレンスなども参加したいなと思いました。

electron-trpc を少し使ってみた

きっかけ

  • 参加したコミュニティ勉強会(kansai.ts #6 - TSKaigi 直前 - connpass)で、@is_ryoさんの発表 tRPC入門tRPC (TypeScript Remote Procedure Call*1を知った
    • TypeScriptでロジック記述+αを書けば、スキーマ定義や追加のコード生成なしで、APIの開発・利用ができる
  • 自分はWebアプリ開発はしていないが、TypeScript ベースで Electron アプリを開発している
  • Electron の IPC を tRPC として扱える electron-trpc というライブラリがあるらしい
    • 現状、IPC の Request/Response の型付けなどは自前でラッパークラスなどを書いて実現している
    • ラッパーのボイラープレートをライブラリにお任せできると良さそうと思って、導入を試してみようと思った

実際にやったこと

dependenciesの追加

{
  "electron-trpc": "0.6.1",
  "zod": "3.23.8"
}

Preload Script の修正

Main から Renderer に必要なオブジェクトを露出するため、Preload Script に以下を追記。

const ELECTRON_TRPC_CHANNEL = 'electron-trpc';
const electronTRPC = {
  sendMessage: (operation) =>
    ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),
  onMessage: (callback) =>
    ipcRenderer.on(ELECTRON_TRPC_CHANNEL, (_event, args) => callback(args)),
};
contextBridge.exposeInMainWorld('electronTRPC', electronTRPC);

ここ、本来は exposeElectronTRPC(); の1行で済むはずなのだが、Renderer で以下のようなエラーが出てどうもうまくいかなかったため、メソッド実装をコピペしてきている。

import { ipcMain as ee, contextBridge as re, ipcRenderer as U } from "electron";
         ^^^^^^^
SyntaxError: The requested module 'electron' does not provide an export named 'ipcMain'
    at ModuleJob._instantiate (node:internal/modules/esm/module_job:132:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:214:5)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async node:electron/js2c/renderer_init:2:33310
    at async loadESM (node:internal/process/esm_loader:28:7)

これは、将来の electron-trpc のアップデートでELECTRON_TRPC_CHANNELの定数定義が変わるなどすると簡単に壊れて修正が必要になるので、よろしくない。 原因は未探求だが、自分のアプリでは Preload Script を ES Modules 対応済み (preload.mjs) で、electron-trpc がそれに対応してないような雰囲気。*2

良かれと思って ES Modules 対応したら変なの踏んじゃったなという感想。。

2024-05-19追記少し調査してみて、Main Process向けのbundleをPreload Scriptからインポートしているのでエラーが出てしまっている、という結論に至った。似たようなIssueも既にあるようだったので、何かの足しになればと思ってIssueを作っておいた:ESM Preload Script fails to import `electron-trpc/main` · Issue #196 · jsonnull/electron-trpc · GitHub

Main Process:tRPC Router/Context の実装

ここは Electron 非依存、tRPC のやり方を勉強して実装すればOK

type Context = { myService: MyService };
const createTRPCContext = async () => {
  return { myService };
};

const t = initTRPC.context<Context>().create();
const publicProcedure = t.procedure;

export const router = t.router({
  listData: publicProcedure
    .input(
      z.object({
        limit: z.number(),
        offset: z.number(),
        keyword: z.string(),
      })
    )
    .query(async ({ input, ctx }) => {
      return ctx.myService.listWithQuery(
        input.limit,
        input.offset,
        input.keyword,
      );
    }),
});
export type AppRouter = typeof router;

Main Process:Router を IPC とつなげる

createIPCHandler({
  router,
  createContext: createTRPCContext,
  windows: [win],
});

Renderer Process

tRPC Client を自前のReact Contextで渡す形にした(tRPC のドキュメントでは React Query の利用推しにも見えたが、現状使っていないので)

// Clientの生成とContextの定義:

export const trpc = createTRPCProxyClient<AppRouter>({ links: [ipcLink()] });

export interface TRPCContextInterface {
  client: typeof trpc;
}
export const TRPCContext = React.createContext<TRPCContextInterface>(
  {} as TRPCContextInterface
);
export const useTRPCClient = () => useContext(TRPCContext);
// AppコンポーネントにContextを渡す:

return (
  <TRPCContext.Provider value={{ client: trpc }}>
    /* コンポーネント中身の定義 */
  </TRPCContext.Provider>
);
// コンポーネントでClientを使う:

export function MyComponent() {
  // ...
  const { client } = useTRPCClient();
  // ...
  const res = await client.listData.query({ limit, offset, keyword });
  // ...
}

所感

  • 👍自前のラッパー実装は削減できそう
  • 👍Electron アプリを 今後 Web アプリ化する可能性もあるため、その準備としてもメリットは多少ある
    • createIPCHandler() などを Web 向けに書き換えれば対応できるはず
    • 自前ラッパーでも既にElectron/Webの差分は極力抽象化してあるので、そこまで大きなメリットでもないが
  • 🤔自分のアプリでは、バリデーションライブラリの導入が必要で手間がかかってしまう
    • 現状、zod のようなバリデーションライブラリを使っていないため、API (IPC) の Request の型定義をzod流に書き換えないと(例:id: numberid: z.number())、型推論が効かない
      • 自前ラッパーでは型推論が効いているので、そこの退化は避けたい
    • ts-to-zodのように変換を助けるライブラリもあるので、やろうと思えば可能
    • ただ、バリデーションライブラリへの依存を追加することへの抵抗も感じる(参考:zod ではなく ajv を使っている話 | blog.ojisan.io
  • 👍レアかもしれないが、Electron アプリを作りつつ Web 開発向けの技術スタックに追加で触れていく、という目的意識であれば、導入する価値はありそう

自分の場合、導入による圧倒的なメリットはなく、導入に伴う一時的なコスト、依存が追加されることの長期的なメンテコスト増大のデメリットもあることがわかった。 まだ数時間試してみただけだが、現時点では一旦様子見。

*1:全然関係ないけど、英語の省略語で「小文字1文字 + 大文字数文字」ってパターンはいつ頃からなんでしょうね。古そうなのは messenger RNA (mRNA) とかな気はするが

*2:なお、Electron の sandboxing が有効なときに似た不具合が起こるという Issue が報告されているが、自分のアプリでは sandboxing は無効化しているのでこれが原因ではない

「サーチ・インサイド・ユアセルフ」感想:マインドフルネス/瞑想で生活が変わるかも

こちらの本を読んで、結構大きなインパクトがあったので、感想をブログにまとめることにしました。

きっかけ

先日参加した Regional Scrum Gathering Tokyo 2023 で聞いた伊藤さん・熊谷さんの講演で本を知ったのがきっかけ。

speakerdeck.com

セッションとしては、社内の噛み合わない議論と対立(空中線)を解決するために「アンガーマネジメント」「NVC (NonViolent Communication)」「マインドフルネス」の3つが役立つ、という話。 まずこれらのうち、自分はマインドフルネスが一番イメージができないものだった。 また、1年半ほど前から会社での役割が Individual Contributor からマネジメントに変化していた。つまり仕事の主な対象がコードから人・チームに移り、問題が長期戦になったり思い通りにならないことが増えた。結果、業務時間の内外問わず心が落ち着かないと感じていた。 その軽減につながるかも、という所からも知ってみたいと思った。 そこで、講演でも紹介されていた本を手に取った。

本の内容ざっとまとめ

  • タイトルの「サーチ・インサイド・ユアセルフ」はGoogle社内のマインドフルネス研修の名前。著者はその創始者の元エンジニア
  • 1章でまずマインドフルネスとは何か、を紹介
  • 以降の章では、マインドフルネスがどんなメリットをもたらすかを、具体的なエクササイズの方法を織り交ぜつつ紹介していく
    • メリットの例:心を鎮められるようにする、集中力を増す、創造性を増す、自信を増す、共感力を増す、愛されるようになる
    • エクササイズは内面に集中する瞑想が中心だが、会話やジャーナリングのように出力を伴うものもある

本を読んでの一番の気づき

ジョン・カバット・ジンはマインドフルネスを、「特別な形、つまり意図的に、今の瞬間に、評価や判断とは無縁の形で注意を払うこと」と定義する。(p.51)

マインドフルネスや瞑想で大事なことは注意力*1、という点が驚きだった。 というのも、自分は瞑想について、なんとなく「心を空っぽにする」「何にも意識がとらわれない無の状態」といった注意力をゼロにするようなイメージを持っていた*2。 しかしここがむしろ真逆で、マインドフルネスでは注意力は無くすのではなく鍛えていくものだった。 ただし判断や評価、反応や行動を廃した純粋な注意を、という理解で合っていると思う。

瞑想は呼吸に注意することで注意力を高める訓練、ということだった。 では注意力を高めてどうするのかというと、注意力を自分の情動や体の状態に向けていく。 これはアンガーマネジメントでも言われることだと思うが、怒りなどの衝動的な心の動きは 刺激 → 情動 → 行動 という順序で起きる。 評価抜きで情動に注意することで、その後の行動(これが怒りやストレス)を抑えていくことができる*3

これは考えてみると納得感がある。 本書でも述べられているが、仮に瞑想で心を空っぽにすることでストレスを軽減できるとしても、再現性がなくて、ストレスイベントが起きる → ストレスを感じる → 瞑想でストレスを減らす → また別のストレスイベントが起こる → ...、ということが繰り返されるだけで本質的に解決しない。 自分の情動から逃げず、理解しうまく付き合っていくことで、同じ出来事があっても動じにくくなる、というのはわかりやすいと思った。

全般、著者がエンジニア出身ということもあって表現がロジカルな傾向があり(著者も自分でそう書いていた)、自分には安心して読める印象だった。

本を読んでから実践してみたこと

マインドフルネスのメリットは複数挙げられていたが、自分としては特に次の2つが得たいと思った。

  1. 意のままに心を鎮める:仕事や生活の時間をより良い状態で過ごしたい
  2. 集中力:特に読書のときなど、気が散りやすいので改善したい

この2つが得られれば、大げさかもしれないが人生にも影響があると思う。 まだ本を読んで1週間しか経っていないが、これまでにやったことと、そこから得られた良い感触についてこの記事を終わりにしたい。

まずは、本に書いてある瞑想の練習をできるだけ毎日やっている。 呼吸に純粋な注意力を向けるよう練習するのだが、やってみると最初は自然な呼吸ができず、自然な呼吸になるよう意識してペースや呼吸量を調整したりしていた。 ここは、目的(注意力を鍛える)を理解した上で続ければ、徐々に上手くできるようになりそうな感覚はあるので、続けていきたい。

また本を読んだ直後に、生活の中、具体的には街中を一人で歩いているとき*4に自分が何を感じているか、にじっと注意してみた。 すると、人とぶつかりそうになったとき、あまり好きでない広告を目にしたとき、冷たい風が吹いてきたときなど、かなり頻繁に、ネガティブな心の反応が生まれていることに気づいた。 ここでちょっと冷静になると、冷たい風が吹いても、すぐ「あー寒い嫌だ」と思わなくても良い、と思えたのが面白かった*5。 冷たいし身体は震えるが、ダウンジャケットも着ているし危険を感じるようなレベルではない、と思い直すと、「嫌だ」という気持ち・反応をやりすごすことができた。 1.については、こういった気持ちのコントロールを色々な場面でできるようにしていくと良いのだと思う。

2.については、自分が読書中にどういうときに気が散るのかに注意し書き出してみた*6。 すると無限にあるわけでもなく、パターンがあった*7。 これを理解することで、気が散ったタイミングに気づきやすくなり、そこから再度本の内容に集中を戻したり、あるいは今は集中できないから休憩しよう、と判断したりしやすくなった。 正直な所、ここ数年、本を読んだが内容がほぼ頭に入っていない、ということが多く、自分の読書スキルに自信をなくし、嫌になりつつあった。 しかし集中できる見通しが見えてきたことで、「集中して読書をする」というチャレンジを込みで、改めて読書を楽しめそうと思えている。 さっそくTrelloを使って積ん読本を読む計画を立ててみると、半年ほどかかることがわかった。 ゲーム感覚で消化していこうと思っている。

まとめ

元エンジニアが書いたマインドフルネス・瞑想の本を読んだら、「注意力が大事」というこれまでと真逆の理解が得られ、

  • ストレスが軽減できるかも
  • 読書を集中力の訓練と捉えて楽しめそう

という感触が得られた、という話でした。

*1:後から見たNetflixの「ヘッドスペースの瞑想ガイド」という番組でも同じことを言っていた

*2:チームのメンバーと雑談しても同じようなイメージを持っていると言っていた。同様の人はそれなりにいるのかもしれない

*3:まだ自分は実践できてはいないが、こう理解はしている

*4:本当は仕事中にそうしたいがまだ余裕がないので

*5:心頭滅却すれば火もまた涼し、という言葉も思い浮かんだ

*6:認知の動きを認知するので「メタ認知」という

*7:書き出したものをそのまま書くと:姿勢、ゲーム、明日の予定、気温、痒さ、飲み物、読書法、「これ読んで意味ある?」という疑念・筋トレ・掃除・本の内容に関係あるエピソード・「この章はあと何ページ?」と思う

「Google ITサポートプロフェッショナル認定」でネットワーク・セキュリティの基礎を学びなおした

2018年頃に公開されたGoogle ITサポートプロフェッショナル認定 (Google IT Support Professional Certificate) の中から、

の2つのコースを受講・完走しました。 仕事が終わった後の夜に進めましたが、合計してだいたい15日くらい(疲れてサボった日を含む)でさくっと終われてよかったです。

目的

前提として、分野のエキスパートになることが目的ではありませんでした。自分は出身は理系ですが、コンピュータサイエンスをこれまで大学などで体系的に学んだことがなく、一般的にこれくらいは知っておくべきだが、自分が今知らないことがあるように日々の業務で感じていたため、そこを埋めるために受講しました。その目的にとっては良い選択だったのではと思います。

内容

内容は基礎的で、既に知っていることも結構含まれていました。しかし、聞いたことや見たことはあるが理解が曖昧だった事柄、例えばネットワークであればレイヤモデルの各層でのパケットのヘッダ概要やTCPの3-way handshake、セキュリティならPKI (Public Key Infrastructure)などを改めて背景から理解できました。動画コンテンツには必要に応じてわかりやすい図が登場するので、多少仕事で疲れていても理解が進みやすかったのは良かったです*1。ただ、3年前に公開された講座ということもあり、一部内容は古くなっています(紹介されているサービスが既にクローズしてたり)。

同じ分野の書籍に比べると内容はかなり浅めで、取捨選択されていると思います("Supplemental Readings"として参考資料を挙げて「詳しくはこれ読んでね」というパターンも結構ある)。そのため、重要だが学習内容に含まれない事項も多分にありそうで、そこは今後都度学んでいく必要があると思っています。取捨選択の仕方は、Google監修だし偏ってはいないだろうと今回は盲目的に信じることにしました🙃。

自分は大体、各動画を2回ずつ見て学習してました。1回目で概要を掴んだあと、2回目にノートにポイントを書き写す、という感じです。リアル講義ではありえないことですが、動画がストップできるのが非常に便利でした。このへん、英語がもっと得意な人はもっとスムーズに学習を進められるのかもしれません。

効果

受講する前よりも、自分の中に大まかな分野の地図ができた感覚を持てています。例えば、以前から持っていたマスタリングTCP/IP徳丸本を読む障壁を低く感じるようになれて、「あー、ここは動画で見たあの話を詳しく書いてあるのね」と思えるようになりました。

補足など

動画・文章とも英語で、日本語字幕はありません。 ただ、発音は明瞭だし、英語字幕もあるので、ある程度英語ができるが、そこまでListeningに自信ない人(∋ 自分)は、その面でも勉強になるかもしれません*2

講座はCourseraという有名なオンライン学習サービスの一つでホストされています。 課金しなくても学習コンテンツ自体は見られます(修了証はもらえません)。このあたりの詳細は、以下の記事が参考になると思います:

www.kimoton.com

あと、講座全体がGoogleのITサポート職の採用が目的でもあるため、短い社員インタビューや模擬面接の動画が時々挟まってきます。自由に飛ばせますが、自分は面白いので一応全部見ていました。

むすび

基礎的な内容を時間取って勉強するのは久しぶりだったため、簡単ですが書いてみました。学び直しは今後もずっと続けていく必要がありますね。以上です!

*1:ネットワークは全般良い感じだったのですが、セキュリティの後半の方で、項目の羅列のような箇所が一部あると感じました。製作者も疲れてきたのかな?

*2:逆に言えば、とにかく内容に集中したい場合はイマイチかも、とも言える

useEffect()の第2引数の差分検出はdeepではない

内容はタイトルで尽きてます。 気になって調べたのでメモを残しておきます。

背景

  • React HooksのuseEffect()第2引数に渡す配列は、「この副作用が依存する値」を表す。
    • つまり、指定した配列に含まれる値が変化したときのみ、第1引数の関数が実行されるようにできる
  • では、変化の検知はどういう方式なのか?
    • 具体的には、配列やオブジェクトを渡した場合にdeepな比較をしてくれるのか?

結論

  • Deepな比較はしてくれない。
    • 値が全く同じでも、別オブジェクトなら変更検知される。逆もまた然り。
  • とはいえ、通常はprops, stateくらいしか指定しないだろうから、同じオブジェクトだけど値が変わった、みたいな状況はほぼ気にしなくて良さそうではある

関連記事

実験

useEffect()の第2引数に

  1. 数値をプロパティに持つオブジェクト
  2. 数値を要素に持つ配列
  3. プリミティブ数値

を渡し、各数値をインクリメントしつつ1秒おきにrenderする。 直接innerTextを書き換えるような副作用を仕掛けておき、差分が検出されるか調べた。

実際、オブジェクト・配列については中身の値が変わっていても副作用が実行されなかった。

コード確認

  • React v16.13.1 を読んだ
  • Reactのコードをちゃんと追うのは時間がかかりそうなので断念した
    • 当たりを付けた上で、DevToolsでデバッグして実際に該当コードが実行されていることを確認した

useEffect()の実装

  • React内部では、ReactCurrentDispatcher.currentというグローバル変数を状況に応じて差し替えながら処理しているらしい
  • それを元に追いかけると、useEffect()の実体がupdateEffectImpl()という関数であることがわかる
  • 差分検知はareHookInputsEqual()がしている
    • 第2引数(配列)の各要素のbefore/afterの値をis()関数で比較している
      • このへん
      • is()関数はObject.is()Polyfill、ざっくり言うと===による同一性比較だが、+0-0は異なるがNumber.NaNNaNは等しいと判定する。
    • 各要素を再帰的に見るような処理は存在しないことから、deepな比較はしないことがわかる

RSGT2020初参加レポート

先週、RSGT2020ことRegional Scrum Gathering Tokyo 2020に初めて参加してきました。*1

既に多くの方がレビューなど投稿されていますが、今後のためにも、自分なりの所感などをまとめておきます。

いまの自分の立場/ロール

大阪で、kintoneというBtoBプロダクトの新機能開発をしています。

チームはスクラム開発を取り入れており、自分は開発チームの一員です。 CSM認定を取っていますが、スクラムマスター専任ではなく、スクラムに関して多少詳しい立場から気になったときに可能な範囲でアドバイスをする、といった立場です。

あとチームの特徴としては、マルチサイト(5拠点+在宅)で、常時リモートでモブプログラミングをしています。

RSGT2020・総評

参加してよかった!

たくさんのインプットや勇気づけをもらえました。特に「今年どんな活動をしていこうかな?」とぼんやり考えていたのが、講演や対話を通してより明確化できました*2

一方、仕事でスクラムをやっていく上で、自身の現状と今後のロールについて再考する機会ともなりました。一言で言うと、「なんか中途半端だけどどうしよう?」という漠然とした危機感が沸いています。

いまの自分のロールは開発チームの一員、スクラムマスターではない。リソースも開発チームとしてほぼフルコミットしている。アジャイル/スクラムに興味はあるし、チームの改善などを主体的に考えているつもりではあるが、リソースは明らかに割けていないので出来ていないことも多い*3アジャイルコミュニティにもどっぷり浸かれてなくてもったいない感じというか。ここはまだ結論出ていないので、継続して考えていく。

また、スクラム/アジャイルのマインドを改めてアップデートできました。セッションや会話を通じて、「こういう時はこうすれば良いんだよな」といった考えの確信が強まった感じ。

セッション別の所感など

Day 1

Keynote: The Ten Bulls of the Scrum Patterns / James Coplien

Coplien (Cope) さんは、自分が受けた認定スクラムマスター研修の講師で、親近感がありました。

内容は、十牛図 (The Ten Bulls) という、禅宗の教えを広めるためのポンチ絵になぞらえて、スクラムの習得の道を説くというもの(最近出版されたA Scrum Bookという、スクラムのパタン・ランゲージ本の冒頭と同じ)。

いわゆる「守破離」を別の切り口で表現したように感じた。 スクラム(=牛)を習得しようと、まずはその姿(=スクラムガイドにあるルール、カタ)を追い求めるが、最終的にはチームの全体性 (Wholeness)、自分たちがどうあるべきか、といった無名の質 (Nameless Quality) に従う自然な営みになっていく。結局「スクラムとはこうやるものだよ」という具体的な姿かたちは無い、という話。

学びとしては、「スクラムの形式にとらわれてはいけない。自分がどうあるべきかを常に考え、それに従い続けることが重要」ということ。これはスクラムパターンの一つ The Spirit of the Game と同様だと思う。 よく考えてみれば当たり前のことなのですが、Copeさんが言うとなぜか印象に残るのが面白い。 改めて意識しようと思いました。

あとは講演を聴きながら、「自分やチームはいま、スクラム習得の過程のどのへんにあるだろうか?」と考えていました。 そういえば最近、「スクラムだからどうこう」という話をあまりしていないな、とは思う。でも、チームの全体性や無名の質みたいな悟り(?)に到達できているかといえば自信はない。

これはスクラムパターンを参照して、自分のチームの現状を確認する良いタイミングでは、と思ってA Scrum Bookを購入しまいました。いやはや思うツボですね。

コーチズクリニック(及部さん @TAKAKING22)

1on1でアジャイルコーチに相談できるとても贅沢なコーナー。

スクラムで重要とされるチームの全体性と、個人の評価の兼ね合いをどうつければ良いか?」という問いを、及部さん (@TAKAKING22) にぶつけさせてもらいました。 最近読んでいたモブプロ本(下記)で解説書かれていたり、去年のScrum Fest Osaka 2019のキーノートで知っていたということで指名させてもらいました。

半分スクラムと関係ない問いにも真摯に向き合っていただきました。 理想は、個人ではなく、チームとしての成果が評価される状態、ということに尽きる(例えば、プロダクトの売上が上がったら全員一律昇給)。 極端な例として、及部さんのチームは、チームごと楽天からデンソーに転職した*4けど、採用自体チーム単位での話だったので、評価についてもよりよい状態になったとのこと。 ここは会社に持ち帰って、何かの形で話題提起してみようと思いました。

あとは、チームの外部発信についてもエンカレッジしていただきました。やっていくぞ!

伊藤 宏幸・高橋 勲:特殊部隊SETチームの日常 - 技術と実験を融合した実践アジャイル術 -

https://confengine.com/regional-scrum-gathering-tokyo-2020/proposal/11774/set-

LINEのSoftware Engineer in Testチームの、プロダクト横断チームならではの実践例。

オンボーディングを促進するためのLearning Sessionはうちのチームでもやっていますが、 Learning Sessionにマネジャー的役割の人が入ることで、業務が説明しやすく人事評価にもプラスの影響があった、というのは経験したことなくて、なるほど、という感じでした。

Day 2

Keynote: Lost in Translation: The Manager’s Role in Agile / Michael Sahota

アジャイルスクラムの文脈で、マネージャーはどんな役割を担っていくべきか、という話。 Day 1のCopeさんのKeynoteが理想主義ならば、SahotaさんのKeynoteは徹底して現実主義で、その対比が非常に印象的でした。

組織階層・マネージャー不要論はよく言われるが、いきなり実践するのは危険な罠で失敗につながる*5。 また、自律的で自己組織的なチーム (Autonomous, Self-Organizing Team) も理想とされるが、いきなりは難しいと。

ではどのように個々のチームメンバーを変えていけばよいかというと、そこでいわゆるマネージャーが活躍できる。 マネージャーがマインドセットを変えることで、組織全体が変わる可能性が高まるのだ、と。 ここではXY理論が引用されていた(初めて知った)。

最後に、本題である「アジャイルにおけるマネージャーの役割とは?」の答え。 理想は、マネージャーも何かの形で、価値創造に明示的に関わる役割を持つこと。 具体的には、Product Owner, Scrum Master/Coach, Tech Lead/Architect, Organizational Coach といった役割につく。 しかしそれも一足飛びには難しいので、マネージャー/リーダーのマインドセットの変化から始めるべき。そのためにはトレーニングなどがあると言って結ばれた。

自分にとっては、内容的にも役割的*6にも、「なるほど明日からこういう風にしてみよう!」と思えるものではありませんでした。 しかし色々と考えさせられる部分は多かったし、実際に組織を変えていく過程は、理想の姿にいきなり改革するよりも、少しずつ変わっていくケースも多いんだろうな、とか思いました。

組織変更して部長がいなくなってから起きたこと / 水戸将弥(サイボウズ

speakerdeck.com

弊社マネージャーの水戸さんのお話。内容盛りだくさん。 自分は中の人なので知っている話がほとんどだったけど、組織運営チームはこんなこと考えていたんだ、という気づきもありました。 組織を変えてからプロダクト/チーム間の人の流動性は本当に高まったと思うし、興味持つ方も多いんじゃないかな、と思います。

最高のScrumキメた後にスケールさせようとして混乱した(してる)話 / 藤村新(クラスメソッド)

www.slideshare.net

現場でスクラムをスケールさせて失敗した話。 内容も具体的な現場の話でもちろん面白いけど、それよりも何よりも、プレゼンの魅せ方が本当にすごかったです。 最初から最後までニコニコして聴かせていただきました。

Day 3

Closing Keynote: NEXT→ACTION / 高橋 一貴(株式会社チカク)

RSGT2011の実行委員長・高橋さんのお話。 資料は非公開とのことですが、noteに講演についてのポストがありました。

主な内容は、2010〜2015年頃のヤフージャパンでの活動のお話。 地道かつ大胆に組織を動かしていく話に心を動かされました。 自分がエンジニアを始めたのが2015年、CSMを取ったのが2018年なので、それよりも全然過去の話なわけで、 ヤフーの強さというか先進性も実感しました。

むすび

他にも講演を聞いたり、飲み会で喋ったり、Open Space Technologyに参加したり、色んなことがありましたが、一旦以上にします。

会場を見渡すと、経験豊富なアジャイルコーチがうようよいる中で、スクラムマスターですらない自分がいていいのか?と思いもしましたが、今年一年頑張ってまた来年も参加できたらと思います。

最後になりましたが、運営の皆様、お疲れさまでした!

*1:2019年にも参加したかったけどチケット争奪戦を甘く見ていたため敗退、今年こそはと発売1分で購入してぶじ参加できました。

*2:具体的には、自分/チーム両方のために、発信を増やしていきたい

*3:チーム外の問題へのアタックとか

*4:及部さんのDay 2の講演のテーマ

*5:ティール組織の成功例でも階層はあるし、アジャイル実践においてマネジメントのサポートが無いことがよく問題となる、という調査もある

*6:自分はマネージャーではない

ElectronのIPCをWeb APIっぽく使えるようなTypeScript実装の試み

背景

  • ElectronアプリをTypeScriptを使って書いている
  • やりたいこと:(1) IPC (Inter-Process Communication)をWeb APIっぽく使えるようにしたい、(2) 同時に、型情報を活かしつつ綺麗に実装したい
    • 一般的なWebアプリをイメージしている。Rendererプロセスがブラウザ、Mainプロセスがサーバーという対応
    • 色んな処理(CRUDとか)をRenderer側からリクエストして、非同期でレスポンスが返ってきたらRenderer側にメッセージ表示などしたい
      • 処理の種類に対応する複数の種類の"API"を実装(例:AddUserGetUserInfo、etc.)
    • IPCのChannelは1つ開きっぱなしにして、全ての"API"で共有する
      • "API"ごとにChannelを分けると、管理が煩雑になったため
+----------+                +----------+
|          |--- Request --->|          |
| Renderer |                |   Main   |
|          |<-- Response ---|          |
+----------+                +----------+

こんな風にしてみた

  • もっと良い実装があるかもしれないけど、現時点ではこれが良いのでは、と思っている方針
  • 一言で言えば、リクエスト/レスポンスの型定義をうまく書いてGenericsIndex typesの機能を活用することで、型情報の活用とボイラープレートの最小化を実現している、ということに尽きます。

"API"の種類

"API"名のunionとして定義

type IpcType = 'AddUser' | 'GetUserInfo' | ...

リクエスト・レスポンスの型

それぞれ別々のinterfaceとして実装する。 各"API"名がプロパティで、値がリクエスト/レスポンスの型になっているという形式. この形式にしている理由はすぐ次の節へ。

interface IpcRequest {
  AddUser: {
    name: string;
    age: number;
    ...
  };
  GetUserInfo: {
    id: number;
  };
  ...
}

interface IpcResponse {
  AddUser: number; // id
  GetUserInfo: {
    name: string;
    age: number;
    ...
  };
  ...
}

"API"のリクエスト側(≒クライアント)

ここが主に試行錯誤したところ。 "API"ごとにメソッドを生やさないようにしたかったけど、最初Genericsがうまく扱えなかった。

最終的に、IpcRequest[T]IpcResponse[T]と書くことで、"API"の種類に応じたリクエスト・レスポンスの型を引っ張ってこれることに気づいた。

class IpcClient {
  private readonly map: ResponseHandlerMap;
  private isOpen: boolean;

  constructor() {
    this.isOpen = false;
    this.map = new ResponseHandlerMap();
  }

  // どの"API"もこの共通メソッドでリクエストできる
  // GenericsとIndex Typesを使って工夫している
  public send<T extends IpcType>(
    type: T,
    req: IpcRequest[T]
  ): Promise<IpcResponse[T]> {
    return new Promise(resolve => {
      const id = uuidv4(); // ユニークなIDを付与しておき、どのリクエストに対するレスポンスなのか区別できるようにする
      this.map.set(id, type, (res: IpcResponse[T]) => resolve(res));
      ipcRenderer.send('MAIN_CHANNEL', type, req, id);
    });
  }

  // IPC Channelを開くために一度だけ叩いておく必要があるメソッド
  // ここは型が付いていない
  public open() {
    ipcRenderer.on(
      'MAIN_CHANNEL',
      (event: any, res: any, err: any, id: string) => {
        const { type, handler } = this.map.get(id);
        handler(res);
        this.map.delete(id);
      }
    );
    this.isOpen = true;
  }

  public close() {
    ipcRenderer.removeAllListeners('MAIN_CHANNEL');
    this.isOpen = false;
  }
}

// Mainプロセスから返ってきたレスポンスのハンドラ関数の型定義
type ResponseHandler<T extends IpcType> = (res: IpcResponse[T]) => void;

// レスポンスハンドラをidごとに保持しておくMap。
// JSのMapの薄いラッパで、こいつの中身は型をちゃんと扱っていない。
// IpcClientはできるだけ型のある世界にしたかったので、汚いものをこいつに押し付けている形。
class ResponseHandlerMap {
  private readonly map: Map<string, { type: IpcType; handler: (req: any) => void }>;

  constructor() {
    this.map = new Map();
  }

  public set<T extends IpcType>(
    id: string,
    type: T,
    handler: ResponseHandler<T>
  ) {
    this.map.set(id, { type, handler });
  }
  public get<T extends IpcType>(
    id: string
  ): { type: T; handler: ResponseHandler<T> } | undefined {
    const got = this.map.get(id);
    if (!got) {
      return undefined;
    }
    return { type: got.type as T, handler: got.handler };
  }

  public delete(id: string) {
    this.map.delete(id);
  }

  public get size() {
    return this.map.size;
  }
}

// 全般、エラーハンドリング系は煩雑なので省略しました

"サーバー"(Mainプロセス)側

工夫した点や実装はリクエスト側と同様なので読み飛ばしてもよいです。

// なんとなくclassとして書いている、classにする必要性は特にない。
// IpcController.initialize()を一度呼べばOK
class IpcController {
  public static initialize() {
    const handlerMap = new RequestHandlerMap();

    ipcMain.on(
      'MAIN_CHANNEL',
      async <T extends IpcType>(
        event: any,
        type: T,
        req: IpcRequest[T],
        id: string
      ) => {
        try {
          const handler = handlerMap.get(type);
          const res = await handler(req);
          event.sender.send('MAIN_CHANNEL', res, null, id);
        } catch (error) {
          event.sender.send('MAIN_CHANNEL', null, error.message, id);
        }
      }
    );

    // ここからずらずらと"API"の実処理を書き並べる
    // reqにはちゃんとIpcRequestの定義に基づいた型情報が付く
    handlerMap.set('AddUser', async req => { ... });
    handlerMap.set('GetUserInfo', async req => { ... });
    ...
}

// "API"のコントローラ関数の型定義にあたる。
// ResponseHandlerと同様
export type RequestHandler<T extends IpcType> = (
  req: IpcRequest[T]
) => Promise<IpcResponse[T]>;

// ResponseHandlerMapと同様、この中は型が付いてない
class RequestHandlerMap {
  private map: Map<IpcType, (req: any) => Promise<any>>;

  constructor() {
    this.map = new Map();
  }

  public set<T extends IpcType>(type: T, handler: RequestHandler<T>) {
    this.map.set(type, handler);
  }

  public get<T extends IpcType>(type: T): RequestHandler<T> | undefined {
    const got = this.map.get(type);
    if (!got) {
      return undefined;
    }
    return got as RequestHandler<T>;
  }
}

"API"を叩くには

Rendererのコードで以下のように書くだけ。型も勝手についてくるし、シンプルに書ける。

ipcClient.send('GetUserInfo', id)
  .then(userInfo => {
    // データを使った処理を書く。
    // userInfoには型が付いている
  });

"API"を追加するには

以下の通り。本質的なことを書くだけで済む。

  • 新しい"API"名をIpcTypeに追加
  • IpcRequestにリクエストの型情報を追加
  • IpcResponseにレスポンスの型情報を追加
  • IpcControllerの中に"API"の実処理を追加

考察

満足している点

  • API追加時にボイラープレートコードを書かなくて済む
  • 各種関数にちゃんとリクエスト・レスポンスの型情報が乗ってくれるので安心

不満/疑問が残る点

  • IpcRequestIpcResponseの定義が別々なので、同じ"API"の情報がコード上並ばないので可読性がよくない
  • 内部的には型無しで扱っている箇所があり、それは良いのかどうか?
  • Channelを1本開きっぱなしで使い回す、というのは良いのか?

その他

そもそもElectronのIPCをAPIっぽく扱いたい、というような需要はあるのか(あるいは筋が良いのか)どうか、よくわかっていません。

↓のようなレポジトリは見つかって、結構モチベーションは近そうなのですが、特に流行ってはいない様子。 github.com

APIっぽい、という話ではないですが、IPCをPromise的にシンプルに扱いたいというライブラリもあるようです↓ github.com github.com

結び

長々とお付き合いありがとうございました。 自分はElectron・TypeScriptのコードリーディングをあまりしてきていないので、典型的なオレオレ実装になっているのでは、という不安もありつつ書いてみました。 より良い実装が見つかれば、追記ないし別エントリを書こうと思います。