Express による SSR から Eleventy(SSG) による静的サイトジェネレートに変更した話

はじめに

Enjoy SFV More のフレームのページを Express による サーバーサイドレンダリング(SSR) から Eleventy という静的サイトジェネレーター(SSG)による静的サイトジェネレートに変更しました。

※静的サイトジェネレートとは言わない・・・?

下記のような各キャラクターのフレームのページが Eleventy を利用して生成しているページです。

enjoy-sfv-more.com

今回は主に Eleventy を解説していきたいと思います。

Enjoy SFV More については下記記事で技術解説をしているのでこちらもどうぞ。

tiwu.hatenablog.com

Express による SSR

先に変更前の構成を解説していきます。

変更前は Firebase FunctionsExpress を動かし、EJSレンダリングを行っていました。

基本的に Enjoy SFV More はクライアントでのレンダリングのページがほとんどです。

実装当初はフレームのページもクライアントでのレンダリングをしていたのですが、AMP 対応で <link rel="amphtml" href=""> を実装する必要がありました。

今まで通りクライアントサイドで <link rel="amphtml" href="">レンダリングしていたのですが、どうやら Google が認識してくれなかったので、SSR で実装をしました。

Express

ExpressNode.js で動く Web アプリケーションフレームワークです。

expressjs.com

EJS

EJSNode.js などで動くテンプレートエンジンです。

ejs.co

Embedded JavaScript 略して EJS っぽいです(今知りました)

コード

Enjoy SFV More では下記のように SSR を行っていました。

index.js

app.get("/frames/:characterEnName", (req, res) => {
  const character = characters.getCharacterByEnName(req.params.characterEnName);
  if (character) {
    res.status(200).render("frame", {
      characterEnName: character.enName,
      characterName: character.name
    });
  } else {
    res.status(404);
  }
});

まず Express では URL からキャラクター名を元に、キャラクターのデータを取得して、テンプレートにデータを渡します。

frame.ejs

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title><%= characterName %> - フレーム表&対策メモ【ストリートファイター V (スト5, ストV)】 - Enjoy SFV More</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="<%= characterName %>のフレーム表です。技の種類で検索ができたり、フレームの有利不利で並び替えができたり、フレームを見ながらリュウの対策のメモを取ったりすることができます。Enjoy SFV More はストリートファイター V (スト5, ストV) のラウンジを募集したり、キャラやLPやルールでラウンジを探したり、フレーム表や確定反撃(確反)を見ながら対策のメモができたりする、上達のためのサイトです。">
    <link rel="amphtml" href="https://enjoy-sfv-more.com/amp/frames/<%= characterEnName %>">
  </head>
  <body>
    <enjoy-sfv-more-body header-back="/frames">
      <enjoy-sfv-more-frame></enjoy-sfv-more-frame>
    </enjoy-sfv-more-body>
    <script src="/src/js/index.js" async></script>
  </body>
</html>

EJS では受け取ったデータを元に titledescriptionamphtmlレンダリングします。

Eleventy による静的サイトジェネレート

SSR でも問題はないのですが表示速度と、https://web.devEleventy を利用しているのを知りリファクタリングすることにしました。

下記はweb.devEleventy の解説記事です。

web.dev

Eleventy

Eleventy は静的サイトジェネレーターで、特徴は

  • シンプル
  • 好きなクライアントサイドの JavaScript が使える
  • データによる生成

といったところです。

www.11ty.dev

使用方法はインストールして実行すれば、コンフィグ無しで HTML がジェネレートされます。

$ npm install --save-dev @11ty/eleventy
$ echo '# Page header' > README.md
$ npx @11ty/eleventy

サーバーを起動させたり

$ npx @11ty/eleventy --serve

ファイルの変更を監視したりできます

$ npx @11ty/eleventy --watch

Nunjucks

NunjucksMozilla が作っているテンプレートエンジンです。

mozilla.github.io

Webpack との連携

Enjoy SFV More では webpack を利用しています。

webpack/src/js/index.js?hash=1fc568a7c0940a33ee7c といったようにハッシュをつけてバンドルしています。

Eleventy でこのハッシュ付きのバンドルされたスクリプトを読み込むためには一工夫必要です。

まず webpack-manifest-plugin を利用してハッシュとのマッピングが記述された manifest.json を出力します。

github.com

出力される manifest.json は下記のようになっています。

manifest.json

{
  "main.js": "/src/js/index.js?hash=1fc568a7c0940a33ee7c"
}

次に、Eleventy に用意されている Shortcodes という機能を利用して、 manifest.json を読み込んでハッシュ付きのスクリプトを返す処理を実装します。

www.11ty.dev

.eleventy.js

const path = require("path");
const fs = require("fs");

const manifestPath = path.resolve(__dirname, "public/src/js/manifest.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, { encoding: "utf8" }));

module.exports = eleventyConfig => {
  eleventyConfig.addShortcode("webpackAsset", name => {
    return manifest[name];
  });
};

上記コードでは webpackAsset という名前で Shortcodes を定義しています。

最後に定義した Shortcodes をテンプレートエンジンで利用しスクリプトを読み込みます。

default.njk

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>{{ character.title or title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="{{ character.description or description }}">
  <body>
    <enjoy-sfv-more-body>
      {{ content | safe }}
    </enjoy-sfv-more-body>
    <script src="{% webpackAsset 'main.js' %}" async></script>
  </body>
</html>

Nunjucks を利用しているので {% webpackAsset 'main.js' %} といった書き方で Shortcodes を呼び出しています。

データを元に HTML を生成する

Eleventy ではデータを元に HTML を出力できます。

www.11ty.dev

_data/possums.json

[
  {
    "name":"Fluffy",
    "age":2
  },
  {
    "name":"Snugglepants",
    "age":5
  },
  {
    "name":"Lord Featherbottom",
    "age":4
  },
  {
    "name":"Pennywise",
    "age":9
  }
]

possum-pages.njk

---
pagination:
    data: possums
    size: 1
    alias: possum
permalink: "possums/{{ possum.name | slug }}.html/"
---

{{ possum.name }} is {{ possum.age }} years old

pagination.data に指定されたデータを元にループ処理が行われて HTML を出力します。

この例では pagination.datapossums.json のファイル名を指定しています。

※これは後述する Global Data Files という仕組みを利用しているのでファイル名の指定になります

pagination.size は指定された数値でデータをチャンクします。

pagination.alias はループする際のデータの変数名です。

この例では possum = { "name":"Fluffy", "age":2 }, possum = { "name":"Snugglepants", "age":5 } ... となります。

permalink は生成される HTML 名です。

slug フィルターは URL で利用可能な文字列に変換されます。

www.11ty.dev

{{ "My Title" | slug }} -> "my-title"

Enjoy SFV More での生成

Eleventy では6つの方法でデータを利用できます。

  • Computed Data
  • Front Matter Data in a Template
  • Front Matter Data in Layouts
  • Template Data Files
  • Directory Data Files (and ascending Parent Directories)
  • Global Data Files

www.11ty.dev

とても簡単に言うとテンプレートファイルに書かれたデータか、ディレクトリに置かれたデータか、そしてどのディレクトリまで有効になるかという6つになります。

最初6つ目の Global Data Files という、どこでもデータを使える方法を利用しようとしました。

www.11ty.dev

_data ディレクトリ(デフォルトの設定)に置かれた *.json.js のデータが利用できるのですが、なぜか Enjoy SFV More では利用できませんでした(ディレクトリの構成が悪いのか原因不明です)

そのため5つ目の Directory Data Files という一部のディレクトリでデータが利用できる方法を採用しました。

Enjoy SFV More ではテンプレートファイルを views ディレクトリに置いてます。

.eleventy.js

module.exports = () => {
  return {
    dir: {
      input: "views",
      output: "public",
    }
  };
};

そのためキャラクターのデータを views ディレクトリに views.11tydata.js という名前で置いてます。

views.11tydata.js

const characters = require("street-fighter-v-data/dist/data/character");

const characterMetadata = [];

characters.characters.characterForEach(character => {
  characterMetadata.push({
    title: `${character.name} - フレーム表 & 対策メモ【ストリートファイターV(ストV,スト5,SFV,SF5)】 - Enjoy SFV More`,
    description: `${character.name}のフレーム表です。技の種類で検索ができたり、フレームの有利不利で並び替えができたり、フレームを見ながら対策のメモを取ったりすることができます!【ストリートファイターV(ストV,スト5,SFV,SF5)】 - Enjoy SFV More`,
    enName: character.enName
  });
});

module.exports = {
  characterMetadata: characterMetadata
};

views.11tydata.js ではキャラクターのデータを取得して、 characterMetadata という配列を生成します。

frame.njk

---
layout: ja/default.njk
pagination:
    data: characterMetadata
    size: 1
    alias: character
permalink: "ja/frame/{{ character.enName | slug }}.html"
---
<enjoy-sfv-more-frame></enjoy-sfv-more-frame>

フレームのテンプレートファイルでは pagination.datacharacterMetadata を指定しています。

Directory Data Filesではファイル名ではなく module.exports で定義された key の名前になります。

layout を利用して、HTML の雛形を継承しています。

ja/default.njk

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>{{ character.title or title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="{{ character.description or description }}">

    {% if character %}
      <link rel="amphtml" href="https://enjoy-sfv-more.com/amp/frames/{{ character.enName }}">
    {% endif %}
  </head>
  <body>
    <enjoy-sfv-more-body>
      {{ content | safe }}
    </enjoy-sfv-more-body>
    <script src="{% webpackAsset 'main.js' %}" async></script>
  </body>
</html>

<title>{{ character.title or title }}</title> では、 character.title があれば表示してなければ title を表示するようにしています。

{{ content | safe }}<enjoy-sfv-more-frame></enjoy-sfv-more-frame> が代入される仕組みになっています。

これで eleventy を実行すればキャラクター数分 HTML が生成されます。

f:id:tiwu:20200803225039p:plain
40対キャラクターがいるので一部のスクショです

この ja/default.njk は他のテンプレートでも利用をしています。

index.njk

---
layout: ja/default.njk
title: Enjoy SFV More - ストリートファイターV(ストV,スト5,SFV,SF5)をより楽しむためのサイト
description: Enjoy SFV More はストリートファイターV(ストV,スト5,SFV,SF5)をより楽しむため、ラウンジの募集や検索、コンボの作成や検索、配信の検索などができるサイトです!
---
<enjoy-sfv-more-top></enjoy-sfv-more-top>

例えばトップページではテンプレートファイル内で、titledescription を定義しています。

Firebase Hosting との連携

連携というほど何かしているわけではないのですが、Firebase Hostingrewrite では動的に sourcedestinationマッピングができません。

どういうことかというと、https://enjoy-sfv-more.com/frames/dhalsim という URLpublic/frame/dhalsim.html を表示するのを変数など使って簡単に書くことができません。

firebase.json

"rewrites": [
  {
    "source": "/frames/:name",
    "destination": "/ja/frame/:name.html"
  }
]

上記のようなパスを変数名に定義して、動的に HTML を表示することができません😭

そのため Enjoy SFV More では40体全ての URL を下記のように書いています。

firebase.json

"rewrites": [
  {
    "source": "/frames/ryu",
    "destination": "/ja/frame/ryu.html"
  },
  {
    "source": "/frames/chunli",
    "destination": "/ja/frame/chunli.html"
  },
  {
    "source": "/frames/nash",
    "destination": "/ja/frame/nash.html"
  }
]

firebase.google.com

まとめ

Global Data Files が何故か動かなかった問題さえ除けば、公式サイトを見ながらスムーズに実装ができました!

もっとゴリゴリレンダリングできるのですが、body 内は Web Componetsレンダリングに任せようと思います(昨今のテンプレートエンジンのつらみが伺えるので)

Enjoy SFV More の技術解説 2020.08.03 更新

概要

f:id:tiwu:20200708204601p:plain

Enjoy SFV More という、ストリートファイターV をより楽しむためのサービスを開発しました👊

日本版

enjoy-sfv-more.com

英語版

enjoy-sfv-more.com

Twitter

https://twitter.com/EnjoySFVMore

サイトの特徴は

  • 静的なページ(Firebase Hosting)と動的なページ(Firebase Functions)で構成
  • Eleventy を利用し静的なページを生成
  • 動的なページや APINode.js + express を利用
  • フロントは Web Components を利用
  • 全て TypeScript で記述
  • React, Vue, jQuery といったフレームワークやライブラリは利用をしていない
  • PWA を導入
  • 一部ページは AMP 対応

といったところです。

この記事では利用している技術について解説していきたいと思います(変更などがあれば本記事を随時更新していきます)

ディレクト

まずはじめにディレクトリ構成です。

f:id:tiwu:20200707211253p:plain

firebase cli で作りました。

更新履歴

  • 2020.08.03 Eleventy 記事を追加

HTML

Web Components で書いている以外特別何かしているということはあまりないです。

CSS

CSS のライブラリなどや SCSS などは導入しておらず、普通に書いています。

display:flex が好きなので、どんなレイアウトでも display:flex で何とかしています🤔

ShadowDOM を利用しているので、FLOCSS のような書き方をしていますが、結構適当な命名ルールで書いてます😇

developers.google.com

github.com

JavaScript

できるだけ標準の機能を使おうと思っているので、axios などといったライブラリなどは導入していません(Fetch API 使ってリクエスト投げたりしています)

github.com

developer.mozilla.org

package.json

Firebase Hosting 側の package.json

開発時は npm run servenpm run watch を実行して開発しています。

スマホデバッグしたい時は npm run dev-servengrok を起動させています。

{
  "scripts": {
    "build": "NODE_ENV=production webpack --mode production",
    "watch": "NODE_ENV=development webpack --mode development --watch",
    "serve": "firebase emulators:start --only functions,hosting",
    "dev-serve": "ngrok http 5000",
    "test": "jest"
  },
  "devDependencies": {
    "@types/jest": "^24.9.1",
    "@typescript-eslint/eslint-plugin": "^2.31.0",
    "@typescript-eslint/parser": "^2.31.0",
    "eslint": "^6.8.0",
    "eslint-config-airbnb-base": "^14.1.0",
    "eslint-config-prettier": "^6.11.0",
    "eslint-loader": "^3.0.4",
    "eslint-plugin-prettier": "^3.1.3",
    "html-webpack-plugin": "^3.2.0",
    "jest": "^24.9.0",
    "prettier": "^1.19.1",
    "script-ext-html-webpack-plugin": "^2.1.4",
    "ts-jest": "^24.3.0",
    "ts-loader": "^6.2.2",
    "typescript": "^3.8.3",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "@types/google.analytics": "0.0.40",
    "@types/gtag.js": "0.0.3",
    "firebase": "^7.14.2",
    "street-fighter-v-data": "github:tiwuofficial/street-fighter-v-data#master"
  }
}

Firebase Functions 側の package.json

開発時は npm run watchtypescriptコンパイルだけ動かしています(なぜか firebase cli で作られる package.json に無い🤔 )

{
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "watch": "tsc --watch",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "engines": {
    "node": "10"
  },
  "dependencies": {
    "canvas": "^2.6.1",
    "ejs": "^3.1.2",
    "express": "^4.17.1",
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
    "googleapis": "^47.0.0",
    "sitemap": "^5.1.0",
    "street-fighter-v-data": "github:tiwuofficial/street-fighter-v-data#master",
    "zlib": "^1.0.5"
  },
  "devDependencies": {
    "@types/node": "^13.13.5",
    "firebase-functions-test": "^0.1.6",
    "tslint": "^5.12.0",
    "typescript": "^3.2.2"
  }
}

Firebase Hosting でも Firebase Functions でも同じクラスなどを扱うために street-fighter-v-data リポジトリを読み込んでいます。

github.com

他に良い方法があるのかもしれないのですが、浮かばなかったのでこのような構成にしています。

一度 npm i でインストールした後、リポジトリを更新して反映を取り込もうとして npm i を実行しても取り込めないので、毎回 node_modules からディレクトリを削除して npm i を実行して取り込んでいます😇

良い方法知っている方いたら教えていただけると🙏

TypeScript

Firebase Hosting, Firebase Functions, street-fighter-v-data など全て TypeScript で書いています。

street-fighter-v-data では Firebase Functions で読み込むために JavaScript にビルドしています。

型定義ファイルも出力するために declarationtrue にしています。

street-fighter-v-datatsconfig.json

{
  "compilerOptions": {
    "outDir": "dist",
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "sourceMap": true,
    "target": "es2017",
    "moduleResolution": "node",
    "declaration": true
  },
  "compileOnSave": true,
  "exclude": [
    "__tests__"
  ]
}

Firebase Hostingtsconfig.json

{
  "compilerOptions": {
    "sourceMap": true,
    "target": "ES6",
    "module": "es2015",
    "lib": [
      "dom",
      "esnext",
      "webworker"
    ]
  },
  "exclude": [
    "functions"
  ]
}

Firebase Functionstsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "outDir": "lib",
    "sourceMap": true,
    "target": "es2017"
  },
  "compileOnSave": true,
  "include": [
    "src"
  ]
}

まだそこまで使いこなせておらず、簡単な型の定義くらいしか書いていません。

唯一工夫したのは Service Worker 関連です。

Service Worker 関連の関数(skipWaiting)の型を効かせるために webworkerlib に指定して、declare let self: ServiceWorkerGlobalScope; を定義して型を効かせています。

declare let self: ServiceWorkerGlobalScope;

self.addEventListener("install", async event => {
  self.skipWaiting();
});

Web Components

サイトのメインどころである Web Components です。わからないことや困ったらよく下記の Google の記事を読んでいます。

developers.google.com

LitElement という GooglePolymer プロジェクトが作っている Web Components ライブラリは利用していません。

lit-element.polymer-jp.org

ディレクトリ構成

ディレクトリ構成は web-components ディレクトリ内に、component (components にすべきと書きながら気づきました😇 )、pageraw-components としています。

f:id:tiwu:20200707211953p:plain

今見るとキャメル、スネークごちゃまぜになっている 😇

raw-components

raw-components は何も import などしていない単体で成立する下記のような Web Components を置いています。

class Button extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" }).innerHTML = `
      <style>
       :host {
          background-color: #888;
          padding: 10px 0;
          font-size: 15px;
          border: none;
          border-radius: 24px;
          display: flex;
          justify-content: center;
          align-items: center;
          margin: 0 auto;
          color: #fff;
          width: 100%;
          cursor: pointer;
        }
      </style>
      <slot></slot>
    `;
  }
}
customElements.define("enjoy-sfv-more-button", Button);

上記はシンプルな Web Components で下記のようにタグで囲ったテキストが <slot></slot> に展開されます。

<enjoy-sfv-more-button>もっとキャラを探す!</enjoy-sfv-more-button>

f:id:tiwu:20200708211507p:plain

textarea など入力を受け付ける Web ComponentsShadowDOM でタグが隠されているので、独自でイベントを発火させています。

class Textarea extends HTMLElement {
  constructor() {
    super();

    this.attachShadow({ mode: "open" }).innerHTML = `
      <textarea id="textarea" cols="30" rows="5"></textarea>
    `;

    this.shadowRoot.getElementById("textarea").addEventListener("input", () => {
      const elm: HTMLTextAreaElement = this.shadowRoot.getElementById("textarea") as HTMLTextAreaElement;
      this.dispatchEvent(
        new CustomEvent("input", {
          detail: {
            value: elm.value
          }
        })
      );
    });
  }
}
customElements.define("enjoy-sfv-more-textarea", Textarea);

イベントは他のタグと同様に addEventListener で処理をしています。

this.shadowRoot.querySelector("enjoy-sfv-more-textarea").addEventListener("input", (e: CustomEvent) => {
  console.log(e.detail.value);
});

componet

raw-components を組み合わせたりほかコードを import したりしている Web Components を置いています。

キャラクター選択モーダルは1つの Web Components として作成しています。

下記2画面で使われているキャラクター選択モーダルは同じ Web Components が使われています。

https://enjoy-sfv-more.com/combos/create

https://enjoy-sfv-more.com/lounge/create

f:id:tiwu:20200708211941p:plain

import { characters } from "characters";
class CharacterSelectModal extends HTMLElement {
 constructor() {
    super();
    this.attachShadow({ mode: "open" }).innerHTML = `
      <enjoy-sfv-more-modal>
        <enjoy-sfv-more-p class="title">キャラクターを選択してください</enjoy-sfv-more-p>
        <div id="list"></div>
      </enjoy-sfv-more-modal>
    `;

    const characterClick = (e): void => {
      this.shadowRoot.host.removeAttribute("open");
      this.dispatchEvent(
        new CustomEvent("enjoy-sfv-more-character-select-modal-selected", {
          detail: {
            characterId: e.currentTarget.getAttribute("character-id")
          }
        })
      );
    };

    characters.characterForEach(character => {
      const elm = document.createElement("enjoy-sfv-more-p");
      elm.textContent = character.name;
      elm.setAttribute("character-id", character.id);
      elm.addEventListener("click", characterClick);
      this.shadowRoot.getElementById("list").appendChild(elm);
    });
  }
}
customElements.define("enjoy-sfv-more-character-select-modal", CharacterSelectModal);

キャラクターが選択されたらモーダルを閉じて(上記からはコードを省いています)、enjoy-sfv-more-character-select-modal-selected イベントを発火するようにしています。

this.shadowRoot.querySelector("enjoy-sfv-more-character-select-modal").addEventListener("enjoy-sfv-more-character-select-modal-selected", e => {
  console.log(e.detail.characterId);
});

受け取り側はカスタムイベントから e.detail.characterId で選択されたキャラクターのIDを取得ができます。

page

Enjoy SFV More は各画面下記のように、enjoy-sfv-more-body タグを共通で記述し、子のタグに各画面に対応する Web Components を記述しています。

<body>
  <enjoy-sfv-more-body>
    <enjoy-sfv-more-frames></enjoy-sfv-more-frames>
  </enjoy-sfv-more-body>
</body>
<body>
  <enjoy-sfv-more-body top>
    <enjoy-sfv-more-top></enjoy-sfv-more-top>
  </enjoy-sfv-more-body>
</body>

raw-componentscomponet ディレクトリにある Web Components を利用して画面を作り上げます。

その他資料

speakerdeck.com

PWA

PWA を利用してアプリのようにサイトを開発しています。

Install

Enjoy SFV More はインストールをしてホーム画面に追加することができます。

インストールできるようにするためには manifest.json を置いて、空で Service Workerfetch を登録すれば最小限で実現できます。

{
  "name": "Enjoy SFV More",
  "short_name": "Enjoy SFV More",
  "icons": [{
    "src": "/src/img/icon/logo-192x192.png",
    "sizes": "192x192",
    "type": "image/png"
  },{
    "src": "/src/img/icon/logo-512x512.png",
    "sizes": "512x512",
    "type": "image/png"
  }],
  "start_url": "/",
  "display": "standalone",
  "background_color": "#3E4EB8",
  "theme_color": "#2F3BA2"
}
self.addEventListener("fetch", e => {});

最小限の構成で実装するとインストールを促す mini-infobar などの表示タイミングはブラウザに委ねられるので、ボタンクリックなど任意のタイミングでインストールできるように実装しています。

window.addEventListener("beforeinstallprompt", e => {
  e.preventDefault();
  this.deferredPrompt = e;
});

まずは、 beforeinstallprompt イベントで mini-infobar が表示されないように e.preventDefault(); を実行しイベントのオブジェクトを保持します。

document.getElementById('button').addEventListener('click', (e) => {
  this.deferredPrompt.prompt();
  deferredPrompt.userChoice.then((choiceResult) => {
    if (choiceResult.outcome === 'accepted') {
      console.log('accepted');
    } else {
      console.log('dismissed');
    }
  });
});

用意したインストールボタンのクリックイベントなどで、保持していた beforeinstallprompt イベントのオブジェクトの prompt を実行することで mini-infobar などが表示されます。

userChoice を経由すればユーザーが許可したか拒否したか取得できます。

window.addEventListener('appinstalled', () => {
  console.log('install');
});

インストール数を取得したい場合は、appinstalled がインストール時に発生するイベントなどでこちらを利用するのが正確に取れるかと思います。

その他資料

speakerdeck.com

Cache

Cache API + Service Worker を利用しキャッシュによる高速な表示を実装しています。

Cache API は困ったら MDN の記事をよく読んでいます。

developer.mozilla.org

Enjoy SFV More では画像と HTML, JavaScript を分けて考えキャッシュしています。

import { CACHE_NAME } from "./env-sw-cache";
const ASSET_CACHE_NAME = "asset-v1";
const IMGS = [
  "src/img/hero-logo.png",
  "src/img/icon/punch.svg"
];
const FILES_TO_CACHE = ["/", "/lounge/create"];

async function onInstall(): Promise<void> {
  self.skipWaiting();

  const imgCache = await caches.open(ASSET_CACHE_NAME);
  await imgCache.addAll(IMGS);

  const cache = await caches.open(CACHE_NAME);
  return cache.addAll(FILES_TO_CACHE);
}

self.addEventListener("install", async event => {
  event.waitUntil(onInstall());
});

async function onActivate(): Promise<boolean[]> {
  const keys = await caches.keys();
  return await Promise.all(
    keys.map(key => {
      if (key !== CACHE_NAME && key !== ASSET_CACHE_NAME) {
        return caches.delete(key);
      }
    })
  );
}

self.addEventListener("activate", event => {
  event.waitUntil(onActivate());
});

install イベント内で、画像と HTML をまるごとキャッシュに保存します。

activate イベント内では古いキャッシュがあれば削除しています。

async function onFetch(request): Promise<Response> {
  const cache = await caches.open(CACHE_NAME);

  return caches.match(request).then(response => {
    return (
      response || fetch(request).then(response => {
        if (response.type === "basic" && response.url.indexOf("src/js/index.js") >= 0) {
          cache.put(request, response.clone());
        }
        return response;
      })
    );
  });
}

self.addEventListener("fetch", event => {
  event.respondWith(onFetch(event.request));
});

fetch イベントではキャッシュにあればキャッシュから返し、src/js/index.js であればキャッシュに保存、それ以外はネットワークから取得するようにしています。

src/js/index.js?hash=154722cfd9ffa6be04b2 といったハッシュにしており、本来 install 内でハッシュ付き index.js を読み込むべきなのですが、webpack とうまく連携ができておらずこのような形に落ち着きました。

JavaScript の更新

JavaScript のハッシュが更新された時、HTMLscript タグのハッシュを変更しただけではキャッシュは更新されません。

HTML をまるごとキャッシュしているため新しくビルドした HTML をネットワーク経由で取得をしないためです。

旧ハッシュが書かれている HTML を破棄するために、キャッシュ名の CACHE_NAME を変更し、Serivce Worker の更新処理を動かします。

Install で新しい HTML を新しいキャッシュ名に保存し、古いキャシュ名にある古い HTMLActivate で削除します。

現状キャッシュ名はビルド時に手動で変更をしているので、webpack のハッシュ名などにして自動化をしたほうが良さそうです🤔

その他資料

qiita.com

speakerdeck.com

Share API

コンボの詳細画面では Share API を使ってネイティブアプリのようにシェアをすることができます。

const newVariable: any = window.navigator;
if (newVariable && newVariable.share) {
  this.shadowRoot.querySelector("button").classList.remove("is-hidden");
  this.shadowRoot.querySelector("a").classList.add("is-hidden");
  this.shadowRoot.querySelector("button").addEventListener("click", () => {
    newVariable
      .share({
        title: "Enjoy SFV More",
        text: this.getAttribute("share-text"),
        url: document.location.href
      })
      .then(() => console.log("Successful share"))
      .catch(error => console.log("Error sharing", error));
  });
} else {
  this.shadowRoot.querySelector("a").setAttribute("href", encodeURI(`https://twitter.com/share?url=${document.location.href}&text=${this.getAttribute("share-text")}`));
}

実装はかなり簡単で share が存在すれば利用して、なければツイッターで共有するようにしています。

その他資料

speakerdeck.com

AMP

Enjoy SFV More のキャラクターのフレームのページは AMP に対応しています。

Enjoy SFV More では通常のページと AMP のページを作っています。

https://enjoy-sfv-more.com/frames/dhalsim

https://enjoy-sfv-more.com/amp/frames/dhalsim

もともと通常のページは JavaScript でタイトルやメタタグや中身をレンダリングしていたのですが、amphtml タグは JavaScriptレンダリングすると認識しないので、ExpressSSR する方針に途中で変更しました。

並び替えや絞り込みを実現させるためにいろいろな AMP のタグを利用しています。

モーダル表示

まず、並び替えや絞り込みを選択するモーダルの表示には、amp-lightbox を利用しています。

f:id:tiwu:20200717164037p:plain

<div on="tap:sort-lightbox" role="button" tabindex="0">
  <p>フレームの並び替え</p>
  <p [text]="sortText">デフォルト</p>
</div>

<amp-lightbox id="sort-lightbox" layout="nodisplay">
  <div on="tap:sort-lightbox.close" role="button" tabindex="2"></div>
  <div>
    <p>並び替える条件を選択してください</p>
    <amp-selector layout="container" on="select: AMP.setState({ 
        listHeight: <%= height %>,
        sortId: event.targetOption,
        sortText: frameSortsState[event.targetOption],
        defaultListClass: 'is-hidden',
      }),
        list.changeToLayoutContainer(),
        sort-lightbox.close
      ">
        <p option="0">デフォルト</p>
        <p option="1">発生の早い順</p>
        <p option="2">発生の遅い順</p>
    </amp-selector>
  </div>
</amp-lightbox>

on="tap:sort-lightbox"id="sort-lightbox"amp-lightbox が表示され、on="tap:sort-lightbox.close" で非表示になります。

要素の選択

並び替えや絞り込みの選択は amp-selector を利用しています。

並び替えのような1つしか選択できないものは、on="select: " 内で event.targetOption 経由で選択した要素の option が取得できます。

フレームの並び替えは、 AMP.setState を利用して選択した並び替えのID (sortId: event.targetOption)やテキスト(sortText: frameSortsState[event.targetOption])を変数に格納しています。

絞り込みのような複数選択できるものは、multiple 属性を付与します。

f:id:tiwu:20200717165508p:plain

<amp-selector layout="container" multiple on="select: AMP.setState({ 
    selectedIds: event.selectedOptions
  })
">
  <p option="1">通常技</p>
  <p option="2">ジャンプ技</p>
  <p option="3">特殊技</p>
</amp-selector>

選択された要素の optionon="select: " 内で、event.selectedOptions 経由で配列で取得できます。

フレームの絞り込みではAMP.setStateを利用して選択された絞り込みのID(selectedIds: event.selectedOptions)を変数に格納しています。

変数の利用

並び替えや絞り込みの選択時 amp-bind を利用して選択した値を AMP.setState を実行し変数に格納しています。

並び替え

<amp-state id="frameSortsState">
  <script type="application/json">
      {
        "0":"デフォルト",
        "1":"発生の早い順",
        "2":"発生の遅い順"
       }    
  </script>
</amp-state>

<p [text]="sortText">デフォルト</p>

<amp-selector layout="container"  on="select: AMP.setState({ 
    sortId: event.targetOption,
    sortText: frameSortsState[event.targetOption],
  }),
  ">
  <p option="0">デフォルト</p>
  <p option="1">発生の早い順</p>
  <p option="2">発生の遅い順</p>
</amp-selector>

amp-state を利用して、並び替えのオブジェクトを frameSortsState 変数に格納します。

並び替え「発生の早い順」が選択されたら sortText 変数には frameSortsState[1] の値(発生の早い順)が格納され、[text]="sortText" により「デフォルト」と表示している p は「発生の早い順」が表示されます。

絞り込み

<amp-state id="frameFilterState">
  <script type="application/json">
      {
        "1":"通常技",
        "2":"ジャンプ技",
        "3":"特殊技"
      }
  </script>
</amp-state>

<p [text]="filterIds.map(id => frameFilterState[id]).join(', ') || 'なし'">なし</p>

<amp-selector layout="container" multiple
  on="select: AMP.setState({ 
    selectedIds: event.selectedOptions
  })
  ">
  <p option="1">通常技</p>
  <p option="2">ジャンプ技</p>
  <p option="3">特殊技</p>
</amp-selector>

<p class="button" role="button" tabindex="4"
  on="tap:AMP.setState({
    filterIds: selectedIds,
  })
">検索する</p>

amp-state を利用して、絞り込みのオブジェクトを frameFilterState 変数に格納します。

絞り込みを複数選択し検索ボタンをクリックすると、filterIds に選択された絞り込みの ID が格納され、 filterIds.map(id => frameFilterState[id]).join(', ') により選択した絞り込みが表示されます。

フレーム一覧の表示

フレーム一覧は、初期表示と並び替え or 絞り込み による検索で表示方法が違います。

初期表示は AMP Cache による高速な表示のため SSR して表示させています。

並び替え or 絞り込み による検索では amp-list を利用して表示させています。

<amp-list id="list"
    [src]="'/api/frames?characterId=<%= characterId %>&sortId=' + sortId + '&filterIds=' + filterIds"
    height="0"
    [height]="listHeight"
    width="auto">
  <template type="amp-mustache">
    <div class="p-frame-cassette">
      <a href="{{ url }}" class="name">{{ name }}</a>
    </div>
  </template>
</amp-list>

amp-list[src] にフレーム一覧のデータを返す APIURL を定義します。

{
  "items":
  [
    {
      "url":"https://enjoy-sfv-more.com/frames/dhalsim/24",
      "name":"ヨガロケット"
    },
    {
      "url":"https://enjoy-sfv-more.com/frames/dhalsim/25",
      "name":"ヨガフープ"
      }
    ]
}

API は上記のような構成でデータを返します。itemsamp-list のデフォルトなため構成を変更したい場合は、items 属性を利用して変更ができます。

繰り返されるフレームは<template type="amp-mustache"> を利用して {{ url }}{{ name }}といったように表示させます。

初期表示では SSR したフレーム一覧を表示するため height="0" により amp-list のフレーム一覧は非表示にしています。

<p class="button" role="button" tabindex="4"
  on="tap:AMP.setState({ 
    listHeight: 100,
    filterIds: selectedIds,
    defaultListClass: 'is-hidden'
  }),
  list.changeToLayoutContainer(),
  filter-lightbox.close
">検索する</p>

並び替えや絞り込みで sortIdfilterIds に変更があった場合、amp-listAPI からデータを取得しフレーム一覧を表示します。

このとき、 SSR しているフレーム一覧は defaultListClass: 'is-hidden' によって非表示に、amp-list のフレーム一覧は listHeight: 100 により一旦 100px になった後、list.changeToLayoutContainer() によりフレーム一覧の要素がちょうど表示される高さに調整されます。

i18n

Enjoy SFV More は英語版があります。

enjoy-sfv-more.com

下記を参考に、/en 配下にページを作り、<link rel="alternate" hreflang="ja" href="https://enjoy-sfv-more.com/">, <link rel="alternate" hreflang="en" href="https://enjoy-sfv-more.com/en"> といったタグを仕込んでいます。

support.google.com

support.google.com

Web Components は可能な限り使い回しをしていて、${this.hasAttribute("lang-en") ? "Damage" : "ダメージ"} といったように、属性を元に表示するテキストを判定しています。

class ComboListLinkItem extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback(): void {
    this.attachShadow({ mode: "open" }).innerHTML = `
      <a href='' class="js-link">
        <enjoy-sfv-more-p id="title">${this.getAttribute("title")}</enjoy-sfv-more-p>
        <enjoy-sfv-more-p class="character-title">${this.hasAttribute("lang-en") ? "Character" : "キャラクター"} : ${this.getAttribute("character-name")}</enjoy-sfv-more-p>
        <div class="info">
          <enjoy-sfv-more-p>${this.hasAttribute("lang-en") ? "Damage" : "ダメージ"} : ${this.getAttribute("damage")}</enjoy-sfv-more-p>
          <enjoy-sfv-more-p>${this.hasAttribute("lang-en") ? "Stun" : "スタン"} : ${this.getAttribute("stun")}</enjoy-sfv-more-p>
        </div>
        <enjoy-sfv-more-p class="combo-title">${this.hasAttribute("lang-en") ? "Combo" : "コンボ"}</enjoy-sfv-more-p>
        <div id="combo"></div>
      </a>
    `;
  }
}
customElements.define("enjoy-sfv-more-combo-list-link-item", ComboListLinkItem);

Eleventy

フレームのページは Express による SSR から Eleventy を使った静的サイトジェネレート(言わない?)にリファクタリングしました。

詳しくは下記記事をご覧ください🙇‍♂️

tiwu.hatenablog.com

webpack

教科書どおりに typescriptコンパイルしています。

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require("script-ext-html-webpack-plugin");

module.exports = [
  {
    entry: "./src/js/index.ts",
    module: {
      rules: [
        {
          enforce: "pre",
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: "eslint-loader",
            options: {
              fix: true
            }
          }
        },
        {
          test: /\.ts$/,
          use: "ts-loader"
        }
      ]
    },
    resolve: {
      extensions: [".ts"]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/html/lounge/index.html",
        filename: "lounge/index.html"
      }),
      new ScriptExtHtmlWebpackPlugin({
        defaultAttribute: "async"
      })
    ],
    output: {
      filename: "src/js/index.js?hash=[contenthash]",
      path: path.resolve(__dirname, "public"),
      publicPath: "/"
    }
  },
  {
    entry: "./src/js/en/index.ts",
    module: {
      rules: [
        {
          enforce: "pre",
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: "eslint-loader",
            options: {
              fix: true
            }
          }
        },
        {
          test: /\.ts$/,
          use: "ts-loader"
        }
      ]
    },
    resolve: {
      extensions: [".ts"]
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: "./src/html/en/index.html",
        filename: "en/index.html"
      }),
      new ScriptExtHtmlWebpackPlugin({
        defaultAttribute: "async"
      })
    ],
    output: {
      filename: "src/js/en/index.js?hash=[contenthash]",
      path: path.resolve(__dirname, "public"),
      publicPath: "/"
    }
  },
  {
    entry: "./src/js/sw.ts",
    module: {
      rules: [
        {
          enforce: "pre",
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: "eslint-loader",
            options: {
              fix: true
            }
          }
        },
        {
          test: /\.ts$/,
          use: "ts-loader"
        }
      ]
    },
    resolve: {
      extensions: [".ts"]
    },
    output: {
      filename: "sw.js",
      path: path.resolve(__dirname, "public")
    }
  }
];

html-webpack-plugin

html-webpack-plugin を利用して webpack が吐き出すハッシュ付きの JavaScript ファイルを HTML に埋め込んでいます。

github.com

script-ext-html-webpack-plugin

埋め込まれる script タグに async が付かないので、script-ext-html-webpack-plugin を利用して付与しています。

github.com

Express

SSR したり、 API 開発のために Express を利用しています。

特別な何かはしていないですが、OGP 自動生成を下記を参考に実装しました。

FirebaseCloudFunctionsとcanvasだけで動的にOGP画像を生成し2020年を生き延びよう - Qiita

FirebaseCloudFunctionsとcanvasだけで動的にOGP画像を生成し2020年を生き延びよう - Qiita

作成処理

const splitByMeasureWidth = (str: string, maxWidth: number, context: { measureText: CallableFunction }): Array<string> => {
  // サロゲートペアを考慮した文字分割
  const chars = Array.from(str);
  let line = "";
  const lines = [];
  for (const char of chars) {
    if (maxWidth <= context.measureText(line + char).width) {
      lines.push(line);
      line = char;
    } else {
      line += char;
    }
  }
  lines.push(line);
  return lines;
};

const canvasData = {
  width: 1200,
  height: 630,
  padding: 60,
  titleMargin: 40,
  bodyMargin: 20,
  comboMargin: 20,
  lineWidth: (): number => {
    return canvasData.width - canvasData.padding * 2;
  }
};

// タイトル部分の文字スタイル
const titleFontStyle = {
  font: 'bold 73px "Noto Sans CJK JP"',
  lineHeight: 80,
  color: "#ffffff"
};

// 本文部分の文字スタイル
const bodyFontStyle = {
  font: '40px "Noto Sans CJK JP"',
  lineHeight: 60,
  color: "#ffffff"
};

const createOgp = async (title: string, body: string, combo: string, memo: string, docId: string): Promise<void> => {
  const loaclTargetPath = "/tmp/target.png";
  const targetPath = `ogps/${docId}.png`;

  // canvasの作成
  const canvas = createCanvas(canvasData.width, canvasData.height);
  const ctx = canvas.getContext("2d");

  // -----
  // タイトル描画
  // -----
  // フォント設定
  ctx.font = titleFontStyle.font;
  // 行数の割り出し
  const titleLines = splitByMeasureWidth(title, canvasData.lineWidth(), ctx);
  // タイトル分の高さ
  const titleHeight = titleLines.length * titleFontStyle.lineHeight;

  // -----
  // 本文部分描画
  // -----
  // フォント設定
  ctx.font = bodyFontStyle.font;
  // 行数の割り出し
  const comboLines = splitByMeasureWidth(combo, canvasData.lineWidth(), ctx);

  const comboHeight = comboLines.length * bodyFontStyle.lineHeight;
  const memoLines = splitByMeasureWidth(memo, canvasData.lineWidth(), ctx);

  // 背景画像の描画
  const gradient = ctx.createLinearGradient(canvasData.width, 0, 0, canvasData.height);
  gradient.addColorStop(0, "#FF1763");
  gradient.addColorStop(0.75, "#F98F78");
  gradient.addColorStop(1, "#FFC778");
  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, canvasData.width, canvasData.height);
  // 文字描画のベースラインを設定
  ctx.textBaseline = "top";

  // タイトルを描画
  ctx.fillStyle = titleFontStyle.color;
  ctx.font = titleFontStyle.font;
  for (let index = 0; index < titleLines.length; index++) {
    ctx.fillText(titleLines[index], canvasData.padding, canvasData.padding + titleFontStyle.lineHeight * index);
  }

  // 本文を描画
  ctx.fillStyle = bodyFontStyle.color;
  ctx.font = bodyFontStyle.font;
  // キャラクター : ${req.body.character}, ダメージ : ${req.body.damage}, スタン : ${req.body.stun} を描画
  ctx.fillText(body, canvasData.padding, canvasData.padding + titleHeight + canvasData.titleMargin);

  // combo を描画
  for (let index = 0; index < comboLines.length; index++) {
    ctx.fillText(comboLines[index], canvasData.padding, canvasData.padding + titleHeight + canvasData.titleMargin + canvasData.bodyMargin + bodyFontStyle.lineHeight * (index + 1));
  }

  // memo を描画
  for (let index = 0; index < memoLines.length; index++) {
    ctx.fillText(
      memoLines[index],
      canvasData.padding,
      canvasData.padding + titleHeight + comboHeight + canvasData.titleMargin + canvasData.bodyMargin + canvasData.comboMargin + bodyFontStyle.lineHeight * (index + 1)
    );
  }

  // tmpディレクトリへの書き込み
  const buf = canvas.toBuffer();
  fs.writeFileSync(loaclTargetPath, buf);

  // Storageにアップロード
  await bucket.upload(loaclTargetPath, { destination: targetPath });

  // tmpファイルの削除
  fs.unlinkSync(loaclTargetPath);
};

取得処理

const checkIsExists = async (id: string): Promise<boolean> => {
  const filePath = `ogps/${id}.png`;
  const isExists = await bucket.file(filePath).exists();
  return isExists[0];
};

const getUrl = async (id: string): Promise<string> => {
  const isExists = await checkIsExists(id);
  if (isExists) {
    const url = `ogps/${id}.png`;
    return `https://firebasestorage.googleapis.com/v0/b/enjoy-sfv-more.appspot.com/o/${encodeURIComponent(url)}?alt=media`;
  }
  return "https://enjoy-sfv-more.com/src/img/ogp.png";
};

EJS

SSR には EJS を利用しています。

<enjoy-sfv-more-combo-show
  combo-title="<%= combo.title %>"
  character-name="<%= character.name %>"
  character-en-name="<%= character.enName %>"
  damage="<%= combo.damage %>"
  stun="<%= combo.stun %>"
  memo="<%= combo.memo %>"
  memo="<%= combo.memo %>"
  frames="<%= JSON.stringify(frames) %>"
  user-name="<%= user.displayName %>"
  user-img="<%= user.photoURL %>"
></enjoy-sfv-more-combo-show>

レンダリングWeb Components に任せているので属性を渡すだけやってます。

GitHub Actions

リリースは GitHub Actions を利用しています。

name: Build and Deploy
on:
  push:
    branches:
      - master

jobs:

  build_and_deploy:
    name: Build&Deploy
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@master
      - uses: actions/setup-node@v1
      - name: Install Dependencies
        run: npm install
      - name: Install Functions Dependencies
        run: cd functions && npm install
      - name: Install firebase-tools
        run: npm install -g firebase-tools
      - name: Deploy to Firebase
        run: firebase deploy --token $FIREBASE_TOKEN
        env:
          FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}

もともと GitHub Actions MarketPlace にある Firebase を利用していました。

github.com

しかし OGP 自動生成の際に Node 環境で canvas を利用するよう修正したところ下記のようなエラーが出てリリースできなくなってしまいました。

Error loading shared library http://ld-linux-x86-64.so.2: No such file or directory (needed by /github/workspace/functions/node_modules/canvas/build/Release/libpixman-1.so.0)

原因としては GitHub Action for Firebase の環境で canvas のインストールに必要なライブラリたちがインストールされてなかったためです(たぶん)

なので、GitHub Action for Firebase を利用をやめ、直接リリースできるよう修正しました💪

Fireabase

Firebase 気に入っているので、できるだけ完結するようにしています。

Hosting

静的リソースはキャッシュを強くするために Cache-Control を設定しています。

rewrites でルーティングを行っていて、glob パターン マッチングでゴリッと書いています。

{
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint",
      "npm --prefix \"$RESOURCE_DIR\" run build"
    ]
  },
  "hosting": {
    "trailingSlash": false,
    "public": "public",
    "headers": [
      {
        "source" : "**/*.@(jpg|jpeg|gif|png|svg|js)",
        "headers" : [
          {
            "key" : "Cache-Control",
            "value" : "max-age=31536000,immutable"
          }
        ]
      }
    ],
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "predeploy": [
      "npm run build"
    ],
    "rewrites": [
      {
        "source": "/en",
        "destination": "/en/index.html"
      },
      {
        "source": "/combos",
        "destination": "/combo/index.html"
      },
      {
        "source": "/combos/my",
        "destination": "/combo/my.html"
      },
      {
        "source": "/combos/my/*",
        "destination": "/combo/myShow.html"
      },
      {
        "source": "/combos/create",
        "destination": "/combo/create.html"
      },
      {
        "source": "/combos/*",
        "function": "app"
      },
      {
        "source": "/combos/*/*",
        "function": "app"
      },
    ]
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 5000
    }
  }
}

firebase.google.com

Functions

SSRAPI などで利用しています。

無料プランでは外部 API は叩け無いので(ただし YouTube など同じ Google のサービスであれば叩ける)ので、Twitter を利用する APIVercel を利用しています。

Database

募集やコンボの投稿は Database に保存しています。

単純な投稿と検索しかないので今の所、特に困ってはないです🤔

Storage

OGP の自動生成で Storage に保存しています。

こちらも壁にぶつかるほど現状は使い込んでないです🙄

Vercel

Functions の無料プランでは Google 以外の外部 API を叩け無いので Vercel を利用しています。

vercel.com

全然理解が浅いのですが Functions のようなサービスなので、 Express を利用して Twitter API を実行する API を実装しています。

Jest

まだ Web Components のテストは書いていないのですが、独自クラスなどは書いています。

street-fighter-v-data/Lp.test.ts at master · tiwuofficial/street-fighter-v-data · GitHub

eslint

特別な何かはしていない(と思います)  

{
  "extends": [
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "env": {
    "browser": true,
    "es6": true,
    "node": true
  },
  "parserOptions": {
    "sourceType": "module"
  }
}

prettier

幅の調整だけやっています🤔

{
  "printWidth": 200,
}

今後

まず、SSR しているところは eleventy を利用して SSG しようと思います。

www.11ty.dev

あとは、Web Components だけの repository を作って Storybook などで管理していければと思ってます。

まとめ

Web ComponentsPWAAMP 辺りに力を入れているので話のネタとしては盛り沢山な気がします👍

質問や間違っている点など何かあれば連絡ください🎉

https://twitter.com/tiwu_dev

チーム・ジャーニーを読んだので感想とかまとめとか

チーム・ジャーニーを読みました

カフェ行って読み切って、しばらく経ったので読み返しながら書いていこうと思う

チーム・ジャーニー

カイゼン・ジャーニーを書いた市谷さんが書いた3作目?

カイゼン・ジャーニーはすごく好きで何回も読み返したり、辞書のように使っている

チーム・ジャーニーも読む前からかなり楽しみだった

Prologue

最初はふーんという感じで読んだのだが、あとがきに「もう一度 Prologue を読んでみてください」と書いてあり、読み直してみた

太秦と江島、2人の Prologue というのに気づいたときはエモかった・・・

こういうの弱い

1話 グループでしかないチーム

ストーリー

1社目でスクラムを経験しチーム開発に自信を持った太秦が、何社か転職するが理想的なチームではなくすぐ転職をしてしまう

そんな中4社目に転職をした会社のタスク管理ツールプロジェクトでリーダーに任命される(リーダーとはどのような役割か明確に定義されてないのは少なくない

黙々と淡々と仕事をこなすチームに不安を覚える太秦蔵屋敷が「君はチームを知らない」と告げる

解説

チームとグループの違い

チーム グループ
存在意義 1人では不可能な成果を上げる(必要があれば学習する) 人の集合を外部から見分けやすくする(内側より外側基準)
主語 わたしたち わたし、あなた
ミッション 存在意義に直結する&共有されている 個々の行動レベルまで落とし込まれていない
役割 ミッション遂行に必要な役割を定義し、補完し合う 相互作用が乏しいため役割分担を必要としない
コミュニケーション 関係性や考え方が相互作用に与える影響に注意を払う 相互作用ではなく相互連絡
プロセス 最適化を自分たちで進める 仕事を受け渡すワークフローがある
ルール 自分たちで決める 外部から決められる

グループは自分たちが自ら集まり形成されるのではなく、外側から見分けられるようにという意図が強い(組織で言う開発グループなど)

グループからチームになるためには段階的に変化をさせていく必要がある

チーム作りは難しいが、我々はプロダクト作りという似たような難しい活動をしている

プロダクトでアジャイルな志向なら、チームもアジャイルになっていく

プロダクト作りと同様に、チームも適当に作っていいものはできない

チームになるための4条件

  • チームの目的を揃える
  • 共通の目標を認識する
  • お互いの持ち味を把握する
  • 協働で仕事するためのやり方を整える

チームの目的を揃える

「我々はなぜここにいるのか?」

与えられた言葉ではなく、自分たちでミッションを捉え、自分たちで言語化する(チーム結成をオーダーした人にすりあわせする

ミッションを表した文書は目にする場所に掲示し、定期的に方向性を違えてないか確認する

共通の目標を認識する

ミッションを大きなままではなく、ブレイクダウンする(スプリントゴールのように

ミッション達成の条件を明確にし小さく分割することで現実感も出てくる

マネージャーだけが把握するのではなく、ミッションと同じく全員が把握する

お互いの持ち味を把握する

上2つはチームの Why

How のためにチームメンバーを知ること

ドラッカー風エクササイズや星取表で知る

協働で仕事するためのやり方を整える

チームメンバーが完全に自由で結果が出ることもあるが、それは成熟したチーム

チームとして練度を上げるために前提・やり方・約束を整える

最初は仕事の進め方を決め(スクラムなど)、活動を繰り返してやり方を見出していく

ある程度やってはいけないことなどを縛ってカオスになりすぎないようにする

定例の時間や、レビューをしてもらうなどといった具体的な内容にする

決まり事は定期的に見直して増やしたり減らしたりする

感想

チームとグループの違いは自分もよく考えていて、チームは 5(人) × 1 = 5(人) だが、グループは 5 × 1(人) = 5(人) のように考えている

人が集まって仕事してもチームではなく、ベルトコンベアーのように仕事が人の手を渡り歩いているような感じ

2話 一人ひとりに向き合う

ストーリー

皇帝がタスクを淡々とアサインしていく。なかなかユーザーに使われてないサービスに対して次から次へと機能を追加していく(慎重に考えたほうが良い)

タスクが積み上げられるので各々淡々とタスクをこなす。相談もできず定例で初めてできているか、できていないかわかる

どのように取り組み、どんな問題にぶつかり、どうやって解決したか誰も知る由もない(一人で仕事をしているのと変わらない)

到達感のない開発で、モチベ・関心事はばらばらになってしまう

どうにかしないといけないと気づいた太秦は最初の一手を悩む(ワークショップ症候群になるのを避ける)

最初の一手はチームでチームの何を知りたいか、何がわからないか

チームの行動の質を高めるために、わかっていないことのうち何がわかればいいかを知る必要があり、何をわかればいいかは自分たちがどうなりたいかに基づく

チームになっていないのにチームとしてどうなりたいかは、わからない

まずは、個人が何のためにここにいるか知る。必要な会話をするためには、適した状況が必要である(今回は振り返りを設定

解説

チームのスタートラインの「出発のための3つの問い」は誰が始めるべきか?

役割で考えてしまうと、責任転嫁させやすくなってしまう。やるべき人は「気づいた人」

現状を問題と認識するためには理想と差分を取る必要がある(現状に最適化して気づけないことも多い

リーダーの役割は「目標を定め、優先順位を決め、基準を定め、それを維持するもの」とドラッカーは定義している

ミッションを捉える際、何を重視するかでリーダーに求められる能力などが変わってくる

状況突破ファースト

問題・膠着の突破のために自分自身が一歩目を踏む。これまでの前提や役割、やり方などを踏み越えることを躊躇しない

長所

それまでの視座では解決できない問題を突破する

短所

メンバーが巻き込まれたくない場合孤立してしまう

プロダクトファースト

プロダクトの作り込みやプロダクトがどうあるべきかに強いこだわりがある

長所

プロダクト作りのスピード感が高まり、短期的に成果が上がる

短所

理想的なプロダクトを作り上げるため、メンバーを振り切ってしまいがち

カリスマ化しやすく、メンバーが指示待ちになる

チーム成長ファースト

チームの中では黒子にまわり、時には試練をあたえたりする。リーダーというよりコーチに近い

答えより問いかけを重視して、チームに思考を促す

長所

チームで考え乗り越える力がつく

メンバー1人ひとりのリーダーシップが求められる

短所

練度の低いチームの場合は段階を設計しないと崩壊する

初期の開発の進みが遅い(外部の期待と合わない可能性がある

タスクファースト

タスクをこなすのに最適化する

長所

短期的にタスクの消化が高い

短所

メンバーの俯瞰する力が伸びず、仕事をする動機づけや、目の前に最適化しすぎてしまう

チームファースト

民主的なあり方を重視する

サーヴァント(奉仕)的な立ち位置になる

長所

チームプレー

短所

多様性が失われる可能性があり、成果が上がらない状況への対処が難しい


柔軟にファーストを選択し、複数を使い分けるのが大事

足りない能力や経験はチームで補完し合う(ファーストはチームで合意してチームで背負う

ワークショップは参考書のようなもので、行って(読んで)実践で使って上達する

ワークショップは現実に合わせた名前で行ったほうがいい場合もある(ふりかえりなど馴染みのある名前など)

ゴールデン・サークル(Why -> How -> What)が参考になる

しかし、チームがビルドできていないときに、チームの Why を問いても難しいので、まずは個人に向き合う

出発のための3つの問い

  1. 自分はなぜここにいるのか?(個人の Why )
  2. 私達は何をする者たちなのか?(チームの Why )
  3. そのために何を大事にするのか?(チームの How )

自分自身がいる理由を思い出してから、チームのミッションに向き合う

積み上がったタスクをこなすのがミッションなのか、プロダクトの向こう側に体験を届けるのがミッションなのか

同じタスクでも視座の置き方によってミッションが異なる

チームの活動が個人の自己実現につながり、個人の目的充足がチームの成果を押し上げる

振り返り

過去の活動を棚卸しして、気づきを得て、次の行動の仮設を立てる

むきなおり

未来に向けて進む方向を変化させる

感想

現状を問題と認識するためには理想と差分を取る必要がある

チームに対して、もやっと今の状態はあまり良くないと感じることがあるがうまく言語化ができない、というのがよくある

たぶん理想がふんわりしているから、言語化できないんだなと思った

ただ、前に理想を言語化しようとしたけどこれも難しく糸口がなかなか見つからなかった・・・

以前インセプションデッキを作ったことがあるが今思うと、個人を知ってからやるべきだなと思った

3話 少しずつチームになる

ストーリー

「自分はなぜここにいるのか?」という問いには「コードを書く」「UIデザイン」「進捗」「チームプレー」になったが目の前のタスクによりがちになってしまった

「私たちは何をする者たちなのか?」という問いには満場一致で「ツールを作り上げること」になったが、違和感がありワクワクはしない

「そのために何を大事にするのか?」という問いには「計画づくり」「コードを書く」「チーム開発」などになり、結果として「チーム開発」を掲げることになり、スクラムを導入したが、アウトプット量は以前と比べて激減した

アサインではなく、タスクを取るスタイルになった結果、誰もやりたくないタスクが三条に集中してしまい体調を壊してしまう(以前は皇帝がタスクをアサインしていたので偏りが生まれることはなかった

以前まで皇帝1人が課題リストを管理していたため大きなバグ等は発生しなかったが、各々が管理をした結果「あれどうなった?」という減少が多発しバグが発生したり、個々の状態がわからなくなった(皇帝が管理していてもわからなかったが

アウトプットの品質も皇帝が全て最終確認をしていたが、各々になったため品質が下がった

チームの進め方の相談をリーダーの太秦か、スクラムマスターの天神川か、皇帝なのか迷ったり

そこで、蔵屋敷が「いきなりスクラムは早い」「一度にすべての問題に取り掛かろうとしても問題は減っていかない」と伝える

  • タスクにサインアップしすぎる問題
    • 毎週のプランニングでタスクのボリュームをチームで見立ててキャパシティを超えないか調整する
  • 個々の課題が見えない問題
    • 課題リストは個人ではなくチームで1つ持ち、運用する
  • アウトプットの品質が低い(完成の定義が会っていない)問題
    • 完成の定義をチームで合わせる(実装が終われば・テストが終われば・STG に配置できれば)
  • 役割がかぶってしまう問題
    • 役割とはその人への期待にラベルをしたもの。定義するのではなく互いにどういった期待をしているか確認し合う
    • ドラッカー風エクササイズなど

ワークショップ1発で状況を変えると思ってはいけない(日常での実践と非日常での取り組みの使い分け)

互いの理解を深めることと、チームが機能することを分けて(段階で)考える

チームの改善も短く、小さい、一定のサイクルで繰り返して問題に早期に出会えるようにする

スクラムをスタートして解決を目指すのではなく小さく進むべきだった(課題やタスクの見える化や、更にその前にチームビルディングなど)

皇帝も自分のタスクマネジメントがなくなったら崩壊するのは知っていたが、リーダー・チームの決定を尊重した

皇帝はチームマネジメントなど得意ではなく、進捗を出すためには自分が背負い込むくらいしかできなかった・・・

解説

チームの成長戦略「チームジャーニー」の根底には段階の設計という考え方がある

いきなり辿り着きたい「目的地」に全力で進むのではなく、途中の段階を意識しながら、段階の設計を組み直しつつ進む

1. 理想とする到達状態をイメージして、最初の目的地としておく

あいまいなイメージでしかないことも多いので、段階を進む中で明確にしていく

2. 目的地に至るために必要な状態を段階として分ける

  • 目的地に至った場合どのような状況・状態か?
  • その前段階の状況・状態は?

と逆算して段階を構想する(無理がないか、ワクワクするかなども込めながら)

最初は計画性に欠けることも多いので、進めながら調整をする

3. 各段階において何を取り組むべきかを見立てる

取り組む内容は、できているできていないを客観的に判断できるものにする

到達度合いはふりかえりなどで判断して、延長や段階の追加や削除などを都度判断していく


有名なフレームワークとして、チームの成長モデルとして有名な「タックマンモデル」やチームの状態構造を表すモデルとして「成功循環モデル」がある

タックマンモデル

チームを形成していくプロセスには段階がある

  • 形成期
    • お互いのことを知らない段階。目的も不明瞭
  • 混乱期
    • お互いの役割・考え方で意見が生まれて対立する
    • 形成期を乗り越えて互いのことを知っているので対立が起きる
  • 統一期
    • 行動規範や役割が確立し、他人の考え方が受容できる
  • 機能期
    • 一体感が生まれ、目標達成に向かう状態

成功循環モデル

仕事や活動の「結果の質」を高めるためには、メンバー間の「関係の質」を高めるべきという考え方

関係性が良くなれば「思考の質」があがり、思考の質が高まれば「行動の質」も良くなる

という循環する考え方

チームジャーニーでの取り組み

形成期では関係の質を高めて、思考の質を高める(関係の質はワークショップなどを活用して高める

この段階では、互いの考え方の差を理解することである(多様性を認める

必要に応じて Working Agreement を設けてチーム活動を進める

星取表でスキルを理解して、ドラッカー風エクササイズで得意なことなどを理解して、インセプションデッキでチームを理解して、ドラッカー風エクササイズB面(以下4つの質問)で不得意なことを理解する

  • 自分は何が不得意なのか?
  • どういう風に仕事をしてしまうか?(他人からも言われること)
  • 自分の地雷はなにか?
  • 以前メンバーの期待に応えられなかったことは?

混乱期では短く、小さく、一定のサイクルを繰り返していく

早めに問題を検出しておくことが大切(一度に多くの問題に取り組むと破綻することがあるので選択と集中を)

また、できる限り最初の結果を早くすることが大切(最初の行動の質は低いかもだが循環を回すことが大切)

成功循環モデルの関係の質を高める最良の手段は、結果をチームで分かち合い、俺達はやれる感を演出すること

統一期では新たな目的地をむきなおりで決めて、再び混乱期を乗り越えていく

コルブの経験学習モデル

「具体的経験」から学びの対象となるインプットを得るために「内省的観察」を行う(ふりかえりなど)

「内省的観察」を経て、次に取り組むべきことを決める際には「抽象的概念化」をする(インプットから理解したことを知識や仮設などに整理して実行可能な状態にする)

「抽象的概念化」をしたものを「能動的実験」をすることで、「具体的経験」を得る

例えばプロダクトの品質を高めるために「テストを先に書いてから開発する」を決めたときにいきなり全員で全部やるのではなく、1スプリントで誰か1人がやってみたりして知見を貯めて全体反映をするといったように、経験学習モデルを高速に回すことを意識する

感想

3つの質問のようなことを答えてもいまいちピンとこない(なにか変わった?)、というのはすごくわかる(ワークショップ症候群)

日常の業務で反映できていない(いくつかのモデルもそうだが)んだろうなと感じた

プログラムとかを勉強するときに、参考書など読んで、実際に書いてみて、バグったりうまく行ったらまた学んで、の繰り返しができているが、チーム関連になるととたんにできないのはなぜだろうと思う

仕事関係ないが、スト5 をやっていて思ったのは、上達するために練習方法がわからなくなる(次の段階に行くためには何が必要なのかわからない)

たぶんこれは同じような現象なのでは?と思っている

プログラミングの勉強でできているんだから、チームでもスト5でも段階を設計できれば、上達すると思っている

この段階を設計するのが難しい・・・

チームもスト5も両方現状の分析をして、段階を設計してほしい(いわゆるコーチ的な役割の人が必要)と感じた

4話 チームのファーストを変える

ストーリー

皇帝は太秦が育つまでといるという約束だったためチームを離脱

チームで合宿をして、ドラッカー風エクササイズ・ふりかえりでのKPTを実施

二日目は「短く、小さい、一巡のサイクル」を意識して短いプランニングとモブプロを行った

テストを先に書く人もいれば、後でしっかり書く人もいれば、こまめに書く人もいる

良い悪いではなく、一緒に開発していたのにやり方の差に気づいてこなかったのが発見

スクラムの理解度もバラバラだったので、最初の段階として状態の見える化を進める

朝回・タスクボード・ふりかえりを定期化・1ヶ月のタイムボックスを置く

何かあっても全員で話し合う時間が増えたが、パファーマンスが落ちている

皇帝の分のタスク量が減り、皇帝の開発の引き継ぎができておらず毎回触る度に調査する時間がかかる

ふりかえりで出しても「チームプレーを高めるべき」と優先度が下げれられる

これ以上踏み込むと関係性が壊れてしまうのではないかと、誰も動けない(全員厄介な問題から目を背けている状態が続く

全員で話すことが多いので時間がかかる(朝会について、タスクボードについて

太秦は個々の自主性が出ていると感じているので強引に介入して、止めたり判断するのを躊躇ってしまう(次の皇帝になってしまう恐れ

そうした結果「チームごっこ」と砂子(PO)に言われてしまう

チームは合宿以降小さな結果を出していないのでまずはそこにこだわるようにする

「プロダクトファーストでアウトプットを出す」をWhyにする

負債返却より機能を優先するのは、いつ経営サイドに止められてもおかしくない状態のため

そのため機能を絞ってもらいそこに集中する

皇帝のコードを全般を追い回すのはやめて、勝負機能のみにする

「勝負機能に絞り込む」「コードのキャッチアップを限定にする」「役割を再定義する」をHowにする

スクラムをやめ、フロントとバックのリード役を定義する(スクラムを取り組めるほどチームが熟していない

すべてを全員で決める方針から、それぞれの分野はリード役が意思決定をする(それ以外は太秦が意思決定をする

解説

チームファースト・プロダクトファーストはプロダクトの文脈によって変えていく

安定の運用が求められるのであればチームファーストで、新規サービスのときはプロダクトファーストで

チームの構造

共有ミッション

チームが達成すべき目標のミッションの定義とチームのファースを設定する

役割

プログラマーやデザイナーなどの専門領域を決めたり、ある領域の先導役としてリードを必要に応じて設定する

その領域のコミュニケーションの活性化と意思決定の促進(場合によっては自分で)を担う

コミュニケーションの場

場、同期目的、場所・手段、タイミングなどを決める

スクラムであれば、計画づくりの「プランニング」、結果を吟味する「スプリントレビュー」、プロセス改善の場としての「レトロスペクティブ」

ルール

制約がないと、活動は集中せず結果への結びつきが弱くなる。Working Agreement や完成の定義など


このチームの構造をデザインすることが大事

ミッションが変わったり、チームの練度が変わったときに、構造を見直したりするべき

アンチパターン

個人商店状態

共有ミッションがない状態だと、それぞれがタスクをこなすだけの個人商店状態になる

塹壕状態

コミュニケーションの場がないと、目の前の仕事をするだけで、状況の同期は誰か1人がやればいいとなる

烏合の衆状態

ルールがないと、それぞれ思い思いに動き、成果が上がらないという状態になりがち

仲良しこよし状態

役割がないと、責務もあいまいになり、結果を出すより共同活動を守りだす

ゴールデン・サークル

構造を決めるときに一緒にチームのゴールデン・サークルを作ろう

Why として何をファーストにするか、ミッションなどを具体的にする

How では、チームの構造設計をしたり、作戦を決める

What では、How に基づいてタスクを決める

一気通貫で整合性をもたせることが大切

さらに、When を足してタイムボックスを意識する

期間が決めれないときは、状況を見直すタイミングの間隔を決める

人は無限に集中ができないので、期間を決めることで集中のしどころを意識的に作り出す

基本的にスクラムのスプリントより長くなるのでこの期間を「ミッション・ジャーニー」と定義する

ミッション・ジャーニー

このミッション・ジャーニーはチームの段階のことで、チームとプロダクトが段階的に成長していく

スプリントは固定だが、ミッション・ジャーニーは可変である

ミッション・ジャーニーは着地の予測が重要になり、想定よりスプリントが必要であれば延長する必要がある

このプラン変更には関係者への説明が伴うため、あらかじめ期待マネジメントをしておく

5スプリントで見積もっていたものを、スプリントごとに見直してわかりしだい即調整を行う

PDCA

Plan, Do, Check, Action

計画に時間をかけすぎたり、計画に現実を合わせることが問題ではある

OODA

Observe, Orient, Decide, Act

空軍パイロットが空中戦闘中に取る意識決定モデルとして定義されており、予測のつきにくい状況に適している

感想

チーム全員による意思決定やMTGはすごい心当たりがある(とても

小さいチームだから、ギリギリワークしているのか(ワークしていないのか)

これも観測できていない・・・

チームのワークをするときは結果を観測できるようにするか、判断する指標をやる前に決めておくべきだなと

何をすべきかわからないときに、進む方向としてのフレームワーク(今回でいるゴールデン・サークルとか)は便利だなと思うし、知識は大切だなと読みながら思う

ゴールデン・サークルやチームの構成もこれが確実な正解というわけではなく、タイムボックスで振り返るようにできているのがチーム・ジャーニーの良いところだなと感じた

ただ人が集まって仕事をしている集団を、フレームワークを元にチームにしていくという知識はエンジニアやデザインナーやPOだけが集まってもできないので、学ぶ意欲が高い

PHPerKaigi 2020 day2 参加レポート #phperkaigi

去年に引き続き参加してきました!

資料をまとめたり、感想やレポートを書いていきます。

PHPerKaigi 2020

phperkaigi.jp

場所は練馬駅

お祭りみたいで賑やかな雰囲気で楽しい。

テーマは「文化祭」、「同窓会」とのことで、楽しい仕掛けが多く、小さいところにもこだわっているなという印象

f:id:tiwu:20200211152216j:plain

なんと、今回からトレーディングカードが貰える!

f:id:tiwu:20200211163822j:plain

参加者同士でカードを交換して、交流するきっかけになればという意図らしい(たぶん)

効果の元ネタは気になるが、可愛い

トートバッグとその中身(一部)

f:id:tiwu:20200211193749j:plain
デニム生地で可愛い

f:id:tiwu:20200211194050j:plain
可愛いグッズたち

↓ここからセッション↓

自作して理解するxUnit

fortee.jp

speakerdeck.com

ジェネレータで無限を手玉に取る術

fortee.jp

マスターデータの管理運用と実装について

www.slideshare.net

fortee.jp

実例から学ぶ、最後まで諦めない決済システム移行方法

speakerdeck.com

fortee.jp

GMOの人で、自宅勤務について聞きたい人は後ほど聞いてくれとのことw

おさいぽ!というサービスの決済サービスの移行

おさいぽ! -ネットのおサイフ-

ペパボ内での決済をするためのAPIとかが生えている

移行の背景は決済サービスが終了するため

成功の秘訣は、事前打ち合わせ、事前に本番環境でテスト、万が一のフローを策定の3つ

APIが生えているので、利用しているペパボのサービスに影響が出る

移行計画を作っていたが、サービス側と打ち合わせをしたら、作った移行計画では無理というのが判明

作った移行計画が駄目になるのは精神的に来るものがある

GW前にメンテナンスを入れて対応をした

本番でテストができないという先入観があったが、聞けば意外とできることがある

本番で動いたという自信を持つことが大事

移行2日前に3Dセキュア決済周りでエラー

これは、契約内容ミス

無事リリース後、エラーが

旧決済サービスの返金処理でエラーが

エラー時のフローを作っておいたので、そのフロー的にはロールバックするレベルではなかった

ステージングで無理やり旧決済サービスを動かしてなんとか対応

当日は疲れている・テンパっているのでエラー時のフローを作っておくと冷静に対応ができるので良い

基本は二人で対応をしていた

感想

エラー時のフローを作っておくのはすごく同意

基本的にエラー時はテンパっているので、行動のフローがあるとそのフローに沿って動けばいいので冷静になれる

本番で動いたという自信を持つことが大事

これもすごく同意

とてもわかる

クリーンな実装を目指して

speakerdeck.com

fortee.jp

PHPerがこれから「型」とお付き合いしていくために

fortee.jp

型システムとは、元々、型理論という分野

→数学などで使われる

最初は int, floatの違いを区別する

→計算速度向上のため

データ構造の不整合が実行前にわかる

PHPは実行前には・・・

特定のバグがないことを保証する

が、柔軟性が持てない、実行されないコードのエラーも検知するというデメリットもある

CならポインターでPC破壊できるが、PCは破壊できない

→型に守られている

とはいえ抜け道はある

しかし、安全の定義が不安定なのでなんとも

コンパイルによって、実行時に効率的なバイトコードになるでメリットが有る

静的型付け

→型に不整合がある場合プログラムは実行できない

動的型付け

→実行時に型検査され、不整合は実行時エラー(にならないこともあるので注意

静的型付けは自明なものは型を書かない

動的型付けは安全のために型を書く

弱い型付けの動的型付け言語、柔軟性が高い

型システムの強化、型宣言で性能が上がる

型が進むと、後方互換性との兼ね合いが心配になる

→新規ならガンガン行ける

ケース1 古くからメンテされているPHPの保守

開発環境はPHPStormなら、PHPDocで行くべき

→急に型を入れると壊れる可能性がある

ケース3 古くからメンテされているPHPに型が定義されたライブラリ導入

→型を合わせて気をつけて入れる

感想

最近TSをわりと書くようになったけど、ちょっと実行したいときにコンパイルが通らず実行すらできないときが多々あって辛いときがある

ちょっと書いて動かしてバグっていないかというトライ・アンド・エラー的な書き方なので途中でも実行できないと困ったりする・・・

ケース1 古くからメンテされているPHPの保守

開発環境はPHPStormなら、PHPDocで行くべき

型書きたいけど、引数に int, string 書いたらバグった(しかも後ほど気づく)とかがあるので、PHPDoc から行くというのはすごく良いなと思った

PHPシステムをコンテナで動かすための取り組みのすべて

fortee.jp

Zend VMにおける例外の実装

www.slideshare.net

fortee.jp

PHPPHPを実装する 〜プログラミング言語実装入門〜

fortee.jp

プログラミング言語処理フローは主に字句解析、構文解析、評価

評価するとは、ASTをたどる、バイトコード変換、機械語に変換

PHPバイトコード変換、GoとかCとかは機械語変換

機械語に変換のほうが早いが人間が読めたりするものではない

まずはコードをパースしてASTを作る

AST = 抽象構文木

ソースを表すオブジェクト

今回は PHP Parser を利用

parser に文字列でコードを渡せば AST ができる(簡単そう

ASTは配列だから for で回して、 switch で実行していく(switch 内は再帰していく

自作PHPの強さは、再代入禁止とか、静的解析できたりカスタマイズ可能

PHP を超える PHP を作れる

感想

フロントエンドの勉強会とかにいくと、たまに話しに上がる AST

PHP Parser 食わせると AST ができて、それを for で回して switch で実行していく」と聞くと、読める・書ける・いけそうな気がしたw

いつも AST の話はよくわからなかったけど、初めてすっと入ってきた気がする

もしもPHPソースコードが読めたなら...

fortee.jp

ぼっちからはじめるレガシーカルチャー改善ガイド 〜はじめの一歩編〜

fortee.jp

PHP 4, 5 のミックスで最初は開発(XAMP

今は Laravel にしたりdocker にしたり Nuxt 使ったり

改善の最初は最初は自己研鑽と信頼貯金を貯める

勉強会などに参加して、他社とのギャップを知って、自社に展開できることを探していく

最初はインデントを整えたり、開発環境を良くしたりという、プロダクションに関係のないところから行った

一人目の仲間を見つけたのが最初のゴール

1人目を見つけた後は?

→2人目を見つける

チームが一人のときは・・・?

→外(コミュニティ)に仲間を見つける

本当に一人?

→人間なかなか、1人で仕事していることはほぼない

感想

他社とのギャップを知って、自社に展開できることを探すフェーズはすごく同意

最初のゴールが1人目の仲間を見つけるところは、カイゼン・ジャーニーっぽいなと思った

実際の改善はプロダクションに関係ないインデントを整えたりというスモールスタートなのも良さそう

チーム(エンジニア)が1人のときはどうするか?という問いに、「本当に1人だろうか?1人で仕事をしているなんでほぼありえない」みたいなこと言っていてメチャクチャ同意の嵐だった

今だからこそ振り返る register_globals

speakerdeck.com

fortee.jp

Webアクセシビリティを支えるための技術

fortee.jp

UX ピラミッドの一番下にアクセシビリティがある

WCAG というアクセシビリティのレベルがある

Web Content Accessibility Guidelines (WCAG) 2.0

レベルA 感覚的な特料

レベルAA フォーカスできる(キーボードで操作ができる

レベルAAA メディア(音声)の代替を用意する

ハイパーテキストはテキストを相互に関連付ける仕組み

→これを実現するものがHTML

サーバーサイドエンジニアとしてwebアクセシビリティを自分ごとにする

適切なHTTPステータスを選択する

感想

UXピラミッドを調べてみたけどたくさん出てきて、どれかわからなかった :eye:

alt 属性を DB に入れておくとかは、ブログメディアとか良さそうとか思った(みんな既にやっている?

ステータスコードは 200 で返して、中身でエラーとかは難しいところがあるので同意

GitHub Actionsで始めるPHPアプリケーションのCI実践入門

speakerdeck.com

fortee.jp

Issue 追加でも動く

GitHub Actions, windows 選択できる

初期状態はコードがない(Issue 発火もあるからか?

Github 公式は actions/ で用意されている

アクションは Docker + TS で作れるらしい

Creating a JavaScript action - GitHub ヘルプ

JSで作れそうな匂い

永続化の手段は公式?に用意されているそう

Persisting workflow data using artifacts - GitHub ヘルプ

感想

最近個人的にも使っている GitHub Actions

Issue で発火できるのは知らなかった

GitHub + Actions + Pages でいい感じのなにか作れそうな匂いを感じた

API リストとか、DBの定義リストとか、開発で必要な何かとか

Serverless Pattern

slide.seike460.com

fortee.jp

LT

PHPでもVTuberになりたい!

fortee.jp

FaceOSC というOSSの顔の頂点座標をとるライブラリ?があるらしい

Releases · kylemcdonald/ofxFaceTracker · GitHub

これをPHPで受け取るライブラリがないらしい

ないなら作る!

PHPでVtuberになりたいとか、機械学習とか// 第47回社内勉強会 #sa_study | BLOG | 株式会社スタジオ・アルカナ

www.slideshare.net

力こそパワー、すごい

PHPでleetCodeのeasyレベル100問ノック

fortee.jp

LeetCode - The World's Leading Online Programming Learning Platform

いろいろな問題がある

8割位PHPで書いてみた

他の人がコードが読めるので勉強になる

試し実行が10秒くらいかかる

先にテストを書いて、PHPStorm で動かしてやっていた

パズルゲームのように解いていくと楽しくできる

PHP未経験者を育てる独自フレームワークの作り方

speakerdeck.com

fortee.jp

沖縄でビーチ駆動開発(開発しづらいw

FWなど使わずブログを作っていく研修

ルーティングとかけっこう大変らしい

RFCの歩き方

speakerdeck.com

fortee.jp

Request for comment

PHPの機能追加の話

英語は基本グーグル翻訳

RFCは前書きさえ読んでおけば良い

コードがあればラッキー(英語は難しい

補足の後に互換性と投票が書かれている

PHPerKaigi2019への参加がきっかけで社内勉強会の主催するようになった話

fortee.jp

YouTube を社内で流す勉強会を開催

→準備は楽そう泣きがする

上映会で一番盛り上がった「PHPデザインパターン」を学ぶ会を主催

参加者0人のときの寂しさはとてもわかる

PHPとRustを組み合わせて音声ファイルをエンコードする話

speakerdeck.com

fortee.jp

PHP FFI

FFI = ネイティブコードを呼ぶ仕組み

PHP7.4 で採用された

PHP音声ファイルエンコードFFmpeg が定番

デフォルトでは有効化されていない

brew 経由 php ならデフォルトで FFI が有効化されているらしい

結構難しかった

自分の名前を"ちゃんと"入力したい人生だった

fortee.jp

濱とはま(変換に出ない)

異体字という

shift_jis とかでは異体字がないので、入力ができない

unicodeならいける

絵文字と同じ要領でいける

🙆‍♂️←色違い

異体字も文字と異体字スキン?で表現できる

異体字の元DB?は3種類

Adobe に濱がない

が、別のDBにあるのでそれを持ってこればいける!

(おまけ)ネットワーク

MacBook Air1台でDNSサーバーにしている

が、今回はルーター?がちょっと弱かったらしい

LAN経由で電源を供給できる機器がある

ついでに通信ができるw

今回は最大153台

最後に

殴り書きのメモの羅列になってしまった

PHP 単体の話も面白いが絡めた話も面白い(LTとか特に)

久しぶりに会った人が何人かいてテーマにある「同窓会」を噛み締めた

Twitter で部屋が暑いと呟いたらすぐ冷房が効いて、スタッフさんありがとうございました :pray:

PWA Night CONFERENCE 2020 参加レポート #pwanight

参加してきたので感想や、資料などをまとめていきたいと思います

PWA Night CONFERENCE 2020

conf2020.pwanight.jp

PWA Night はPWAのコミュニティで毎月? イベントをやっています

PWA NightはPWAに関わるすべての方のためのコミュニティです

pwanight.connpass.com

僕も何回か参加したり、登壇したりしたことがあります

毎月イベント開催するのすごいなと思ってますが、更にこんな大きいイベントもするなんてすごいなと思います

基調講演 : Edge of the web

conf2020.pwanight.jp

gist.github.com

いろいろな新しいAPIや技術の紹介で、上記にURLがまとまっています

TWA (Trusted Web Activities)

PWA をGoogle Play Storeで配信できるようにする技術(たぶん)

Google Developers Japan: Trusted Web Activity のご紹介

Project Fugu

ふぐのように毒があるか美味しい機能(まだ実験段階の機能でセキュリティリスクなどあり)という意味で、Project Fugu だったような気が🐡

Web Capabilities (Fugu) - The Chromium Projects

公開されいているスプレッドシートにリストがある

🐡 Chromium https://goo.gle/fugu-api-tracker - Google スプレッドシート

Origin Trials

まだ実験段階の機能を使って貰う代わりにフィードバックをもらうようなシステム(だと思う)

Origin Trials

Badging API

ネイティブアプリのように通知のバッジをつけられるAPI

Badging for app icons

実装自体はメチャクチャ単純っぽい

// Set the badge
const unreadCount = 24;
navigator.setExperimentalAppBadge(unreadCount);

// Clear the badge
navigator.clearExperimentalAppBadge();

Shortcuts

ネイティブアプリにあるホーム画面のアイコンから直接アプリのいろんな画面?にいける機能

今の所 Edge だけで、Chrome は実装予定らしい

MSEdgeExplainers/explainer.md at master · MicrosoftEdge/MSEdgeExplainers · GitHub

f:id:tiwu:20200201193229p:plain
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/master/Shortcuts/explainer.md から引用

mafifest.json に追加するだけっぽいので、これも実装は簡単そう

"shortcuts": [
    {
      "name": "Play Later",
      "description": "View the list of podcasts you saved for later",
      "url": "/play-later",
      "icons": [
        {
          "src": "/icons/play-later.svg",
          "type": "image/svg+xml",
          "purpose": "any"
        }
      ]
    },
    {
      "name": "Subscriptions",
      "description": "View the list of podcasts you listen to",
      "url": "/subscriptions",
      "icons": [
        {
          "src": "/icons/subscriptions.svg",
          "type": "image/svg+xml",
          "purpose": "any"
        }
      ]
    },
 ]

Web Share

ネイティブアプリのようにシェア先の一覧を出すAPI

Share like a native app with the Web Share API

シェアするときは下記の様な実装っぽく、 share() を叩くと(たぶん)シェア先一覧のリストが出てくるんだと思う

今までは自分たちで似たようなシェア先モーダルを作っていたがそれが不要になるっぽい

if (navigator.share) {
  navigator.share({
    title: 'web.dev',
    text: 'Check out web.dev.',
    url: 'https://web.dev/',
  })
    .then(() => console.log('Successful share'))
    .catch((error) => console.log('Error sharing', error));
}

Web Share Target API

ネイティブアプリのように PWA に対してシェアが可能になるAPI

Receiving shared data with the Web Share Target API

manifest.json に追加するだけで、自動的に検知されるっぽい?

"share_target": {
  "action": "/share-target/",
  "method": "GET",
  "params": {
    "title": "title",
    "text": "text",
    "url": "url"
  }
}

SMS Receiver API

SMS 認証のAPI

インドはほとんど電話番号で認証をするとのこと

Verify phone numbers on the web with the SMS Receiver API

こんな感じで受け取れるっぽい

const sms = await navigator.sms.receive();
const code = sms.content.match(/^[\s\S]*otp=([0-9]{6})[\s\S]*$/m)[1];

Clipboard API

テキストのコピーはもともとあったが、画像もコピーできるとのこと

Image support for the async clipboard API

テキストのコピペ

// copy
await navigator.clipboard.writeText(location.href);
// paste
 let text = await navigator.clipboard.readText();

画像のコピペ

セキュリティリスクがあるらしく権限周りの確認が必要とのこと

try {
  const imgURL = '/images/generic/file.png';
  const data = await fetch(imgURL);
  const blob = await data.blob();
  await navigator.clipboard.write([
    new ClipboardItem({
      [blob.type]: blob
    })
  ]);
  console.log('Image copied.');
} catch(e) {
  console.error(e, e.message);
}

Contact Picker

端末の連絡先を取得するAPI

A contact picker for the web

こんな感じで取得できるらしい

ユーザーは連絡先一覧から任意の人を選択するようなフローで、全員を一気に取得ではないとのこと

const props = ['name', 'email', 'tel', 'address', 'icon'];
const opts = {multiple: true};

try {
  const contacts = await navigator.contacts.select(props, opts);
  handleResults(contacts);
} catch (ex) {
  // Handle any errors here.
}

Native File System

ローカルのファイルを取得できるAPI

The Native File System API: Simplifying access to local files

chooseFileSystemEntries を呼べば、OSのファイル選択ダイアログが出てくるとのこと

let fileHandle;
butOpenFile.addEventListener('click', async (e) => {
  fileHandle = await window.chooseFileSystemEntries();
  const file = await fileHandle.getFile();
  const contents = await file.text();
  textArea.value = contents;
});

書き込み処理

async function writeFile(fileHandle, contents) {
  // Create a writer (request permission if necessary).
  const writer = await fileHandle.createWriter();
  // Write the full length of the contents
  await writer.write(0, contents);
  // Close the file and write the contents to disk
  await writer.close();
}

Web Authentication

アクセストークンのAPI(イマイチ理解できていない

FIDO Authentication  |  Google Identity Platform  |  Google Developers

なお、いらすとやにアクセストークンの絵を依頼したら書いてくれたらしいw

セキュリティキーのイラスト | かわいいフリー素材集 いらすとや

f:id:tiwu:20200201194939p:plain

Cookie

先日話題になったCookieの話

Google Developers Japan: 新しい Cookie 設定 SameSite=None; Secure の準備を始めましょう

来週くらいには Chrome 80 が来るので準備をとのこと

MiniApp Standardization White Paper

最近中国とかで話題の MiniApp (スーパーアプリ)

MiniApp Standardization White Paper

中国で乱立してきたので標準化しようというのが中国のサービス同士で話題になっているらしい

PWA is just a branding. Believe in the "web"

最後の方のスライドに書かれていた言葉

ウェブを信じろ

スケーラブルPWA 〜こえのブログ最新事例〜

見てきました

conf2020.pwanight.jp

speakerdeck.com

何回かこえのブログの発表は聞いたりしたのですが、今回はスケーラブルにフォーカスをした発表でした

Single Origin を心がけ、SWのスコープやパーミッションCookieなどを扱いやすくしている

いろいろな端末に対応をするために polyfill で対応をしている

パフォーマンスにとても注力しており、いろいろなキャッシュ戦略で立ち向かっている

Faslty によるキャッシュ、SW によるキャッシュを効かせている

パフォーマンスはバジェットとして管理をしている

SpeedCurve などのデータと、実際のユーザーのデータを見ながら計測している

強豪より 20% ほど早くするのを目標にしている(20%の差があればユーザーは認知できるらしい)

ファイルサイズなどは PR などでチェックをして予算を突破したら、コードを減らせるか?機能を減らせるか?などフローに沿って対応していく

アクセシビリティにも色々力を入れており、キーボード操作やダークモードなどを対応している

Project Fugu も積極的に取り入れており、Web Share, Wake Lock (画面が自動で暗くなるのを制御できる), Native File System などなど

感想

パフォーマンスバジェットの容量エラーのときの対応フローがあるのは驚いた

Webでできる体験を考える会

こえのブログを見ていたので、こちらは見ていない

conf2020.pwanight.jp

speakerdeck.com

PWA で音ゲー?を作った人の発表

qiita.com

ツイッターを見ていたらどうやら音響機材?を持ってきていて、すごいことをしたらしい

また、暗号化の話やプッシュ通知の話や、ブロックチェーンみたいな話もあったらしい

こっちも聞きたかった😢

日経電子版のモバイル/PC版統合と今後の取り組み

conf2020.pwanight.jp

speakerdeck.com

日経電子版の高速化の話もよく聞きに行っているけど、今回はPC統合の話

SPはFastly + microservicesで作られていて、PCはモノリシックな状態

SPとPCで別の開発チームで、ドメイン知識が分散したりしてカオスな状態に

また、PCは10年以上保守をしているので結構ぼろぼろな状態

そこで、SPとPCを統合

第一弾はトップ画面

統合時に機能を色々削除

UI/UX 担当がかなり権限を持っており、ビジネスサイドと調整をして削っていった

今まで、 node.js + handlebars だったが、TS + Preact に置き換え

Pure JS だったクライアント側は web components + TS に置き換えし、 SW は workbox を利用することに

PCも Fastly を通し、トップだけSPに、他は今までのPCのアーキテクチャにルーティング

無事リリースしたが、パフォーマンススコアが70くらいで、思ったより成果が出なかった(80以上を目指していた)

リリースはゴールではなく、スタートだった

SW がSPとPCの両方を見るようになり、カオスに

パフォーマンスモニタリングを強化(SpeedCurve に加えて、Lighthouse CI)

開発時の push などで Lighthouse CI を回し、本番は今まで通り SpeedCurve を利用

チューニングは泥臭く、Resource hints や使われてないJS・CSSの削除などを行った

トップにある記事を SW でキャッシュしていが、ユーザーがキャッシュした記事を見ないことが多く、無駄にキャッシュをしていた

CTRに基づきキャッシュする記事を変えるようにして、キャッシュヒット率を高めた

ユーザー体験を高めるために、ユーザーは何が嬉しいかを一番に考え常に動く

ScrapboxでのServiceWorkerとCacheの活用

日経電子版の発表を見ていたので見れていない😢

conf2020.pwanight.jp

scrapbox.io

キャッシュ戦略の話がメインで、CacheStorage の話も

CacheStorage は自分もよく調べていたりしたり、発表をしたこともあったのでとても気になる 👀

Why PWA should be on strategy map for eCommerce companies in 2020.

conf2020.pwanight.jp

事前に収録されたビデオを放送するというスタイル

日本語字幕があって、英語がわからなくても大丈夫だった

Vue Storefront というEコマースのオープンソース

名前にある Vue は Vue.js で作られているからっぽい

www.vuestorefront.io

Eコマースの75%はモバイルで、速さにとても注力している

デスクトップをしらないモバイルを使う子供も増えてきた

端末が大きくなってきたので、親指で使えるところに一番大事なボタンを置くようにした

ヘッダーのハンバーガーメニューとかは遠すぎて使いづらい

アニメーションを多用して、楽しい・面白いサイトとの認識してもらって滞在時間を長くする

バーコードリーダーを搭載しており、店舗で見て、バーコードを読み取り、サイトで買うという流れを作っているっぽい

Eコマースではチャットボットがより流行り、CSはどんどん減っていくと予想している

購入時も簡単にアニメーションで楽しませる

しかし購入時のクレジットカード入力はモバイルで難しいので、Payment Request API で解決する

AMPも利用している

オフライン状態でもカートに商品を入れて、決済までいける(決済はオンライン状態で処理される

アレクサやグーグルホームにも対応している

サイトに対するエンゲージメントをPWAで高めるという考え

ランコムはPWAでCVを17%あげた

OLXは最エンゲージメントが250%アップ、離脱率を80%改善

アリババはCVが76%上がり、インストールしたユーザーは4倍CVした

www.storefrontui.io

https://docs.storefrontui.io/

UIライブラリも存在する

The PWA Book | DIVANTE

本も出しているらしい

PWA x PlayCanvasについて

Vue Storefront を見ていたので見れていない😢

conf2020.pwanight.jp

ゲームと PWA

AMP + PWAで実現するストレスのないページロード

conf2020.pwanight.jp

AMP はフレームワークであり、コンポーネントライブラリ

AMPキャッシュは別で考えたほうがとっつきやすい

Bento AMP で普通のサイトでも AMP Components が利用可能に

amp-script で JS を書いていく

amp-script は Web Worker で動き、DOMを触れるAPIを用意しているのでそれを利用する

AMP 用の SW もあるので PWA と親和性は高い

進化するHTTP

AMP の話を聞いていたので聞けていない😢

conf2020.pwanight.jp

PWAに取り組む前に知っておきたいSPAとSEO

conf2020.pwanight.jp

speakerdeck.com

SEOの土台

・グーグルボットにクロールされること ・HTMLが適切に解釈されること

クロールされるのは sitemap とか内部リンクで頑張ろう

OGPがSEOの文脈で語られることが多い(関係ない)

Fetch as Google とグーグルのインデックスで検証した人がいる

Fetch as Google は5秒、グーグルのインデックスは20秒待った

Google 的には5秒位らしい

Googleはサイトにアクセスをして、サイトをRender Queueに登録する

Render Queueは数時間から数週間かかる可能性がある

これはGoogleも課題に感じていおり、現在改善中とのこと

OGP取得の際はJSが動かないので、レンダリングされたものを返す必要がある

BOTとユーザーに別のコンテンツを返すのは本来アウトだが、ダイナミックレンダリングGoogleが推奨している

developers.google.com

自分たちのサイトの性質やエンジニアをみて、SSRするかSSGするかを考えるのがベスト

まもなくやってくるVue.js 3

SEOを聞いていたので見れていない😢

conf2020.pwanight.jp

speakerdeck.com

最近 Vue Composition API を使っているので聞きたかった!

Taking your web app offline (in a good sense)

conf2020.pwanight.jp

workbox の話

地球で最大規模?のPWAのSLACKグループ

https://aka.ms/pwa-slack

ここらへんでMACの電源が死んだ😭

Unlocking new capabilities for PWA

conf2020.pwanight.jp

speakerdeck.com

NFCが web にくる

developer.mozilla.org

デモ

vimeo.com

コード

github.com

エンディング講演 : PWA on Windows

conf2020.pwanight.jp

www.slideshare.net

Edge の話

Windows は SW のキャッシュ上限なしらしいw

Microsoft Store ? からインストールする PWA はラップされており、Chromium ベースじゃないので要注意とのこと

PWA じゃなくても Edge は無理やりインストールができるらしい

まだまだ Edge にはバグが多いので気をつけようとのこと

LT

懇親会は参加していないのですが、LTがあったそうなので資料を貼っていきます

docs.google.com

speakerdeck.com

speakerdeck.com

感想

PWAが話題になった当初はSWによる高速化、ホームスクリーン追加、プッシュ通知が話題になってたが、最近は Project Fugu をベースにいろいろなAPIがあり、本格的に Progressive に web を作ることができる気がしてきて、今後がとても楽しみです

最後に

PWA Night CONFERENCE 2020 お疲れ様でした!

とても楽しかったです!

Web+DB Press vol.113

ドメイン駆動設計

  • エリック・エヴァンスが提唱
  • 仕様を探すのは難しい
    • 記憶力には限界がある
    • UserというEntityはあくまでユーザーが持つ情報しか持ってない
      • 仕様ではない
    • ドキュメントは常々信用ができない
    • 登録で最大文字列をチェックしていても、更新でもチェックしていることもある(気付けるか?
    • 実際は2箇所でも(一応)全てをチェックして2箇所であると判断しないといけない
  • そのためにはコードの中に気づけるポイントをいれること
    • 例えばユーザーオブジェクトにIDとNameがあったとき、Nameだけ変更できるメソッドを用意するとか
    • UserのNameをただのstringにせず、UserNameというクラスで定義してしまうなど
  • ビジネスの変更の速度に合わせる
    • 究極的にはどんな変更でも一箇所だけにする
  • ドメイン = 領域
  • ドメインとは何かではなく、何がドメイン内に含まれるかを考えるべき
  • ソフトウェア上の表現として十分かつそれを抽出する過程をモデリング
    • 成果物がモデル
    • ドメインのうち重要な知識を抽出した結果できあがる概念をドメインモデルと呼ぶ

ティール組織

ティール組織

ティール組織――マネジメントの常識を覆す次世代型組織の出現

ティール組織――マネジメントの常識を覆す次世代型組織の出現

著者はフレデリック・ラルー。マッキンゼーで10年以上組織改革を行っていた。

2年半にわたり新しい組織モデルについて調査を行い、本書を執筆した。

はじめに 新しい組織モデルの出現

  • 人間の脳は3つあるが、かなり最近まで1つだと考えられていた
    • 司令塔が複数存在して人間(会社)が回るはずがないと無意識に考えたからでは?
  • 組織のトップも下層もそれぞれ辛い(トップは権力争い、下層は退屈でつまらない)
  • いろいろな組織を調査して、多くの組織が互いの存在を知らないまま新しい組織モデルで動いていた

歴史と進化

変化するパラダイム 過去と現在の組織モデル

  • 人類が作ってきた組織タイプは、その時代や世界観に密接に紐づく
  • 人間は常に進化を続けるのではなく、段階的に進化する(オタマジャクシが蛙になるように)
  • 組織も同じように段階的に変化していった

受動的パラダイム(無色)

  • 最も初期の発達段階
  • 家族などの血縁関係の集団(10人位)
  • これ以上増えると人間関係が難しいので、増えると分解する
  • 自我(エゴ)が完全に形成されていない
  • 他人や環境から自分を完全に切り離してはいなく、分業を必要としないため階層やリーダーもいない

神秘的パラダイム(マゼンダ)

  • 数百人規模で自己と他者を区別しているが、自分自身が世界の中心と考えている
  • 因果関係がわからないので、神頼みなどが流行る

衝動型パラダイム(レッド)

  • 完全に自己が目覚めており、最初の首長制度と原始的な王国が生まれる
  • 世界は危険で、力を求めだし、力があれば自己の欲求を満たせると考える
  • 組織は対人関係に力を入れる(マフィアやギャングのように、常にトップは部下に対して力を示し続ける)
  • 正式な役職や階層はなく、恐怖と服従で組織を構成する
  • 組織としては未来ではなく今が重要(カオスな紛争地帯とかが得意

順方型パラダイム(アンバー)

  • 農業・国家・宗教等が生まれる
  • 過去、現在、未来という認識をしているのでスケジュール等が作れる
  • 他人の思考を考えることができるようになる
  • 属する社会集団からはじき出されないように考え始める
  • 「私のやり方かあなたのやり方か」から「私たちか、彼らか」に変わる
  • 正しい世界を成り立たせる不変の法則がある
  • 階層化しやすくなる

順応型組織

  • 組織は中長期的な計画を立てられるようになった
  • 規模を拡大できる安定した組織構造をつくれるようになったい
  • プロセスを適用することで過去を未来に複製できる
    • 去年の授業カリキュラムや去年の収穫量など
  • 知識は個人ではなく組織に蓄えられる
  • 世界は不変と信じているから、過去と同じプロセスを踏む
  • 組織図は強固なピラミッド
  • 人類の最初のグローバルな組織はこの形でできた
  • 計画立案と実行は厳格に分けられている
  • レッド組織は暴力で支配されていたが、アンバー組織は懲戒や罰則で支配する
    • 根底には労働者は怠け者で、常に指示待ち人間という考え
  • そのため自己表現などは求められない
  • しかし、レッド組織と比べればルールを守れば見の安全が保証される
  • メンバーは自分の地位で満足し、より高い褒美を求めないから成り立つ
  • 自分の階級と業務内容に期待されている態度を自分のものとして取り込む
  • 重要なのは社会的な帰属意識
  • 「自分たち」対「彼ら」という問題
    • 「看護師」対「医師」
    • 「営業」対「財務」
  • 集団内の争いを避けるため外部に責任を押し付ける
  • 明確な縄張り意識があり、境界線に目を光らせる
  • できる限り自給自足を追求し、外の世界を不要にさせる
    • 職場と社宅
    • 辞めづらくなる

達成型パラダイム(オレンジ)

  • 世界は不変のルールに支配されているのではなく、複雑なゼンマイじかけのように捉え始める
  • 正解・不正解という絶対的ではなく、他よりうまくいくという相対的な世界観
  • 世界がどのように動いているかを理解すればするほど良い成果が出る
  • この認識を持つと、代々受け継がれてきた規則などに疑問を抱くことができる
  • そのため科学的研究やイノベーションが発達する
  • 達成型の絶対的な善悪を「成果があるかないか」という基準に置き換える

達成型組織

  • 可能性の正解に生きていて、変化とイノベーションは驚異ではなくチャンスと捉える
  • アンバー組織はプロセス重視だが、オレンジ組織はプロセスとプロジェクト重視
  • 基本ピラミッドだが、オンラインで繋がる横断的な仮想チームなど部門や階層に風穴を開け、コミュニケーションの速度を上げる
  • アンバー組織は「指揮と統制」だが、オレンジ組織は「予測と統制」になる
  • 組織内の多くの人材の知性を引き出すことが競争優位になる
  • 自ら考えて実行できる業務範囲において、権限と信頼が与えられなければいけない
  • そこで、目標管理制度が生まれる
    • トップは目標が達成されている限りは、どのように達成されたかをそれほど気にかけない
    • 目的(予測)とフォローアップ(統制)を確定するための経営プロセスが多く生まれた
    • 具体的な成功を目指して動く
      • ボーナスや表彰制度などが生まれる
  • 個人の目標と組織の目標が一致すれば、対立関係になることが多い経営者と従業員が双方の利益となる目標を追求する
  • しかし、実際には統制をやめる恐れが、部下への信頼に勝ってしまう
    • 本来委譲すべき意思決定権を与えられない
  • 実力主義の考えなので基本的には誰でも上に上がることができる
  • 組織を機械と考える
  • 企業は成功すると、より成功するためにニーズを作り出してしまう
  • 物質主義、社会的不平等、コミュニティの喪失

多元型パラダイム(グリーン)

  • 人生には成功か失敗か以上の意味があると考える
  • 人々の感情に敏感で、考えは全て平等で尊重されるべき
  • そのため、密接で協調的なつながりを築く努力が必要
  • 奴隷制の廃止や女性の解放、宗教の自由、民主主義が生まれた
  • 非営利団体、社会事業家、地域社会活動家など
  • 成果よりも人間関係のほうが価値が高い
  • グリーン組織はボトムアップのプロセスを模索する
  • リーダーは奉仕するべきと主張
  • 差別や不平等が続いているので、人生は自己中心的な成功よりも大事なことがあると主張する
  • しかし、悪いアイデアも平等に扱われる

多元型組織

  • 権力や階層をなくしたいと考える
    • しかし、白紙から階層のない組織を作ると一定の規模になると成功はしない
  • オレンジ組織のような階層を残しつつ、意思決定の大半を最前線の社員に任せる
  • 分権化と権限委譲を大規模に行うのはかなり難しい
  • トップ層は権力を分け合い、統制力を弱めるのが求められる
  • そのために中間管理職に対して期待するリーダーシップのあり方を定義する必要がある
  • 部下に耳を傾け、権限を委譲し、育てるサーバントリーダーになる必要がある
    • 管理職候補は「権限を分担する用意ができているか?」、「謙虚に引っ張れるか?」
    • 教育コストかなり払って、サーバントリーダーとしての心構えを教える
    • マネージャーは360度評価によって評価する
    • 部下からマネージャーを指定することもある
  • 今日な文化が共有されていないと、権限委譲は難しい
  • 最前線の社員はルールではなく、価値観で判断をする
  • オレンジ組織だと価値観は、利益の都合が悪くなると無視してしまう
  • リーダーが共有価値に心から従っていると、従業員は敬意をもって扱われていると感じ、権限を得て、貢献しようとする
  • オレンジ組織は戦略と執行が絶対だが、グリーン組織の最も重要なのは文化
  • CEOは文化と価値観を育てるのが重要と考える
  • HR部門が中心的に動く
  • オレンジ組織は株主の視点で経営する
  • グリーン組織は株主だけでなく従業員や社会や環境なども考え、責任を負っている

レッドからグリーンへ

  • 組織という考えは人類の歴史としてはごく最近のこと
  • 加速度的に組織は変化しており、新しい組織モデルが1,2個生まれる可能性もある
  • 別の組織モデルが同じ時代に存在するのも特徴

まとめ

  • レッド組織
    • 暴力と恐怖により支配
    • 短期志向
    • マフィア・ギャングなど
    • 労働分担と指揮権限
    • 狼の群れが近い
  • アンバー組織
    • ピラミッド型の階層構造でトップダウン
    • 未来は過去の繰り返しで「安定」を求む
    • 軍隊・公立学校
    • 長期的視点のプロセス、役割
  • オレンジ組織
  • グリーン組織
    • 文化と権限委譲を重視して、モチベーションを高める
    • 文化重視
    • 価値観、文化

発達段階について

  • 青年は幼児と比べて「良い」人間というわけではない
  • それと同じように各組織も優劣で見てはならない
  • 状況に適しているかという判断をすべき
  • 段階と色は現実の抽象化に過ぎない
  • 人類は1つの段階に収束できない
  • どの段階でも、前段階を内包している
  • 精神面、倫理観などが全て同じ段階にいることもない
  • 人はある特定の瞬間に、ある1つのパラダイムに基づいて活動をする
  • 人生の大きな試練には「解決するために複雑な視点を持つように成長する」か「無視する」か
  • 自分よりも複雑な世界観をすでに獲得した仲間に囲まれ、安心して自分の心理的葛藤を探求できる環境が大切
  • 組織に発達理論を適用するときには単純化しすぎないよう注意する
  • 社長が自由に給料を上げ下げできるのならレッド組織
  • 階級によって固定給になっているならアンバー組織
  • 目標管理による報酬アップならオレンジ組織
  • チーム単位のボーナスを重視するならグリーン組織
  • これらはあくまで組織で、働いている人を指しているわけではない
  • 組織のリーダーの発達段階をチームは超えることができない
  • 組織の神髄は、人々をその気にさせて実力以上の能力を引き出す

進化形

  • 意識レベルが1段階高くなると、世界をより広い視点から眺めることができる
  • そのためには自分の欲求を衝動的に満たそうとするのを抑えるとレッドからアンバーに移行する
  • また、集団の決まりごとを拒否するようになるとアンバーからオレンジに移行する
  • 自分自身のエゴから自らを切り離すとティールへ移行する
  • エゴに埋没していると外的要因に左右されがちになる
  • レッドの観点では自分の欲しいものが獲得できるかが軸
  • アンバーでは社会規範への順応度が軸
  • オレンジでは効果・成功が軸
  • グリーンでは組織への帰属意識・調和が軸
  • ティールでは意思決定の軸が内的なものになる
    • この判断は正しそうか?
    • 自分に正直になっているか
    • 自分がなりたいと思う理想の人物は同じように考えるか
  • 承認欲求や富や愛は結果にすぎない
  • 愛や名声や成功を追い求めると最終的に「他人の顔を身にまとう」
  • ティールでは内面の正しさを求める旅を続ける
    • 自分が何者で、人生の目的は何か
  • 人生の目的を設定してどの方向に向かうのか決めるのではなく、人生を開放しどんな人生を送りたいか考え学ぶ
  • 他人の顔を身にまとうようになるので、自分自身の強さの上に立てない
  • 欠点を見るのではなく、長所を活かす
  • ティールでは「失敗などはこの世になく、全て自分自身の本当の姿に近づくための経験にすぎない」と悟りをひらく
  • 以前の段階では人生の生涯は不運なものとして扱われる
  • ティールでは自分に原因があったのでは?そこから成長するためには?と考えるようになる
  • エゴから自己を切り離し、全体性を求める
  • 脱同一視化を経ると完全に独立し、逆説的に全てのものの1部となる
  • 判断と寛容という対立を超越する
  • 意見が異なる場合、今までは説得や間違いを正したり、意見の違いを吸収して寛容的に取り込むという手法を取る
  • ティールでは意見の違いを正さず、決めつけをしない
  • 会話をするとき修正・否定するための情報収集ではなく、他の人が自分の声や真実を見つけられる手助けをする
  • 人間のエゴによる組織の動きを極力へらす
  • ティールでは自分の人生の使命を探すことに忙しくなる
  • 収益性や成長、市場シェアよりも存在目的が組織の意思決定を道ぎく

第二部 ティール組織の構造、慣行、文化

3つの突破口と比喩

新たな比喩 生命体としての組織

  • オレンジ組織は「機械」
    • 組織の頂上でレバーを引くと従業員が機械の歯車のように働く
  • グリーン組織は「家族」
    • 父は子供に奉仕する
  • ティール組織は「生命体」
    • あらゆる細胞がそれぞれ動く

ティール組織が開く3つの突破口

自主経営

階層やコンセンサスに頼ることなく、仲間との関係性の中で動くシステム

全体性

職場に行くときは「専門家」の仮面を被るのではない

精神的な全体性が呼び起こされ、自分をさらけ出して職場に来ようという気にさせるような、一貫した慣行を実践する

存在目的

組織自身の生命と方向感を持っている

組織が将来どうなりたいのか、どのような目的を達成したいのかに耳を傾け理解する場に招かれる

自主経営/組織構造

  • 権力をトップに集め、権力者とそれ以外に分けると問題を抱え病んでいく
    • 権力は戦って勝ち取る価値のある希少なものになる
  • アンコントローラブルな状態になると、コントール可能な楽しみに引かれるようになる(プライベート
  • グリーン組織は権力の不平等という問題を、組織ピラミッドの下位に一部与えることで解決を目指す
  • 全員が強い権力を持ち、無力なものが一人もいないのという考えが最初の突破口

オレンジ組織からティール組織へ

  • オランダの地域密着型在宅ケアサービス
  • 元々オランダは地元の看護師が在宅ケアサービスを行っていた
  • 大勢の看護師のスキルや数を活かそうと組織化した
    • 休暇を取る看護師のフォロー、知識のシェア
  • 日々のスケジュールの最適化、患者から患者への移動の最適化
  • コールセンターによる患者の窓口の最適化、効率をあげるために治療の時間の設定
  • スキルによる医療の分担、効率の向上のために活動の時間の監視
  • 規模とスキルの効率化を求めるオレンジ組織からすると理に適う
  • しかし患者と看護師の個人的な信頼関係はなくなった
  • 患者を人と認識せず、商品が適用される対象となった
  • 看護が金持ちになるための仕事となった

自主経営チーム

  • 看護師を10-12名程度のチームに分けて、担当地域を割り当てる
  • ケアサービスを提供する以外にも、何人受け持つか、ケアプランの作成、休暇や休日のスケジュールも決める
  • チーム内にリーダーは存在せず、重要な判断は集団で決める

驚くべき成果

  • 介護にかける時間は他の組織と比べると4割ほど少ない
  • 他の組織は「商品」の提供の時間を分刻みに計測し分析して記録するので人数的な話をすると成果が出る

上司の不在

  • 上司(管理職)は存在せず、チームごとに発生する管理業務全般に取り組む
  • 全員「相互座用による問題解決法」を学び、集団での意思決定をするためのスキルを得る
  • 人と人との協力に関する基礎知識を学ぶ

チームミーティング方法

  1. メンバーが抱えている問題を元に、ファシリが意見を吸い上げる
  2. 検討し修正や改良が施される
  3. 信念によって異議を唱える人がいなければ案が採用される

全員が心から賛成する完璧な解決案は存在しないはず

信念に基づく反対がなければ、将来新たな情報が手に入ったときはいつでも見直すという約束をする

  • 外部の助けをいつでも呼んで良い
  • チームに難しい問題を解決するだけの権限と裁量があることを知っている
  • しかし、自由を得て責任を負えるようになるには時間がかかる
  • これは個人が成長する過程で、情熱とモチベーションを持つようになる
  • チームに上司などはいないが、平等であることを意味しない

ドルマネジメントは存在しない

  • マネージャーは存在せずコーチが存在する
  • コーチはチームの意思決定権は持っていない
    • 成果責任もなく、売上目標もない、チームが好成績だからといってボーナスが貰えるわけでもない
  • チームは組織の上部から権限を与えられているわけではなく、自分たち以上の意思決定権を持つ階層に支配されていない
  • コーチはチームのアドバイザー
  • 助言を与えたり、他のチームの解決事例を教えてくれたり、自分たちで解決策を見つけられるように示唆に富んだ問を投げる
  • チームが悪戦苦闘するのは問題ない。学びがあるため。
  • コーチは問題を防ぐのではなく、問題を解決しようとするチームを支える
  • 解決方法を知っていても、チームに選択させる
  • チームの姿をそのまま見せたり、問を投げかける
  • 出発点は、チーム内の情熱と強みと能力を引き出すこと
  • コーチは一人あたり40~50チームを見る
  • チームにかかりきりになるとチームの独立性を冒すかもしれない
  • チームの最も重要は課題にだけコーチは注力する
  • チームは12人を超えない。超えるなら分割する
  • 業務をメンバー間で幅広く分担する
  • メンバー間、コーチとの定期的なmTGを開く
  • メンバーは互いに評価し合う
  • チームは毎年取り組みたいテーマと実行計画をたてる
  • 顧客への労働は6割ほど割く

必要最小限のスタッフ機能

  • 大組織は人事、法務、戦略策定、財務、意思疎通、リスク管理などスタッフ機能が増殖してきた
  • スタッフはルールなどを改正したり、専門技術を積み上げたり、問題を探したりといった付加価値を出す方法を見つけることで、存在意義を証明しようとする
  • そのうち、現場から離れたところに権限と意思決定権を集中させるようになる
  • すると現場は権限を奪われたと感じる
  • ルールは理論的に正しいかもしれないが、現場で直面する状況の複雑さには対応ができない
  • ティールではスタッフ機能を抑える
  • スタッフ機能を大きくして実現できる規模とスキルによる利益よりもモチベーションの低下による不利益のほうが大きいこと承知している
  • スタッフ機能は指針を提供するが、ルールを押し付けることができない
  • 真に現場のサポートであり、チームから要請があったときのみ動く
  • 7000人の看護師に対して本社は30人ほどしかいない
  • 採用、コールセンターなどのスタッフもしていない
  • ほとんどのスタッフ機能がチームに委譲されている
  • 採用をしたいと思ったチームは自分たちで採用活動を行う
  • 自分たちで意思決定をするので、思い入れが強くなる
  • 専門知識グループを本社に作ると、給料の高い専門家と給料の安い格下という2階層ができる危険がある
  • チームメンバーが専門知識を身に着け、チームの枠を超えた連絡窓口になる
  • だれがどんな専門知識をもっているか見える状態である
  • ボランティアによるタスクフォースが立ち上がり、専門知識を蓄える
  • 専門家はフリーランサーとして雇う
  • チームに意思決定権を持たない状態でスタッフを雇う
  • スタッフ機能から課されるルールや手続きがないため社内には解放感と責任感が満ちている
  • スタッフ機能は効率性を期待できると議論になるが、金銭の効果は見積もりできてもモチベーションの低下を数値に示すのは難しい
  • スタッフ機能は幹部が現場をコントールできるという感覚を持てる
  • モチベーションの圧倒的な向上のために、規模の経済を捨てると思うこと
  • スタッフ機能が現場をコントロールできるという幻想を捨てる

労働者が進化型を向く理由

  • 以前はピラミッド型の工場会社だった
  • 15-30名程度のチームに分割した
  • 採用、スケジュール管理等々は以前はピラミッド型の上部に権限があったが、今はチームにある
  • 以前は注文が営業部に届き、企画部等がスケジュールをひき、人事部が人をアサインし、現場の人が働く
  • 部署をまたぐと情報はブラックボックスになり、見えなくなる
  • 営業が報告するのは営業のトップではなく、チームメンバー
  • チームメンバーとは毎日顔を合わせ、仲良くなる
  • 営業トップから与えられる目標より、チームのために働くほうがモチベーションとなる

経営陣はなく、ミーティングもほとんどない

  • ティール組織は、営業や保守などのトップが集まったMTGは存在しない
  • チームは毎日、毎週、毎月など自分たちで決定し運用する
  • MTGは上から目線の意識が生じるリスクが発生する
  • ピラミッド型の場合、情報が指揮命令系統の中を円滑に上下するように伝達するように階層間でMTGをする
  • トップに情報が集まるため、意思決定の権限が上に押し上げられる
  • 問題が発生したときに初めてMTGが開かれる
  • 有機的な組織

チーム間の人員調整と知識の交換

  • チームを超えた調整の際に、ピラミッド型であれば管理職の出番である
  • 人の割り振りは、ティール組織では全チームから誰かが集まって会話して決定する
  • 支援業務する人はチームを超える意思決定権はなく、自分たちの説得力を頼る

信頼 vs 統制

  • 生産性の管理(時間ごとのノルマなど)を辞めたら、逆に生産性があがった
  • ノルマが上げられても対応できるようにセーブをして仕事をしていたから
  • ノルマを管理し、他人をクビにする仕事は、幸せを生み出せていない

信頼のエネルギー

  • 組織の大きな阻害要因は「恐れ」である

プロジェクト

  • 本当の意味でプロジェクトに必要じゃない作業はしていない
    • 報告用の資料など
  • 意思決定、優先度の決定はシステムの持つ集団的知性を信頼する
  • 継続的改善、リーンの専門家はいなく、チームの中に溶け込んでいる
  • 課題は壁に貼られて挙手制で対応する
    • 誰も取らないのであれば重要ではないという認識になる
  • 机はキャスター付きで移動がしやすい

自主経営を数万人規模に拡大する

  • 個人の権限と責任を大きくすると、市議とは楽しくなり、業務改善の余地が大きくなる

ボランティアによるタスクフォース

  • 8割を自分の業務に当てて、2割を会社の中で動いているタスクフォースに時間を使う
  • 予算など専門的なものも多くの従業員で決める
  • 自分のスキルや才能や好みなどが見つかることもある
  • 会社を変えられる権限を持っていることに気づくと、責任感がます
  • また、学習がハイスピードでできる

組織図も、職務記述書も、肩書もない

  • 事前に決められた仕事に合わせる必要はなく、興味や才能、組織のニーズに基づき決まっていく
  • 方向性を決め、予算を立て、分析し、計画を立て、測定し、採用し、評価し、意思疎通を図るのは分担される
  • 流動的に仕事が行われる
  • 管理系が集中すると階層的な組織に戻るリスクがある
  • チームリーダーが上司のように振る舞い出す危険がある
  • メンバーはチームの移動の自由が約束されているので、権限を持とうとしても離れられる
  • 役職はエゴの蜜で、自分が役職そのものと勘違いしてしまう
  • 役職はないが、全員が同じ仕事をしているわけではない
  • 全員が互いに同僚と呼び合う
  • 外からみたらCEO的な仕事をしているかもだが、中ではCEOとは呼ばれない
  • 仕事をチームメンバーに約束をするので、互いに管理職となる

自主経営/プロセス

  • 前章では組織構造について述べた
  • この章では実際のプロセス(意思決定方法など)について述べる

助言プロセス

  • 原則として誰でも意思決定ができるが、必ず決定する前に誰かに助言を求める
  • 意思決定は上が決定するか、全員で一致させるかという2通りだけと思いこむ
  • 相談された側は同じ仲間だと思うようになる
  • 必要とされていると感じる
  • 助言をするというトレーニングになる
  • 実施する人と意思決定がかなり近くなる
  • そして楽しい

危機発生時の意思決定

  • 業績が悪いときに解雇すべきか?
    • リーダーはメンバーに素直に伝えて案を募った
  • 従業員は難しい問題を解決できないのでは?
  • といった恐怖に打ち勝つ必要がある

購買と投資

  • 誰でもいくらでも使って良い
  • ただし助言プロセスは必須
  • これは信頼の上に成り立っている
  • 管理して大量購入をしたほうが節約では?
  • それに気づいた社員が取りまとめをすればよい

暗黙の前提を明らかにする

  • 信頼か統制かについて真正面から議論されることが少ない
  • ↓古き前提
  • 労働者は怠け者だ。すぐサボる
  • 労働者は金のために働く。
  • 労働者は組織より自分の利益を優先させる
  • 労働者は繰り返し可能な単純作業をする場合に効率が良い
  • 労働者は業績に影響を及ぼすような重要な問題を解決できない。それは管理職が得意
  • 労働者は重い責任の仕事をしたくない
  • 労働者は子供のように保護を求める
  • 労働者は時間や製品の数で報酬を得るべきである
  • 労働者は交換可能である
  • 労働者は指示を待つ
  • ティー
  • 創造的で、信頼に足る大人で、重要な意思決定を下せる
  • 自分の判断と行動に説明と責任をもてる
  • 失敗したってかまわない
  • ユニーク
  • スキルで世界に貢献したい
  • アンバーな組織になると人はアンバーになる

内部のコミュニケーション

  • 情報を断絶していくと不利益が多い
  • 従業員は信用できない
  • 従業員は予測不可能で、非生産的な行動をする
  • という思い込み
  • 上が隠していると思うようになる
  • 組織改造がないので最善の判断をするためには互いに情報を知る必要がある
  • 公表されないとなぜ隠すのかという疑念をうむ
  • 情報の知っている度合いで見えない階層が生まれる
  • 全員参加、全員知れるというリスクに力が眠っている

紛争の解決

  • 前提
  • 個人は決して他の人に何位かを矯正してはいけない
  • それぞれの約束を守ること
  • ↓紛争解決プロセス
  • まずは二人だけで解決しようと努力をする。
  • 要求ではなく相手にお願いしたいことを述べ、それに誠実に返答をする
  • 合意できなければ二人が信頼をしている別の同僚を調停者に選ぶ
  • サポートをするが解決策は強要できない
  • それでもだめならば委員会を発足し合意形成を手伝う
  • 進んで互いに説明を求め会える文化がないと難しい
  • 自由と責任はコインの裏表のようでバランスが大事

役割の配置と決定

  • 役割は必要になる問題などが発生したときに自然に生まれる
  • 新しい役割が必要だという意見を助言プロセスにのっとり行っている

約束を文章化する

  • 個人的なミッションを・ステートメントを書く
  • 約束した役割をすべて書き出す
  • 作業がラインになっている場合は隣接する人たちと会話をする
  • 互いの約束を話し合い交渉をし、社員間の繋がりを強くする
  • 人との繋がりと、組織のピラミッドの階層がまざると混乱する
  • 階層がないため昇進がない
  • 経験を積むに従い、大きな責任を持つようになる
  • 上司に良く映りたいではなく、同僚にいい顔を見せるようになる

チーム内の役割とガバナンスをはっきり決める

  • 物事の進め方の最も洗練されたものは「ホラクラシー」である
  • ラクラシーは組織のOS
  • 人=役職という融合を切り離す
  • 人々は仕事を持つのではなく、多くのきめ細かな役割を果たそうとする
  • 役割が必要と感じたら、役割のみを決めるMTGを開催する
  • 提案を発表し、問題点の明確化をおこない、意見を述べ、反対意見を集め、統合する
  • 役割を決めるために裏でこそこそせず、場があることで健全になる
  • 助言プロセスの亜種
  • 目的は完璧な答えを出すのではなく、解決策を見つけ、必要があればすぐ練り直すという商法
  • 頻繁な変化で対応をしていくというマインド

全責任

  • 階層的な組織ではマネージャーは数字に対する責任を負っている
  • ティールでは役割を負い、責任範囲も明確だが、縄張りはない
  • 自分が気づいた問題は自分の役割以外でも何かをする責任を追う
  • 何かとは、問題に関連する役割を持っている同僚に話すこと

任命プロセス

  • 自分のしたいことの全権限があると、出世したいと思わなくなる

役割を交換する

  • 自主経営されていると役割は明確化されるので交換しやすい
  • 役割はホラクラシーでいうとOSの上にのるアプリのようなもの
  • 元気が出る仕事か、疲れる仕事か?
  • 才能が生かされているか、生かされてないか?
  • スキルと知識は役立っているか、いないか?

タレントマネジメント

  • 見込みのある人材を見つけ、特別な研修をする
  • 管理職ポストに対して後継者を作ったり
  • あらゆる人にキャリアパスを考えたり
  • ティールではリーダーシップは分散される
  • 自然に多くの機会に触れて学び育つので、マネジメントはいらない

チームレベルでの実績管理

  • オレンジ組織はプレッシャーをかけ手を抜かないようにさせるのが管理職の仕事
  • ティールではなぜ甘えないのだろうか
  • 仲間との励まし合いと市場の要求によって、内側のモチベーションがあるから
  • なぜ成果を上げるためにはプレッシャーを与えたほうがいいと考えるのか
  • 目的があり、意思決定と資源を持っているときは自発的に動く
  • 従来の組織は自己表現の機械はルールや管理職によって制限される
  • 情熱を持って働くなった場合は人間関係などが悪化している兆候
  • 個人の成果は測定しない
  • 誰でもデータを見れるので、ノウハウを模倣しようという機運が高まる
  • いい意味で仲間からの圧力
  • 簡単にチーム間で成績の比較をできる
  • ティールでは情報が不利になるように使われることはない
  • 事実が良くても悪くても、守ってもらう必要はない
  • 比較できないチームは同僚を前に自己評価のプレゼンを行う
  • これは数時間かかる

個人の実績管理

  • 実績と成果は真っ先にチームレベルで話し合われる
  • それでも個人のフィードバックがほしいと思う
  • 人は感覚が遮断された部屋にいるとすぐ狂ってしまう
  • つまり外部の刺激を欲しがる(フィードバック
  • 影で他人を評価するのではなく、信頼度の高い状態で評価し合う
  • 助言プロセスもフィードバックをもらう1つ
  • 従来組織では、評価はテンション下がるものになりやすい
  • 管理職は狭いところでしか見ない
  • 話し合う内容が狭くなりがち

解雇

  • 基本的には自分の実現したいところで働くことができるのであまり解雇ということはない
  • 従来組織では管理職が決定権を持っている
  • ティールのなかでは自ら適していないと重い自分から去ることがおおい
  • もし誰かが会社の大切にしている価値を壊した場合助言プロセスのフローで解雇が進む

報酬とインセンティブ

同僚間の話し合いに基づくプロセスと自ら定める給与

  • 基本的には話し合いで決定する
  • 自分で給与を決めるところは説明責任が発生する

個人への報奨金はないが、会社全体の賞与はある

  • アンバーでは地位に応じて決まるので、成果報酬はない
  • オレンジは成果報酬がある
  • グリーンはチームに対して成果報酬がある
  • ティールでは内在的欲求で動くので、報奨金は存在しないことが多い

報酬の不公平を減らす

  • 生活に必要なニーズをカバーできる最低給与かが重要

自主経営への4つの誤解

  • 長い間人間は互いを支配しあおうとすることが必要と感じていた

誤解1 組織構造も経営もリーダーシップもない

  • ティールにも組織構造、意思決定プロセスなどがある
  • 管理の仕事はなくなったわけではなく、経営陣に集中しなくなったにすぎない

誤解2 全員が平等

  • 権力の不平等を解決するのではなく、超越する
  • 解決しようとすると全員が同等の権力を持つことになる
  • ティールでは「どうすれば全員が強くなれるか?」と考える
  • 権力を誰かが持つと他の人の分が減るというゼロサムゲームと見ない
  • 全員が繋がっていることを認め合い、あなたが強くなれば自分も強くなると考える
  • 助言プロセスにより必要な権限はすでに持っている
  • 全員を平等にすると考えず、健康になることを認める
  • スキルなどの階層は生まれる

誤解3 要するに、権限移譲

  • 委譲ということはトップに権限があることを認めている
  • 自由は責任を伴う
  • 経営陣に投げていた難しい問題も自分たちで考えることになる
  • 権限は共有されるもの

これは、まだ実験段階の組織形態

  • ボランティア組織はかなりの大きさになっている
  • なぜなら会社で階層組織に触れすぎているから

まとめ

オレンジ組織

  • ピラミッド型の階層構造
  • 人事、財務などのスタッフ機能が多数
  • 階層間でMTG
  • プロジェクトはガントチャートなどで厳重に管理
  • どの仕事にも役職があり、内容は決まっている
  • 意思決定は上位で行われる
  • 危機は上位が秘密裏に対応する
  • 投資は予算で管理される
  • 情報は力であり、知る必要がある限り開示される
  • 紛争は解決されずうやむやにされる
  • 少ない昇進機会で争いが発生される
  • 実績は個人のパフォーマンスに注目され、評価は上位に決定される
  • 報酬も個人のインセンティブで上位に決定される
  • 解雇は管理職が権限を持つ

ティール組織

  • 自主経営の組織、コーチがフォローする
  • 各チームにスタッフ機能がある
  • 必要に応じてMTGが発生する
  • プロジェクトは自分たちで管理し運用する
  • 決まった役割はなく、流動的になる
  • 意思決定は助言プロセスで行う
  • 透明な情報共有
  • だれでも助言プロセスのもと予算を使える&同僚間で予算を話す
  • 紛争は解決の仕組みがある
  • 役割は流動的に再配分される
  • チームのパフォーマンスを注目する
  • 給与は互いに評価し合う

全体性を取り戻すための努力/一般的な慣行

  • 組織は仮面を付ける場所だった
  • 制服は会社が定義した役割を果たすものになる
  • 自分自身を家に置いて出社することになる
  • 組織はありのままの姿の従業員を恐れている
  • 従業員側は互いにバカにされるなど恐れている

人間性を仕事に呼び込む

  • 犬や子供を連れてくる
  • ペットや子供の前で、普段の仕事でしか見せない姿しか見せないのは難しい

開放的な、真の意味での「安心」できる職場環境

  • 自分自身を全てさらけ出すのは危険と感じる
  • 大切な部分が攻撃される可能性があるから
  • 基本前提
    • 人はみな、平等に尊い存在である
    • 人は明確にそうでないと証明されない限り、本質的に善良だ
    • 組織の問題にうまく対処する単一の方法はない

安全な環境のための基本ルール

  • まずは全員の意識を高めることから始まる
  • 人と人がいる限り意見の不一致は必ず起こることを認める
  • ただし、険悪な敵対的な怒りは受け入れられない
  • 自分が正しいという思い込みをやめること
  • 思考と行動との区別せよ

価値観と基本ルール

  • 価値観に命を吹き込むには文章だけでは足りない
  • 研修を行ったり、話し合う時間が必要

内省のための空間

  • 心を鎮めるために定期的に沈黙し、内省が必要だ
  • 定期的に沈黙の時間を作ったりなど

大集団での振り返り

  • 毎週全員集めて振り返りを行う
  • 集中筋力トレーニングのようなもの

チームへの助言

  • チームは外部コーチに助言を求められる

ピア・コーチン

  • 同僚同士で相談し合うことができる
  • ただし、オープンクエスチョンのみ

個人へのコーチン

沈黙

  • 空間を埋める言葉がなくなったとき初めて、うちにある深い声に気づける

物語ること

  • 信頼が鍵になるが、仮面をつけるので難しい
  • 飲み会を開いても仮面の状態で接するだけ
  • 大事なのは物語ること

ミーティング

  • MTGをすると、悪い部分と良い部分がでる
  • 自分のエゴを抑え、組織として全体性を実現するために、ルールがある
  • MTGの最初の1分間は沈黙する
    • 落ち着くため
  • もしくは雑談から入る
  • 組織の目的よりも個人のエゴを優先していると感じたら止める権利を持つ

紛争に対処する

  • エゴによる紛争は激しくなるが、魂の紛争は激しくならない
  • ティールでは職場で必要な対立を表面化し対処する方法を提示する
  • 定期的な討論の場を用意
  • 紛争解決のプロセス
  • 人間関係のスキルを学ぶ
    • 私はこう感じています
    • 私はこれを必要としています
    • あなたは何が必要ですか

建物と地位

  • CEOなどに大きな役割や設備を与えるとエゴが増幅する
  • 労働力を提供する場となってしまう

環境問題と社会問題

全体性を取り戻すための努力/人事プロセス

採用

  • 採用期間中に、嘘をつくことが多い
  • ティールは人事ではなくチームメイトが面談を行う
  • 互いに一緒に働きたいかという判断基準
  • 盛って紹介しても自分に跳ね返ってくる
  • ティールだと役割は流動的なのでスキルを重視しすぎない

オンボーディング

  • ティールでは新しい社員に時間をかなりかける
  • 自主経営の手法や全体性の仕組みなどを学ぶ
  • エンジニアも管理職も機械の動かし方を学んだりする

研修

  • ティールでは新たな取り組みを試そうというときに邪魔されることがない
  • 色々なスキルを共に学ぶ機械が多い

個人の責任と研修を受ける自由

  • 従業員自身が研修を企画・実施する

さまざまな研修分野

全員参加の共通研修

  • 新入社員を対象とする研修が多く実施される
  • しかし、一度の研修では難しいので定期的にやったりもする

従業員が講師になる

  • 互いに意欲が刺激される

職務経歴書、役職、キャリア・プラン

  • ティールには説明できる単一の仕事をしていない
  • 様々な役割を兼務している
  • 役割がないと自分は何者かという組織と自分の結びつきを減らせる
  • 役割が自分自身と勘違いしだす
  • 役割がなければ一人の人間として存在する

約束、労働時間、柔軟性

  • 決まった労働時間を課すのは、人を資源として扱う
  • 決まった就業時間がない場合プライベートを犠牲にしろというメタファー
  • 毎週仕事にどれくらい時間をかけるか話し合ったり
  • 時間が取れない期間は仲間と話し合ったり、解決策を探す

フィードバックと実績管理

  • 職場への貢献度を知りたいのは一般的な欲求
  • 仕事はきちんとこなすことが当たり前で、具体的にFBがもらえることは少ない
  • 後ろ向きなFBは真剣に向き合うことを躊躇され先延ばしにされる
  • その結果評価面談が楽しいものにならない
  • FBを利用し「こうあるべき」という考える方向に誘導しがち
  • 実績管理はチームで行い、評価とFBもチームで行う
  • 相手のだめなところを治すという話ではなく、マインドフルで相手のことを考え会話をする
  • 客観的ではなく主観的な自分の意見を言うことで相手もされけだしてくれる
  • FBは相手と自分が共同で行う探索である
  • 改善点は1年中言い合おう

解雇

  • 賃金カットをすることで、一時解雇をせず耐えきる
  • 人が多いと縄張り争いをするようになる

要約

  • 全体性と分離状態・愛情と恐れという対立を解消する
  • 組織から提供されていると信じている安全を得ようと分離状態を求める
  • やがて仮面をつけるようになり、分離される
オレンジ組織 ティール組織
建物と組織図 標準化された、機能に特化した、面白みのない社屋
多すぎる肩書
自分たちで飾り付けた、あたたかい雰囲気のスペース。子どもたちにも動物にも自然にも開放されているオフィス
肩書がない
価値観と基本ルール 組織の価値観は壁に飾られているだけのことが多い  明確な価値観が、組織内で受け入れられる(受け入れられない)行動や態度の基本ルールとして具体化され、働く人々にとって安全な環境を守ろうとする
価値観と基本ルールに関する継続的な討論を深めるための慣行 
内省のための空間 なし 静かな部屋
集団での瞑想と沈黙の慣行
大集団での振り返り会
チームでの監督と仲間同士でのコーチン
コミュニティの構築 なし 自分をさらけ出してコミュニティを作るための、物語ることの実践
役職と職務内容 役職は「自分は何者か」を示す標識
組織内に確立した職務記述書
役職名がないため、社員は自分が何者かを深く追求せざるを得ない
自分の役割を自分で決められる
業務時間の拘束 仕事にかけられる時間と自分が生活の上で大事にしている他の時間との割合についての誠実な話し合い
紛争 対立を明らかにし、対処するための時間が定期的に定められている
複数の段階を踏む紛争解決の仕組みがある
社員全員が対立に対処するための訓練を受けている
ミーティング ミーティングの決まり事がない エゴを抑え、全員の意見に耳が傾けられるような具体的な決まり事がある
環境と社会への取り組み 事の本質とは無関係な「金額的基準」がある
業績への影響を考慮しながら経営トップだけが取り組みを始めることができる
本質的な基準としての「誠実さ」
何をするのが正しいかを誰もが感じ、誰もが取り組みを始められる
採用 訓練を受けた人事部スタッフが行い、職務記述書が大事 社員たちととの面談で、組織と存在目的が重視される
オンボーディング・プロセス 管理面に関する入社プロセス 人間関係と企業文化に関する徹底的な研修
組織に溶け込むためのローテーション・プログラム
教育研修 人事部が設計し、スキルやマネジメント訓練が多い 研修は自由に自己責任で受ける
文化構築の研修が極めて重要
実績管理 過去の実績に関する客観的な断面を把握しようとする その人がこれまで何を学んだか、使命は何か探求する
解雇 法的、金銭的プロセス 学習機会へと転換する思いやりのある支援

存在目的に耳を傾ける

  • 組織が掲げるミッションが空虚に響くのは、勝利を優先しているから
  • 空虚に響くのはミッションが行動や意思決定を左右するほどの力を持っていないから
  • ではなぜ存在するのか?
  • 根底にあるのは恐れで、競争相手が自分たちの利益を奪おうとしていると感じる
  • 生き残るためには市場シェアを拡大すること
  • 戦いの最中に組織の存在目的を考える時間などありはしない
  • 外に競争がなかったら内側に競争が発生する
  • エゴを抑える事が必要

競争、市場シェア、成長

  • ティールには競争という概念がほぼなくなる
  • 組織が自社の目的のために存在するとき競争はなくなる
  • 例えば病院を経営する会社は別の病院の会社にノウハウを教える
  • 会社の目的は患者を助けることなので、目的に沿っている動き

利益

  • オレンジ組織は株主価値が支配的な価値観となっている
  • 優先する義務は利益の最大
  • ティールでは利益は空気みたいなもので、生きるために必要であるが呼吸するために生きてはいない
  • 良い仕事をしたときの副産物
  • 存在目的を追求しているうちにビジネスとして成立するようになった
  • 逆になってはいけない

存在目的に耳を傾けて意思決定を行う

  • オレンジは組織を機械ととらえる。機械は心はなく方針もない
  • 機械が何をするか決定するのがCEOになる
  • ティールでは組織を生きたシステムと考える

存在目的に耳を傾ける慣行

  • 組織がどこに行きたいかどうやって知ればいいのか

感じ取る

  • 特別なことはせず、自主経営に任せる
  • 人間はもともと感知器がある
  • 自主経営組織では全員が組織の感知器となり、変革をおこなう
  • しかし、従来の組織では情報は少しずつ届く
  • また、情報は歪められ届く
  • 自分のアイデアを何層も通して経営陣に検討をしてもらうという長いフローを誰が取りたいと思う?
  • また、採用された案を各チームに押し付けてうまくいくのだろうか?
  • 変化は必要と感じている人が起点となって起こる

神領域での練習

  • 瞑想や沈黙をすることでより感じ取れる

誰も座らない椅子

  • MTGに組織と組織の存在目的を考えることのできる椅子を用意する

大集団でのプロセス

  • 全員が集まり発言権を得て、会話を行う
  • グループの集団的な意思決定がリーダー1人の意思決定より優れていると信じないとできない

外部からの働きかけ

  • 会社が存在目的を明確にしていると外から連絡が来る

有機的なプロセスとしての戦略

  • 従来は経営陣が戦略を決定し、下部組織へ伝達される
  • ティールでは全員が組織の存在目的に対して明確で鋭い感覚を持っている

マーケティング

  • ニーズを満たすようになるとニーズを作ろうとし悪循環が生まれる
  • ティールでは正しい提案と感じる声に耳を傾ける
  • 顧客調査などせず、世界のニーズに答える
  • 自分たちが誇りを持てるか、世界のニーズを満たせるか考える
  • 分析よりも美と直感で導かれる

プランニング、予算策定、統制

  • 予測や管理をしようとせず、状況を感じ取り対応を行う
  • 自転車を乗るときに、予測をして予測どおりに乗る人はいない
  • 常に周りからデータを取りながら補正しながら動く
  • 目の前の現実を感じ取り調整を行う

実行可能な解決策と高速反復

  • 予測と統制で動くと完全な答えを探したくなる
  • 入り組んだ(complicated)世界で予測するのは有益
  • 複雑(complex)なセキアでは関連性が失われる
  • 予測をすると自分が統制できているという安心感を得ることができる
  • ティールでは考えられる限りでベストの判断を狙うのではなく、すぐ使える実行可能な解決策を狙う
  • 新しい情報が入ると、それに応じて見直され、どの時点でも改善が図られる
  • リーンやアジャイルの核心にある
  • 全ての部署で意思決定を組み込み、実行可能な解決策が示されると採用する
    • 誰が見ても事態が悪くならないと判断できる解決策を意味する
  • もっと多くデータを集めるか、分析をもっとすれば良い判断ができるかもしれなくても、判断を先延ばしにすることはない
  • 新しいアイデアがあれば見直す
  • 自転車であれば正しい角度を計算するのではなく、だいたいで乗って調整をする
  • 高速に反復することでスムーズに前進できる
  • ベストな決定をするために無駄にエネルギーを使うことがない
  • 判断を保留にすることはない
  • 小さな判断を何度も修正することに慣れていれば間違いを正すのが楽になる

目標を設定しない

  • ティールではトップダウンの目標を設定しない
  • 目標設定をする3つの問題
    • 自分たちは未来を予測できるという前提に立っている
    • 内なる動機から遠ざかった行動をするようになる
    • 新しい可能性を感じ取る能力がせばまりがちになる
  • 設定する目標はだいたい当てずっぽうになる
  • 標数値は人々の行動を歪める
  • 目標がなければ内なる動機と相談しながらベストの仕事ができる
  • 意味があると思ったら自ら目標数値を定める
  • 目標だけ見ていると他の視野が見えない

予算を簡素化し、予実分析をしない

  • 予算策定は、データと予測を求められゆるいとトップダウンで予測を引き上げられる
  • 予算が達成できないときは呼び出され説明させられる
  • 市場や部署への責任転嫁にエネルギーが使われる
  • 目標がない場合、従業員の達成度をどうやって図るか?
    • 誰も知らない、気にしない

チェンジマネジメント

  • 変革とチェンジマネジメント
  • 変化が大変でストレスがたまるものと考えない
  • ティールでは常に変化が自然に発生する
  • オレンジでは変化は外から圧力がかかるもの
  • 変化事態に抵抗はなく、変えられることに抵抗を感じる

顧客。サプライヤー・情報フロー

  • 自社の目的達成のために自然と周りを巻き込む
  • 取引相手は価格と品質だけでなく、価値観があっているかも判断する
  • 外部に開放的になるのは避難されるのではなく、誰かが助けてくれるようになる
  • つまるところの避難される恐れがなくすといいことが起きる

意図的な「気分」の管理

  • 組織にも、人間と同じく、気分がある
    • 諦めの気分、恐れの気分、野心が満ち溢れている気分
  • 何ができるかは気分次第で変わる
  • 組織の目的に関しては、自分の個人的な希望を投影しないよう注意をする
  • 個人の性格によって好きな気分は異なる
  • 重要なのは組織が目的達成のためその瞬間に最も寄与する気分はなにかである
  • 感謝を促す慣行を作り、感謝を起こしやすくする
  • MTGの最初に感謝したり、休みと資金をもらい感謝する日を設けたり、1人ずつ感謝し合ったり、金曜日にメールを飛ばし合ったり

個人の目的と組織の目的

  • 個人と組織は互いに成功し合う必要がある
  • 社員が各自の使命を模索する環境を提供しないと、社員は給料のために仕事と捉える
  • 社員が組織の存在目的に耳を傾けるよう求められると、自分の人生の使命を模索するようになる
  • 会社の存在目的は自分と共鳴するか、この会社で働くことが使命と感じるか、ここで成長ができるか
  • 採用、訓練、評価面談は個人と組織の存在目的の交差点を探る機会
  • 組織の存在目的との適合性は個人の存在目的を確認しないと検証が難しい
  • 最終的には「一緒に働くのは運命づけられているか?」
  • 相当深い話になるので、長くなる
  • 評価面談でも再び話し合うことになるかもしれない
  • 仕事は物事を遂行するためであって、使命を見極めたい人を助けるためでないと感じる

存在目的に耳を傾ける 要約

  • 競争相手を打ち負かし利益と市場シェアが最重要項目となる
  • これは株主モデルの本質で、経営者の義務は世界に対する目的に奉仕することではなく、株主価値の最大化
  • ステークホルダーモデル」という、投資家、顧客、環境などのニーズに答えるという主張
  • ステークホルダー同士のニーズの調整をリーダーが行う
  • ティールでは組織を資産としてみない
  • 組織は独自の存在目的を追求する1つのエネルギーが集まる場所
  • 誰も組織を「運営」しない
  • 組織の存在目的に耳を傾ける慣行を行っている
オレンジ組織 ティール組織
目的 ミッションが何を言っていても、組織の存続 組織は自らの存在目的を持った生命体として見られている
戦略 組織のトップが決める 自主経営ができる従業員の集団的知性から自発的に生まれる
意思決定 競争の中でいかに生きるか意思決定の原動力 組織の存在目的に耳を傾ける慣行
全員が感知器
瞑想、誘導視覚化、外部からの働きかけに対する反応
競合他社 競争という概念はなく、受け入れ、共に目的を追求する
成長と市場シェア 成功への鍵 存在目的の達成に寄与する限り重要
利益 先頭の指標 正しいことをしていれば自然についてくる後続的にな指標
マーケティングと製品開発 顧客の調査とセグメントが商品を決める
必要に応じて顧客ニーズが作られる
何を提供するかは存在目的によって定める
直感と美によって導かれる
プランニング、予算策定、管理 予測と統制に基づく
中期計画、年次予算、月次予算という厳しい周期
計画への固執。差分は説明し埋める
やる気を出させるための野心的な目標
感じ取る反応に基づく
予算、予実分析はない
完璧な答えを探すのではなく、実用的な解決策と迅速な繰り返し
常に何が必要か感じ取り、目標数値はない
チェンジマネジメント AからBに動かすため 常に内部から変化しているのでこういう概念はない
サプライヤーと透明性 サプライヤーは価格と品質で選ばれる
守秘が当たり前
サプライヤーは存在目的への適合度で選ばれる
透明なので部外者から提案をもらえる
気分管理 なし どんな気分が組織目的に寄与するか常に感じ取る
個人の目的 組織の役割ではない 交差点を探るためにいろいろする

共通の文化特性

  • 組織の構造、システム、プロセス、慣行という見える部分と見えない組織文化
  • 組織に属する人々によって共有されている、前提や規範
  • それについて意識しないままで物事が進んでいくあり方
  • 空気のようなもの(内装、会話や冗談、対処の考え)
  • 組織の性格
  • オレンジは文化をソフトなものとして無視をする
  • 組織をハードな機会として認識をする
  • グリーンでは文化が最大の要素

文化、システム、世界観はどのように作用し合うのか 4つの象限

  • ウィルバーの四象限モデル
内面的な次元 外面的な次元
個別的な次元 人々の信念と心の持ち方 人々の行動
集合的な次元 組織文化 組織のシステム、構造、プロセス
  • 4つの角度から現象にアプローチできる
  • オレンジ組織
内面的な次元 外面的な次元
個別的な次元 人々はお金と称賛によって動機づけられる 目標達成のために抜け駆けをする
集合的な次元 内部競争、個人の達成がチームプレーより高く評価される トップダウンによる目標設定、インセンティブ
  • 1つの象限が変化すると他の象限にじわじわ広がる
  • グリーンでは文化を重視しすぎて、構造やプロセスを見直すことをあまりしない
  • マネージャーが権限を行使しないようにするためには、常に一定のエネルギーが必要
  • エネルギーを使うのをやめると、階層主義が幅を利かせる
  • 自主経営は文化と制度は反発し合うのではなく協力する
  • 権限は自然に分配され、権限移譲しやすい環境を作る必要もない
  • そもそもマネージャーに権限がないので、委譲は発生しない
  • ティールでは文化は必要性が低いが、影響力は高い
  • 階層構造を克服するための文化ではない

進化型組織の文化

  • 追求する目的によって独特の文化が生まれる
  • ↓共通の文化的要素

自主経営

信頼

  • お互いに好意的な糸を持った存在として親しみを感じる
  • 自分たちが間違っていることがはっきりするまで、同僚を信頼することが、組織に関わる際の前提条件である
  • 自由と説明責任はコインの裏表である

情報と意思決定

  • すべての情報はあらゆる人に開放されている
  • 扱いの難しい事件が起こっても、誰もが冷静に対処できる
  • 集団的知性のちからを信じている。全員で出し合う知恵に勝るものはない。したがって助言プロセスですべて決める

責任と説明責任

  • 一人一人が組織のために完全な責任を持つ。対処すべき問題を感じたときには、行動する義務を負う。問題意識を自分の役割の範囲にとどめることは認められない
  • FBや敬意を持った指摘を通じて、だれもが安心してお互いに説明責任を問うことができなければならない

全体性

等しい価値

  • だれもが本質的には、等しく価値ある存在だ
  • 同時に、すべてのメンバーがそれぞれの役割や教育、スキルの違いを尊重し合って、自分なりのやり方で貢献できるようになれば、組織のコミュニティは豊かになる

安全で思いやりのある職場

  • どのような状況であっても、恐れと分離の精神で対処することも、愛に基づいてアプローチすることもできる。愛を選ぶ
  • 私達は誰もが自分らしくふるまえるような、安全な環境を作り出そうと努力している
  • 愛、思いやり、感謝と言った気分や雰囲気を尊重する
  • 職場の中で思いやり、魂、目的といった語彙を抵抗なく使える

「分離」を克服する

  • それぞれの人のすべての部分を尊重できる職場を目指す
  • 認知的にも、物理的にも、感情的にも、女性的にも、男性的にも
  • 私達は互いに深く結びついて、自然とあらゆる生命体を含む大きな全体の一部ということを認識している

学び

  • あらゆる問題は、学びと成長を促すヒントである。いつでも学習者になる
  • 目的に向かって大胆に努力し続ければ失敗はあり得る。失敗についてオープンに語り、学ぶ。隠したり、無視したりすることは受け入れられない
  • FBと敬意を失わない対立は互いの成長を支える
  • 弱みより強みに、問題よりも機会に注目する

人間関係の構築と対立

  • 他者を変えることは不可能だが、自分は変えられる
  • 思想、信念、言葉、行動は自分のもの
  • 噂を広めない、陰口を言わない
  • 意見が一致しないときは当事者同士で解決を図り、巻き込まない
  • 問題の責任を他人になすりつけない

存在目的

集団としての目的

  • 組織にはそれ自体、魂と目的がある
  • 組織がどこに行きたいか耳を傾け、無理に方向を決めようとしないよう気をつける

個人の目的、使命感

  • 私達は自分の使命が組織の存在目的と共鳴するか、大切にする
  • 自分の役割に対して、自分のエゴではなく、魂を吹き込む

将来を計画する

  • 未来を予測し、統制しようとすることは無駄である
  • 統制しようとせず、その場の状況を感じ取り、対応する

利益

  • 長期的には存在目的と利益の間にトレードオフは存在しない
  • 利益は必ずついてくると考える

組織文化の出現をどう支えるのか

  • 文化が単にリーダーの規範、関心事が反映される
  • 組織は自らの生命力を持つと考えると、文化も単体で自律的なものになってよい
  • 組織文化を作るには他の3つの象限を追求する努力を並行で行う
    • 組織文化を支える構造、プロセス、慣行を整える(右下
    • 社内で倫理的権威を持った人々が、模範となれる環境作る(右上
    • 個人としての信念体系が新しい文化を支えるのか、崩すのかを人々に探求してもらう(左上
  • 行事を数ヶ月続けることで文化ができあがる(右下
  • 尊敬されている人に依頼する(右上
  • 自らワークショップを開いていく(左上

進化型組織を創造する

必要条件

  • リーダーにビジネスの成功の鍵があると信じられており、注目され過ぎである
  • 倫理的リーダーシップが組織の寿命と成功に及ぼす影響が過小評価されている
  • 経営トップ
    • ティールの世界観を養い、精神的な発達をとげていなければならない
  • 組織のオーナー
  • この2つの条件が絶対的な要素
  • 業種や組織の大小、地理
  • 担当部署でティールをやろうとするのはやめたほうがいい
  • トップがアンバーーやオレンジのレンズで眺めると破壊される
  • 上司と戦い続けることになるので長期間続かない
  • 上司にティールを押し付けると、今までの組織と同じになる
  • 外側から強制されるのではなく、内側からの変化にならないと意味がない
  • 具体的な売上数値をもとにティールをおすすめすると、オレンジと変わらない
  • 色を変えるのではなく、不健全なオレンジを健全なオレンジにするといった方法をとる
  • 目標値は存在するが、社員が創意工夫でき自らを表現できる余地があるならよい
  • 支配的な色の中で健全な仕組みを作る努力のほうがよい

経営トップ

  • 組織意識はリーダーの意識レベルを超えられない
  • ティールになりたいならリーダーはティールのレンズで組織を見ないと行けない
  • ティールにおけるリーダーは意思決定をしない役割だが、組織空間をつくり維持するという役割を持つ
  • それ以外は組織のメンバーが自主経営をする
  • リーダーは外から見た代表なのは変わらない
  • リーダーはティールの運営と、空間を作り維持すること

ティールの空間を保持する

  • リーダーの役割はティールの組織構造と慣行のための空間を維持すること
  • 組織は規則や方針を作りたがる傾向がる
  • 人を統制する仕組みがあれば安全という前提
  • ティールでは信頼を守り再確認する
  • 会社の資源を私的に利用していた場合
    • 1件の窃盗で他の社員全てを疑うべきか
    • 他の社員を信頼しないしくみを作るべきか
  • 信頼という文化を維持するために規則を作らない

ティール組織を支える3つの突破口の模範となる

  • 階級的な権限はないが倫理的権威を持っていることが多い
  • 自主経営、全体性、存在目的に結びつく行動の規範となる必要がある

自主経営

  • リーダーは自分たちの権限が助言プロセスを通じて厳しく制限されることを認識する
  • 正しい意見とかは簡易なく相談が必ず必要となる
  • だれも強制ができない
  • 誰かが助言プロセスを飛ばしたら誰かが私的をして正す
  • 何かアクションしたい場合助言プロセスを通じてプロジェクトを作り動き出す
    • このとき、口出しをしたいという欲求と戦うことが一番難しい
    • 何度も仲間を信頼せよと言い聞かせる
  • 自主経営は情報が透明化されたときに進展する
  • チームで自分たちの数値を監視するのでチーム外が監視する必要はない
  • 「社員はリーダーに行動を起こしてほしいと考えている」という感覚を捨てること

全体性

  • 謙虚さ、信頼、勇気、弱さを晒すといった自分らしさを見せることで社員も同じリスクをとれるようになる

存在目的に耳を傾ける

  • リーダーたちが謙虚さを示す方法の一つは、自身と組織のメンバーに仕事が個人を超越した目的への申しであることを思い起こさせること
  • 時間と情熱などを注ぎ込むと実ってほしい、認められてほしいと考える
  • 個人や組織の成功は、意味ある目的を追求したときの結果として得られるもの
    • 成功を目的にしないようにする
  • これは無私無欲で仕事をするということではない
  • 完全に自分らしさを保ちながら、組織の存在目的に向かって努力をすること
  • どの判断が組織の存在目的に寄与するか?
  • この役割は組織の存在目的に寄与するか?
  • この顧客は組織の存在目的に寄与するか?
  • 組織に方向性を強制する必要がないと認識する

その他の役割

  • CEOは大量のMTGで埋まりがち
  • 判断を下すか承認するためのスライドの消化することが求められる
  • 全体を見ることができる組織のトップの役割になっている

助言プロセスを伴ったリーダーシップ

  • 全体に関わる意思決定はトップダウンがありがち
  • 助言プロセスは聞いて回ったり
  • ブログで気軽に何でも発信して社員が読み、意思決定をスピード高く行う
  • 弱さなどをさらけ出す姿勢が必要

CEOの役割と眺める別の方法

  • 活動、人間関係、文脈というエネルギーがあると説く「生きている組織」
  • 活動は「私達が何を、どのようにするか」という行動に注がれるエネルギー
  • 人間関係は「私達が何を、どのように言い、お互いにどう関わっていくか」という他の人々とのやりとりに注がれるエネルギー
  • 文脈は「組織全体に社員がつながることの意味や目的に宿るエネルギー
  • オレンジは活動がすべて。人間関係は悪と感じる
  • 文脈と人間関係を大切にし、残りの時間を活動に注ぐ

取締役会とオーナー

  • 2番目の条件は取締役会もティールのレンズになる必要がある
  • 取締役会はCEOを指名し、解雇する権限を持っている
  • 取締役会がティールを好きなのは、株価を押し上げていたからという理由がある

法的な枠組みを作る

  • グリーンでは株主はステークホルダーの1人
  • ティールでは組織の存在目的が超越する
  • ラクラシーでは憲法の草案を作った
  • ベネフィット・コーポレーションと呼ばれる企業形態が注目を浴びている
  • キャピタリズムコーポレーションは株主が強い
  • 組織は所有者などではなく管理者という視点を持つ

必要だが不十分

  • CEOと取締役会が組織のあり方を理解するのは必要条件だが、十分条件ではない
  • リーダーのレベルが上っても組織のレベルはあがらない
  • 権力構造、人々の職場での振る舞い、慣行、文化をリーダーが採用して初めて組織レベルがあがる
  • 不完全な人間性、自分が思い描ける像と外に示せるものの乖離が表面化すると、疲れ辛くなる
  • 忍耐と謙虚さと人付き合いのスキルを身につける必要がある

ティール組織を立ち上げる

  • ゼロから作るほうが楽
  • 考えること
    • 自身の希望や夢を無視して、組織の声に耳を傾けると目的はなにか?
    • 組織はどのような形をしたいのだろう
    • どれくらいのペースで成長したいのだろう
    • 複数、単独どちらの形で運営すべきだろう
  • 最も大切なのは自分がいることで組織にいい影響、悪い影響を考えること
  • FB、メンタリング、読書、瞑想なんでもよい
  • 人が増えたときにどうするかが組織の分岐点

前提と価値観をつなぐもの

  • いくつか前提を明らかにしてから会話をすると良い
  • 人はみな、平等に尊い存在である
  • 人は明確にそうでないと証明されない限り、本質的に善良だ
  • 組織の問題にうまく対処する単一の方法はない
  • 社員は権力または強制力を使わずに一緒に働くべきだ
  • 社員は約束を守らなければならない
  • 人はそもそも善良な存在
  • 幸福感なくして成果はありえない
  • 価値は現場でつくりだされている
  • 前提を考える際にはチームで考えること
  • そして最初にオレンジ組織の前提を考える
    • 労働者は怠け者で信頼できない
    • 年長者がすべてを知っている
    • 従業員は難しい問題を取り扱えない
  • こうした前提は理由の説明がしやすく、新しい慣行のときに利用できる

自主経営に関する3つの慣行

助言プロセス

  • 影響を受ける人や専門知識を持った人の助言を受けている限りだれでも意思決定ができる
  • だれもが承認をしてはないけない

紛争解決メカニズム

  • 問題を解決せず、紛争解決メカニズムを導入し運用させる

同僚感の話し合いに基づく評価と給与決定プロセス

全体性に関する4つの慣行

安全な空間を作るための基本ルール

  • 自分をさらけ出すには、そうすることが安全だと感じないといけない
  • 大切にする価値観を定め、運用していく

オフィスや工場

  • 働く場所で、それ以外の行動をすることが許されない状態
  • 模様替えをすべき

オンボーディングプロセス

MTGで実践すべき慣行

存在目的に関する2つの慣行

採用

だれも座らない椅子

  • MTGは組織の存在目的の達成に貢献したか

組織を変革する

  • 2つの条件が揃ってないと変革は難しい
    • CEOはティールをわかっているか
    • 取締役会は理解しているか
  • 条件が揃ってない場合、既存の組織の中で健全化するように水平的な改革をおこなうべき
  • 生きている組織は段階的に変化していくものなので3つの突破口のどれかから初めて徐々に変化させるべき

自主経営を導入する

  • 組織の下位にいる人ほど自主経営にすぐなじみ受け入れる
  • ティールでは自主経営で活躍できそうな人を採用するのが重要
  • 指揮と統制によって働いていた人たちは自主経営は難しいシステム
  • 自分の行動と関係の維持に責任を持ち、困難な選択から逃げられない

心理的オーナーシップ

  • 会社がもうけようが損しようが気にしなく、驚異や機会について心配しない
    • 修正が必要なときはリーダーが判断すると考えている
  • 自主経営の成功は「心理的オーナーシップ」を持てるかにかかっている
  • 心理的オーナーシップは自主経営の権限をもらってもすぐに生まれるものではない
  • 組織と存在目的に思い入れがなく、仕事を軽くしようと考えている人がいても驚いてはいけない
  • 内発的モチベーションを高める必要がある
存在目的
  • 明確でないとき、かき立てる要素がないと感じるときはこの問題を解決すべき
競争意識
  • チームに自分たちで計画と目標を設定し予算作成をする
  • そして、互いに発表し合う機会を設ける
  • チームが共同作業をすることが大切
  • 透明化を進めるのも1つの手
市場からの圧力
  • 発注数や強豪の価格などを知る
  • 顧客と直接つながる

条件

  • 自主経営を導入しようとしているリーダーが信頼されていなければならない

ドルマネジメント以上のマネージャー

  • 役割がなくなるので、新しい仕事を社内・社外で探す必要がある
  • 信頼が欠如した制度が多いと結果的にスピードが下がる
  • 全ての非信頼の制度を撤廃して、従業員とともに新しい組織を作ろうとした
  • 正当な退職金は用意する
  • ティールへの課題は、ミドルマネージャーやスタッフ機能の人たちをどう処遇すべきか
  • 最も適切な組織構造は
    • 自主経営チーム
    • ラクラシーのような入れ子構造か
    • 社員との個別契約組織か
  • いつ自主経営をどのように導入するか?
創造的カオス
ボトムアップの再設計
  • 組織内の全員に組織の将来像の設計に参画してもらう
  • 慣行を導入すべきかグループに決めてもらう
  • アプリシエイティブ・インクワイアリー
  • フューチャーサーチ、プロセスデザインなどが有効
  • 社員が自主経営をしたいという意欲を持てるほどリーダーが信頼されている
  • ドルマネージャーは反対だからといって、移行作業をさぼらないこと
  • マネージャーを自主経営組織に研修に行ってもらうなど
既存テンプレートの活用と切替日の設定
  • すでに実績のある方式を採用すること
  • ラクラシーなど
  • ラクラシーのためには複数の円が入れ子に重なった組織構造を決定する必要がある
  • 最初から完全じゃなくてもいい

全体性を醸成するための組織慣行を決める

  • 自主経営よりは導入が楽
    • 強制ではなく徐々に慣れることが可能
    • 仕事用の仮面を脱ぎ始める人が増えると仲間になれる
    • 組織の風土に合わせて導入ができる

全体性を醸成する

  • MTGでなにかルールを提案してみたり
  • 受け入れられたら組織全体での実践を提唱する
  • 評価面談を学びの敬意や使命感を語ってもらう
  • 素の自分で職場に来ることを語り、実践してから導入したほうがいい
  • なぜ、社員がさらけ出して付き合える組織をつくることに情熱を注いでいるのか
  • など語ってもいい
  • 全体性と組織目的を結びつけても良い
  • なぜ自分らしさが必要か

存在目的に関する慣行

  • 存在目的はすぐ忘れられるようなミッションを作る話ではない
  • 存在目的は「組織はこうあるべき」「組織はこうすべき」というものではない
  • 「自分の組織が世界の中で何を実現したいか」という独自の目的をあなたや同僚が感じ取れるもの
  • 会社が一つの生命体と感じるられるもの
  • 自分の組織がどうありたいと願っているかに耳を傾けられるか?
  • この問を探求するためにどんな手段をとってもいいし、どれくらい時間をかけてもいい
  • 耳を傾けることに加わっていた同僚が存在目的と個人的なつながりを感じる
  • 感じ取れたらそれを日々の会話に取り組み、意思決定を行う
  • 心の奥底ではだれもが目的と意味のある仕事を望んでいる
  • 自分がどう見られているか意識すること

成果

  • ペンギンは陸地と水中でぜんぜん違う
  • 組織も同じなのでは?
  • ティールの前までは外的な動機で働く
  • 「どんな問題も作り出したときと同じ意識レベルでは解けない」アインシュタイン

証拠となる逸話

  • 成功をどう定義するかが難しいのでなんともいえない
  • マネジメントは不要

画期的なパフォーマンスを引き起こす要因

  • トップの数人ではなく全員が権限を握っていれば組織としての力が何倍にもなる
  • 人が自分らしさを失わずに職場に来るので、権限の使い方に知恵が絞られている
  • 社員の知恵が組織の生命力と一致するとうまくいく
  • 存在目的を通じて、自分より大きな目的を理解すると、個々のエネルギーが高まる
  • 権限の分散を通じて、内在的基準に照らし働く
  • 学びを通じて、学習への強い意欲がわき、内面の発達まで広がる
  • 出世するために管理職的な役割を押し付けられることはなく、流動的に調整する
  • 出世のためのエネルギーがなくなる
  • 報告義務などのエネルギーがなくなる
  • 階層間のMTGがなくなる
  • 直接感じ取った知識に基づき活動する
  • 助言プロセスにより判断をし、直感や感覚で判断する
  • 意思決定をそれぞれでする
  • 意思決定を待つことがない

ティール組織とティール社会

  • 人間の意識レベルの変化とともに社会も変化していった
  • 未来の推測は難しく「未来についておおよそわかっているのは現在と違うこと」ドラッカー

ティール社会はどのように見えるだろうか

ゼロ成長、循環型経済

  • 資源が限られている世界で無限の成長はできない
  • どこかで100%再生可能な循環型経済にならないと滅びる

大量消費の見直し

  • 成長しなくなるが、反映しないわけではない
  • 最近の商品はエゴを失う恐れに訴えかけている
  • 外定期ではなく内的な訴えになるとこういった商品はなくなる
  • 顧客との人間的なつながりに訴求するサービスが増える

既存産業の再生

  • 学校のような型にはめるものはなくなり、医療は心がこもったケアになる

金融システム

  • 利子を前提とシステムはなくなる
  • 備蓄ではなく共有関係の信頼が大切になる

管理責任

  • 所有という概念がなくなる

グローバルコミュニティ

  • VRなどSNSなどのローカルとグローバルがつながる

仕事の終焉

  • 機械化が進むことで、面白くない仕事をしていた人は面白い仕事をできるようになる

ティール社会の中のティール組織

株主

  • 営利事業と非営利事業の垣根がなくなる
  • 管理責任者となって、互いに助け合う構造になる

存在目的と高通気性組織

  • いろいろな働き方が増え、組織間で協力し合うようになる

未来を作り出す

  • 未来は予測できないが、作ることはできる
  • この本を教本のように使うのは望まれない

おわり。長かった。