Dave Thomasが"Agile Is Dead (Long Live Agility)"を書いたきっかけ

あやふやな記憶からFacebookのみなさんに助けていただいたので、記録として。

pragdave.me

あらすじ(動画の話をベースに):
インドで開催されるカンファレンスへ飛行機で移動していたときのこと。時間の関係で、登壇者がたくさん乗っていた。自分の前の席にもアジャイルコンサルタントかトレーナーが2人いて、しゃべってるのが聞こえてきた。聞いていると、話題はプログラミングの話へ。
「プログラム書いてないなー」
「もう10年書いてないよ」
「こっちは12年だよ」
 
プログラムを書くことを教える立場の人が、プログラムを書いてない? それってもう書けないくせに教えてるってことだろ!? とすごく頭にきて、帰ってから書いたのが "Agile Is Dead (Long Live Agility)" だったんだ。

 

情報源
InfoQの記事: 
本人がその話をしている動画(24:20くらい):
角さんによる紹介(9枚目): 
 

www.slideshare.net

Facebookのスレ: 

www.facebook.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その3

yattom.hatenablog.com

その1でasobannをAWS ECS上に構築その2でその構成をCloudFormation化した。したのだけどもう一息、Service Discoveryの対応と、MongoDBのデータ永続化が残っている。

SRVレコード対応にはPythonコードの実装が必要だった

その1にも書いたが、起動タイプがEC2でネットワークモードをbridgeにすると、個々のタスクはIPアドレスとポート番号の組み合わせでアクセスすることになる。1個のEC2インスタンスが10.0.0.82にあって、その上にアプリのタスクが3つ、それぞれポート32801, 32802, 32803で動いていたりする。公開ポートはDockerのポートフォワーディングによって、基本ランダムに決まる。ロードバランサーからアプリを指定するのはALBのTarget Groupを使うと、自動でマッピングしてくれる。

いっぽうアプリからMongoDBやRedisを指定するのは、Service Discoveryを使う。Service DiscoveryはプライベートのDNS名前解決なのだけど、DNSでIPとポート番号を両方解決するためにSRVレコードを使う。

このSRVレコードというのは初耳だったが、MongoDBは接続文字列でサポートしていた。mongodb+srv://... と書けばよいのだけど、気をつける点(引っかかった点)がいくつか。

  • 今回のアプリはPythonで、FlaskからPyMongoを使っている。SRVを使う接続文字列をPyMongoで扱うには、DnsPythonが必要で、パッケージをインストールしないといけない
  • サービスのURIのホスト名がhost.domain.tldという具合に3つ分ないと、エラーになる。たとえばmongodb.asobann-dev ではダメで、mongodb.asobann-dev.local ならいける。PyMongoがmongo+srv://に対応するときに利用するDnsPythonの制約。Private Namespaceを作るときに気をつける。
  • mongo+srv://を指定すると自動でtls=trueになるので、明示的にtls=falseを接続URIに付加する必要がある。とドキュメントに書いてあるが嘘で、ssl=falseじゃないとエラーになる。これはPyMongoの実装の問題な気がする。
  • SRVレコードは_Service._Proto.Nameという名前でレコードを作ることになっている。接続文字列が mongodb+srv://server.example.com/ だったら、AWS::ServiceDiscovery::ServiceのNameで指定するのは_mongodb._tcp.server.example.com.になる、という感じ。

Redisはそういう自動でSRVを解決するみたいな機能がまだないみたいなので、自前で何とかしないといけない。さっきDnsPythonを入れたので、以下のようなコードを書いて自分で解決してRedisに接続すればいい。(PyMongo内部の実装 を参考にした。なお本来のSRVの仕様では複数のAレコードに対応したり、TXTレコードから追加情報を引けたりするのだが、今回は端折っている。あとRedisはSSLもサポートしているが、それも省略。)

 def resolve_redis_srv(uri: str):
     '''
     Resolve redis+srv:// and return regular redis:// uri.
     Redis itself does not support connecting with SRV record but current AWS ECS
     configuration requires to use SRV record.
     Does not support TXT record.
     :param uri: connection uri starts with redis+srv://
     :return: redis://host:port/ uri resolved with SRV record
     '''
     assert uri.startswith('redis+srv://')
     import re
     from dns import resolver
     auth, host, path_and_rest = re.match(r'redis\+srv://([^@]*@)?([^/?]*)([/?].*)?', uri).groups()
     results = resolver.resolve('_redis._tcp.' + host, 'SRV')
     node_host = results[0].target.to_text(omit_final_dot=True)
     node_port = results[0].port
     node_uri = f'redis://{auth or ""}{node_host}:{node_port}{path_and_rest or ""}'
     return node_uri

EBSをDockerボリュームマウントするためREX-Rayドライバを使う

MongoDBはAWS DocumentDBを使えばマネージドサービスで楽ちんと思っていたら、課金がインスタンス時間単位で最小構成のdb.t3.mediumでも月額57USDとかになる。これにIO数やデータ量への課金もあるので、今のasobannで使うには贅沢すぎる。

というわけで、MongoDBも普通にServiceとしてECSで動かすことにした。なにもしないと、タスク再起動やEC2インスタンスの作り直しの時点でデータが消えてしまう。そこで、MongoDBのデータのみを別に保存するようにしたい。Dockerボリュームのマウントは、比較的最近(といっても2018年)にサポートされたらしい。EC2起動タイプではREX-Rayドライバを使えばEBSに保存すればいいようだ。

基本的な考え方から手順の解説まで見つけ、ほぼ同内容だけどYAMLで書いてあるものもあった。これに従ってTask Definitionを書き、スタックを更新したらすんなり成功して、よしこれでEC2インスタンスを停止してもデータが残るぞ!と思ったら残らなかった。コンソールから見てもEBSを使ってる様子がない。

結論としては、Task DefinitionのUserDataにあるrexrayドライバー(プラグイン)インストールのスクリプトが動いていなかった。この部分なんだけど、最終的にはdocker plugin ... の行だけにしたら動くようになった。

 #open file descriptor for stderr
 exec 2>>/var/log/ecs/ecs-agent-install.log
 set -x
 #verify that the agent is running
 until curl -s http://localhost:51678/v1/metadata
 do
    sleep 1
 done
 #install the Docker volume plugin
 docker plugin install rexray/ebs REXRAY_PREEMPT=true EBS_REGION=<AWS_REGION> --grant-all-permissions
 #restart the ECS agent
 stop ecs 
 start ecs

このスクリプトの結果は、/var/log/ecs/ecs-agent-install.logでわかる。curlでEC2インスタンス上のECSエージェントの起動を確認しているようなのだが、そんなものは起動していない。stop ecsだのstart ecsだのあるけど、そんなコマンドはない(systemctlのことだろうか?)。Metadataのcommandの02_start_ecs_agentという記述も、同じく動いていない。

   # 関係ない箇所を削除している
   ContainerInstances:
     Type: AWS::AutoScaling::LaunchConfiguration
     Metadata:
       AWS::CloudFormation::Init:
           commands:
             02_start_ecs_agent:
               command: start ecs

最終的にTask Definitionはこう、LaunchConfigurationはこんな設定となって、無事にデータを永続化できるようになった。関係ないけど、execとsetでこんなふうにログが出せるというのは知らなかったので勉強になりました。

ひとまずできた

以上で、asobann開発環境としての扱いで、AWS ECS上に構築できるようになった。この時点のCloudFormationテンプレートはこちらです。実行方法もREADMEに書いてあります。

github.com

yattom.hatenablog.com

yattom.hatenablog.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その2

yattom.hatenablog.com

その1でasobannをAWS ECSに手作業でインフラ構築できた ので、これを自動化したい。CloudFormationかTerraFormを使うといいらしいと聞いて、あまり考えずにCloudFormationを使ってみることにした。これも初めて。

CloudFormationにアプローチしてたくさん間違える

軌道に乗るまでに、こんなことをした。

  1. デザイナーを用いたチュートリアルで流れを把握
  2. デザイナー上で自分で1から作ろうとしてみて、なにを作ればいいのか把握し切れてないことに気づく(コンソールからECSのクラスターを作ったときは、裏側で作ったり設定したりしてくれていたものがあって、それがわかってない)
  3. アプローチについて調べていたらこちらの記事からテンプレートのサンプル にたどり着き、これを参考にすることにした
  4. 手で作ったECSのクラスターからCloudFormationテンプレートを生成しておき、参照できるようにした

3番で見つけたテンプレートのサンプルが死ぬほど便利で、助かりました。

  • どんなリソースが必要で、どうリソース同士をつなげるのか書いてある
  • テンプレートファイルが分割してあり、わかりやすいし、分割するときの書き方もわかる
  • YAMLの書き方、!Refや!Subの書き方の例が豊富にある

必要なリソース種類がわかれば、個々のリソースに必要な設定はリファレンスを見るだけなので、あまり苦労がない。

変える部分を見落とさないためにも、自分の理解のためにも、写経のように見ながら再入力しつつテンプレートを書くことに。理解は深まったけど、見落とし、ぽかミス、勘違いなどでたくさんトライ&エラーを繰り返すことになったのでありました。

  • 複数のアベイラビリティゾーンとサブネットを作って渡さないと、AutoScalingGroupでエラーになる(最低2つ、みたいな)。ここでサブネットを増やしたとき、AWS::EC2::SubnetRouteTableAssociationも合わせて増やさないと、ルーティングが狂って変な挙動になる(EC2インスタンスクラスターのインスタンスにならない、サブネットのルーティングテーブルが勝手に変化する(ように見える)、EC2インスタンスsshできたりできなかったりする)。エラーにはならなかったので気づきにくい。
  • Service Discoveryで指定したホスト名が解決できず、VPCの設定で「DNS解決」と「DNSホスト名」の両方を有効にしたら解決した。テンプレートでは AWS::EC2::VPCに対してEnableDnsHostnamesとEnableDnsSupportを両方trueにする。(DNSホスト名が必要なのは解せないけど、こうしたら動いた)。
  • Service DiscoveryのためにPrivate NamespaceリソースをCloudFormationで作成すると、HostedZoneが作られる(明示的に作らなくてよい)。サンプルにはなかったのでちょっと迷ったところ。
  • LoadBalancerからTargetGroup(アプリ)にアクセスするには、SecurityGroupの設定が必要。LB用のSGとアプリのSGが異なるなら、アプリのSGはLBのSGからのアクセスを明示的に許可する必要がある。手で作ったときはアプリとLBを同じSGにしていたので、気づかなかった。なお本来は同じSGであっても通信は許可されていなくて、これはSecurity Groupについての基礎知識っぽいのだけど、VPC作成時にデフォルトでできるSecurity Groupでは自動で許可されているので、どうやら気がつかなかったみたい。SGさんいつも厳しい。いつか仲良くなれるだろうか。
  • CloudFormationテンプレートを変更してスタックを更新するとき、Task Definitionが変わったりしてタスクが再起動することがあるが、このとき新しいタスクを起動→古いタスクを終了、という順序で動く(これはデプロイの設定で変わるような気がするんだけど、ちゃんと調べていない)。そのとき、新しいタスクを起動する分の余裕(CPUとメモリ)がコンテナインスタンス(EC2インスタンス)にないと、当然ながら起動しなくて、スタックの更新自体も先に進まなくなる(手で元のタスクを停止するとうまくいったりした気がする)。
  • よくわからなくなったら、コンソールからスタックを削除して作り直すと、きれいにうまくいく場合がある。後から考えると、同じ論理ID(テンプレート中のリソース名)で後から矛盾する設定変更をしたせいだった気がする。スタックの削除は普通自動で進み放っておけば終わる(5~10分くらいだった。コンソール上で進捗を追える)のだけど、たまに削除が進まなくなることがあった。VPCが消えないことが多かった気がする。対象リソースをコンソールから手で削除すると、スタックの削除も無事に完了した。
  • なお4番の、構築済みのClusterからCloudFormationのテンプレートを作ってくれるので、それで構築済みのものを見て参考にしようと思ったら、肝心なリソースがぜんぜん入ってなくてダメだった。リソースは手で選ぶので漏れたのかもしれないけど、再確認はしてないのでよくわからない。動いてるやつを元に作ったテンプレートを直すだけって、すごいいいアイデアだと思ったのになあ。

あとはまあ、テンプレートを更新して実際にCloudFormationで更新するのに、実行開始→作業中…→失敗!になるまでの時間が割とかかる(10分とか)のが、トライ&エラーを繰り返すのに地味に厳しかった。また失敗したときの理由や状態を調べる方法が、なかなかつかめなかった。

  • コンソール上でCloudFormationのスタックを指定してイベントを見る
  • ネストしたテンプレートは別のスタックになっているので、そのイベントを見る。削除済みとかのものを見ると新しいことがわかるときもある。
  • 作成したClusterをコンソールで開いて、Serviceを指定して、そこからイベントを見る
  • 作成したClusterをコンソールで開いて、Taskを指定して、そこからLogsを見る。反映が遅いことがあるので、ページ下の方のコンテナから詳細を開いてCloudWatchのログを表示した方がいいかも(CloudWatchを設定している必要がある)。Stoppedのタスクを見ると新しいことがわかるときもある。
  • EC2インスタンスsshして調べる。インスタンス自体のログは/var/logに出ているし、docker execしてコンテナの中を見ることもできる

f:id:yach:20200807154138p:plain


インスタンスやコンテナのことはあるていど探れたが、ネットワーク周りはどうやって調査したらいいのかまだよくわからない。また、ECSのタスクが起動しないで、必要な要件を満たすインスタンスがないよというメッセージを出すのがとても困った。いちおうここにガイドラインがあるけどあんまり役に立たない。

続く

これで環境はほぼ構築できたのだが、まだ動かない。クラスター内でアプリがMongoDBとRedisを見つけるところで、追加の作業(実装も含む)が必要になった。またMongoDBのデータがコンテナ内なので、なにかあったらデータが消えてしまう。その話はまた次回。

 

yattom.hatenablog.com

asobannのAWS ECSデプロイでやったことと引っかかったこと その1

asobannは現在herokuにデプロイしてMongoDBを使っているが、「herokuのMongoDBが11月で使えなくなるよー」という通知が来た。なるほど。また、サーバーをスケールするためにはFlask-SocketIOを複数プロセス起動してロードバランサを置かないといけないのだけど、herokuでそれを実現できない。あるいはすごいお金がかかる(herokuのガイド通りに1プロセス1Dynoにすると、ロードバランサ1つ+メッセージキュー1つ+アプリn個ぶんのDynoが必要になる。処理負荷はぜんぜん小さいのにもったいない)。

もともとパフォーマンス改善を考えていて、複数プロセス構成に載せ替えたかったので、これを機会に取り組んでみることにした。いちおう開発環境レベルでは動くようになったので、そこでやったことを主に自分のメモとして、もしかしたら誰かの参考になる情報として、書いておく。どれも、今さら?と思われそうな新しくない要素だが、初めての人はいつでもいるし(←自分)。ハマったところもたくさんあり、調査不足や勘違いが多いけれど、それも誰かの役に立つといいな。

 

初めてのDocker Composeで、nginxが使えないと知る

Flask-SocketIOを複数並列で動かすには、gunicornのworkerを増やすのでは対応できず、プロセス自体を増やさないとならない。プロセスを増やすということはリバースプロキシあるいはロードバランサが必要になる。またサーバー同士が調整するためのメッセージキューも必要(RedisやRabbitMQなど)。公式ではnginxの設定方法が書いてあるので、これに取り組むことにした。

AWSに上げる前に、まず手元でリバースプロキシ+複数プロセスの構成を実験すべく、Docker Composeを使うことにした。docker-compose.ymlファイルを書くのは今回が初めて。このへんの成果は残っているが、結論としてはnginxをロードバランサにするとどうしても動かなかった。SocketIOがWebSocketに切り替えようとしても切り替わらず、結果としてまったく通信ができない。eventletをgeventに変えたり、gevent-websocketを入れたり、nginx.confの書き方をあちこち調べたりしたけどわからない。ごくシンプルなサンプルを書いて試したけど同じ現象が再現するので、諦めることに。AWS ALB (Application Load Balancer)を使えばなにもせずそのまま動いたので、結果的にはそれで問題なかった。

こんにちはAWS ECS、お前はいつもそうだなSecurity Group

前段の実験でDockerコンテナで動くようにしてあるので、AWS ECS (Elastic Container Service)を使うことにした。ECSはパフォーマンステストを作ったときにも使っているので、初めてではない。Docker Composeで一発デプロイができたら嬉しい気もしたけれど、まだそこまでECSの気持ちがわからないので、まずはWebコンソールからひとつずつ手作りすることにした。Cluster、VPC、Task Definition、Service、Load Balancerと作っていく。

起動タイプはEC2に。Fargateではネットワークが awsvpcになってネットワークの設定がシンプルになるが、軽いタスクをたくさん上げるとherokuのDyno数と同じ問題でお金がかかる。EC2 Launch Typeで1インスタンスに軽いタスクを複数入れて安上がりにしたい。最終的にはEC2のt3.smallインスタンス1個にMongoDB+Redis+アプリ3つが収まって、月15USDくらい(us-east-1リージョン)。

ところがEC2でawsvpcに設定すると、EC2インスタンスのタイプによって持てるネットワークインターフェース(ENI)の数に制限があって、たくさん実行できないと制限があり、今回はここに引っかかった。いくら待ってもTaskが起動せず、コンソール上でServiceのイベントを見ると、条件の合うインスタンスがないからタスクを起動しないよというエラーになっている。調べていたら、ENIの数に制限があるというドキュメントにたどり着いて原因がわかった。いったんawsvpcで作ってしまったので、いまは練習として放置し、EC2インスタンスを2つにしてしのごう。なおいま調べたら2019年にENI制限が緩和されたらしいけれど、NITRO以降のタイプのみだったので関係ない。

今回は練習としてawsvpcのままだが、本番のときはネットワークをbridgeに設定する。こうするとENI数の制約はなくなる。同じIPに複数のTaskが存在して、ポート番号で呼び分けることになる。Service Discoveryを使うとき、AレコードではなくSRVレコードを使う必要が出てくるのだが、これがあとで一手間増える原因となった。

ALB (Application Load Balancer)もServiceを作るときに設定する。ALBはそのままでWebSocketに対応していて追加設定不要だが、SocketIOにはスティッキーセッションが必要なのでそこだけ設定をONにする。設定はしたのだけど、ALBのURLをアクセスしても503になってアプリに到達できない。これはSecurity Groupの設定が悪くて、ALB自体にもSecurity Groupが働くので、ポート80番を開けておかないといけない。Security Groupはいろんなところに影響するのに、設定が悪いと気づける方法がなくて、いつもハメられている気がする。キミの気持ちがわからないよ。

続く

これでいったんAWS ECS上でasobannが動くようになった。固定URLがないし、MongoDBがコンテナで動いているので何かあるとデータが消えてしまうが、開発環境という位置づけならとりあえず大丈夫だ。httpsがないのはちょっとダメかも。

今度は今回手作りしたものを参考に、CloudFormationで自動構築できるようにするのだが……次回に続く

 

 

yattom.hatenablog.com

yattom.hatenablog.com

 

 

受入条件はプロダクトオーナーのもの、完成の定義は開発チームのもの

プロダクトバックログアイテム(PBI)が完成したか、リリース可能と言えるかどうかは、開発チームとプロダクトオーナが判断する。判断基準は様々で、ちゃんと動くか、使いやすいか、重かったりしないか、デザインが整っているか、脆弱性が存在しないか、SLAを満たすか、十分テストしてあるか、アーキテクチャの一貫性を壊していないか、ドキュメントは書いてあるか…… どういう基準が必要か、基準をどのくらい緩く、厳しくするかは、プロダクトとチームによって千差万別になる。

受入条件は判定基準として、個々のプロダクトバックログアイテム固有になる。このように機能すること、動作がこうであること、前提がこうなら結果がこうなる、などのPBI個別で定める内容になる。言い換えると、受入条件はPBIの内容の一部、PBIがどんなものか説明する一部だ。PBIをユーザーストーリーで書いていれば、ユーザーストーリーは「プロダクトオーナーと開発チームが会話する約束」なので、会話の中に受入条件が登場し、相互に理解して、スプリント終了までに判定すると約束する。

完成の定義(Definition of Done)は、すべてのプロダクトバックログアイテム共通となる判定基準だ。 プロダクト全体で守るべき非機能要求や、組織・開発・運用などの観点から制約される、あるいは達成したいラインによって構成される。完成の定義は開発チームとプロダクトオーナーが相談して定める。

受入条件も完成の定義も、誰かが判定しなくてはならない。開発チームとプロダクトオーナーが議論したり伝達したりするものだが、最終的に判定する責任は誰にあるのか。受入条件はPBIの内容の一部であり、従ってプロダクトオーナーが責任を持つものだ。そのため判定するのはプロダクトオーナーである。プロダクトオーナーが受入条件を元に自分で受け入れテストを作り、自分でテストするというのが純粋な形となる。この部分だけ取り出すと、請負契約の納品物を発注者の責任で検収するのと同じ構図になる。

完成の定義は開発チームが判定するものである。PBIが完成したと言えるかの条件であり、すなわちリリース可能であるか、スプリントレビューの対象とできるか、受け入れ判定をしてよいかの条件である。受け入れ判定をする前に完成の定義の判定をクリアしないとならない。いっぽう、完成の定義は、PBIが完成してるのかどうか、透明性を高めるための道具立てでもある。そう考えると完成の定義は、判定のための仕事をしなくても判定できるような、見える化や自動化を進めるべき領域でもある。

 

リモートモブプロ勉強会をrepl.it+Zoomでやれそう

モブプロ勉強会のリモート開催の可能性を考えて、簡単な素振りをしてみました。

  • repl.it(オンラインIDE)は言語やフレームワークの幅が広く、使える
  • replt.itのMultiplayer機能で、ブラウザ上で複数人(無料では4人まで)で共有できる。VSCode LiveShareとほぼ同じ感覚。参加者もアカウントが必要 https://repl.it/site/blog/multi
  • ファイル追加したときに同期されないことがあった(リロードしたら見えるようになった)
  • 誰がどのファイルを開いているかわからないので、ぼんやり見てると見失う可能性がある。ドライバーは編集箇所を頻繁に口に出すとか、ドライバーの画面を共有するといいかも。(なお、ファイル内では誰がどこにカーソル置いてるかはわかる)
  • テスト駆動開発や自動テスト実行は、repl.itの機能としてはないけど、実現可能(テストケース実行を外部コマンド叩くとか、言語内から呼び出すとか。言語にもよる。今回はruby+rspecで、main.rbからexecでコマンドを実行した)
  • コミュニケーションはZoom
  • Zoomの文字チャットを使うのが一番ハードル低い(replt.itのチャット機能は貧弱なので厳しい。Slack使えるといいけれど、そのためだけにワークスペース追加してもらうのはちょっと心理的ハードルが高い)
  • タイマーは、誰かが手元で動かしておく
  • ドライバーは参加順などで強制的にまわすのがよさそう(リモートで空気読んで手を挙げるのは難しい

なお来週のテスト駆動飲み会自体は、予定変更せず普通のモブでやる方向で考えています。その後、リモート開催に切り替えることになりました

 

 

自分がどんなふうにインセプションデッキ作りをファシリテートしているか

インセプションデッキ Advent Calendar 2019 - Adventarの参加エントリです。

アジャイルコーチとして活動する中で、インセプションデッキ作りを手伝うことがよくあります。私がファシリテートする場で、どんなことを考え、どんなことをしているのか、思いついたところを書き出してみました。一例として参考になる部分があればいいなと思っています。書き出してみると、我ながらちゃんと考えられていないところがあるなあと気づいたりしますね。

 

目次

 

インセプションデッキ作りが重要だ、作ったインセプションデッキはどうでもいい (いくないけどそれほどでもない)

私がインセプションデッキを作るねらいは、参加者がプロジェクトの背景や目的、ねらいとこれからの進め方について議論し、理解し、合意する活動そのものです。その意味で、デッキ作りの成果は、参加者の中にある認識であったり、作ったデッキに対して合意したという仲間意識であったり、これから一緒にやっていこうという意気込みであったりします。

 

デッキの完成は、そうした成果に比べると優先順位は下がります。個々のスライドの完成度はあまりこだわりませんし、すべて完成しようともしません。組織、プロジェクト、参加者によって重要な要素は毎回異なるので、時間をかけたいスライドも変わってきます。

 

もちろん完成に意味がないというわけではありません。完成したデッキは有用で、目に付くところに貼っておいたり、将来のタイミングで見直したり手直しをしたりできます。ですが、完成のためにやっているわけではないのだと認識しておくほうが健全です。またデッキの完成を目標にしてしまうと、議論が足らなくなったり、空気を読んで“合意”してしまったり、しやすいようです。

 

タフクエスチョンカードを手作りする

タフクエスチョンカードを、最初に作ります。これは物理的な紙のカードで、「(タフクエスチョンするぞ…!)」という気持ちを表明するのに使います。質問、ツッコミ、反論をするときにこのカードを出してから言うという使い方をします。説明としてはそういうふうに言いますが、実態としては質問することを忘れないための手元のトークンになるものです。


インデックスカードを1人1枚配り、各自カードを作ってもらいます。デザインは人それぞれで、びっくりマークやクエスチョンマークを描く人や、人間の顔を描く人、爆発やツッコミのイラストなどもあります。インパクトあるデザインでもいいし、逆に雰囲気を和らげるように描くのもよいでしょう。


タフクエスチョンカードを手元に置いておくと、質問から議論が起こりやすくなります。話を聞いたときそのまま納得するのではなく、なにかツッコミを入れるように意識が向きやすくなるのではないかと考えています。

 

全体の進め方も合意で決める

インセプションデッキ作りの進め方そのものも、議論と合意で決めていきます。最初に導入として、インセプションデッキの全体像や個々のスライドの内容を簡単に紹介し、スライドのタイトルを付箋に書いて並べます。ここからは、参加者全員でのプロセスになります。どのスライドを選ぶか、どの順番で作るか、どのくらい時間をかけたいか、その場で検討しながら決めていきます。結果として作るスライドを順番に並べたものができます。これはスクラムのプロダクトバックログと同じで、進みながら見直し、随時変更、入れ替えていきます。


スライドを自由に選ぶ流れではありますが、実際には「なぜここ」が外れることはありません。他にも「エレベーターピッチ」「ご近所さん」「夜も眠れないこと」「トレードオフスライダー」あたりは採用されることが多かったです(私がファシリテーターをやるときの話なので、私自身のバイアスが影響してそうな気もします)。順序は「なぜここ」が最初、あとは気になっている箇所を優先していくと、けっこうバラバラになります。Why→Howの順序が崩れることもあります。


少なくとも最初の数枚を決めてから、スライド作りに入ります。1枚終わったら、本当に完成にしていいかを確認したうえで、付箋に大きなチェックマークを付けていきます(確認にはサムズアップ・ダウンを使います)。また、そこで残り時間を確認し、次以降のスライドを見直します。予定時間を調整したり、順序を入れ替えたり、ときには準備が不足だと気づいてスケジュールを立て直すこともあります。

 

オーナーは目的意識を持って事前準備する

インセプションデッキ作りには、通常オーナーがいます。すでにバスに乗っていて、今回の参加者もバスに乗せたいという意図をもった人々です。この人たちには、作りたいスライドを事前に考えておいてもらい、いくつかは叩き台を準備してもらうこともあります。しかし当日、スライドを決めるに当たっては、そうした意見もあくまでいち参加者の意見として発言し、そうしたい理由を説明してもらい、全員の合意で決めるようにしています。


オーナーは場合によっては、事前に他の参加者に状況説明や参加してほしい理由などを説明していることもあります。インセプションデッキ作りは合意を一とする場で、アイデア出しや各論の検討、議論をするには時間が足りないようです。


(こう書いてみると、まるで根回ししまくりの、当日は意見もアイデアも出ない静寂とした、オーナーの都合に合わせた予定調和の、粛々と進行してシャンシャンとまとまる、つまらない会議みたいに聞こえますね。実際はそんなことなく、それぞれの人がそれぞれの立場から色々な案や反論や感想が出てきますし、はじめに思っていたのとは違う合意があらわれることも多いです。上に書いたような気を使うのは、参加者の数が多く多様で、仕事上の立場もミッションも違い、利害対立する可能性があるような場合で、オーナーの判断によります。一例として、いわゆる顧客や発注側の内部で合意を作りたいときです。)

 

タイムボックスはあんまり使わない

完成を意識しすぎないよう、インセプションデッキではタイムボックスをあまり設けないようにしています。「なぜここ」を話し始めて、そのまま丸1日まとまりきらないこともありました。


議論をファシリテートするうえでのテクニックとして、時間を意識してもらうことはありますし、割と大事にしてもいます。ですが「このスライドはあと30分で打ち切りましょう」というような進め方は避けています。参加者が話し尽くしたと感じられる、挙げられた論点がひととおりカバーされている、熱意を持って「これでいこう!」という雰囲気ができているかで、終了にしてよさそうか判断します。ファシリテーターや、参加者から「これで完成にしましょう」のような意見が出て、みんなが喜んで合意すれば、完成です。合意できないときや、不承不承な感じがする、疲れてきてどうでもよくなっているようなときは、長めの休憩(15分~30分)をとってから再確認します。


スライドにより、時間あたりの収穫が急に下がるものや、時間で切らないと終わりにしにくいものもあります。最初にタイムボックスを決めることが多いのは「パッケージ」や「夜も眠れないこと」などです。
もっとも参加者が全員集まれる時間は限られているので、いくらでも時間をかけていいというわけにはいきません。時間がかかりそうな部分は、事前に叩き台を準備してもらったり、一部の関係者で議論を進めておいてもらったりするようアドバイスしています。

 

休憩は戦略的に

休憩は重要です。4時間、あるいは丸1日かけてインセプションデッキ作りをするのであれば、休憩もしっかり確保します。休憩には戦略性があって、お手洗いに行くていどの5~10分くらいの休憩は前の議論を続けやすくなります。議論が詰まったような場面では、15分~30分くらいの長めの休憩を取って、頭を切り替えます。場のあったまり方にもよりますが、休憩時間にリラックスしてコーヒーやお菓子をとりながら話しているときが、実は突っ込んだ話ができていたりもします。当然、ちゃんと休むのも大事なので、話が続いているのを遮ってしまうこともあります。


休憩を取るタイミングも工夫ができます。話が盛り上がったとき、特にテンションや感情が高まったときは、すこし落ち着くために休憩を挟むのも有効です。結論や合意になんとなく不満がありそうなときも、いちど休憩を入れて個別に話すタイミングを取るとよいです。デッキ完成など区切りがついたときに取るのもよいのですが、完成したときはテンションが上がっていて、そのまま休まず次に進んだ方がスムーズに移行できるようなときもあります。

 

それぞれのスライドについて個人的なメモ

なぜここでは、以下のような質問を使って、状況を深掘りすることが多いです。

  • なぜ今、このタイミングなのか?
  • なぜこのメンバーなのか?
  • 他のことではなくこのプロジェクトをやる理由は?
  • うまくいったとき、本当に喜ぶのは誰か?

会社組織の中で立ち上がるプロジェクトでは、プロダクトとして実現したい目標だけではなく、組織の事情、人の都合、予算や年度区切りなど、本質っぽくないドロドロしがちだけど必要な話というのがよくあります(面倒くさい……)。きれいごとだけではない背景まで明るみに出して、コアメンバーで共有するようにします。こうした話はデッキには書き残せないこともありました。

 

エレベーターピッチには苦手意識を感じています。おそらく、企業の社内システムに関わる仕事が多いためもあって、エレベーターピッチ作りで紛糾したり、議論がダレる場面を多く見ます。エンドユーザーにはっきりしたペインがない、他の選択肢がなく優位性を考えられない、上からやれと言われてやっている感がにじみ出てしまう、などです(そうした認識が表面化するのは、それ自体は有益だと思います)。ステークホルダーが顔の見える特定の個人で、その人を説得する言葉が政治じみていて、プロダクトからフォーカスが外れてしまう場合もあります。

 

パッケージデザインは、私は心の中で「プロダクトビジュアル」と呼んでいます。プロダクトのポスターを描く形式にすることが多いです。プロダクト名や特徴などの文字も使いますが、なにかしらの絵、イラスト、アイコン、ロゴを入れてもらいます。アイデアを出しながら、全員がペンを握って描くように、絵が上手い人に押しつけるようなことがないようにガイドします。上手だったり綺麗だったりするよりも、参加者全員が「自分が描いた」と感じられる方が大事です。できたポスターはそのまま、チームの仕事場に貼り出しておきます。

 

ご近所さんでは、顔が見える(個人名が分かる)ようにしています。部署だったり会社だったりではなく、その中の誰が担当なのか、内部にはどんな人がいるのか、何かあったときに誰に連絡することになるのか、わかる範囲で出してもらいます。

 

トレードオフスライダーの基本4項目(スコープ、予算、時間、品質)だけ議論するときは、必要ではあるもののあまり面白い話にならないことが多いようです。面白いかどうかは、大いに主観ですけれど。それ以外の項目の話が出てくると、このプロジェクトならではの話に発展します。

 

以上です。アドベントカレンダー次回は、12/21稲野さんの予定です。