上司ガチャ - 組織の体制変更がチームを壊す日

大きめの企業でスクラムで回し始めて、ビジネスもうまくいくようになってきたところで、急に訪れがちなのが「体制変更」です。これまで陰に日向に支援してくれた上司や役員の方がいなくなって、新しい方がいらっしゃるという。ここで苦労したり、諦めてしまう例が多いと感じます。

f:id:wayaguchi:20190505091942j:plain

答えはないのですが、雑多な考えをここに記録しておこうと思います。まとまってもいないので、無駄に長くてごめんなさい。

うまくいく確率は論理的に50%を下回る

「新しい人とうまくいく可能性もあるので、50:50じゃないの」という意見もあるかもしれません。しかし、なにかを始めるときはだいたいうまくいきそうな上司や役員を選んで相談するわけです。しかし、人事異動で次に来る人は選べません。ですから、うまくいっていたことがうまく行かなくなったり、説明を求められたり、基本、いままでよりは手間が増えることは確実ですし、その結果、フラストレーションが溜まりがちです。

良さそうな上司が来たけど、最初だけだった、というケースも聞きます。外面がよいので上司の上司受けは良かったんでしょうけど、意外とチームの要望に対応できなかったり、(酷ですが) 十分な人生経験が不足していて器が小さかったり。付き合ってみると人間そう都合よくはできていなかったりするのでしょうね。成長には時間が必要ですが、本人や周りが待ってくれるとも限りません。まだ対象領域の業務知識があれば、議論ができるところなのですが。

kawaguti.hateblo.jp

上司ガチャ

最近はソーシャルゲームの用語から、上司ガチャというようです。組織変更に関していえば上司になる人も被害者なんじゃないかと思います。お互いがガチャ。

sayonara-shachiku.com

博士の教え: くそったれ上司には近づくな

スタンフォード大学経営学の権威ロバートサットン教授はこの本で、エネルギーを奪い、惨めな思いになるようなクソったれ上司には近づくなと言っています。身もふたもないですがそれくらい強力だということでしょう。話が通じない人は変わりようもないですしね。また、誰にでもくそったれの素養があるとも言ってます。

あなたの職場のイヤな奴

あなたの職場のイヤな奴

 

アメリカのいくつかの企業では360度評価を使って、問題のあるマネージャーを見つけ出せるようにしているようです。

kawaguti.hateblo.jp

日本の場合はメテオフォール感ありますよね。ご神託が降ってくる。上司の文句言うのは他人のことを悪くいうみたいで嫌だし、自分が悪いような気がしてしまう。我慢する方がいいというアドバイスをもらうことも多いです。

eiki.hatenablog.jp

マネージャーの仕事はチームの能力を最大化すること

マネージャーという役割は優れたチームの成果を最大化することです。マネージャーが生産の中心に来ることはありませんので、あくまで手助けするのが役割なのです。いなくても回るけど、それでいて、いることで価値を出す宿命があります。

皆さん知らないかもしれないが、この世界は実は、売上とそれを支える進捗が救いなんだ。進捗の源泉はアーキテクチャでありドメインモデリングでありシステム設計者だ。マネージャではない。

medium.com

Microsoftデベロッパーの河野さんもこう言ってました。

そういうデベロッパーがバラバラにやっているのに対して、「今はこっちじゃない。今フォーカスすべきはこっちだよ」とちゃんと指導してくれる、ディレクションをしてくれる。船のキャプテンと同じですよね。そこをしてくれる人がいないと、チームとして発散しちゃいます。

ポイントとして、常にマネジメント側から「なにがゴールか」というのをメッセージとして出し続ける必要があります。「我々がこうやるのはどうして必要なのか?」あるいは「これをやることでお客さんに喜んでもらえる。だからやろう」と。これって最初に言うだけじゃなくて、毎日のように言うんですね。

そこへ向かうためのマインドシフトが必要なのだそうです。

というのは、やっぱり日本的な考えだと、上司が指示して自分は「へへー」みたいなイメージがあったので。でも、そこで「上司とは自分のパフォーマンスを最大化してくれるための良きパートナー」だと(考えた)。そこってマインドシフトがすごく大事でした。

logmi.jp

一方、Amazon創業者のジェフ・ベソズはこんなスタイルだそうです。謙虚でやりやすいリーダーが来てくれるといいですね。

「『多くの場合、正しい』を実践できているリーダーを観察すると、そこにはいくつかのパターンがあった。そうしたリーダーは他人の話をよく聞く。そして考えを頻繁に変える。政治家が頻繁に考えを変えるのは問題だが、リーダーにとっては必要なことだ。単に新しいデータを得たから考えを変えるだけではない。多くの場合、正しい判断ができるリーダーは、新しいデータがないときでさえも考えを変える」(ベゾス氏)

tech.nikkeibp.co.jp

頭が硬くて、必要な知識を持っていないリーダーが上司に来た時には、緊急停止スイッチを押さないといけないのかもしれません。人を変えるのは、難しいか、とても時間がかかります。

enterprisezine.jp

これは最近になってスクラムが普及して云々とか、シリコンバレーがどうこうではなく、私が知る限り昔からできるマネージャーはチーム若手をそそのかすのが上手くて、報いるのも上手です。

うまくいっているのを壊さなければいいのでは

歯車が噛み合っている上司とチームのセットは貴重な存在ですので、なるべくなら変えないほうがよい、と思います。逆にいうと、そういうことを見る目を持たず、壊してしまう上司や経営陣の判断に注意すべきかなと思います。「見る目」くらいは期待したいですよね。

岩田 基本的には、その会社が
「得意なことをする集団であろう」
ということを目指すとしても、
人と人がいっしょに仕事をするためには、
最低限、苦手だろうがなんだろうが、
やってもらわないと困るということを
決めないといっしょに働けないんですね。
というときに、その「最低限のこと」を
なるべく小さくすることが、
経営者として正しいんじゃないかなと
わたしは思うんです。

HOBO NIKKAN ITOI SHINBUN - 1101.com

 

文化のシャボン玉を守るにはリーダーの力がいる

マイケル・サホタ氏は、企業の中でアジャイルなどの新しい文化を普及させるには、Culture Bubble (文化のシャボン玉) を維持するべきだと主張しています。新しいことを行っている組織は、うまくいくと外部との衝突を起こしやすくなります。その際にリーダー(図上で一番上にいる人)は、とても重要とのこと。

ほかにも外部とのつなぎ役(Adapters)になる人たちがうまくふるまう必要があるとのことです。急速に膨らんだり、外からつつかれると、しゃぼん玉は弾けて、また小さなシャボン玉(組織関係なくいやりたい人たち)に戻ります。

f:id:wayaguchi:20190708012345p:plain

アジャイル系のカンファレンスで常に参照される、社内政治といえばこの本です。

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

Fearless Change アジャイルに効く アイデアを組織に広めるための48のパターン

 

 こちらの本は新規プロダクト開発の本なのですが、意外と社内説得の話が多くて、おもしろかったです。アダプターにお勧めかもしれません。

SHIFT:イノベーションの作法

SHIFT:イノベーションの作法

 

今日はアジャイルリーダーシップサミット

こうした組織運営についても、みんなでどんどんとうまくなっていけたらいいですね。今日行われるアジャイルリーダーシップサミットでも、こうした議論が行えるといいなと思っています。

Home | Agile Leadership Summit 2019

f:id:wayaguchi:20190708061805p:plain

 

f:id:wayaguchi:20190708061904p:plain

 

OSTでの議論の内容

当日の議論の内容を森雄哉さんがまとめてくれました。

二年後に来る上司ガチャの悪い影響を止める方法
 - チームにとって上司にしてほしいことを書く(ディスクリプション
 - 自分のチームの状態を話す(自分達ができることと、上司にやってもらいたいことを話す
 - チーム側がマネジメントを受けているだけになっていないかをふりかえる
 - 実はチームがクレクレ君になってない?
 - 上司の上司と関わりを持って対応出来るようにしておく
 - チームを鍛えまくる
 - チームFA

scrapbox.io

 

ソシャゲに詳しい人の説明もあります。ありがとうございます!

上司ガチャ問題の対策をソーシャルゲームのガチャから考える - ミッションたぶんPossible

 

 

The Phoenix Project をアジャイルコーチがやると....

以前見学させていただいた The Phoenix Project のワークショップ。継続的デリバリーの先のバリューストリームを体験するカードゲームとして面白いなと思っていたら「こんどアジャイルコーチスペシャル体験会やるんで、誘いますね」とITプレナーズの岡本さんよりお誘いいただきまして。

kawaguti.hateblo.jp

ということで昨日体験してきました。講師はクリエーションラインの笹健太さんと荒井裕貴さん。DevOpsDaysTokyo実行委員でもあります。

先に中村知成(@ikikko)さんのレポートがありますのでこちらもどうぞ。

ikikko.hatenablog.com

雰囲気のレポート

詳しい内容はネタバレになるので書きにくいので、雰囲気だけお伝えします。

f:id:wayaguchi:20190706093115j:image

ロゴにかぶってしまう講師。

f:id:wayaguchi:20190706093144j:image

必死の採点。

f:id:wayaguchi:20190706093203j:image

午前中はこんな感じ。ルールを理解する人と自由な人。

f:id:wayaguchi:20190706093214j:image

午後はフローが見える化される。環境を変える人たち。 イノベーションの発生と普及。考えながら手を動かしながら歩きながら会話する人たち。問題を中心として有機的な組織が形成され、全員が考え、全員が動く。

f:id:wayaguchi:20190706093227j:image

昼休みにご飯食べながら問題を解析して組まれるワークプレイス。緊急イベントへの対応すら考慮されている。残り3分でも焦らずチャレンジする人たち。「それってどういうこと?」「ここなにやってんのー?」「はーい質問があります。」「ちょっとみんな聞いてー」f:id:wayaguchi:20190706093241j:image

結果発表。惜しくも目標を達成できませんでしたが、売上は単調増加して株価も回復。V字回復とはまさにこのこと。このまま次期ももっと良くなることが間違いないです。CFO役としては文句ございません。 

f:id:wayaguchi:20190706094021j:plain

遊びに来たわけではありません。真面目に仕事しております。休憩中や懇親会でも、闊達に意見が出る場を作るための心理的安全性について、人数が多くても議論が建設的効率的にできる条件や振る舞い、一時的に余った人はどう動くか、情報共有と手分けのバランス、セットベース開発とWIP制限、などの話題で盛り上がりました。

f:id:wayaguchi:20190706094013j:image

楽しかったでーす。(語彙力

f:id:wayaguchi:20190706094052j:image

ピンぼけ! 

f:id:wayaguchi:20190706100040j:plain

 

 

inedo.co.jp

なぜアジャイルの方が成功しやすいのか?

スクラムギャザリングというカンファレンスの実行委員会がありました。実行委員会といっても、会議をやってるというより、淡々と決めて作業を進める会です。このカンファレンスは2011年からやっていて、来年でちょうど10年になります。紆余曲折ありながらも、ここ数年は実行委員業も枯れてきて、ああうまくいくアジャイルチームってこういう特性あるのかもな、と気づくところもありまして。今日はそのあたりを記してみます。

今日は趣意書の公開まで

今日は 4-5時間ほどで、趣意書(スポンサー向け資料含む)と、スポンサー受付のサイト作成までが終わりました。ちなみにWebサイトまでは終わらなかったので基盤まで整えたところで、次回の作業になりました。

趣意書はこちらです。

f:id:wayaguchi:20190630072527j:image

f:id:wayaguchi:20190630072338j:image

うまくいってることは変えないでおく

やりながら考えたことは、

  • うまくいっていることは変えないでおく
  • というか問題なければ変えない
  • なので問題だけ議論して決めて
  • なる早でなるべく細かい単位でデプロイする

ということです。

趣意書は去年のものをベースに、昨年実績の追加記載と会場が変わるのでそれに伴う変更を入れてます。逆にいうとそれ以外は変えてません。

チームの中で作業を進めているので、面倒くさいところや、議論は盛り上がるけど前に進まなさそうな点は「あとで必要になったら話そう」と一旦話を切って進めました。今日中にある程度アウトプットしたいからです。

議論と作業のバランス

分業をしていないので、議論も作業も同じ人たちがやります。決定や作業計画だけ詰めたところで、作業ができなければアウトプットが出ません。一方で議論や決定が必要な部分ももちろんあるので、適宜、必要なタイミングで議論をやっていきます。

これが、多重請負のウォーターフォールだったとしたらどうなるでしょう。「私たちの責務は意思決定。今日は議論と意思決定に集中して、実作業は全体工程が決まった後でやりましょう。作業はそれぞれの担当や、その道のプロにお願いしましょう」ということになりがちかなと思います。たぶんそれ自体は「そういうもの」なのだと思いますので、悪い話ではないと思うのですが、問題は「いつ結果がわかるか?」です。もし今日、方針やスケジュールだけ決めたのだとすると、間違いがわかるのは実装後になります。来月か、もっと先か。

一方、趣意書の実装と即日公開で得られたものは以下です。

  • 会場見取り図でスポンサー受け入れ可能数を見積もる
  • 収益見込みを確認し、キャッシュフローとリスクのある変数について見当をつける
  • 公開後、すぐにスポンサーの応募を数件いただく。それによって趣意書が機能しているという認識を持つ
  • スポンサー申し込みフォームも機能していることがわかる。いくつか細かな改善を行いつつ今後のオペレーションの見当がつく

このようにいくつかのアウトプットと、その結果のアウトカム(成果)がありました。最大のアウトカムはスポンサーのお申し込みをすぐもらえたことです。嬉しい誤算。本当にありがたいご支援でした。

イムリーなアウトプットがアジャイル成功のヒント

ほとんどが去年と同じことをしているにもかかわらず、「マンネリ」「つまらない」と感じることがない点も特筆すべきかもしれません。それどころか一年ぶりの作業なので、むしろ多くの発見や学びがありました。使っているサービスの機能が進化していたり、逆にうまく動かなくなってて直したり。ConfEngineというサービスの開発者に「前回のイベントからのコピーで新しいイベントを作る」という要望を出していたのですが、なんとそれが実装されていて、喝采が上がりました。

これももし多重請負のウォーターフォールだったら、企画会議で「昨年と同じではつまらないのではないか?」という話になって、アイデアが足された挙句に、実施部隊に情報が渡るのが遅れ、実施部隊は膨らんだ企画と変わった状況の狭間で右往左往しながらギリギリでリリースにこぎつけた後に問題が発覚して予定外かつ緊急の作業に追われる未来が見えます(知らないけど)。

アジャイルでは、というか別にアジャイルと呼ばなくても良いのですが、私たちが重視していたのは、ローコストで、タイムリーに、アウトプットしながら、結果を得て、それを通じて学びを得て、時間内で手仕舞いする。...ということです。私たちはアジャイルの実践を通じて、この「あたり前」のことを学んできたに過ぎないのかもしれません。

イノベーションは安定したチームから

あらゆる企業活動にこれが通じるなんて思いませんが、多くの作業はこうして日々回っていくような気もします。その結果、本人たちすら気づかないうちに「簡単には真似できないチームのノウハウ」が溜まってるような気もしなくもないです。小さなイノベーションの積み重ねは安定したチームから。...そんな気もしました。

きっとそんなことはみんなわかってるけど、意外とそこまで我慢できずに新しいアイデア投入をやってしまったり、人を入れ替えてみたり、売り上げが立たずに予算が尽きたり、ステークホルダーや偉い人お客さんに振り回されたりするものなのかもなー、とも思います。頑張っていきましょう。

アジャイルを始める時の成功のヒントは、今いる人で今できるアウトプットを最速で目指してみる。...そんなところにあるんじゃないかと思ったのでした。

 

...というわけで、今日も作業が進んで満足でした。早速のスポンサーのお申し込みもありがとうございます。「ほんとこのカンファレンスは愛されてるね(私たちではなく)」ということで、またちょっとやる気も補充できました。フィードバック大事ですね。次回作業日を決めて今日は終了しました。

 

P.S. 4-5時間作業して3時間飲んでたみたい。ワークライフバランス。まあ仕事じゃないですけど。

f:id:wayaguchi:20190630072850j:image

 

実行委員のてやまぐさんの資料もいいですね。こんな話をいつもしてる。XP祭りの実行委員も2010年くらいからやってました。

Conference Organize Tips at 2019/06/22 #devlove #devlovex

Electron + Angular でマルチウインドウタイマー

前回の続きの作業記録です。

kawaguti.hateblo.jp

 

Electronでのマルチウインドウ化

マルチウィンドウを積み残しにしたのでその点を進めました。

Electron ではウインドウを起動するために、new BrowserWindow する、という記述があり、これを使うことを考えました。

BrowserWindow | Electron
... が、このウインドウはネイティブのウインドウでした。今回欲しいのは JavaScript 上で起動し、JavaScript で通信できる別ウインドウなので、ちょっと用途が違いました。

今回、ここの理屈を理解するまでに多くの試行をしました。最終的に、イベントでモブをしまして、よた(@y0t4) さんの手助けもあって、筋違いに気がついた次第です。モブすごい。

ayumi-hsz.hatenablog.com

 

ほんとうに必要だったものは window.open

結局のところ、必要なコードは window.open でした。あ、それもう20年ぐらい使ってるわ。どうってことのないこの一行のために費やした時間は数日(のちょっとした時間)。学びが多いです。

window open · kawaguti/tiimer@8618f42 · GitHub

ということで、二枚目をウインドウを起動できるようになりました。普通にJavaScriptなので、複数ウインドウ間のメッセージングについても、前回と同じく npx-multi-windowが使えそうです。

Angular 7 でカウントダウンタイマー - kawaguti’s diary

ということで、組み込みました。

普通にメッセージのやり取りができるようになったので、引き続き、メッセージにコマンドを仕込んで、時計のセットをできるようにして、アプリ完成です。

f:id:wayaguchi:20190623062358p:plain

 

Mac用にパッケージ

Mac OSX 用にパッケージしたものをこちらにおいておきます。セキュリティ上、App Store以外のものは動かないと思いますので、実行する場合は、詳細設定-セキュリティ から許可をしてあげる必要があります。

https://www.tiimer.net/tiimer-darwin-x64/tiimer.app.zip

Windows用も動作したのですが、うまく配布用のZipが作れていないので、また調べまーす。

 

はこだて未来大学さんと DevLOVEXさんでお話しました

先週、はこだて未来大学の大場みち子先生に呼んでいただきまして、学生さん向けにアジャイル開発のお話をしてきました。どうしてそういうものが生まれたのか、というところを私なりに整理してみたものです。「アジャイル開発の時代」という大仰なタイトルになっており、恐縮です。釣りバカ日誌の話をしたかっただけです。

speakerdeck.com

 

また、昨日は DevLOVE Xさんに呼んでいただきまして、モブプロのお話をしました。こちらは5月の連休で行ってきた Hunter Industries さんのモブプロの報告になってます。

speakerdeck.com

学んだことの詳しい内容は、以下の記事ですでに書いていますので、よかったらどうぞ。

kawaguti.hateblo.jp

kawaguti.hateblo.jp

並行セッションの裏番組の登壇者の皆さんが豪華すぎて、全部聞きに行きたい感じでした。そんな中で選んで聞いていただいた方に大感謝です。同窓会のようなカンファレンスに呼んでいただき、ありがとうございました。楽しかったです。今日は二日目、盛会をお祈りしております。

 

f:id:wayaguchi:20190623064646p:plain

 

Electron でカウントダウンタイマーをアプリにする

前回のカウントダウンタイマーに関して、早速ネットで繋がってないときにも使いたいという要望をいただいたので、Electronを試してみようと思いました。....の、作業ログです。

kawaguti.hateblo.jp

Chroniumブラウザを使って Webを単体アプリにパッケージするElectron

Electronというのは、ChromeやSaferiなどのブラウザのコアになっているChroniumを使って、Webフロントエンドの技術でスタンドアローンのアプリを作れるというものです。必要なソースを全部固めてexeとか実行ファイルにするツール群とデバッグ環境、という感じかなと思います。

人気のあるクロスプラットフォームエディタのAtomとかVSCodeがElectronで作られているそうなので、信頼性もありそう。

Electron Apps | Electron

2000年代前半くらいにあったWindowshtaという仕組みがあるのですが、それに近い感じもします。AngularやReactなどのWebフロントエンドのツールがそのまま使えるというのが面白いところかなと思います。普通にツール作るならなんとなく Swift とかのほうが簡単なんじゃないかと思うのですが、クロスプラットフォームで動くものを作れる(ChroniumとかJavaScriptのおかげ)ので、そういうときには便利なのではないかという感じがします。その分複雑度は上がってしまうので、ハマった時に抜け出すにはそれなりに熟練が必要そうな気がします。まあなんでもそうですね。

とりあえずマルチウインドウは避け、カウントダウンタイマーが動くところまで

今回はまず Electron で Angular のカウントダウンタイマーが動くところまでやりました。マルチウインドウでの同期は少しハードルが高いかもしれないので、まずはありがたいサンプルの組み合わせで、できそうなところまで。

前回のソースは agx-multi-window を組み込んでしまったので、その前まで戻します。別のフォルダを切って一からやることにしました。

ElectronでAngular初期画面まで

結構ここで時間を消費して調べることになってしまったのですが、AngularもElectronも変化が速くて、しかも別の組み合わせもあるので(React+Electronとか)、ハマった時に情報を調べるのがなかなかしんどかったです。Angular7 + Electronというキーワードで見つけたこちらがドンピシャでした。

qiita.com

ということで淡々と作っていきました。

npm i -g @angular/cli

ng new single-window

cd single-window

npm install --save-dev electron@latest

npm install --save-dev electron-packager@latest

npm install --save-dev ngx-build-plus@latest

https://github.com/kawaguti/tiimer/commit/fa170f51078e35a0ea80f51516cffc5e060ec902

プロジェクトを作ったら、上記のサイト通りに修正を入れました。

https://github.com/kawaguti/tiimer/commit/122398d0a3b38053134dd5c785fd682ec83ce5ba

実はこの時、ちょっとした失敗をしました。package.json の main 項の追記を忘れて、ビルドエラーはないのに起動しないという状況になりまして。手がかりが見つからずに数時間迷子になりました。簡単なサンプルが動かないと、疑うところが無限にあってしんどいですね。ミスなのでだいたい情報もなくて。

"main": "main.js",

f:id:wayaguchi:20190616194921p:plain

別の環境でもう一回作ったらけろりと出なくなって、気がついた次第。対照実験だいじです。

ちょっとハマった先で Angular の初期画面がElectron上で表示できたときには感動しました(自作自演)。同じプロジェクトで Web版も動きます。

f:id:wayaguchi:20190616195601p:plain

リアルタイム時刻表示は動くか?

次は時刻表示です。rxjs の Observable (pub/subイベント機構) が動くことの確認です。上記のサンプルがちゃんと動いたので、なんとなくシングルウィンドウものは動きそうな感じがしますが、一歩一歩確認します。

ありがたいこちらのブログを見ながら、コードに時刻表示を入れていきました。

【Angular】小ネタ:現在時刻の表示 - Qiita

コミットログはこちら

Add clock · kawaguti/tiimer@d69efb2 · GitHub

f:id:wayaguchi:20190616201328p:plain

Electronでも時刻が表示されました。同じプロジェクトで Angular CLI も動くので、ブラウザでも動きます。WindowsでもMacでも配布用のビルド(packager)できました。

f:id:wayaguchi:20190616223156j:plain

カウントダウンタイマーにする

時刻表示までいければ、あとはカウントダウンタイマーをちょっと作り込むだけです。

Add countdown timer · kawaguti/tiimer@e5c8253 · GitHub

remove example · kawaguti/tiimer@20167d7 · GitHub

Add timer set button · kawaguti/tiimer@40da1ff · GitHub

Angularのロゴを消すなどして、カウントダウンタイマーになりました。

f:id:wayaguchi:20190616202140p:plain

 

Beep音の実装

あ、beepもありました。こちらもありがたく拝借

JavaScript で Beep 音を鳴らす方法 - Qiita

音もちゃんと出るようです。

Add beep · kawaguti/tiimer@9f42b66 · GitHub

Beep音がオフィスで鳴り響いてドキッとしたので、BeepOffボタンも追加しました。押さなくても10回で勝手に止まるようにしてます。

Add BeepOff button · kawaguti/tiimer@86598e3 · GitHub

f:id:wayaguchi:20190616202626p:plain

以上で、 Electron でカウントダウンタイマーが動きました。

 



まだデバッグ版なので、本番用のpackage作成は次回。あとマルチウインドウもやりたいですね。

 

 

Angular 7 でカウントダウンタイマー

Angularチュートリアルを一通りこなしました。わーい。HTTPの章がボリューミーでしんどかったですが勉強になりました。

angular.jp

インメモリで仮設のAPIを作って試せるところがなかなかいいですね。

で、ちょっとなにか作ってみようと思って、デュアルモニターに対応するカウントダウンタイマーを作ってみました。よかったら使ってみてください。

http://tiimer.net/


f:id:wayaguchi:20190610164229p:plain

使い方はシンプルです。

サイトにアクセスして、”Open a new Window” ボタンで別ウインドウ(タブ)を作って、それをデュアルディスプレイ側に持っていきます。そのあとでどちらかのウインドウで分秒を入力して”Set”をクリックするとタイマーが両側のウインドウでスタートします。00:00になるとアラームが鳴ります。音を止めるには”BeepOff”をクリックしてください。

起動時やウインドウオープン時のソースの読み込み以外はサーバとの通信はありません。

 

ソースコードはこちらです。

tiimer/angular at master · kawaguti/tiimer · GitHub

作った過程を以下に記録しておきます。

 

要件: デュアルモニター対応のカウントダウンタイマー

研修でよくカウントダウンタイマーを使うのですが、よく使っていたアプリケーションがOSアップデートの波についていけず、次々と使えなくなるという問題がありました。いろんな人が軽い気持ちでつくてみることができるような性質のアプリですので、マーケットにも常に似たようなアプリが存在していて、次のものを見つければいいのですが、ちょっとめんどくさいんですよね。今回は一つ加えたい要件もあったので、自作してみることにしました。

その要件とは

デュアルモニターでプレゼンしているときに、発表者ディスプレイ側で操作して、受講者向けディスプレイに時間を出したい!

です。

 これを実現する方法は数限りなくあると思いますが、今回はAngularの練習ということもあるので、こういうソリューションを目指しました。

JavaScriptでタイマーを実現し、かつ、HTML の New Window で新しいウインドウを起動して、子ウィンドウ側にタイマーを表示する 

結果的にはもう少し踏み込んだ、複数ウインドウを同期するカウントダウンタイマーになりました。(実装の都合により)

 

要素技術1: AngularのObservableを使って時刻を更新する 

まずはカウントダウンタイマー部分の実装ですが、Angular Tutorial に出てきた、 pub/sub型のイベント処理を扱う Observable という仕組みをつかうと簡単にできそうです。

....と思って探したら素晴らしいサンプルがありました。

qiita.com

このサンプルのいいところは、Observable の使い方を端的に示すだけではなく、html側のパイプの使い方も教えてくれているところです。「もっとシンプルな方法や、間違いの指摘等あればコメントよろしくお願いします。」ということですが、全く思いつきませんでした。ありがとうございます。

要素技術1の派生: カウントダウンタイマー

現在時刻が更新できたら、次はカウントダウンタイマーです。毎秒このイベントが起こることを信じて、残り秒を remainedSec という変数に持ち、これを秒ごとに減算して、ゼロになったら完了、ということにしてみます。

targetSec が最初にセットした残り秒数、remainedSec が現在の残り秒数、elapsedSecが現在の経過秒数です。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

export class AppComponent implements OnInit, OnDestroy {
now: Observable<Date>;
intervalList = [];
targetSec: number;
elapsedSec: number;
remainedSec: number;
remainedString: string;
public ngOnInit() {
this.now = new Observable((observer) => {
this.intervalList.push(setInterval(() => {
if ( this.targetSec > 0 && this.remainedSec > 0) {
this.elapsedSec++;
this.remainedSec = this.targetSec - this.elapsedSec;
this.remainedString =
Math.floor(this.remainedSec / 60).toString().padStart(2, '0') + ':' +
Math.floor(this.remainedSec % 60).toString().padStart(2, '0');
}
if ( this.remainedString === '00:00' && this.beepFlag ) { AppComponent.beep(); }

observer.next(new Date());
}, 1000));
});

残り秒数表示が秒だと分単位がわかりにくいので、表示用にremainedStringというのを作っています。これは mm:ss 形式で残り秒数を表示します。

表示が 00:00 になったらビープ音を鳴らす条件式が最後に入っています。ビープ音については次項で説明します。。

要素技術2: ビープ音を鳴らす

カウントが 0 になったら (表示が 00:00 なら) ビープ音を鳴らしたいです。いくつも思いつくのですが、できる限り簡単で、ネットワークに依存しない実装が欲しかったので、こちらを参考にさせていただきました。

qiita.com

音声データもそのまま頂いてしまいました。すみません。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

public static beep() {
// tslint:disable-next-line:max-line-length
const base64 = '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';
const sound = new Audio('data:audio/wav;base64,' + base64);
sound.play();
}

beepFlag という変数を持っておいて、カウントダウン開始時にこれをTrueにします。カウントがゼロになったときに、このフラグがTrueだったら、このbeepメソッドを呼んでビープを鳴らします。ユーザーが止めない限り毎秒鳴らしておきます。

ユーザーが止める操作をしたら、beepFlagを False にして次のイベントからはbeepが呼ばれないようになります。

if ( this.remainedString === '00:00' && this.beepFlag ) { AppComponent.beep(); }

要素技術3: マルチウインドウでの同期

普通のHTMLでは、window.openというメソッドで新しいウインドウを開くことができます。このときの戻り値がウインドウオブジェクトというもので、これを経由して小ウインドウのオブジェクトにアクセスすることができます...のですが、ちょっとめんどくさいので、ライブラリを探しました。いいのがありました。

nolanus.github.io

メッセージのキューを実装していて、ウインドウ間でメッセージをやりとりできます。Angular の Observable でメッセージを受け取ることができるところが素敵でした。サンプルがよくてきていて、初心者の私でもわかりやすく、とてもありがたかったです。

f:id:wayaguchi:20190610154950p:plain

サンプルをちょっと変更して、受け取ったメッセージが 0 < n < 3600 の数字だったら、 それを目標の秒数としてセットしてカウントダウンを開始せよ、という司令ということにします。

あと、ビープ音が鳴っているときに消す命令として「beefOff」を設定しました。この文字列が来たらビープ音をOffにします。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

this.multiWindowService.onMessage().subscribe((value: Message) => {
// tslint:disable-next-line:radix
const newsec = parseInt(value.data);
if ( newsec > 0 && newsec < 60 * 60 ) {
this.targetSec = newsec;
this.elapsedSec = 0;
this.remainedSec = newsec;
}
if ( value.data === 'beepOff' ) { this.beepFlag = false; }
this.logs.unshift('Received a message from ' + value.senderId + ': ' + value.data);
});

ウインドウIDごとに出し先を変えられるのですが、それは不要なので、基本ブロードキャストします。ブロードキャストといっても、ウインドウのリストにforEachで全部メッセージを投げるだけの実装にしました。

public broadcastMessage( message: string ) {
this.windows.forEach((window) => {
this.sendMessage(window.id, message);
});
}

困ったところ: ビープ音がウインドウごとに鳴ってしまう

要素技術を合わせるときに困ったところは、ビープ音がウインドウごとに鳴ってしまうことでした。

これは、カウントダウンの指示を出したウインドウでだけビープ音が鳴るようにする( beepFlag を Trueにする) ということで解決しました。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<button type="submit" class="btn btn-primary"
(click)="broadcastTime(minInput.value, secInput.value);minInput.value='';secInput.value='';beepFlag=true;">Set
</button>

ただ、カウントダウン開始の指示を出したウインドウ以外で止めたいこともあるので、beepFlag の Off はブロードキャストするようにしています。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<button class="btn btn-secondary" (click)="beepFlag = false; broadcastMessage('beepOff');">BeepOff</button>

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.ts

this.multiWindowService.onMessage().subscribe((value: Message) => {
      (中略)
if ( value.data === 'beepOff' ) { this.beepFlag = false; }
      (中略)
});

 

ウインドウサイズに合わせて文字を大きくしたい

細かい話ですが、ウインドウサイズに合わせて文字を大きくしたかったので、HTML5で加わった vh/vw というCSS要素を使いました。

dev.classmethod.jp

とりあえず style 要素で貼っています。そのうちCSSファイルに動かしたい。

https://github.com/kawaguti/tiimer/blob/master/angular/src/app/app.component.html

<p style="Font-Size: 20vw; Margin: 15vh 2vw 0">
{{ remainedString }}
</p>

 

ビルドとデプロイ

手元で動くようになったので、Angular で本番用にビルドしてデプロイしました。

$ ng build --prod

Date: 2019-06-10T06:17:12.545Z
Hash: acbc4eea2ebde4796da1
Time: 29359ms
chunk {0} runtime.26209474bfa8dc87a77c.js (runtime) 1.41 kB [entry] [rendered]
chunk {1} es2015-polyfills.bda95d5896422d031328.js (es2015-polyfills) 56.6 kB [initial] [rendered]
chunk {2} main.780e3db59e24bd6cf759.js (main) 242 kB [initial] [rendered]
chunk {3} polyfills.8bbb231b43165d65d357.js (polyfills) 41 kB [initial] [rendered]
chunk {4} styles.3ff695c00d717f2d2a11.css (styles) 0 bytes [initial] [rendered]

Azureへの静的ファイルのデプロイは前回の記事の手順で行っています。

kawaguti.hateblo.jp

kawaguti.hateblo.jp

 

今後の課題

  • ネットに繋がってない状態でも使いたい!
  • スマホだとスリープに落ちたところでカウントがずれる!
  • スマホだとビープ音がならない!

といった課題が見つかったので、次は Electron か Swift で作ってみたいと思います。あ、二番目は内部ロジックの変更(命令を残り秒数ではなく、目標時間にする)で対応できそうですね。

蛇足ですが、こういう要望が見つかるところが、「動くソフトウェア」の大事なところだな、と思いました。