ジンジャー研究室

長めのつぶやき。難しいことは書きません。

QA と出会う

現職はスタートアップには珍しく、しっかりした QA 組織がある。日々一緒に仕事をしていて色々と発見があるので、今思うことをメモしておく。

前提として、自分は今までちゃんとした QA の人と仕事をしたことがなかった。前職はアルバイトの人に QA 相当の仕事をやってもらっていたが別に専門性があるわけではなかった。前前職には QA 組織が存在していたが、自分は技術基盤的な組織に所属していてプロダクトを作っていたわけではなかったので、関わりは全くなかった。 そうなると、そういう職種・組織が存在するということを本で読んで知ってはいたが実際どのように連携して仕事をしているのか分からない。そういう人々と関わってみたいというのが転職動機の一つでもある(ちなみに QA と同様に PdM という職種とも関わりがなかったので興味があった)。 そして9ヶ月ほど経った今になってみると「よく今まで QA なしで開発してたな」と思う。

(ところで QA ってロールを指したりプロセスを指したりでややこしいが、この記事ではロールを指している)

継続的なテスト活動

いま一緒に仕事をさせてもらっている QA は出来上がった成果物に対してテストを実施するだけでなく、企画段階から関わって仕様の明確化を手伝い、設計ドキュメントを読んで細かい仕様を質問し、テスト計画書を開発者にレビューしてもらい、そして最後にテストを実施しながら逐次フィードバックするという一連の活動をしている。このように、なるべく前工程から活動を開始することを「シフトレフト」と呼ぶようだ(恥ずかしながらこの言葉を知ったのは最近のこと)。また、テスト設計・実施だけではなくプロセス全体に関わって品質向上に貢献することを「テスト活動」と界隈では呼んでいるようだ。

テスト技法

QA がスプレッドシートにテスト項目をずらっと並べてチェックしているのを眺めていたのだが、あれは「ディシジョンテーブル(DT)」と呼ばれる歴とした技法であることを後から知った。同じエンジニアなのにテストの話をする時に今まで全然そういうワードが出てきたことがなかったなと思った。面白そうだったので、有名そうな「はじめて学ぶソフトウェアのテスト技法」を読んだ後、おすすめで出てきた「ソフトウェアテスト技法練習帳 ~知識を経験に変える40問~」を一通り解いてみた。結構気づきが多くてユニットテストや E2E テストにも応用できそう。

品質保証という観点

E2E テストは QA と開発者が同時に関わっているのだが、テストケースの見方が大分違うなという感想を得た。一言で言うと、我々開発者が適当すぎた(笑)。開発者はユニットテストについて語る時「不安なところにテストを書いて自信をつけよう」などと言うが、「不安とか自信とかなんなの」感は正直ある。ユニットテストの延長で E2E テストも「まーこんなもんだろ」みたいに感覚で書くので、多分 QA としては困惑していたと思う。結局「そのテストでどこまで品質を保証できていますか?」に答えるためには、テストケースも整然と管理されている必要がある。

コミュニティ

QA 界隈にも WACATE とか JaSST のようなイベントが頻繁あり、コミュニティが形成されているようだ。ホント全然知らなかった。観測範囲では、個別の技法を突き詰める話よりは QA が組織にどう関わるかみたいな話が多い印象。まあ重要だからそうなるの分かる。

いま考えていること

そもそもなぜ今 QA 活動に興味があるかというと、最近設計ドキュメントの粗を QA に指摘されまくって凹んだからである。「それちゃんと考えればケースが漏れてること気づいただろう」と。つまり QA がテスト設計を始める前に開発者が設計段階でミスに気づければ、その分だけ戻りが少なくなりシフトレフトに貢献できるわけ。でも QA が旗を振って開発者にそういう活動をしてもらうようにお願いするのってかなり難しい気がするので、じゃあもう自分が開発者として直接協力するのが一番早いでしょうと。痒いところにも手が届くので。 そして、前回の反省からの TRY として直近の機能では設計時に PdM・デザイナーと仕様をとことん議論してみた。単に意識を変えただけだが、おかげで今度は QA に質問攻めにあって泣きながら書き直すことは無さそうだ(多分)。

近況:転職して半年

7月から新しい職場で働いているので、そろそろ半年。整理のために近況を綴ってみる。以下は個人的な話で会社の紹介とかはしないので、気になった人はホームページを見てほしい。

kwork.studio

前職に引き続き、職種としてはフロントエンドエンジニアという位置付け。

業務として書く React

前職では業務で Elm を書いていたので React は小さなプロジェクトや趣味で触っていた程度。なので大規模アプリケーションで必要となるような React の知見は当然なく、 Suspense とかもちょっと聞いたことある程度でそもそも何なのかよく分かっていなかった。あとは Next.js とか Storybook とか Jest とか MSW とか Recoil とか TanstackQuery みたいなフレームワーク・ライブラリも未経験のものが多く、何からキャッチアップすればいいのやら途方に暮れた。が、流石に半年も触っていたのでだいぶ分かってきた。

それまで触っていた Elm はシンプルで綺麗な唯一の方法を提供する一方で、禁欲的というか「そんな無理して複雑な事をするならボイラープレートを書いてた方がマシでしょ」みたいなところがある。React (というかフロントエンドは大体そう)は真逆で、大勢がとにかく物凄いエネルギーとコストを投じて理想を追求していく。自分はミニマリスト気質なのもあって前者で満足していたのだが、正直ブルーオーシャンすぎて5年もするとやることが無くなってしまった(それは良いことでもある)。現職は React をバリバリやる感じなので正直ついていけるのかという不安はあったものの、他にもいろんな要因が重なり「もう一度フロントエンドやり直してみるか〜」という気持ちになった。

そういうわけで、それまで横目でウォッチしていた数年分のフロントエンド技術を一気にキャッチアップすることとなった。心配していたのは、ずっと Elm をやっていたせいで React のやり方に拒否反応が出て「Elm ではこんな風に書くんだけどなぁ」みたいな事をつい口走って煙たがられないか、というところだったが、蓋を開けたら全然そんなことはなくて安心している。アンラーンは特に意識していたので順調に出来たし、むしろいろんな新しい書き方を覚えて発想の幅が広がった。転職前の思惑通りとにかく学びが多い。

フロントエンドガチ勢の集い

そもそも転職のきっかけは現職のフロントエンドエンジニアからお誘いを受けたこと。その方は界隈でも有名な方だということは知っていたのだが、入社して一緒に仕事をしてみるとやっぱり凄い。フロントエンドガチ勢は CSS の手札がとにかく多い。多い時は知らない文法を4、5個見つける日があったほど。しかも同等の実力者が何人もいたりする。

前職ではフロントエンドが実質的に自分一人のような状態だったので、設計をゼロから好き勝手に出来た一方で他の人から学ぶことも難しかった。そうすると、自分の発想の幅の中で問題を解決するのだが、なかなか手札が増えていかない。70 点が求められる仕事で 80 点の解決策を思いついたらそこで終わってしまい、90 点以上の解決策は一生知ることができない。

人によって得意分野と不得意分野があってパラメータが上手く凸凹になっているのも面白い。ある人は CSS が得意、別の人はライブラリにめちゃ詳しい、Figma を使いこなす人もいればアクセシビリティに強いこだわりを持っている人もいる。なのでお互いに学び合えるし、それらが集合知になっていくのが良い。

職種の多い開発組織

フロントエンド内でも多種多様なスキルを持った人が居るのだが、開発組織全体でも同じことが言える。デザイナー・PdM が多数在籍するほか、スタートアップには珍しく QA 組織(内製)がある。あとセキュリティとかイネーブルメントをやるグループがあったり。プロダクトの開発者はフロントエンドとバックエンドが明確に分かれている。もちろんフルスタックなスキルを持ったメンバーも居るが、皆がそうであるよりはそれぞれが専門性を活かした方が良いという思想が根底にある。

前職ではエンジニアのレベルが総じて高かったが、職種のバリエーションは少なかった。なので、本などで「世の中にはそういう職種(例えば PdM とかデザイナーとか QA とか...ただし正社員で)が存在するらしい」ということは知っていたが実際に見たことはなかったので、そういう人々のいる環境に興味があった。自分はどっちかというと、誰か器用な人が片手間にやったり大勢がたまに集まって何かするよりも、ある専門領域をガッツリ見る人が居た方が上手く回るだろうと信じているので、その辺の思想ともマッチした。

というわけで、色んな人が居て面白い。ただし、そのぶん開発プロセスを整えるのは難しい。それはもう本当に。

最近の興味

最近フロントエンドの中で担当分野を決める動きがあったので「テスト(ユニット, 結合, E2E)」の担当になってみた。理由としては、相対的に手薄かつ他にやりたい人が居なかったから。設計とか UI コンポーネントとかも興味なくはないけど、他にやりたい人・得意な人が沢山いるからいいやという感じ。テストは書き慣れていないとどんどん腰が重くなるので、書くのが面倒そうな場所を開拓してハードルを下げていきたい。

Vitest の Browser Mode (experimental) でファイル読み込みのテストを書く

趣味でブラウザ上に画像や音声を読み込んで作業する React アプリを作っているのだが、 Vitest + Testing Library でテストをしようと思ったらファイル読み込み部分でつまづいた。Node.js 上でブラウザ環境をシミュレートしている部分がそのままでは上手く動かないので、 polyfill を入れたり沢山モックを差し込んだりするとなんとか動く。が、色々弄りすぎて本当にテスト出来ているのか怪しいし、やはりリアルなデータでテストしたい。

で、リアルなブラウザ環境でテスト出来ないかなと調べていたところ、2つの候補が挙がった。

両方とも experimental 。前者は Vitest をそのままブラウザ上で実行するというもので、後者は Playwright のテストをコンポーネント単位で行えるようにするもの。どちらも一長一短だが、今回はアサーションもブラウザ上で行うのが都合が良かったので前者の Vitest Browser Mode を採用した。

導入

導入は簡単。vite.config.ts の testbrowser の設定を追加するだけ。デフォルトでは webdriverio が使われるのだが、馴染みのある playwright を使うことにする。

export default defineConfig({
  ...
  test: {
    ...
    browser: {
      enabled: true,
      provider: "playwright",
      name: "chromium",
      headless: true,
    },
  },
});

このように設定を変えてテストを実行すると、あれこれライブラリが足りないと親切なプロンプトが出るので従っていくと色々インストールしてくれる。(CI には npx playwright install を追加する必要がある)

テストが再び動くまで

上記の手順でとりあえずテストを起動できるようにはなるのだが、そのままでは正常に動かなかった。 @vitejs/plugin-react can't detect preamble というエラーに遭遇して途方に暮れていたところ、Vitest のコントリビュータが書いている example を発見。この example は公式ドキュメントにマージされていないので、 2023/9 現在この fork されたブランチでしか見ることができない。惜しい。

まず、 setupFiles におまじないを入れると上記エラーが消える。

// 引用: https://github.com/vitest-dev/vitest/blob/userquin/feat-isolate-browser-tests/examples/react-testing-lib-browser/src/test/setup.ts
import "@testing-library/jest-dom";

if (typeof window !== "undefined") {
  // @ts-expect-error hack the react preamble
  window.__vite_plugin_react_preamble_installed__ = true;
}

あとは、ブラウザ環境なので本物の DOM の上にマウントするようにテストコードを書き換える必要がある。

// 引用: https://github.com/vitest-dev/vitest/blob/userquin/feat-isolate-browser-tests/examples/react-testing-lib-browser/src/utils/test-utils.tsx
export async function createContainer(id: string, node: ReactNode) {
  const container = document.createElement('div')
  container.setAttribute('id', id)
  document.body.appendChild(container)
  const root = createRoot(container)
  root.render(node)

  await nextTick()

  return root
}

testing-library を使っている場合、 root の代わりに within(root) を返しておくと screen と同じように使えたりする。

ところで、上のコードの通りにすると複数の root からそれぞれ React アプリが起動するため、同じグローバルオブジェクトを参照していたりすると問題になることがある。自分のケースでは jotai の Provider で <App> を囲むなどして問題を回避することができた。

他に細かい部分では timers/promisessetTimeout が応答しなくなったりしたのでブラウザ標準の setTimeout で書き直したりした。

が、問題としてはそのくらいで、テストの本質的な部分に関してはほぼノータッチで移行することができた。 polyfill にも別れを告げることができた。

実ファイルを読み込むまで

ブラウザ環境で動かすため、テストに使うファイルを fs モジュールで入手することが出来なくなってしまった。そこで、代わりに Vite の loader を使って直接 import した。Vite では import foo from "foo.bar?raw" のように書くとファイルをテキスト形式で読み込むことが出来るが、バイナリは対応していないようだったので、自前で用意した。

const arrayBufferLoader = () => ({
  name: "arraybuffer-loader",
  transform(_code: any, id: string) {
    const [path, query] = id.split("?");
    if (query != "buffer") {
      return null;
    }
    const hex = fs.readFileSync(path, "hex");
    return `
    const hex = "${hex}";
    const arrayBuffer = new Uint8Array(hex.match(/../g).map(h => parseInt(h, 16))).buffer;
    export default arrayBuffer;
    `;
  },
});

export default defineConfig({
  plugins: [arrayBufferLoader(), ... ],
  ...
});

参考: javascript - Import raw image data into a script with vite - Stack Overflow

これで import foo from "foo.bar?buffer" のようにして ArrayBuffer を読み込むことが出来るようになった。ただしこのままだと foo の型が分からないと TS のコンパイラに文句を言われるので、vite-env.d.ts に次のような宣言を加える。

declare module "*?buffer" {
  const src: ArrayBuffer;
  export default src;
}

これで @ts-ignore とか as とか使わなくても ArrayBuffer として扱われる。嬉しい。

その他、モックの制約など

今回は最終的に使わなくなったのだが、vi.mock が Browser Mode では動かないと書かれている。その Issue は未だ open だが、vi.hoisted が実装されたことにより named export に対して spyOn できるようになったとのこと。

it's now possible to use vi.spyOn on an ES module named export in browser environment

実際やってみたら確かに問題なく動いていそうだった。

実際の移行の様子

github.com

感想

  • ファイル読み込みのテストでモックまみれになっていた部分がごっそりなくなってスッキリした
  • テストの本質的な部分に関してはほぼ書き換えずに移行できた
  • Playwright を起動する時間だけ立ち上がりが遅いが、個人的には全く気にならないレベルだった

ブラウザ上のテスト実行、流行るといいなぁ。

Deno Deploy で WebAuthn を使ったサイトを作ってみた

作ったもの

Kaleidoshare という、オンライン万華鏡を作って共有できるサービスを作ってみた!

kaleidoshare.deno.dev

コードはここ。

github.com

作品は Twitter で公開できる。

まだまだ粗い部分が多いのだが、そもそもの目的が技術検証なのでまあこのくらいの完成度で良いでしょうということで(メンテするのがめんどくさい)。でも面白いと思ったら試してみてね。

動機

  • Deno Deploy は Cloudflare Workers のようにエッジに簡単にデプロイできるサービスの中でもパフォーマンスで群を抜いているらしく、以前から気になっていた。最近 Deno KV という分散ストレージが使えるようになったので試してみたい。
  • せっかくなので WebAuthn によるパスワードレス認証も試してみたい。最近はプラットフォームのパスキー対応が進んでいるのでデバイスを跨げるはず。
  • ついでに最近のフロントエンドのツールを色々キャチアップしたい。

この辺を満たす適当なお題を考えたところ、万華鏡に行き着いた。

サインアップ・ログイン体験

指紋認証でパスワード不要。最高。

サインアップ

ログイン

この例ではデバイスに保存しているけど、パスキーで登録すれば別のデバイスでもログインできる。

感想など

  • 開発時のコードがそのまま Deno Deploy 上でも動いて素晴らしい。サーバーレス環境で動くようにコアレベルで抽象化されていて、例えるなら express で作ったアプリが Lambda 上で動くような感じ。KV も LocalStorage くらいの手軽さで使えて面倒なセットアップが不要。
  • Deno 自体には慣れる必要がある。deno.json に import map を書く方法とか、エディタのプラグインの使い方とか(再起動・ライブラリのキャッシュが必要)。Node.js 互換は日が浅いので制限があり、例えば esm.sh を使うなどして回避する必要がある。最初は Vite も Deno で実行していたがバグがあったので諦めて Node.js に切り替えた。今後に期待。
  • Deno KV は value のサイズが 64KB に制限されており、作品ごとの OGP 画像がそのままでは保存できなかった。仕方がないので image uri を分割して保存して、取得時に結合して復元するということをした。あまり大きなデータの保存は想定されていないのかもしれない。
  • WebAuthn はクライアント側が簡単なので油断していたらサーバー側がしんどすぎたのでライブラリに頼った。とはいえフローは簡単で、サインアップ・ログイン共に「サーバーにリクエスト -> サーバーが生成した情報をブラウザに渡す -> ユーザーが認証ステップを踏む -> 結果をサーバーに返す -> サーバーで検証して情報を保存」をすればいい。
  • Playwright で WebAuthn を動作させるには Chromium で virtual authenticator を使う。ただしこの方法ではログイン時にユーザを指定しないと動作しなかったため、テスト時だけはサインアップの時と同じユーザー名をサーバーに送信するようにした。
  • まだまだベストプラクティスが分からない。パスキーはデバイスを跨げるがプラットフォーム(Apple, Google, Microsoft)は跨げない。一応それを考慮して複数のクレデンシャルを登録できるようにしてはみたが、そんな罠を知っているユーザーは稀なので不慮の事故が避けられない。復帰用にメールアドレスなどを登録した方が良いかも。

(Appendix) 要素技術一覧

想像以上に多岐に渡る技術をキャッチアップ出来て面白かった(本当は Deno Deploy や WebAuthn 以外にも色々工夫した部分を語りたいのだが、流石に話題がとっ散らかるので泣く泣く削った)。

2023 年、改めて React と Elm Architecture を比較する

最近 React のドキュメントが新しくなったということで読んでみた。第一印象としては、とにかく懇切丁寧で React というか JavaScript すら初心者という読者でも基礎的な考え方が身に付くようになっている。ただ、深い内容まで読み進めると「同じ Virtual DOM のフレームワークでも Elm とだいぶ違うな」と改めて思った。

これはどちらが良いとか悪いということではなく、一長一短あると思う。筆者は長いこと Elm を使ってきたが React も嫌いではなく、趣味を含め色々な場面で重宝している。ただ、 Elm Architecture の提供するシンプルな仕組みには依然として価値があると思っており、それがあまり世の中に知られていないのが勿体無い。というのが、この記事を書こうと思った動機である。

昔は「部分的に取り入れても Elm メリットは享受できないから Elm やってよ」だったが、そんな余裕はなくなってきたので良いアイデアだと思ったら盗んで欲しい。

状態の居場所

React の状態はコンポーネントの各所に偏在している一方、 Elm の状態はコンポーネント*1の外にある。 次のコードは、Elm アプリケーションを定義する最もシンプルな3つの型である。

init : model
update : msg -> model -> model
view : model -> Html msg

この中の model というのが「状態」を表す型であり、init が初期状態、 update が状態を更新する関数である。見ての通り、view は model を外部から受け取る純粋な関数であり、 React の useState のように内部で状態が初期化・更新されることは決してない。このように Elm アプリケーションの状態は view とは完全に独立しているため、view のないヘッドレスなアプリケーションを init と update だけで構築してテストすることも可能である。

一方、React の場合はまずコンポーネントがなければ何も始まらない。実は React でも自前で Virtual DOM のライフサイクルを制御しようと思えばできるのだが、そんなことをしている人は誰もいない。チュートリアルや有名フレームワークはまず App というコンポーネントを定義し、その中で useState するように教えている。

コンポーネントの中で状態を扱うと、 DOM のライフサイクルの影響を受けやすい。例えば、一度マウントしたコンポーネントを一時的に消して復活させると、そのコンポーネントの状態は保存されていない。そのような状況下でも状態を維持するためには一つ上の階層にコンポーネントの状態を持っておく必要がある。

Elm では、最初から全ての状態をコンポーネントの外に持っているため、そのような問題は起こらない。また、状態をコンポーネントの外に持っているとリセット操作も直接的にできる。React でコンポーネントの状態をリセットしたいときに key を使うことがある。これはコンポーネントの状態がライフサイクルの影響を受けることを逆に利用しているのだが、直感的かどうかはやや疑わしい。

と、ここまで Elm の状態管理のメリットを述べてきたが、欠点もある。全ての状態を外で管理しているため、コンポーネントの外側にボイラープレートが増えるのだ。ただし、私見を述べると「想像するほどの面倒さではない」とは思っている。

コンポーネントの外に書かれるボイラープレートとは、例えば「あるコンポーネント A でイベントが発火したら、そのコンポーネント A の状態を更新する」というものだ。これだけ聞くと、コンポーネント内部のあらゆるイベントハンドリングを使う側が行わなければいけない(言い方を変えるとカプセル化を破壊する)ように思えるが、実際にはそうならない。コンポーネントの外側で把握すべきことは「何らかのイベントが発生したら何らかの更新をする」ということだけだ。運送業者が宅配便の中身を把握していないのと同じだ。

全ての状態がコンポーネントの外にあるということは、巨大なツリー構造のデータ(model)を描画関数(view)に渡すということになり、これも何となく乱暴に思える。が、規則的にネストすることによって、ある view が扱う状態はその view が関心のある局所的な部分だけで済むようになる。例えば elm-spa-example の Page 以下のモジュールは、それぞれのページに必要な Model や Msg が定義されており、 view もそれ以外の情報を参照しない。また、React.memo 相当の処理をする Html.lazy 関数もあるのでコスト面も問題ない。

副作用の扱い

React は useReducer という仕組みによって、状態の更新ロジックを分離することができる。ここで reducer 関数には、何らかのアクションを受け取って古い状態を新しい状態に更新する処理を書く。が、ここで reducer 関数は「純粋」である必要があり、つまりは非同期処理が書けないという問題が発生する。例えば HTTP リクエストを送信しつつ、何らかの状態を更新するには、前者をイベントハンドラ内に書き、後者を reducer で行う必要がある。すると、関連のある二つの処理が分離され、かえって見通しが悪くなる(useReducer の使い方を間違っていたら教えてほしい)。

Elm では話は至ってシンプルで、reducer 関数に相当する update 関数が副作用を同時に起こせるようになっている。これは上のコードを「副作用あり版」に変形したものだ。

update : msg -> model -> (model, Cmd msg)

Cmd というのはコマンド(Command)のことで、これを Elm ランタイムに返すことで副作用を実現する。例えば HTTP リクエストの場合、Elm ランタイムに「リクエストを送信してくれ」というコマンドを送る。すると Elm ランタイムはレスポンスを msg 型に変換し、再び update 関数を呼んでその msg を渡してくれる。Cmd msg という型は「msg 型でコールバックされるコマンド」を表している。

同じことが init にも言える。

init : (model, Cmd msg)

この型が示す通り、 Elm では初期化プロセスとして副作用を起こすことが最初から想定されており、あまり特別感はない。実際、初期データを取得するために HTTP リクエストを送信するというのはありふれた話だ。しかし何故か React のドキュメントでそのケースが扱われるのはかなり後の方で、探すのに苦労してしまった。「エスケープハッチ」扱いはどうにもモヤモヤする。

コールバック関数は update だけ

先日、同僚氏に「Elm で dispatch はどうすれば良いか」と尋ねられて、ああ確かに Elm はそういうものがないな、と思った。

なぜか。最初のコードを再掲するので、今度は view 関数の戻り値を見てほしい。

init : model
update : msg -> model -> model
view : model -> Html msg

Cmd msg が「msg 型でコールバックされるコマンド」ならば、Html msg は「msg 型でコールバックされる HTML」である。すなわち、イベントは msg の型で update 関数に渡される。何かがあれば必ず update 関数を通るし、それが唯一のコールバック関数であるということだ。この強力な性質により、 Elm のコードは誰が書いても大体似たような感じになる。

シンプルさを貫いた設計

Simple vs Easy という話がどこまで共通認識になっているのかは分からないが、 React に比べて Elm は圧倒的に「シンプル」側に倒した設計になっている。個人的に考えるシンプルさのメリットは以下のようなものだ。

  • コードを見れば何が起きているか分かる
  • 覚えなければいけない特別なルールが少ない
  • 予想外の挙動が起きにくい

ただし、これらのメリットによって喜ぶ程度は、かなり人によりバラツキが大きいように思う。ぶっちゃけ、シンプルで美しいからと言って機能性の乏しさを我慢できるのはオタクだけなのではないかと思ったりする。実際、ボイラープレートが多いのは客観的に見ても面倒だと思っており、「シンプルさのために多少の面倒は我慢しなさいよ」と言うのは乱暴だろう。

しかし、考えてみれば古来から言われてきた Model と View の分離、 React も是としている「ビューは純粋な関数」を何よりも体現できているのが、この Elm Architecture であると思う。ある種の完成された形として後世に受け継いでいきたい。

Elm Architecture に関しては公式ガイドがかなり分かりやすく書いてあるので、詳しくはこちらを参照してほしい。 guide.elm-lang.org

*1:Elm コミュニティはコンポーネントという呼び方を避けているが、この記事は React ユーザ向けに書かれているので気にしないことにする

極端なことを言っている時、何を考えているのか

たまに極端なことを言ったりすることがある。

「ぶっちゃけ X って要らなくない?」
「それなら X しちゃえばいいんじゃないか」

そうしたら、ある日そこから議論が収集つかない方向にいってしまい、後になって「ああ、あれはそういう意図だったんですか。あんまり極端な物言いをされると拒否反応を起こすのでやめてください」のようなことを言われた。まあ会社なのでそこまで出鱈目なことは言っていなくて真面目に議論していたつもりだったのだが、なるほどそれで噛み合わなかったのかと妙に納得してしまった。

色々考えているうちに、自分がそういう極端なことを言っている時に何を考えているのか整理したくなった。

自分の中の常識や思い込みを壊したい

長い時間をかけて自分の中に培われた常識や思い込みを、時に疑ったり真逆の思考をしてみたくなることがある。

例えば、ずっと静的型付き言語でやってきたのにいきなり「型なんて要る?」とか言い出してみる。この時「いや普通に考えて要るだろ」という心の声をまず押し殺す。そして「わざわざ分かりきってることを書くの面倒なんだよな」とか「ノイズが少ない方がコード読みやすくね?」とか「スキーマを変えるときに書き直す箇所が極力少ない方がいいよね」とか「型の名前を書くためにいちいちライブラリを調べに行くのだるくね?」とか、そっち派の思考にどっぷり使ってみる。すると「全然ダメだと思ってたけど良いこと沢山あるじゃん」となる。そこで結論が出ればそこで終わり、動的型付けで行きましょうという話になる。が、そういうわけにも行かないので今度は反対意見を出してみる。「書いたり読んだりする時間が多少増えたところで、後でバグを追跡している時間の方が長いから全体としては大幅な短縮になる」とか「僕らは Matz みたいな天才じゃないのでしょうもないところで良く間違える」とか。で、何回か反論を繰り返していると最終的に「少なくとも自分にとっては数百行を超えるコードは静的型付き言語の方がメリットが大きい」みたいな結論になる。

こうして、あえて真逆の思考に乗ることで「意外とメリットも多くあった」ことが分かったし、自分が今の思想を持っている根拠も確認することもできた。全体として解像度も上がった。最初から自説を全く疑わなかった場合、得られるものは少ないと思う。ついでに言うと、相手の言い分を理解していた方が議論を進めやすい。「なるほど、あなたの言っていることはもっともです。しかし〜」と続ければ、相手は自分の主張が理解されたことにまず安心するし、続く議論も建設的なものになる。

この例では自説は最終的に曲がらなかったが、最終的に完全に否定されることもある。しかし、むしろその方が成長できるので良かったりする。「あの時はこんな風に考えてたな」と語ることに何も恥じることはない。価値観のアップデートは心地良いものだ。

「盲信している人」と思われたくない

思えば、自分の考えを疑う癖がついたのは新卒時のある先輩社員の影響を多分に受けている。

当時、その先輩が「コードは極力綺麗に書け」と口を酸っぱくして言っていた。ほとんど洗脳に近い状態になっており、研修後の仕事でそれを実践した。プロジェクトの進捗は思わしくなかったがコードは綺麗に書いていたので仕方ないかくらいに思っていたのだが、ついに先輩に「この調子だとコードを綺麗に書こうとする人にはプロジェクトを任せるなってことになるよ」と言われてしまった。その時、固く信じていた「コードは(いつでも・極力)綺麗に書くべき」はもっと早く疑うべきだったと反省した。

ところで、その職場では Java が使われていたのが、ある日 Ruby の話をしていた時に「いやいや型書かないとかw」みたいなことを口走ったら、先輩が動的型付け派の立場を借りて猛反論してきた。「僕は静的型付けの方が良いと思ってるけど、世の中には動的型付けの方が良いと言っている人も沢山いるし、現にそれで生産性を上げている例が沢山あるが、そこに対してはどう思うの?君は静的型付けのメリットを色々説明してくれたけど、それは本当にメリットと言えるの?」と、確かそんな感じで説教された気がする。

彼は自身の考えも疑っていた。「僕はどうしても今流行りの関数型言語のメリットがわからない。コードをいくらか読んだがカプセル化が破壊されていて酷いものだ。しかし流行っているからにはメリットがあるのだろうし、自分がその境地に至れていないだけかもしれない。だからどういうメリットがあるのか経験者の君に教えてほしい」と言ってきた。自分の理解できないものにも価値を見出そうという姿勢は見習うしかない。

これらの経験から、自分の考えをとことん疑ってみる癖がついたと思う。自分の言っているメリットは本当にメリットなのか、「なぜ」を繰り返して論拠を緻密に言語化することに労を割いている。逆に「盲信している人」は思慮が浅くかっこ悪いと思うようになったのかもしれない。だから Elm にどっぷり浸かりながら「関数型そんなに良い?」とか「オブジェクト指向いいよね」とか「普通は React じゃね?」とか「C++ いいじゃん」とか言ってしまう。「盲信している人」の宣伝は、推しの評価をかえって下げてしまう。選定された理由が説得力を持たないからだ。

「一理ある」をきっかけに議論を進めたい

話を戻す。極端なことを言っている時、相手にもゼロベースで考え直してもらうことを暗黙に期待していると思う。

(なんかこの人は変なことを言っているが、どういう意図なのか興味あるな。一見あり得ない発想だが、よく考えてみると言っていることは一理あるし、変に思うのは自分の前提が間違っているのかもしれないな。)

と、少々わざとらしいが受け手に期待する反応はこんな感じだ。そして

「それってどういう意味ですか?」

と聞いてくれたならば、「待ってました」とばかりに真意を説明するだろう。「一理ある」というのがポイントで、極端な意見には欠点が沢山あるので、そこを否定に来られても困る。こちらも完璧だとは思っていなくて、伝えたいのは「一理ある」の部分だけだ。言い回しを極端にしたのは、その一番言いたい一点をインパクト強めに伝えるためだ。 そこを汲み取ってもらえたのであれば、「じゃあ、その良い点を上手く活かすために欠点となっている課題を解決しましょう」という風に建設的に議論が進むはずだ。

しかし、このやり方は相手の受け取り方に大きく依存するため、諸刃の剣ではある。

(なんかこの人は変なことを言っているな。)

で終わってしまうと、そこで詰む。こちらの真意が伝わる前に相手が論破モードに入ってしまい「その意見は色々おかしいんですよ。いいですか。」と始まってしまう。こうなるともう何を言っても「いいえ、それはこういう点で穴があります」となり建設的な議論とは程遠くなってしまう。こちらの意図、つまり「打開策のアイデアを出すために、一旦常識的な考えを捨てました。もちろん穴があるので塞ぐ方法を一緒に考えてください」という意図が伝わらないといけない。素直にそう言えばいいという話ではあるが。

建設的なモードに入るか論破モードに入るかは相手との関係性にもよる。当たり前だが、信頼していない相手の言うことはおかしく聞こえる。「ストーリーポイントなぞ要らん」と言っているのが有名なアジャイルコーチだったら、一発で「なるほど」と思って聞くと思う。

定説を覆してマイノリティの賛同を得たい

世の中的には常識になっているが、自分にとってはしっくりこない言説があったとする。最初は「そんなものかな」と、騙されたと思って従ってみるが、だんだん違和感が隠しきれずストレスが溜まってくる。そのうちに

「有名な人が言ってるからみんな影響されてるだけじゃないの?」
「多数の二流・三流の声が大きいだけで、一流は全然違う考えを持ってるんじゃないの?」
「本当はみんな違和感を感じてるけど、空気を読んで言えないだけじゃないの?」

などと考え始めてしまう。 「マイクロサービスで生産性を上げよう」みたいなやつだ。しばらく経ってから「やっぱり基本はモノリス」の波が来たが、最初は誰も否定できる空気ではなかったと思う。

とは言え、何者でもない人が真っ向から反対しても勝ち目はないし、何より自分の立場が危うくなる。仕方がないので、なんとか適当なノリを醸し出しつつ本音を言ってみたりする。予想通りあまり賛同は得られないが、引っかかった人がたまにいいねを押してくれる。

無難な表現は面白くない

そして最後に、単純に面白い言い回しで興味を引きたいという話もある。

バランスの良い当たり前の意見を普通に言ってもあまり面白くない。ちょっとひねりが効いていたり、インパクトのある表現が必要だ。これが政治家だとちょっと皮肉の効いたギャグでも揚げ足を取られて大変なことになるが。インフルエンサーが放つ意外な言葉も面白い。こんまりが片付けを諦めたと言えば、それだけでもう面白い。

とまあ、大体こんな感じで考えていることは整理できたかな。