ひだまりソケットは壊れない

ソフトウェア開発に関する話を書きます。 最近は主に Android アプリ、Windows アプリ (UWP アプリ)、Java 関係です。

まじめなことを書くつもりでやっています。 適当なことは 「一角獣は夜に啼く」 に書いています。

MySQL (InnoDB) とトランザクション分離レベル・ロック

(注記) 去年書いて下書き状態になってた記事をそのまま公開した。 MySQL 8.0 公開前に書いた内容なので MySQL 5.7 について言及しているが、多分 MySQL 8.0 でも基本的には変わらない気がする。

背景・目的

DB 上に指定の ID の行が存在していれば UPDATE して、存在していなければ INSERT したい、ということは web アプリケーションを書いているとよくあること。 SQL:2003 標準には MERGE が導入されていて、Oracle Database なんかだと MERGE を使用できるようだが、MySQL では使えない (バージョン 5.7 時点)。 (代わりに INSERT ... ON DUPLICATE KEY UPDATE がある。)

また、複数のテーブルで 1 つの集約 *1 を表現していて、集約の更新時に不整合が起きないようにロックをかけたいということも多い。

といったあたりで、MySQL (InnoDB) における参照や挿入、更新とトランザクション分離レベルやロックについて調べたのでまとめておく。 (初心者が調べてみたぐらいの内容なので、指摘やコメント等頂けると嬉しいです!)

調べた結果どうしたらいいかの個人的見解

いろいろ調べた結論としては、個人的には以下のような感じがいいのかなーと思っている。

  • 参照系ではトランザクション分離レベルを REPEATABLE READ にする。 → 参照だけであれば一貫性のある読み取りができる。
    • REPEATABLE READ で一貫性のある読み取りができるのは InnoDB の実装に依存した挙動で SQL 仕様としてはファントムリードが起こり得るので、実装依存にしてよいと判断するかどうかは状況次第だと思うけども、多くの場合はそうするのが開発コスト的には一番安い気がする。
  • 単一の集約に対する更新系 (新しい集約の新規作成や既存の集約の更新; INSERT or UPDATE) では、トランザクション分離レベルを READ COMMITTED にして *2、集約ルート (に対応するテーブルの行) の存在確認を SELECT FOR UPDATE で行う。
    • 存在すればそのままロックを保持して更新処理を行う。 → 集約ルートに対する排他ロックを獲得しているので、他のトランザクションによる更新と衝突しない。
    • 存在しなければ INSERT する。 Duplicate error になったら (すなわち同時に実行されていた他のトランザクションによる挿入が行われた場合は) トランザクション自体を再開してやり直すか、エラーということでそのまま処理を終了する。 → こちらも挿入に成功すれば排他ロックを獲得するので、そのまま処理を進めて良い。 Duplicate error になった場合には対象行の共有ロックを獲得してしまうため、やり直す場合にはトランザクション自体をやり直さなければならない *3
      • トランザクション自体のやり直しをどう設計するのかは難しいけど、トランザクションの扱いをアプリケーションレイヤの責務にすることが多いと思うので、アプリケーションレイヤでやるのがいいのかも。 他の方法として、INSERT ON DUPLICATE KEY UPDATE を使うとかは有り。
    • 実装依存でも良ければ INSERT ... ON DUPLICATE KEY UPDATE で良い場面が多そう。
    • パフォーマンスを気にしなくてもいいならトランザクション分離レベルを SERIALIZABLE にするとかも有りかもしれない。
    • (だいたい songmu さんが書いてるとおりの結論ではある。)

参考ページ

MySQL (InnoDB) のロックについて

以下、自分が知らなくて調べた話をだらだら書いておく。

ロックの種類

MySQL 5.7 の英語ドキュメントにまとまっている。

インテンションロック

特殊なテーブルレベルのロックとしてインテンションロックがある。 これらはテーブルレベル・行レベルといった複数粒度のロックの共存のために存在するっぽい。

  • インテンション共有 (Intention shared; IS) ロックは、他のトランザクションによるテーブルレベルの排他ロックと衝突する。 (行レベルの共有ロックの前に獲得される。)
  • インテンション排他 (Intention exclusive; IX) ロックは、他のトランザクションによるテーブルレベルの共有ロックや排他ロックと衝突する。 (行レベルの排他ロックの前に獲得される。)
インデックスに対するロック

InnoDB では、インデックスに対するロックによって行レベルロックを実現している *5MySQL の公式ドキュメントやかみぽさんの解説がわかりやすい。

  • レコードロック (Record Locks) : インデックスレコードに対するロック。
  • ギャップロック (Gap Locks) : インデックスレコード間の隙間 (gap) に対するロック。 隙間は、複数のインデックス値にかかることもあるし、単一のインデックス値や空のところにさえもかかりうる *6。 ギャップロックは、他のトランザクションが同じ隙間のギャップロックを獲得することは防がない。 対象の隙間へのレコードの挿入のみが防がれる。 (つまり、共有ギャップロック (gap S-lock) も排他ギャップロック (gap X-lock) も効果は同じ。)
  • ネクスキーロック (Next-Key Locks) : レコードロックと、そのレコードの前の隙間に対するギャップロックの組み合わせ。
Insert Intention Locks

INSERT 操作時に、行の挿入前に獲得されるギャップロックの一種として、挿入インテンションロック (Insert Intention Locks) がある。

MySQLでINSERTのデッドロックに嵌る人を1人でも減らすために - ichirin2501's diary』 におけるデッドロックは、別のトランザクションが行を挿入して獲得した排他レコードロックと、挿入しようとしている行のための挿入インテンションロックの衝突が 2 箇所で起こってしまう、ということなのかな? と思ったりしたけど、InnoDB monitor の出力を見た感じでは共有レコードロック待ちになってたので挿入インテンションロック待ちとは違ってそうだった。 挿入インテンションロックは純粋にギャップロックとのみ衝突するもので、レコードロックとは衝突しない模様。

ギャップロックとファントム行、あるいはデッドロック

ファントムリードを防ぐために

ギャップロックと挿入インテンションロックのデッドロック : mysqlのネクストキーロックと挿入インテンションギャップロックのデッドロックを確認する | ++頭道++
mysqlのネクストキーロックと挿入インテンションギャップロックのデッドロックを確認する | ++頭道++ → この人の解釈では DELETE により指定の ID に対するロックができる (存在しないレコードに対するロックという解釈をしている) となっているが、私の手元の MySQL 5.7.20 で試した限りでは、存在しない ID を指定しての DELETE でギャップロックが獲得されていた。

下書きはココで終わっていた

多分他にも書きたいことがありそうだったけどもはや何も覚えてないのでこのまま公開する。

*1:DDD でいうところの集約。 Aggregates。

*2:この理由は主にギャップロック無効化のため。 ギャップロック有効だとデッドロックしうる; REPEATABLE READ でもギャップロック無効化できるしそうする方がいいのかもしれない

*3:そうしなければ、複数トランザクションで同一行に対する duplicate error が起こった場合にトランザクションをやり直さないと排他ロックの獲得ができず、デッドロックになる

*4:複数のトランザクションが DELETE するとそれぞれのトランザクションがギャップロックを獲得し、その後の INSERT がそれぞれ待ち状態になってしまう。

*5:という理解であってるよね?

*6:空のインデックスでも全体に対するギャップロックがあり得る、ってことかな?

PostgreSQL と H2 Database の両方で使える SEQUENCE 値生成方法 (MySQL の AUTO_INCREMENT 的なやつ)

SQL DB に新しい行を挿入したときに自動的に ID 値を生成して欲しいことはままある。 このような処理をしてくれるテーブル定義についての話。

MySQL だと AUTO_INCREMENT を使いがち

MySQL を使ってる場合は AUTO_INCREMENT で済ませちゃうことが多い (H2 Database でも使用可)。

CREATE TABLE "foo" (
  "id" BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
);

PostgreSQL のための AUTO_INCREMENT 以外の方法

ただ、PostgreSQL では AUTO_INCREMENT が使えない。 代わりに使える方法を 2 つ書いておく。

BIGSERIAL 型を使う

PostgreSQL で、テーブル定義時に型として BIGSERIAL 型を使用すると内部的に SEQUENCE を利用するようになる。

The data types smallserial, serial and bigserial are not true types, but merely a notational convenience for creating unique identifier columns (similar to the AUTO_INCREMENT property supported by some other databases). In the current implementation, specifying:

CREATE TABLE tablename (
    colname SERIAL
);

is equivalent to specifying:

CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
    colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
PostgreSQL: Documentation: 10: 8.1. Numeric Types

上の例では SERIAL 型が使われているが、これは INTEGER 相当なので、BIGINT 相当のサイズにしたければ BIGSERIAL を使用すること。

ちなみに BIGSERIAL は H2 Database でも使用できる (ちゃんとしたドキュメントは見当たらなかった)。 MySQL でも SERIAL は使用できる (MySQL の場合は SERIALBIGINT になる) が、BIGSERIAL は使えない (MySQL 8.0 で検証)。

外部 SEQUENCE 生成子を使う

標準 SQL に従った方法はこれっぽい。 上の SERIAL 型を使った場合と等価な例に載っているような形になる。 ただ、H2 Database は SEQUENCE の所有カラムを明示できないようなので、そこは指定しないでおく。 (H2 Database でも使いたいので。)

CREATE SEQUENCE "foo_id_seq";
CREATE TABLE "foo" (
    "id" bigint NOT NULL DEFAULT nextval('foo_id_seq')
);

WebdriverIO を使い始めるときのハマりどころ (geckodriver を添えて)

WebDriverFirefox を操作するために Node.js 用の WebDriver バインディングである WebdriverIO を使ってみました。

webdriver.io

使ってみると意外とハマりどころがあってちゃんと使い始めるまでに時間がかかったので、自分がはまったところを書き残しておきます。

そもそも WebDriver とは?

という方は、昔私が書いた下記の記事をご覧ください。

自分の環境

私は TypeScript で書いてるので、もともと JS で書かれてたサンプルコード以外は全て TypeScript のコードである。

ハマったところ

クライアントの生成 (接続先の指定)

例などを見ると、クライアントの生成は以下のように書かれているが、詳細なドキュメントが見当たらない。 (どこかにあるかもしれないが見つけられなかった。)

var webdriverio = require('webdriverio');
var options = { desiredCapabilities: { browserName: 'chrome' } };
var client = webdriverio.remote(options);

geckodriver を使う場合に、WebDriver のリモートエンドの URL を指定する方法がわからなかった。 TypeScript の型定義を見ると、オプションに baseUrl があったので以下のような指定をしてみたが、

import * as wd from "webdriverio";
let wdClient = wd.remote({ baseUrl: "http://localhost:4444" });

残念ながら以下のようなエラーが返ってきた。

Error: POST /wd/hub/session did not match a known command

このエラー内容から察するに、どうやら Selenium Server への接続を想定しているようである。 そしてよくよく調査すると、baseUrl に何を指定してもホスト 127.0.0.1 の 4444 ポートに対してリクエストを試みているようであった。 (baseUrl の指定が効いていない。) 他のオプションも試してみて、結論としては hostportpath といったオプションで、接続先を指定できた。

import * as wd from "webdriverio";
let wdClient = wd.remote({ host: "localhost", port: 4444, path: "/" });

非同期処理とエラー処理

サンプルコードを見ると、メソッドチェインで処理を繋げられるようである。

client
    .init()
    .url('https://duckduckgo.com/')
    .setValue('#search_form_input_homepage', 'WebdriverIO')
    .click('#search_button_homepage')
    .getTitle().then(function(title) {
        console.log('Title is: ' + title);
        // outputs:
        // "Title is: WebdriverIO (Software) at DuckDuckGo"
    })
    .end();

この例を見てもどういう順序で実行されるのかわからないだろう。

普通に実行するとすべてのメソッドは非同期処理になる。 しかし、WDIO というテストランナー上では同期的に実行される。

Each command documentation usually comes with an example that demonstrates the usage of it using WebdriverIO’s testrunner running its commands synchronously. If you run WebdriverIO in standalone mode you still can use all commands but need to make sure that the execution order is handled properly by chaining the commands and resolving the promise chain.

WebdriverIO - API Docs

初見殺しもいいところである。 ちなみに @types/webdriverio の型定義としては、同じメソッドで非同期的なときの返り値の型と同期的なときの返り値の型の intersection type で定義されてたりする。 難しすぎる。

上のサンプルコード (+ インポート処理やクライアント準備) を、TypeScript の async/await を使ってエラー処理も含めていい感じに書き直すと以下のような感じになる。

import * as wd from "webdriverio";

(async () => {

let client = wd.remote({ baseUrl: "http://localhost:4444", path: "/" });
let session = client.init();
try {
    await session.url('https://duckduckgo.com/');
    await session.setValue('#search_form_input_homepage', 'WebdriverIO');
    await session.click('#search_button_homepage');
    let title = await session.getTitle();
    console.log('Title is: ' + title);
        // outputs:
        // "Title is: WebdriverIO (Software) at DuckDuckGo"
} finally {
    await session.end();
}

})().catch(e => console.error(e));

一度理解してしまえばメソッドチェインで書けるのは便利ではあるが、最初の理解が難しかった。

余談だが、バージョン 3 でコア部分が Ajax/Promise ベースの Monad に書き換えられて、コマンドチェインや Promise の扱いがやりやすくなって今の形の API になったらしい。

Some big changes came along with v3. We’ve rewritten the whole core to an ajax/promise based monad. Instead of implementing a complex command scheduler or request queues we built the whole library on top of a monad construct. This allows us to chain commands as we are used to and keep stacktraces sane. In addition to that we wanted to have 1st level promise support. Therefore we used the Q library (there are already plans to move to native Promises) to integrate promises into the monad system. This works astoundingly well. Each command execution represents a promise. If you chain commands, the command waits until the previous command is resolved. On top of that, the optional modifier that you can pass to a monad makes the library incredibly flexible and extensible.

WebdriverIO - What's new in WebdriverIO?

セレクタについて

WebDriver では、要素の選択に使用できるセレクタに種類がある。

12.1 Locator Strategies

An element location strategy is an enumerated attribute deciding what technique should be used to search for elements in the current browsing context. The following table of location strategies lists the keywords and states defined for this attribute:

State Keyword
CSS selector "css selector"
Link text selector "link text"
Partial link text selector "partial link text"
Tag name "tag name"
XPath selector "xpath"
WebDriver

しかし、WebdriverIO ではその指定ができないようである。 どうやら Sizzle みたいな既存の一般的なセレクタライブラリに近くなるように内部的に使い分けしてくれてるっぽい。

The JsonWireProtocol provides several strategies to query an element. WebdriverIO simplifies these to make it more familiar with the common existing selector libraries like Sizzle.

WebdriverIO - Selectors

具体的にどういうセレクタを書けるのかは WebdriverIO の Selectors のドキュメントに書かれている。

id セレクタを使用しようとする問題

セレクタの話でいうと、geckodriver に対して await session.setValue('#search_form_input_homepage', 'WebdriverIO') を実行すると以下のようなエラーが返ってくる。

Error: Unknown locator strategy id

WebDriver には id lacator strategy というものはなく、geckodriver には実装されていないのだが、WebdriverIO は id locator strategy を使おうとしてこのエラーが出るようになっているらしい。 回避方法としては 「*#foo」 みたいな感じで ID 以外の条件も含めると良い模様。

WebdriverIO の Issue にもなっているが、「We can't remove the id selector just yet as it is still supported my many other drivers (also on mobile)」 って言って閉じられてる。 CSS セレクタがサポートされてる環境なのなら普通に移行できると思うのだけど、CSS セレクタがサポートされてない環境があるってことなのだろうか……。 (わからん。)

おわり

というわけで私がハマった WebdriverIO の罠でした。 多分ここら辺にハマっておけば後はいい感じに使えるはず。

それでは、よき自動化人生を!

読んだ : 最高のリーダー、マネジャーがいつも考えているたったひとつのこと

ラインマネージャに任用されたのでマネジメント系の本を読んだりしてる。 これは社長おすすめの一冊。

最高のリーダー、マネジャーがいつも考えているたったひとつのこと

最高のリーダー、マネジャーがいつも考えているたったひとつのこと

本書では、「マネジメント」、「リーダーシップ」、「個人の継続的な成功」 の 3 つの主題について、それぞれ最も重要なことは何かということが述べられる。 実際にマネージャとして仕事をしていくうえでは学ぶべきこと、知っておくべきことはいろいろあるけれど、根幹となる考え方として本書の内容は非常に明確でわかりやすく、実際に有用であると感じた。

読書メモ

組織の成功について

本書の第 1 部では、組織の継続的な成功について、マネージャとリーダーの違いと、それぞれで大切なたったひとつのことが述べられる。

マネージャについては以下のようなことである。

  • 部下の才能を業績に結びつけるいちばんの方法を見つけ出すことが、優れたマネージャの仕事。
    • ひとりひとりの強みや弱み、特色を見いだし、有効に活用すること。 ときには仕事の方をメンバーに合わせて変更する。
  • 部下のために働く。 部下にとっての目標を出発点にして、そこから企業の目標に結びつけることを考える。
  • 成長を手助けすること。 自社でのキャリアだけでなく、本人にとって最も良い道を探す手助けをする。
  • ひとりひとりの特色を見いだす。 そのための質問例。
    • 強みや弱み : 最近仕事が楽しかったのや辛かったのはいつか。 何をしていたときで、それは何故か。
    • 引き金 (動機づけ?) について : これまで一番うまくいったマネージャとの関係は? なぜ? いままでに承認を得た中で一番心に残っているのは? それはなぜ?
    • 独自の学習スタイル : これまでの仕事で一番多くを学んでいると思ったのはいつか? なぜ? 一番の学習スタイルはどういうもの?

ひとりひとりの 「人」 を見る、ということをすごく重要視している。 個人的にはあまり得意ではない部分なので、意識してやっていきたい。

会社のミッションと個人のミッションをすり合わせるという話については 『ALLIANCE』 が非常に参考になる。

ALLIANCE アライアンス―――人と企業が信頼で結ばれる新しい雇用

ALLIANCE アライアンス―――人と企業が信頼で結ばれる新しい雇用

リーダーについては以下。

  • すぐれたリーダーは、よりよい未来に向けて人々を一致団結させる
  • そのためにたった一つの大事なことは、普遍的なことを発見して、それを活用すること。
  • 未来を明確に示し、不安を取り除く。
  • リーダーシップの 3 つの規律。
    • 考える時間を作る : 未来を明確に示すためには熟考して結論を出す必要がある。 多忙な中でも時間を割いて熟考する。
    • ヒーローを慎重に選ぶ : 未来に向けてとるべき行動を示すために、ヒーローを選ぶ。 (この人のこういう行動が未来に向けて良い、ということを示す。)
    • 練習する : 伝えるための言葉やイメージ、ストーリーを練習する。 同じ言葉を何度繰り返しても繰り返しすぎだと思わないこと。 自分が繰り返しに飽き始めるころにやっと相手の心に届く。

目指す先を明確にするというのは非常に大事だと思う。 事業のビジョンを明確に打ち出すというのも、そのひとつなのだと思う。

意識はしていてもなかなか難しいことではあるが、マネージャとしては最初にこの 2 つの 「たったひとつのこと」 を大事にしていくと良さそう。

個人の成功について

第 2 部では、個人の継続的な成功について語られる。 本書では、個人の継続的な成功を 「可能な限り大きな影響を最も長い期間与えること」 とおいている。

  • 個人が継続的に成功するために大切なたったひとつのことは、自分がしたくないことを見つけ出し、それをやめること。
  • 正しい戦術を見つけ、それを活用するだけではありきたりな存在になる。 自分の個性をいかすこと。
  • 弱みを克服することは重要ではない。 人が最も多くを学び、やりがいを感じるのは強みの分野。
  • 強みを見つけ、それを伸ばすことは、必要ではあるがそれが最も重要なことではない。 強みを活かしつつも他のやりたくない仕事が多量にあると継続的な成功は難しい。
  • 自分がしたくないことをやめるための戦術 : 役割をやめる、役割を微調整する、正しいパートナーを探す、役割の中で自身の力を引き出す側面を見いだす
  • チームに対して、そして未来に対して最高の貢献ができるように軌道修正を行うのは、常に自身の責任である。

感想

組織で仕事をしていくうえで、本書に書かれている内容はいずれも重要であると感じた。 マネジメント研修でも、「仕事の側面に注目しがちだけど、人の側面も大事」 ということや 「仕事のサイクルの中で一番大事なのは計画で、目的を明確にしてそれを伝える必要がある」 というようなことを言われたのだけど、本書に書かれてる内容を踏まえてそういう話を聞くと納得感も高かった。

個人の成功についての話も大事な話で、「強みを伸ばす」 というのも一つの大事なことではあるけれど、強みとやりたいことが違う場合もあるし、強みを伸ばしながらも他の作業に忙殺される状況もあるし、そういったもろもろを避けるために 「やらないことを決めて、それをしないようにする」 というのをやっていこう。

読んだ : エンジニアリング組織論への招待

広木大地さんの 『エンジニアリング組織論への招待』。 良い本だと話題になってましたね。 うちのチームでも皆で読んでます。

本書の 「はじめに」 には、以下のように書かれています。

本書は、「不確実性に向き合う」 というたった 1 つの原則から、エンジニアリング問題の解決方法を体系的に捉える組織論です。 わからないものを避ける本能を、どのように理解し、克服し、導くのか。 テクノロジーを力に変えたい経営者やエンジニアリーダー、そして、今、かつての私と同じように悩んでいる人へのチャレンジのきっかけとなればうれしいです。

エンジニアリングとは 「何か役に立つものを」 「実現していく」 ことであり、そこで重要なことが 「不確実性を小さくしていくこと」 である、というのが本書の根幹にある考え方です。 役に立つものを実現していく流れの中には、「役に立つものとは何か」、「どうやって実現するのか」、「いつ実現するのか」 といった不確実さが多くあります。

不確実性を大別すると未来に対する不確実性 (環境不確実性) と他人に対する不確実性 (通信不確実性) に分けられます。 それらへの対処として、認知と意思疎通、組織論やアジャイルといった考え方について説明されます。

認知の話や組織論の話、アジャイルなどについて様々な本が世の中には存在していますが、「エンジニアリング」 という観点で一冊にまとめあげられた書籍は本書しかないのではないでしょうか? エンジニアリング組織における組織論についての、ある種の教科書的な書籍であると感じました。 エンジニアリングについての組織論の入り口としてはオススメの一冊です。

個人的に気になったこと

本書を読んでいて自分が感じたことや新たに発見したことを紹介しておきます。

リアルオプション戦略 (遅延した意思決定)

早期に大きな投資をして大きなリスクを取るのではなく、最初は小さな投資を行って不確実性を小さくしてから (成功する見込みが大きくなってから) 大きな投資をする、というようなものがリアルオプション戦略。

システム思考

ロジックツリーを作るような単純な依存関係の要素分解をするのではなく、様々な要素が複雑な依存関係を持っているとみなして全体を捉えるのがシステム思考。 同僚に聞いたら 10 年前ぐらいに流行ったらしい。 下記の書籍をおすすめしてもらったので今度読んでみる。

世界はシステムで動く ―― いま起きていることの本質をつかむ考え方

世界はシステムで動く ―― いま起きていることの本質をつかむ考え方

ジョハリの窓

自己について、自己が認識しているかどうかと他者に認識されているかどうかの 2 軸で四象限に分けたもの。

  • 自己が認識しており、他者からも認識されているもの : 開放の窓
  • 自己は認識しているが、他者からは認識されていないもの : 秘密の窓
  • 自己が認識しておらず、他者からは認識されているもの : 盲点の窓
  • 自己が認識しておらず、他者からも認識されていないもの : 未知の窓

メンタリングにおいては、メンターとメンティ互いの開放の窓を大きくしていくことが、対人リスクを下げて生産的な関係を構築していくことにつながる。

能力は習慣の積分、習慣は行動の積分

能力や、それによって生み出される成果についてはコントロールできないが、行動やその積み重ねである習慣についてはコントロールしやすい。 という話。

プロダクトマネジメントとプロジェクトマネジメント

プロダクトマネジメントはマーケット不安を抱えて、目的不確実性を扱う。 プロジェクトマネジメントはスケジュール不安を抱えて、方法不確実性を扱う。 プロダクトマネジメントはプロダクトを続けること (価値を提供し続けること) が目的で、プロジェクトマネジメントはプロジェクトを完了させることが目的。

アジャイル

アジャイルというのは開発方式を表しているのではなく、状態を表す。 アジャイルな状態まさに自己組織化された状態で、下記のような状態を指す。

  • 情報の非対称性が小さい
  • 認知の歪みが少ない
  • チームより小さい限定合理性が働かない
  • 対人リスクを取れていて心理的安全性が高い
  • 課題・不安に向き合い不確実性の削減が効率よくできている
  • チーム全体のゴール認識レベルが高い

アジャイルに関してはこれまでもいろいろ読んできたけど、本書では歴史なども踏まえて説明してくれていて、特に思想的な部分がわかりやすくて良かった。

暗黙知形式知についての SECI モデルや、リフレーミングに関わるダブル・ループ学習の話なども (これまでも見たことがあったけど) 紹介されていた。

プリンシパル・エージェント理論やエージェンシースラック

経済における人間関係を、依頼者 (プリンシパル) と代理人 (エージェント) の契約の束としてとらえる考え方をプリンシパル・エージェント理論というらしい。

そして、代理人が依頼者に対して嘘をついた方が利益を得られる状況の場合に、その利益分をエージェンシースラックというらしい。 (例えば、代理人が実際よりも多くコストを算出して依頼者に請求する場合とか。) エージェンシースラックを解消するために依頼者が払う監視やインセンティブなどのコストを、コントロールコストという。 また、代理人側がエージェンシースラックを解消するために払うコストをシグナリングコストという。

こういうの、いたるところで発生するはずだから意識していきたい。

多点見積もり

工数見積もりにおいて、平均的な場合の工数だけでなく、最悪の場合の工数 (もうちょっとちゃんとした言葉で説明されてる) なども見積もって見積もりに幅を持たせる手法が紹介されてた。

確かに見積もりには幅があるはずだけど、普段チームで会話するときには見積もりの幅を意識してないなーと思った。

権限移譲

権限移譲のレベルについて、デリゲーションポーカーで測るという方法や、その後に権限の可視化をしっかりしておくことが大事。 (チームが自発的、自律的に動きやすいように。)

技術的負債

マーティン・ファウラーの 「技術的負債の四象限」 やフィリップ・クルーシュテンの四象限、クラウス・シュミットによる数学的定義が紹介されてた。

本書では 「技術的負債の問題とは、経営者とエンジニアの間に存在する 「認識の差」 の問題」 って書かれてて、確かにそういう部分が問題になることも多いと思うのだけど、結構エンジニア側のチーム内でも認識の差があることも多いよなぁ、ということを思ったりした。 (個人的には、チーム内で認識を統一できていれば経営者側への説明もしやすいのではないか、って気がしている。)

取引コストと技術組織

プロダクト開発を内部のリソースで行うべきか、外部のリソースで行うべきか、ということを考えるために取引コスト理論が紹介されていた。

経済取引には取引コストが発生し、それは次の 3 つに大別できる。

  • 探索のコスト : 取引相手を見つけるために支払うコスト
  • 交渉のコスト : 取引相手と交渉を行うために発生するコスト
  • 監督のコスト : 取引相手が契約した取引を履行するように監督と矯正を行うコスト

外注して取引を続けた場合、システムについてその外注先しか把握できない状態になり、ホールドアップ問題 (取引コストが上がる) が発生したりもする、とのこと。

あと、社内でも IT 部門を P/L 独立させた場合などは、部門間で取引コストがかかることになって、外注するのと変わらない状況が発生しうる、みたいな話も。 耳の痛い話だ。

関連書籍の紹介

上でも言ったように、本書では様々な内容を取り扱っているので、特定の内容について掘り下げたい場合は別の書籍を読むのも良いと思います。 また、本書の内容とはちょっと違っているが、関連する内容の書籍もいろいろあります。 自分が読んだことのある書籍をいくつか紹介します。

認知についてや、なぜ行動を起こせないのか、リフレーミングという点については、『なぜ人と組織は変われないのか』 が興味深かったです。

なぜ人と組織は変われないのか ― ハーバード流 自己変革の理論と実践

なぜ人と組織は変われないのか ― ハーバード流 自己変革の理論と実践

組織学習 (リフレーミングについても) については 『チームが機能するとはどういうことか』 も良かったです。

チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ

チームが機能するとはどういうことか――「学習力」と「実行力」を高める実践アプローチ

自己組織化については 『エラスティックリーダーシップ』、自律やメンタリングについては 『人を伸ばす力』 も良かったです。

エラスティックリーダーシップ ―自己組織化チームの育て方

エラスティックリーダーシップ ―自己組織化チームの育て方

人を伸ばす力―内発と自律のすすめ

人を伸ばす力―内発と自律のすすめ

社内でも部署間で取引コストが発生してしまうというような話については、『サイロ・エフェクト』 が参考事例として面白い書籍です。

サイロ・エフェクト 高度専門化社会の罠

サイロ・エフェクト 高度専門化社会の罠