Notionに書きました。
https://www.notion.so/Angular-Elements-Angular-542cfebc09d7456791a66d7af8bbce5e
いつもの読後メモです。
今回読んだのはダニエル・コイル著の「THE CULTURE CODE」の日本語訳版。
この本は「チームの文化」というそこにあるのは確かなのに実体がつかめないダークマターのようなものに焦点を当てる。
実在の成功しているチーム、失敗しているチームへのインタビューや調査、そして多くの心理学的、社会学的実験のデータから、成功するチームのつくり方を明らかにしていく。全体を通して非常にエビデンス重視になっている。
ここ最近で読んだ本の中で一番面白かった。
これからチームを作る人、チームを任された人、あるいは自分のチームが十分に成功していないと感じる人には、この本をぜひ読んでもらいたい。 チームを成功させるためにやるべきこと、やってはいけないこと、その基本がこの本には多くのエピソードやエビデンスと共に語られている。 エピソードが多いため登場する人名も多いが、全体を通して読みやすく翻訳されている。
よくGoogleやNetflixのような一流企業のチームは素晴らしい文化を持っていると語られる。 この本を読むと、そうした文化は先天性のDNAのようなものではなく、スキルによって勝ち取り、育み、支えられているものだとわかる。 それは優れたリーダーが扇動するだけで出来上がるものでもない。日々の積み重ねで価値観を醸成していかなければならない。
この本では「心理的安全性」という言葉は使われないが、この本の中で「成功しているチーム」として挙げられるチームは例外なく心理的安全性が高い。 心理的安全性を高める方法に悩んでいる人にとっても、良いガイドになるだろう。
成功するチームには文化がある。その文化を作るスキルの磨き方を教えてくれる本だった。
強いチームの文化を醸成するためのカギは、高度なスキルを持つ優秀なメンバーを集めることにはない。迅速な意思決定と実行でもない。そもそも「強いリーダー」は必要ない。個性的でエキセントリックな天才も必要ない。野心的で挑戦的なビジョンは不要。最先端のテクノロジーも無用。決め手は日常の仕事での、ちょっとしたさりげない行動──それはしばしば当人も意識していない──にある。小さな行動の積み重ねが大きな違いを生み出す。 強いチームのエンジンに火をつけるのはいたって常識的な「普通の人」である。
多くのエピソードが紹介されるが、キーマンとなるのは特別な才能を持ったリーダーではなく、人当たりのよい常識人であることが多い。そういう人柄こそがチームをうまく機能させる。
チームの文化が大切だということは誰でも知っている。しかし、そのしくみとなると、きちんと理解している人はほとんどいない。... しくみがよくわからないのは、もしかしたら「文化」というものを誤解しているからかもしれない。私たちは、文化はDNAのようなものだと考えている。 強固なチームの文化というと、たとえばグーグルやディズニー、ネイビーシールズなどが思い浮かぶだろう。彼らには固有の文化がある。あまりに独自の文化なので、彼らにしかないDNAから生まれたとしか思えない。 つまり、文化は運命のようなものであり、努力でどうにかなるものではない。強固な文化を持つチームもあれば、持たないチームもある。運命とはそういうものだ。 しかし、この本はその考え方に賛同しない。私はこの4年の間に、世界でもっとも成功している8つのチームを実際に訪ね、分析を重ねてきた。たとえば、軍の特殊部隊、都市部の貧困地区にある公立学校、プロのバスケットボールチーム、映画スタジオ、コメディ集団、宝石窃盗団などだ※2。 分析の結果、それらのチームには共通のスキルがあることがわかった。
文化とはスキルによって作られるものである。というこの本の主題。
たいていの人は、「言葉は言葉でしかない」とは考えない。むしろ大切なのは言葉であり、チームのパフォーマンスはメンバーの「言葉の知性」と比例し、複雑なアイデアを的確に伝える能力が高いほど、チームのパフォーマンスも上がると考えている。 しかし、その考え方は間違っている。言葉はノイズだ。 チームのパフォーマンスを決めるのは、「ここは安全な場所だ。そして私たちはつながっている」というメッセージを伝えるしぐさや態度なのだ。
多くの場合、人を動かすのは言葉ではなく仲間の振る舞いだ。という集団的知性の話。
人間の脳が「安心」を合理的に理解するのであれば、シグナルは一度で十分なはずだ。しかし私たちの脳はそのように進化していない。脳のいちばんの仕事はむしろ心配することであり、とりつかれたように「危険」のシグナルを探している。だからこそ人類は生き残ることができた。 この危険を執拗に恐れる態度は、脳の奥深くにある扁桃体という部位から生まれている。扁桃体は原始的な脳の部位で、つねに周りの環境を監視して危険を探している。
繰り返し帰属のシグナルを送り続けなければならない理由について。「ここにいても安全か」という心配は本能であり、どこかに危険なサインがないかをいつも探している。
帰属のシグナルに、人格や規律は関係ない。大切なのは、メンバーが安心できる環境をつくることだ。 「私たちはつながっているか?」 「私たちは未来を共有しているか?」 「ここは安全な場所か?」 という問いに対して、「イエス」という答えを与えられる環境がカギになる。
「メンバーが安心できる環境」とはつまり心理的安全性の高いチームであるということだ。その環境をどう作るかという話が細かく書かれている。
いいチームをつくるうえでいちばん大切なのは、優秀なメンバーを集めることでも、経験豊かなメンバーを集めることでもない。それは、メンバーの机の位置だ。 「目で見えるといったごく単純なことが、とても大きな意味を持つ」とアレンは言う。「他のメンバーの姿が見える、他のメンバーが働いている場所が見えるといったことが、彼らの存在を思い出させるきっかけになってくれる。それが大きな力を持つようになる。
すべては 「私たちはつながっているか?」 「私たちは未来を共有しているか?」 「ここは安全な場所か?」
を確認できるということに通じる。
成功しているチームは、メンバー選びの段階からすでに成功している。 ... 成功しているチームは、腐ったリンゴに対してとても厳しい。それに加えて、腐ったリンゴを鋭く見抜くこともできる。おそらく後者のほうが、より重要な資質だろう。 「オールブラックス」の愛称で知られるラグビーのニュージーランド代表チームは、世界史上もっとも成功したスポーツチームの1つに数えられる。そんな彼らのモットーは、「愚か者は去れ」だ。シンプルな言葉だが、だからこそ大きな効果がある。
この本を読むとわかるが、チームに悪影響を与えるメンバーというのは明確に存在する。非協力的なメンバーを「腐ったリンゴ」と表現しているが、そのようなメンバーがひとりいるだけで他のメンバーによってそこが危険な環境になってしまうのだ。なので、チームメンバーは慎重に選ばなければならないし、チームのためにならないメンバーは改心してもらうか、出ていってもらうしかない。
成功しているチームは、チームワークが生まれるのを偶然にまかせたりはしない。メンバーに期待されていることを明確にしている。そして言葉や態度で、協力することの大切さを何度も伝える。
いいチームワークを生むためにメンバーにはメンバーの責任がある。リーダーがそれを主導する。
成功しているチームは、実行しやすいように、ごくシンプルなしくみをつくっている。たとえば、「すべてのミーティングでかならず全員が発言する」というようなルールである。
チームワークを高められるように仕組みとして当たり前化する。エピソードとして登場する多くのチームが何らかの習慣を持っている。
OKRについての本を読むつもり。
OKR(オーケーアール) シリコンバレー式で大胆な目標を達成する方法
いつもの読後メモ。
今回は巷で大絶賛の「エンジニアリング組織論への招待」を読んだ。
エンジニアリング組織論への招待 ~不確実性に向き合う思考と組織のリファクタリング
いままで曖昧に理解していたものの、いざ自分で話そうとすると言語化できなかっただろうたくさんの概念を、改めて理解できた部分が大きい。
「エンジニアリング」とは何か。「エンジニアリング組織」とは何か。そういった根底から改めて考え直すいい機会になった。
中でも「不確実性」という概念はさまざまな不安、問題と向き合うときの心強い味方になりそうだと思った。
人間にとって、本質的に「わからないこと」はたった2つしかありません。それは、「未来」と「他人」です。
未来がわからないことによる「方法不確実性」と「目的不確実性」、そして他人の考えがわからないことによる「通信不確実性」の3種類をどう扱っていくか、という話。
仮説思考は、経験主義をさらに生産的な(不確実性を削減する)ものにするための「大胆な跳躍」をもたらします。そして、仮説は、今あるデータからは、演繹的・帰納的には導くことのできないものです。人間的な直感やひらめきによって、今までの情報や様々な偶然が積み重なって生まれる跳躍であって、天下り的な結論や合議による凡庸なアイデアは「仮説」にはなりえないのです。
データや前提から導けないからこそ仮説検証の意味がある、という「言われてみればたしかにそうだ」と納得したフレーズ。
メンタリングでは、見えていない課題に自分から気づかせることを重視します。自分で気がついたことのほうが、積極的に解決することができるからです。
そうだよな〜〜、うまくそうやって導けるようになりたいと思う。
メンタリングでは、「次にとるべき行動」がはっきりするように促す必要があります。それが曖昧なままでは「悩み」は継続します。しかし、「次にとるべき行動」がはっきりすれば、「考える」ことはあっても、「悩むこと」は少なくなるでしょう。
「悩む」と「考える」の違いについて。悩んでいる人を、考えるフェーズに送り出すのがメンターの役割なんだなあと学びになった。
従業員にとって明示的でない権限は、最も不自由な状態とちがいがありません。権限が明示的でないことが意味しているのは、上司の胸先三寸で権限について差配できるということです。これは実質、すべての権限が上司にある状態と変わらないのです。
責任が曖昧な権限は「タダより高いものはない」と同じような状態ってことだと思う。セットで与えられるからこそ自律できるしモチベーションも出てくる。
アーキテクチャとは、システムのどのポイントが「変更しやすく」どのポイントが「変更しにくい」のかを見極めて、構造として組み込むものです。 そのため、負のアーキテクチャである「技術的負債」は、変更していくだろうと思っていたポイントがあまり変更しなかったときと、変更しないだろうと思っているポイントが変更されるときに生まれます。
技術的負債が「負のアーキテクチャ」であるという捉え方は目からウロコだった。いい語彙をもらえた。
次は、Kindleストアにおすすめされた「THE CULTURE CODE」を読む。 少し読み始めたところだが、「エンジニアリング組織論への招待」を含め、昨今広く語られるようになった「心理的安全性」というものの源泉について解き明かせるような気がする。 安心できるチームとそうでないチームの差、「帰属のシグナル」、またいい語彙がもらえそうな本なのでわくわくしている。
この記事では NgRx v7.4で導入される Action Creator 機能と、それを使った実装パターンを紹介します。 Action Creatorはまだ ngrx.io のドキュメンテーションに含まれていませんが、将来的に追加された後はそちらを参照するようにしてください。
簡単なカウンターを実装しながら、これまでのNgRxの書き方をおさらいしましょう。
今回のカウンターは、任意の数値を受け取って加算する Increment
と、カウンターをリセットする Reset
をアクションとして定義します。
これまでのアクション定義では、アクションタイプのEnum と、それを持つ各アクションクラス、そしてそのクラス型のUnion Typeを定義するのが一般的でした。
たとえば Increment
と Reset
というアクションとする counter.actions.ts
を定義すると次のようになります。
Increment
は与えられた数だけカウントを進め、 Reset
は カウントを 0 に戻すためのアクションです。
// counter.actions.ts import { Action } from '@ngrx/store'; export enum ActionTypes { Increment = '[Counter] Increment', Reset = '[Counter] Reset', } export class Increment implements Action { readonly type = ActionTypes.Increment; constructor(public payload: number) { } } export class Reset implements Action { readonly type = ActionTypes.Reset; } export type ActionsUnion = Increment | Reset;
このファイルはAction Creatorによって次のように書き換えられます。
// counter.actions.ts import { createAction, union } from '@ngrx/store'; export const increment = createAction( '[Counter] Increment', (payload: number) => ({ payload }) ); export const reset = createAction( '[Counter] Reset' ); const actions = union({ increment, reset, }); export type ActionsUnion = typeof actions;
createAction
関数まずクラス定義を置き換えている createAction
関数について解説します。
この関数は Action Creatorを返します。Action Creatorはアクションオブジェクトを返す関数です。
つまり、ディスパッチするアクションが、クラスをnewしたインスタンスから関数の戻り値に変わります。
import * as Actions from './actions'; // アクションクラスのインスタンス store.dispatch(new Actions.Increment(1)); // Action Creator // 関数がActionを返す store.dispatch(Actions.increment(1));
引数を取るアクションは、 createAction
関数の第2引数に関数を渡します。
この関数は任意の引数を取り、任意のオブジェクトを返します。
これは従来のアクションクラスにおけるコンストラクタとクラスフィールドの定義と同じです。
increment
アクションをもう一度見てみましょう。
第2引数は数値を payload
引数として受け取る関数で、戻り値は payload
プロパティをもつオブジェクトです。。
この関数の戻り値は第1引数から作られるアクションオブジェクトとマージされ、 最終的に { type: '[Counter] Increment'', payload }
というアクションオブジェクトを作成することになります。
// アクションを作成する const action = Actions.increment(1); // アクションオブジェクトは `type` を持つ console.log(action.type); // => '[Counter] Increment' // 第2引数で返したオブジェクトがマージされている console.log(action.payload); // => 1
ちなみに、これまで Enumで管理していたアクションタイプの文字列は、これまではクラスインスタンスを作らないと type
が手に入らないためにクラスと別にEnumを置いていましたが、
今後は increment.type
という形でアクセスできるため、いちいちEnumを作る必要はありません。
これについては後述する Reducerの変更部分で詳しくわかります。
union
関数一連のアクションの型を合成したActionsUnion
型は、ReducerやEffectなどいくつかの場所で必要となります。
従来のアクションクラスでは、クラス型の Union Type をそのまま扱えたが、関数の場合はその関数の戻り値の型を合成する必要があります。
それを補助してくれるのが NgRxの union
関数です。
すべてのAction Creatorを union
関数に渡し、その戻り値を エクスポートせず 宣言します。
なぜエクスポートしないかというと、欲しいのはその型だけだからでです。エクスポートして外部から参照可能にしたところで使いみちはありません。
actions
変数を宣言したら、typeof
を使ってその型を Union
型として外部にエクスポートします。
// 戻り値はエクスポートしない const actions = union({ increment, reset, }); // 型だけエクスポートする export type ActionsUnion = typeof actions;
Action Creatorを定義したら、次はReducerを対応させます。
もともとアクションクラスとEnumを使っていたときは、次のような Reducerになっていました。
引数に渡されるアクションの型は ActionsUnion
型で、 action.type
を ActionTypes
のEnum文字列と照らし合わせるswitch文を記述します。
import { ActionsUnion, ActionTypes } from './actions'; import { State, initialState } from './state'; export function reducer(state = initialState, action: ActionsUnion): State { switch (action.type) { case ActionTypes.Increment: { return { ...state, count: state.count + action.payload, }; } case ActionTypes.Reset: { return { ...state, count: 0, }; } default: { return state; } } }
このReducerに先ほどの アクション定義の変更を反映すると、次のようになります。
変わったのはcase文だけです。
case文で指定するアクションタイプは、Action Creatorがもつ type
プロパティに変わりました。
このように Action Creatorから直接取得できるため、アクション定義側でEnumに分離する必要がなくなっています。
import { ActionsUnion, increment, reset} from './actions'; import { State, initialState } from './state'; export function reducer(state = initialState, action: ActionsUnion): State { switch (action.type) { case increment.type: { return { ...state, count: state.count + action.payload, }; } case reset.type: { return { ...state, count: 0, }; } default: { return state; } } }
NgRxのEffectsを使って、カウンターの加算とリセットがおこなわれるたびにログを出力する副作用を定義します。 従来のアクション定義では次のようになります。
import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; import { tap } from 'rxjs/operators'; import { ActionsUnion, ActionTypes } from './actions'; @Injectable() export class CounterEffects { constructor(private actions$: Actions<ActionsUnion>) { } @Effect({ dispatch: false }) logger$ = this.actions$.pipe( ofType(ActionTypes.Increment, ActionTypes.Reset), tap(action => { console.log(action); }), ) }
これも Reducerと同じように、アクションタイプの部分だけに影響があります。
import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; import { tap } from 'rxjs/operators'; import { ActionsUnion, increment, reset } from './actions'; @Injectable() export class CounterEffects { constructor(private actions$: Actions<ActionsUnion>) { } @Effect({ dispatch: false }) logger$ = this.actions$.pipe( ofType(increment.type, reset.type), tap(action => { console.log(action); }), ) }
最後にアクションをディスパッチする部分です。 従来のアクションクラスでは、クラスインスタンスを生成して次のようにディスパッチしていました。
import * as CounterActions from './state/counter/actions'; @Component({ selector: 'my-app', template: ` <div>{{ count$ | async }}</div> <button (click)="incrementOne()">+1</button> <button (click)="reset()">Reset</button> `, }) export class AppComponent { count$ = this.store.pipe( select(state => state.counter.count), ); constructor(private store: Store<AppState>) { } incrementOne() { this.store.dispatch(new CounterActions.Increment(1)); } reset() { this.store.dispatch(new CounterActions.Reset()); } }
これはすでに説明したとおり、Action Creatorの関数を呼び出した戻り値をディスパッチするように変わります。
import * as CounterActions from './state/counter/actions'; @Component({ selector: 'my-app', template: ` <div>{{ count$ | async }}</div> <button (click)="incrementOne()">+1</button> <button (click)="reset()">Reset</button> `, }) export class AppComponent { count$ = this.store.pipe( select(state => state.counter.count), ); constructor(private store: Store<AppState>) { } incrementOne() { this.store.dispatch(CounterActions.increment(1)); } reset() { this.store.dispatch(CounterActions.reset()); } }
これですべての置き換えが終わりました。
クラスで定義されるアクションは、インスタンスを作るまで type
にアクセスできない不便さや、形式的に書かなければならないコードの量が多かったのが課題でした。
Action Creatorでは関数で記述できるので、無駄なコードが大きく減ります。 そして機能やテスタビリティは以前と変わらず、特にデメリットはありません。
プロジェクトのNgRxをv7.4にアップデートしたら、基本的にはAction Creatorへの置き換えを進めるべきです。
createAction
関数が導入されたこの記事で扱ったカウンターアプリケーションが実際に動作する様子を確認してみてください。