プロフェッショナルマネジメント

この記事は Akerun Advent Calendar 2020 - Qiita の25日目の記事です。

2020年6月にフォトシンス に入社して、WEBグループのマネージャをしていますNonです。

2000年に大手ベンダーのSEとして社会人をスタートし、今年で21年目となりますが、ベンチャーから大手企業まで、複数の会社を通じて、大小様々なプロジェクトを経験しております。

ここ10数年は、主にエンジニアチームのマネジメントに従事してきて、マネジメントの難しさを地肌で感じると共に、色々な案件を通じてマネジメントスキルを磨くことができました。

今回は、マネジメントに携わる上で、私が重要視していることを少しお話できればと思います。

マネジメントとは?

マネジメントとは?聞かれた場合、皆さんはなんと答えるでしょうか?

アメリカの経営学者P.F.ドラッカーによると下記のように述べています。

  • マネジメント:組織に成果を上げさせるための道具、機能、機関
  • マネージャー:組織の成果に責任を持つ者

【出典】P.F.ドラッカー(1999)「明日を支配するもの 21世紀のマネジメント革命」ダイヤモンド社

組織の成果に注目し、マネージャーは、組織の成果に責任を持たなければいけません。

つまり、メンバーの動機付け、評価、育成などを通じて組織を作っていく役割を求められます。

マネジメントの課題

マネジメントは難しいとよく言われますが、具体的にどういった課題があるのでしょうか?

  • 部下の育成が出来ない、仕事を任せられない
  • 組織目標やビジョンが打ち出せず組織をまとめられない
  • 組織間の調整がうまくできない
  • 上司と部下の間に挟まれる立場になり判断に困る

このようなことを良く耳にします。

一見どれも重要な課題に感じますが、引き起こされる結果に注目する必要があります。

これらが原因となって、組織の目標を達成できないということが最も重要なポイントです。

マネージャーは、組織の成果に責任を持つ者なので、組織の目標を達成すべく、様々な手段を講じる必要があります。

マネジメントに求められるスキル

マネージャーは自分の業務だけではなく、メンバーの業務と組織を管理する能力が問われます。

プレイヤーからマネージャーに昇進すると、今までとは違う壁にぶつかります。

部下を育てられずにいると、いつまで経っても自分自身の業務が減りません。

結果、プレイヤーとしての負荷が高くなってしまい、マネジメントの比率が低くなってしまいます。

この問題を解決するためには、マネジメントスキルを高める必要があります。

では、マネージャーには、どんなスキルが求められるのでしょうか?

私のこれまでの経験から14のスキルに整理してみました。

マネジメントに求められる14のスキル
1 見積管理 要件からタスクを洗い出し、メンバーのスキル状況も考慮して、適切な見積もりができるか。
2 業務管理 「何を(What)」、「いつまでに(When)」、「どうやって(How)」やるかという具体的な指示をメンバー与え、その過程や結果を管理・監督できるか。
3 指導・教育 目標設定を適切に行い、教育プランを考えて部下をゴールまで導いて行けるか。
4 業務改善 日々の業務をこなすだけではなく、課題を解決したり、仕組み化したりして業務効率をあげることができるか。
5 リスク対策 リスクの洗い出しや対策準備、リスク発生後の適切な処置が迅速に行えるか
6 課題の発見と解決 業務における課題や問題点を見つけだし、その解決策を提案できるか。
7 トラブル収束 問題が起きた際にその内容に応じて迅速かつ柔軟に対応できるか。
8 組織ビルディング 組織に必要な役割を明確にし、規模に応じた組織の体制作りができるか。
9 労務管理 残業、休日出勤などの管理を徹底し、健康面を第一に考えた判断ができるか。
10 経営理念の周知・徹底 経営者の目指す方向を正確にメンバーに伝え、同じ方向に導けるか。
11 予算管理 収支・収益を意識し、より少ない投資で最大限の利益をあげられるか。
12 業務成果の適切な評価 メンバーの成果に対して、主観的ではなく客観的に評価ができるか。
13 適材適所への配置 メンバーのスキルや強み・弱みを把握し、育成の観点も含めてリソース配置を行えるか。
14 戦略立案 中長期の戦略や方針を正しく策定できるか。

マネージャになると、大手の企業ではマネジメント研修などもありますが、ベンチャー企業など、教育制度の整ってない企業では、いきなりマネージャーをさせられことも少なくありません。

マネージャーは、プロジェクトの進捗管理をしたり、リソースの管理をしたりすることに、フォーカスされがちですが、もっとたくさんのことを求められます。

組織の成果を出すためには、上記のようなスキルを身につけて、業務を遂行していかなければなりませんので、マネージャーになったら自身の役割と必要なスキルを確認しましょう。

プロジェクトマネジメントで重要なこと

ここまでは、マネジメントという少し広い範囲でのお話をしましたが、もう少しスコープを絞って、プロジェクトマネジメントについても見ていきましょう。

私は、プロジェクトマネジメント目的は、チームパフォーマンスの最大化と考えています。 限られたリソースの中でどれだけパフォーマンスを最大化できるかが、プロマネに求められることだと思います。

プロジェクトマネジメントで重要な三要素
  • タスクを適切に振り分けること
  • トラブルを未然に防ぐこと
  • 仕組み化を徹底し、無駄な時間を減らすこと

簡単にいうとムリ・ムダ・ムラの徹底排除です。

タスクを適切に振り分けること

タスクを漏れなく洗い出し、期日やメンバーの育成などを踏まえて、適切にタスクを振り分けていかなければなりません。経験値の高い人にばかり頼っていては、中長期的に見たときに、チームとしてのパフォーマンスは最大化されません。

トラブルを未然に防ぐこと

トラブルは、どんなにケアしていても起こってしまいますが、トラブルが少なければ少ないほど、計画はスムーズに進みます。ありとあらゆるリスクを想定して、未然に防ぐ努力をしましょう。

仕組み化を徹底し、無駄な時間を減らすこと

同じような作業を、他の人が繰り返していることは、色々な現場でよく見かけます。テンプレート化したり、プログラムで自動化したりすることで、大幅に作業時間を減らすことができます。少しでも、無駄を削減できるように、常に仕組み化できないか考えることが重要です。

また、プロジェクトマネジメントをする上での心構えとして下記の点に注意しています。

  • 自分ができるから、他人もできるはずという考えを捨てること
  • 自分でやったほうが早いと思っても、自身の手を出さないこと
  • チームが持っている力以上のことを求めないこと
  • チームの課題は、チーム全体で取り組むこと

マネージャがやるべきことは、方向性を示すことジャッジメントであり、自分自身で作業をすべきではありません。

一方的に指示を出すのではなく、メンバーの思考停止が起きないように、チームメンバーで決めたことをやらせましょう。

ただし、メンバーの進めようとしてる手段に対して、リスクがある場合には、リスク観点について質問をすることで、リスクの存在と対策をメンバー自身で考えられるようにリードすることも忘れてはいけません。

現在のチーム力を把握し、それをベースにプロジェクトの計画を立て、中長期的にチームの戦力を強化していくことが重要です。

目先の案件に捉われていると、いつまでたっても開発スピードが上がらなかったり、やりたいことができないなど、負のループに陥ってしまいます。

プロジェクトマネジメントにおける仕組み化

プロジェクトマネジメントの手法は色々ありますが、一番重要なのは、タスクの可視化課題の可視化だと思います。

やらなければならないことが全て洗い出せていること、適切な優先順位で作業を行なっていることをマネージャーはチェックしなければなりません。 また、課題を特定しない段階で、手段を考えても課題は解決しませんが、本質的な課題が見つかれば、改善策を考えるのはそんなに難しくありません。

2つの可視化をする上で、私が必ず行なっている仕組みがあります。

デイリーミーティング
  • 昨日やったこと、今日やることをチケット管理ツールをみながらメンバーに共有する
  • 全てのタスクはチケット化する
  • タスクは、担当者、工数、期限を設定する
  • タスクは、1日単位など細かいものに分解する
  • タスクは、完了定義を明確にする
  • 正しい優先順位でタスクに着手しているか確認する
  • 今抱えている課題を共有する
週次KPTミーティング
  • 毎週KPTを実施する
  • プロジェクトメンバーの参加を必須とする
  • 課題(Problem)についてメンバー全員で議論する
  • 本質的な課題にたどり着くまでブレークダウンする
  • Tryはメンバー全員が合意して決定する
  • Tryは1週間でできることを定義する
  • 残った課題は、積み上げないで捨てる

KPTについてわかりやすい記事がありましたので共有します。

この二つのミーティングをやり続けるだけで、プロジェクトは割とうまく回ります。

ただ、意外とこれだけのことをずっと続けるのが難しいのです。

タスクの期限を入れなかったり、完了定義が曖昧だったりというのはよくあります。

期限が不明確でいつまで経っても終わらなかったり、完了の定義が曖昧で、終わったと思っても、実際には終わってなかったりするので、タスクの進捗確認はさらっと終わりがちです。

管理者はタスクが確実に終わるまで、状況を正確に把握し、きっちり管理することが求められます。

KPTでも毎週全員が参加しなかったり、課題をうまくブレークダウンできずに、本質的な課題に辿り着けず、適切な解決手段を打ち出せなかったりということもよくあります。

ファシリテーションをしながら、課題洗い出し方、解決までの思考などの教育も合わせて行っていくのがポイントです。

個の力をチームの力に変えるべく、こういった仕組みを活用しながら、仕組みがうまく回っていることを日々チェックしてマネジメントしてます。

まとめ

マネージャーは、偉い人のポジションという位置付けでは決してありません。

一方で、業務知識に加えて、コミュニケーション能力も求められる難易度の高い仕事ではあります。

性格の合わない人、経歴もバックグラウンドも違う人、カルチャーの違う人など様々な人とお付き合いしていかなければなりません。

マネージャーという役割をしっかり理解し、マネージャに求められるスキルを身につけ、メンバーのパフォーマンスを最大化させるために尽力するのが、プロフェッショナルなマネジメントだと思います。

これからのフォトシンスは、大幅に人を増員していきますが、それに耐えうる組織づくりもしっかりしていきます。 スケールしていく組織の中で、強いチームでの働き方を体感したい方は、是非エントリーしてください。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

目に見えない信号やノイズについて

この記事は Akerun Advent Calendar 2020 - Qiita の24日目の記事です。

初めまして、 hiro_kawa です。2020年3月からPhotosynthの開発部で電気設計を担当しています。今年は久々に一担当としてガッツリ電気設計を行うことが出来て、忙しいながらも楽しい一年だったな~と思います。ホントあっという間でした。

さて、私からは電気設計関連のお話をさせて頂きます。 電気設計で取り扱いが難しく注意が必要なのは、目に見えない信号やノイズです。 電波や磁界、静電気など、種類は様々。静電気の場合はパチンと光って見える事もありますが、その後の電流の流れまでは見えません。 Photosynthに入社してからも、いくつか新しい発見や経験ができたので、その事例をご紹介します。

波形測定の際に

NFCカードリーダーの電源波形をオシロで確認していた時に、大きなリップルノイズが確認されることがありました。ICが正常に動作しない、場合によっては壊れてもおかしくないレベルです。プローブをあたる位置でレベルがかなり変動しており、NFCのアンテナに近い位置だと特に大きくなります。

そこで、その波形の時間軸を拡大してみると13.56MHzの信号を発見、NFCの周波数です。

f:id:photosynth-inc:20201224100704p:plainf:id:photosynth-inc:20201224100713p:plain
オシロスコープよりキャプチャしたNFC波形

その時は手軽に信号をモニターするためにパッシブプローブを使い、写真のようなリードクリップでGND接続をしていました。

f:id:photosynth-inc:20201224100701j:plain:w300
リードクリップ

実はこのGND線のループが悪さをしており、NFCのループアンテナとリード線とのかぶり加減でノイズレベルが変動している事がわかりました。パッシブプローブなのでインピーダンスが高く、プローブ自体がアンテナとなってNFCの電波をひろってしまっていた、という訳です。

画にしてみるとこんな感じでしょうか。

f:id:photosynth-inc:20201224100708p:plain:w300
プローブが作るループを、アンテナの磁界が通る

プローブ線のループが大きいほど、その中を通る磁束が増えるため、NFCの信号を拾いやすいという原理です。

そこで、ループを小さくするため、プローブ付属のバネ接点にGNDを変更すると、ノイズレベルはぐっと低減して、安定した信号を確認することができました。

f:id:photosynth-inc:20201224100710j:plain:h250f:id:photosynth-inc:20201224100717p:plain:h250
バネクリップと改善後の波形

これなら問題ないレベルです(これでも若干ノイズを拾っていますが)。 更にアクティブプローブにすればより正確な波形が確認できるでしょう。

ちょっとした波形確認の時でも気をつけないといけないな、と思いました。

静電試験の際に

カードリーダーの静電試験を行っていた時のこと。

※静電試験の概要については以下サイトなどご参照下さい。

カードリーダーを試験卓と水平に横置きし静電気を印加すると読み取りが出来なくなる症状が発生しました。

一方で実際に設置される環境通り、縦置きでの静電気印加では問題ありません。 しかも横置きでもカード読み取り部を上にし下側から静電気を印加しても問題ありません。 カード読み取り部を下にして上から電極に静電気を印加した時にのみ問題が発生します。

ここでのポイントは、試験サイトのテーブルには鉄板が敷かれていることです。図にしてみるとこんな感じです。

f:id:photosynth-inc:20201224100658p:plain
静電試験の模式図

NFCのループアンテナを挟むような形で静電気を印加した場合に問題が発生しています。 そこでアンテナが静電気の誘導ノイズをひろっているのではないか?と想定しました。 NFCのデバイスとアンテナとの接続部分を分断し、同条件で症状確認してみると、問題は発生しなくなりました。

原因が特定出来たので、そこを対策しようとなる訳です。

アンテナの性能が良い方が、よりカードを読み取りやすい訳ですが、その分ノイズ影響も受けやすくなります。 アンテナ性能を保ちながら、ノイズ影響は受けにくくする工夫が必要で、そこが設計の面白いところですね。

終わりに

上の2例は電気設計者ならわかりやすい内容だと思います。 他の事例では、動作中の基板の写真を撮ろうとフラッシュをたくと誤動作が発生、ということもありました。この原因はどのように推察されますか? これも調べると興味深いことがいくつかわかったのですが、そのお話はまたの機会に。。。

仕事の上では問題は起きないに越したことはないですが、私なんかは問題が起きると少しワクワクします。 というのも、その問題を解決することで新しい発見や勉強になることが多々ありエンジニアとして成長できるからです。

Photosynthでは、そんな経験が出来ると思いますので、ご興味持たれた方がいらっしゃいましたら、是非、ご連絡下さい。 ひょっとすると来年この記事を見た方と一緒にお仕事しているかも知れませんね。 そんな事になったら嬉しいです。では、皆さま、良いお年を!!


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

人に優しい識別子を使おう

この記事は Akerun Advent Calendar 2020 - Qiita の22日目の記事です。

こんにちは。@ps-tsh です。フォトシンスでは API Server などバックエンドシステムの開発を担当しています。先日、設計レビュー中に「識別子として UUID を使うのは妥当か」という話でちょっと盛り上がったので、きょうはそのあたりの話をしたいと思います。

連番IDのデメリット

「データの識別子にはどのような値を使うべきか」というテーマに対し、まずはもっとも一般的な連番IDの話から入りましょう。

たとえば Restful な API であれば、id=123 のユーザがいたとして GET /users/123 のようにアクセスします。実装レベルでは id として RDBMS が生成した連番を使うことが多いので、フレームワークのサポートも手厚く開発するのも楽です。しかし、連番のIDは数値を増減させるだけで他のデータにアクセスすることができるため、いわゆる総当たり攻撃を受けやすいというデメリットがあります。また、商用サービスであれば連番の最大値、すなわちデータ量からおおよその事業規模を推測されてしまうリスクもあるといえるでしょう。

ならばランダムIDだ

そこで、上記の問題を解決するためのアイディアとして「IDをランダムに発番する」という方法が出てきます。ID の本来の役割は識別子(identifier)なので、他と重複しないランダムな文字列を生成して使えばいいんじゃないかと考えるわけです。最近のプログラミング言語であれば、なんらかのランダムな値を生成する方法が提供されていると思います。たとえば Ruby では SecureRandom モジュールのメソッドを使うことで簡単にランダムな文字列を生成することができます。

SecureRandom モジュールの利用例

irb(main):001:0> require 'securerandom'
=> true
irb(main):002:0> SecureRandom.uuid # ランダムなUUIDを生成する
=> "5308b22c-2692-4b99-a404-316eaaf3088a"
irb(main):003:0> SecureRandom.base64 # ランダムなBase64文字列を生成する
=> "e/5gLBPoaxntIDeRwZlWPA=="
irb(main):004:0> SecureRandom.random_number(100000000) # 最大100000000のランダムな数値を生成する
=> 35831984

連番のかわりにランダムな値を使用することで、連番IDで課題となっていた総当たり攻撃やデータ量の推測は難しくなります。

ランダムIDのデメリット

連番のIDをランダム値に置き換えることで、エンジニアリング視点では問題が解決されたように見えます。では、ランダムなIDはあらゆる点でパーフェクトな解決策なのでしょうか。もちろんそんなことはなく、ランダムなIDにもデメリットがあります。

デメリット1: 長い

ランダムなIDは登録時の衝突を回避する必要があるので、連番の数値と比べると長い文字列になってしまいます。コードで処理する分には変わりませんが、人間が目視して入力する必要がある環境では、長いID文字列はどうしても使い勝手が悪くなってしまいます。たとえばUUIDを目視一回で覚えて、ミスせずにすべて入力するのはなかなか難しいですよね。

ランダムなIDの例: UUID

irb(main):009:0> SecureRandom.uuid
=> "033f9297-4393-463d-b63d-d6e3b4b05339"

また、長いランダム値はAPI設計にも影響をおよぼします。APIエンドポイントにパラメータとしてIDを含める場合、数値型のidであれば

GET /organizations/123/members/456?option=789

のようになりますが、これがUUIDだと

GET /organizations/648df1f4-03a7-450a-9d0a-352e742a5d57/members/83f50878-3726-4cb2-ab25-6db4f994ccb1?option=fd1b42bb-226c-4242-a667-13b13523147d

のようにとても長い文字列になってしまいます。頻繁に起こるケースではありませんが、パスパラメータやクエリパラメータに長大なIDが多数並ぶとブラウザやWebサーバが許容するURLの上限を超えてしまうこともあるため、IDはできるだけ短くしておいたほうが良いといえるでしょう。

この他、ストレージ効率が悪いとかインデックスサイズが増えて検索パフォーマンスが落ちるといった話もありますが、これらはあくまで副次的な話で、計算機リソースが安い現在においては最初に考えることではないと思います。

デメリット2: 紛らわしい文字が入る

「ランダムIDの文字列長が長くなってしまうなら短くすればいいじゃないか」とうことで、今度は使用する文字を増やして桁数を減らすことを考えてみましょう。たとえば Base64で使用する範囲の文字(0-9A-Za-z, +, /)を用いたランダムIDを生成してみます。

ランダムなIDの例: Base64

irb(main):008:0> SecureRandom.base64(16)
=> "ClnkCQEMel0XeDLYjgI0bg=="

同じ16byteのデータですが、Base64で表現することで文字列としてはUUIDと比べて(36→24と)短くなりました。しかし、文字の種類が増えたことで今度はIDに紛らわしい文字が含まれてしまいます。これも人間が手入力する上で困難なポイントになりえます。紛らわしい文字はITリテラシーの高くない人ほど間違えやすく、そうでなくてもフォントによっては非常に見分けがつきにくいため、クーポンコードや問い合わせ番号のようなものに採用すべきではありません。以下に、数字と紛らわしい英字の例を挙げます。

数字 紛らわしい英字の例
1 I, i, l
2 Z, z
5 S, s
6 b
9 q, g
8 B
0 O, o

また、英字には大文字・小文字で同じ形のものが多いため、文字種を増やしたいからといって混在させることも避けた方がよいでしょう。

デメリット3: 並べ替えられない

これは必ずしもランダムIDのデメリットとして挙げるべきものではないのですが、連番IDに期待されている性質として「並べ替えができる」というものがあります。たとえば「注文履歴を注文番号で並べ替えたら時系列順に並んで欲しい」といった要件がある場合、ランダムIDを採用してしまうと目的を果たすことができなくなります。もちろんこのケースは「注文日時で並べ替える」という方法で解決すべきですが、「IDに大小関係(並べ替え可能性)が期待されていることは多い」と心得ておいて損はないと思います。

人にやさしいIDをつくろう

ここまで、ランダム値ベースのIDについてデメリットをいくつか挙げてみました。ここからは、ランダム値の性質を保持しつつ、かつ人にもやさしいIDを設計するためのポイントについて紹介したいと思います。

ポイント1: 名前空間のサイズを考える

最初に考えるべきことは名前空間のサイズ想定です。IDを短くするためには必要以上に広い名前空間を使わないことがポイントになります。本記事の前半ではランダムIDの例としてUUIDを紹介しましたが、IDとして本当にUUIDが必要なケースははたして多いのでしょうか。UUIDの名前空間は128bitもあります。対象データの件数が数十万〜数千万程度であれば、32bit(正の整数で約21億)もあれば十分に収まります。多少上振れしても64bitあれば十分であることがほとんどです。

ポイント2: 表記上の桁数を減らす

UUID→Base64のところでも少し触れましたが、ID文字列の桁数を減らすためには、使用する文字数を増やす方法が効果的です。以下に示すように、同じ数値を表す場合でも2進数より8進数、10進数、16進数の方が桁が短くなりますよね。数字と英字の組み合わせであれば36進数まで表現できます。Rubyの場合、Numeric#to_s(n)を使えば36進数までの数を文字列に変換してくれます。

同じ数値(12345678)を2, 8, 10, 16, 36進数で出力したもの

irb(main):010:0> 12345678.to_s(2)
=> "101111000110000101001110"
irb(main):011:0> 12345678.to_s(8)
=> "57060516"
irb(main):012:0> 12345678.to_s(10)
=> "12345678"
irb(main):013:0> 12345678.to_s(16)
=> "bc614e"
irb(main):014:0> 12345678.to_s(36)
=> "7clzi"

ポイント3: 文字を増やした上で紛らわしい文字は除外する

上記で紹介した36進数を使うと、紛らわしい文字が含まれてしまいますよね。そこで、以下のように紛らわしい文字を除外したエンコーダを作ってn進数を出力することを考えます。

# 所定の文字セットを使って数値をn進数表記に変換する
def encode(n)
  # 例として数字の0, 1と英大文字のI, O を除外したものを使う。英小文字は使わない
  # 並べ替え可能性を保つため、文字の順番はASCIIコード順を保っておく
  chars = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'.split('')
  result = []
  while n > 0 do
    div, mod = n.divmod(chars.length)
    result.unshift(chars[mod])
    n = div
  end
  # 連結して出力
  result.join
end

出力例

irb(main):067:0> encode(12345678)
=> "DSSCG"

ポイント4: 並べ替え可能にする

前述の通りこれはIDとしての必須要件ではありませんが、ランダムIDを並べ替え可能にするためには、先頭に並べ替え可能なパートを付与する必要があります(厳密にはこの時点でもはやランダムではないのですが、そこは置いておきましょう)。以下はタイムスタンプを含めたランダムIDの例です。

# 前半にタイムスタンプのパートが含まれているため並べ替え可能になっている。
# 実際はID文字列を連結する場合「各パートを固定長にする」「タイムスタンプの精度が秒単位でよいか確認する」など
# もうひと手間かける必要がある
irb(main):097:0> encode(Time.now.to_i) + encode(SecureRandom.random_number(100000000))
=> "3HY23UC34C4EF"
irb(main):098:0> encode(Time.now.to_i) + encode(SecureRandom.random_number(100000000))
=> "3HY23UE3AQUGQ"
irb(main):099:0> encode(Time.now.to_i) + encode(SecureRandom.random_number(100000000))
=> "3HY23UF3QBUMH"

ポイント5: 桁区切りを入れる

最後になりますが、人間からみた可読性を上げるテクニックとして、適切な文字数で桁区切りを入れる方法も有効です。たとえば3HY23UF3QBUMH であれば 3HY2-3UF3Q-BUMH のように分割することで入力値の確認がしやすくなると思います。

おわりに

今回は「人に優しい識別子を使おう」ということで、識別子としてランダムIDを採用する場合に考えておきたいことをいくつか紹介しました。id設計においてちょっとでも役立ったと思っていただけたら嬉しいです。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

専用アプリはもう要らない?LINE BotからAkerunを操作する(その2)

この記事は Akerun Advent Calendar 2020 - Qiita の13日目の記事です。

WebエンジニアのBunです。主にiOSアプリの開発を担当しています。

ちょうど1年前、「LINE BotからAkerunを操作する(その1)」を書かせていただきました。 LINE Botのグループ機能を使ったBotLINE BOT AWARDS グループトーク部門賞)を作成した経験もあるので、今回はその続きの内容(LINEグループでAkerunを操作する)になります。

akerun.hateblo.jp

17日目の記事(LINE Botから施錠解錠する)も書いたので、こちらも読んでみてください。

akerun.hateblo.jp

事前準備

LINE Botの設定

その1を参考にLINE Botを作成し、Botと友達になります。

Akerun合鍵発行

対象となるAkerunの合鍵を発行します。弊社のAkerun Developersをご参照ください。

developers.akerun.com

LINE Botのリッチメニューからインタラクティブ形式で複数の組織、Akerun、合鍵の設定も可能ですが、長くなってしまうので、別の機会で書きたいと思います。

データ保存用DB

グループ、ユーザー情報を保存する必要があるので、Bot側でデータベースを使う必要があります。Firebase、AWSなど各自の環境に合わせてDBを構築すれば良いと思います。 今回は、軽量ドキュメントデータベースのTinyDBを使いますが、DB周りの詳細処理は割愛します。

LINE Loginチャンネル作成

LINE Developersにログインし(アカウント、Providerが無い場合作成する)、LINE Loginチャンネルを作成します。

  • チャンネル作成

チャンネル作成

  • Channel IDとChannel secret

環境変数に設定する必要があるので、Channel IDとChannel secretをメモします。

Channel ID

Channel secret

  • Callback URLを設定

Botに合わせてドメインとPathを設定します。

Callback URL

LINEのグループ(或いはルーム)を作成

LINEで任意のグループを作成し、Akerunを使うユーザーを招待します。Botと連携してから招待しても問題ありません。

Botをグループに招待する

通常ユーザーの招待と同じ方法で既に友達になったBotをグループに招待します。

  • JoinEvent

Botをグループに追加した場合、JoinEventが発生します。eventからグループID(或いはルームID)を取得し、DBにグループを追加します。

  • LINE Login用Link作成

グループ内のユーザーがBotと友達になるようにLINE LoginのLinkを作成し、グループにメッセージを通知します。 LinkにLINE LoginチャンネルのChannel IDとCallback URLを指定します。 LINE Loginボタン(画像デザイン)について、以下をご参照ください。

developers.line.biz

# BotをGourpに追加した時、/callbackが呼ばれて、その後JointEventが呼ばれる
# 同じグループに複数Bot追加できない。2個目からは招待待ちになる。
@handler.add(JoinEvent)
def handle_join_message(event):
  print(event)
  if event.source.type == 'group':
    gid = event.source.group_id
  elif event.source.type == 'room':
    gid = event.source.room_id
  
  # 既に存在する場合追加されない
  db.add_group(gid, event.source.type)

  msgs=[]

  text = u'下のログインボタンで鍵を申請してください。'
  msgs.append(TextSendMessage(text = text))
  
  link_uri = 'https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={}&redirect_uri={}&bot_prompt=normal&scope=profile&state={}'.format(line_login_channel_id, urllib.parse.quote(auth_url), gid)

  print(link_uri)
  msgs.append(ImagemapSendMessage(
    base_url=base_url + '/images/LINELogin',
    alt_text='this is an imagemap',
    base_size=BaseSize(height=302, width=1040),
    actions=[
      URIImagemapAction(
        link_uri=link_uri,
        area=ImagemapArea(x=0, y=0, width=1040, height=302)
      ),
    ]
  ))

  line_bot_api.reply_message(event.reply_token, msgs)

LINE Loginでログインし、Botと友達になる

グループ内のユーザーはLINE Loginボタンを押してBotと友達になります。 Botと友達になった場合、LINE Loginチャンネルで設定されたCallback URLにリダイレクトされます。ここでログインユーザー情報を取得し、DB上のグループにユーザーを追加します。LINEグループにもメッセージを通知します。

# グループに入るユーザーにログイン用ボタンLink(clientID/redirectURL)を用意し、ログインしてもらう
# ユーザーがログインボタンを押して、Botと友たちになる時、/authが呼ばれる
# ログイン成功後、/authにcodeが発行される(10min)ので、アクセストークンを取得する
@app.route("/auth", methods=['GET'])
def auth_callback():  
  print(request)
  code = request.args.get('code')
  gid = request.args.get('state')

  # 認証エラー
  if(code is None):
    #print 'Auth error: '
    error = request.args.get('error')
    errorState = request.args.get('state')
    errorMessage = request.args.get('error_description')
    print(error)
    print(errorState)
    print(errorMessage)
    return 'Auth Error'

  # token取得
  token = line_login_get_access_token(code)
  profile = line_login_get_user_profiles(token)
  uid = profile.user_id
  name = profile.display_name

  db.add_user(uid, name)

  msgs = []
  msgs.append(TextSendMessage(text = u'{}さんがグループに入りました'.format(name)))

  line_bot_api.push_message(gid, msgs)

  # ログイン成功時の画面を用意
  return render_template("index.html", title="Akerun Login", message=u"ログイン成功", friend_url=line_friend_url, qr_url=line_qr_url)

Token取得処理は下記通りです。LINE LoginチャンネルのChannel IDとChannel Secretが必要です。

def line_login_get_access_token(code):
  headers = {'Content-Type': 'application/x-www-form-urlencoded'}
  payload = {
    'grant_type': 'authorization_code',
    'client_id': line_login_channel_id,
    'client_secret': line_login_channel_secret,
    'code': code,
    'redirect_uri': auth_url
    }

  obj_request = Request(
          "POST",
          'https://api.line.me/oauth2/v2.1/token',
          headers = headers,
          data = payload
  )

  obj_session = Session()
  obj_prepped = obj_session.prepare_request(obj_request)
  obj_response = obj_session.send(obj_prepped,
                  verify=True,
                  timeout=60
                  )
  print('status_code:' + str(obj_response.status_code))
  print('obj_response.text:' + obj_response.text)
  response_dict = json.loads(obj_response.text)
  print(response_dict)

  #{
  #  "access_token": "xxxx",
  #  "expires_in": 2592000,
  #  "id_token": "xxx",
  #  "refresh_token": "xxx",
  #  "scope": "profile",
  #  "token_type": "Bearer"
  #}

  return response_dict['access_token']

以下でログインユーザーの情報を取得できます。

def line_login_get_user_profiles(token):
  headers = {'Authorization': 'Bearer ' + token}

  obj_request = Request(
          "GET",
          'https://api.line.me/v2/profile',
          headers = headers,
  )
  obj_session = Session()
  obj_prepped = obj_session.prepare_request(obj_request)
  obj_response = obj_session.send(obj_prepped,
                  verify=True,
                  timeout=60
                  )
  print('status_code:' + str(obj_response.status_code))
  # {
  # 'userId': 'xxxx', 
  # 'displayName': 'xxxx', 
  # 'pictureUrl': 'https://profile.line-scdn.net/xxxx'
  # }
  print('obj_response.text:' + obj_response.text)
  response_dict = json.loads(obj_response.text)
  print(response_dict)

  return response_dict

LINEグループでも誰がBotと友達になったか分かります。

ログイン

施錠解錠する

Botと友達になったユーザーは事前に設定されたAkerunに対して施錠解錠操作ができます。DBに施錠解錠時のログを追加すれば、履歴を管理することも可能です。

lock unlock メニュー

施錠解錠処理についてはその1もご参照ください。

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
  reply_msgs = []
  push_msgs = []
  uid = get_id(event.source)

  #profile.display_name #-> 表示名
  #profile.user_id #-> ユーザーID
  #profile.image_url #-> 画像のURL
  #profile.status_message #-> ステータスメッセージ
  profile = line_bot_api.get_profile(event.source.user_id)
  name = profile.display_name
  if event.message.type == 'text':
    if event.message.text[0] == cmd_prefix:
      cmd = event.message.text
      if cmd == cmd_prefix + 'lock':
        job_id = akerun_bot_lock('akerun api token', '組織ID', 'AkerunID')
        reply_msgs.append(TextSendMessage(text=u'施錠'))
        push_msgs.append(TextSendMessage(text = u'{}さんが解錠しました'.format(name)))
      elif cmd == cmd_prefix + 'unlock':
        job_id = akerun_bot_unlock('akerun api token', '組織ID', 'AkerunID')
        reply_msgs.append(TextSendMessage(text=u'解錠'))
        push_msgs.append(TextSendMessage(text = u'{}さんが施錠しました'.format(name)))

  # todo:pooling -> push
  if len(reply_msgs) > 0:
    line_bot_api.reply_message(
      event.reply_token,
      reply_msgs)
  if len(push_msgs) > 0:
    line_bot_api.push_message(test_group_id, push_msgs)

施錠解錠時、LINEグループにもメッセージを通知できるので、誰が操作したが分かります。

施錠解錠

まとめ

  • LINEグループ機能を活用すれば、簡単に複数のユーザーに合鍵を共有し、管理できるので、楽ですね。
  • 追加対応すれば、複数のAkerun、合鍵の設定と管理も全てLINE上で完結できるので、更に便利になります。
  • その1で既にAkerunを施錠解錠するBotを作成したので、グループでの施錠解錠は割と簡単にできました。ただ、DB周りもしっかり作る場合、そこそこ手間がかかりそうです。
  • LINE Bot、グループの制限かもしれませんが、ユーザーは同じBotが所属している複数のグループを同時に使うことができないので、複数のBotを作るなど工夫する必要があります。

そのうちLIFFも試しみたいと思います。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

Hello legacy codes!

Intro

When being an engineer longer than one year, having experiences to handle code written by other people is a common issue. With any luck, when handover, well-documented specifications and senior members would help you get over any challenge in the situation.

I'm Tetsu, the Akerun Android team member. Today, let's talk about the unlucky situation and how I faced (and fixed!) it at Photosynth.

Overview

  • Why need to face legacy code?
  • How to handle it?
  • Finally fixed!

Why need to face legacy code?

"Hey, the code is so dirty, let's do refactoring!" or "No, it's impossible to refactor it, making a new one is easier." These kinds of conversations are common for engineers, but why do we need to tackle it? just for code quality?

My answer, the legacy code has provided values to users already-- and maybe from now on. But the legacy code brings challenges to engineers when we want to deliver more values quickly. So we need to consider how to face it. This answer sounds common? However we must tackle it anyway. ;p

(I also recommend a video talking about the importance of having "why" questions in our job. I hope this would worth to take your time) www.ted.com

How to handle it?

Now we confirmed the reason why we need to tackle a legacy code. The next step---we have to sort out the current situation. I would like to show an example at Akerun Android:

  • We have a project to improve the Akerun app's UI/UX.
  • Having not so much time to do code refactoring.
  • No architecture policy and full of smelly code.

Hmm... I want to show an ideal case first, and escape from the real world... Here is an architecture diagram in Akerun Android we want to have. f:id:photosynth-inc:20201222102745p:plain

This looks perfect!

Usually, we choose the domain layer (user story, business logic) or frequently feature (like account register/login) first if no exception in refactoring. But in our case, UI/UX improvement is the most important task we want to deliver to users, and refactoring the domain layer is not the better way with limited time.

Now, back to the real world, we consider a “plan B”, which does not aim to refactor all things at once, but focus on refactoring the UI/UX improvement tasks only.

Finally fixed!

Here is an overview of the “plan B” (which we have actually done): f:id:photosynth-inc:20201222102812p:plain We focused on the UI/UX tasks and refactored it to multiple feature-modules and a UI-component-module without changing almost all domain & data layers. It was unsophisticated yet and was not ideal. But it became better, huh?

Outline the actions:

  • Draw a blueprint that your team want to realize
  • Check the blueprint if it fits your objective
  • Keep enough buffer for unexpected incidents

Hope this article will provide you with any ideas when facing legacy code.

Thanks for your time!

By the way, we always welcome our new colleagues!~ hrmos.co

Please join us and let's innovate peoples’ lifestyle together.

P.S. For the limited time issue, another important part is to secure additional time to improve the team's overall working efficiency.

As examples, we did in the past year:

  • Use the CircleCI tool to run unit tests.
  • Add DeployGate Gradle task to distribute binary automatically.
  • Use Danger + Klint to improve code quality without human review.
  • Make a reusable UI component to keep UI/UX’s same behavior.

This makes our work quickly/easier and we can focus on more challenging tasks.

UICollectionView vs UITableView

この記事は Akerun Advent Calendar 2020 - Qiita の21日目の記事です。

Webエンジニアのbeginer_rider - Qiitaと申します。 主にiOSアプリ開発を担当としています。

はじめ

iOS開発者の皆さんにはよくある話と思いますが、リスト画面を作るときに、UICollectionViewとUITableViewのいずれを検討することがあったと思います。 リストの要素数、セルの色や表示名を動的に変更する中でどれがよいか、悩んだ方も多いのではないでしょうか。 そんな方々に少しでも助けになればと思い、UICollectionViewとUITabeViewのそれぞれのメリデメをご紹介させていただきます。 プチ読み物的なものとしてご一読ください。 ※あくまで個人的観点でのメリデメとなりますので、気になった方がいらっしゃればご指摘いただけますと幸いです。

UIColletionViewのメリデメ

メリット

UICollectionViewLayoutを利用することで、セルのレイアウトを変更することが可能

prepareメソッドをoverrideすることで、セルのレイアウトを変更することができます。 UICollectionViewLayoutのサブクラス、UICollectionViewFlowLayoutにはitemSizesectionInsetなど、セルのUIを変更するのに役立つ様々なプロパティを備えています。 加えて、セルだけでなくHeaderFooterのレイアウトを変更することができます。

また、他にもUICollectionViewFlowLayoutだけでなく、UICollectionViewLayoutAttributesUICollectionViewLayoutInvalidationContextなどのレイアウトを調整するためのクラスが用意されています。 そのため、柔軟にレイアウトを組むことができます。(1行ごとに表示する要素を変えるなど)

アニメーションのあるセルの挿入や削除を行うことができる

performBatchUpdateクロージャの中でreloaddeleteの処理を行うことで、一律でアニメーションのアップデートを行うことができます。 例えば、5つあるリストの1つ目を削除し、1つ目に新規要素を挿入するなら、deleteinsertの処理を行うことで、実現することができます。

デメリット

セルの挿入や削除の制御に手間がかかる

performBatchUpdateクロージャの中で、制御対象とするセルに対して、IndexPathを設定する必要があります。performBatchUpdateでは処理の順番に関わらず、削除→挿入の順に処理が実行されます。この時、削除や挿入する対象のセルがなければエラーが生じてしまうため、設定するIndexPathを正しく選択しておく必要があるので、削除や挿入前後のセルの順番を把握しておく必要があります。 画面を回転させた場合、セルの再生成を行う場合においてもIndexPathを正しく洗濯しておく必要があるため、あらゆる場合で考慮しておく必要がありそうです。

UITableViewのメリデメ

メリット

シンプルなリスト画面を作成しやすい

UICollectionViewとは違い、リスト画面のみを想定して用意されているため、簡単なリスト画面を作成する際には利用しやすいです。 リストに表示したい要素にSwitchButtonToggleBarなどを複数表示するためにそれぞれのセルを作成する必要はありますが、セルを表示する順番さえ決めておけば、あとはシステムがよしなに処理してくれるので楽です。

レイアウトの更新処理はlayoutIfNeededでなんとかなる

UITableViewのセルを更新完了の際にlayoutIfNeededを呼ぶだけで更新を完了させることができ、非常に楽です。ただ、他にreloadDatalayoutSubViewsなどのセルを更新するメソッドがありますが、こちらはUITableView全体を更新してしまい、処理の遅延に繋がることがあるので、利用するには向いてないです。

デメリット

セル数の更新操作が挿入と削除のみ

ネイティブライブラリから提供されているUITableViewのセルに対する操作は挿入と削除のみとなっています。 そのため、セルの移動に関してはUITableViewDelegateとUITableViewDataSourceで定義されている下記メソッドを実装する必要があり、手間がかかります。

・UITableViewDelegate

func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle

・UITableViewDataSource

func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath)

最後に

いかがでしたでしょうか。検討する際の一助となれば幸いです。 UICollectionViewとUITableViewにはそれぞれの特徴があるので、うまく使い分けてみてください! 最後までご覧くださりありがとうございました。


株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 hrmos.co

Akerun Proの購入はこちらから akerun.com

Webエンジニアがラズパイでセンサーデータ取得してBlinkでグラフ化するまで

この記事は Akerun Advent Calendar 2020 - Qiita の19日目の記事です。

Webエンジニアのt-ikeと申します。

入社2年目、Akerun Connectの開発などをメインで行っていました。

そのあたりの話をしようかなと思ったのですが、せっかくIoTの会社にいるのだから、それっぽいことに挑戦してみよう! ということで、比較的簡単にできそうなことを考えてみました。

はじまり

唐突ですが、最近鼻詰まりがひどいんです。

まぁ息するのができないというほどではないんですが、 唯一の趣味である飲酒において、香りを楽しみきれないというのは許せんということで、悶々とする日々をすごしていました。

原因はまぁちゃんと掃除してないとかだと思うんですが(←)、判断基準になるものがあったらいいなぁと思い、調べてみることに

調査

市販のものでも、空気質を計るようなスマートホームバイスもあったのですが、

  • リアルタイムな値しかみれず、過去の情報が参照できなさそう
  • 空気質の変化の検知を柔軟にできなさそう
  • せっかくだしもうちょいIoTっぽいことしようよ

などの理由で、ラズパイ&センサーでいけないか調査。

そもそも安価で、ハウスダストを計測できるものなんてあるんか、と調べると普通にありました。

www.seeedstudio.com

どうやらラズパイで使えるらしいということで、購入決定 (恥ずかしながら人生初ラズパイ

しかしながら、この値段でメモリ8GB、4Kも対応とかすごい時代になりましたね...

データの可視化

要件として、過去のデータも参照できて、それをグラフ化したいみたいなのがあって、 知ってる範囲でやるならcloudwatchとかに雑になげこめばいいかなぁと思ったんですが、 取得したデータをスマホで簡単に見れたらいいなぁと思い、調べてみるとよさそうなものがありました。

blynk.io

簡単にまとめると

  • インターネット経由でIoTデバイスを操作できるスマホアプリ
  • Virtual Pinという機能を使うことでGPIOの入力操作や出力データ取得が可能
  • 5つまでデバイス登録無料
  • Virtual Pinで取得したデータはクラウド上で保持
  • グラフ表示も可能

ということで、ちょっと触ってみたいユーザには最適なものがあり感激

センサーデータを送る

ラズパイ動かすとか、センサー取り付けるとか、初心者な障害は多々あったけど、そこらへんは省いて、blynkにセンサーデータを遅れるようにしてみる 上記のダストセンサーのリンクでも紹介されていますが、濃度取得のソースコードをもとに実装してみる。

自分はインタプリタ言語しかまともに触れない悲しいエンジニアなので、pythonでかきました

(抜粋してるのでこのままではうごきません)

import os
import RPi.GPIO as GPIO
import time
import blynklib
from blynktimer import Timer
from dotenv import load_dotenv
import threading

load_dotenv()
BLYNK_AUTH_TOKEN = os.environ['BLYNK_AUTH_TOKEN']
# ダストセンサーの出力PIN
PIN = int(os.environ['PIN'])
VPIN_RATIO = 0
VPIN_CONCENT = 1
VPIN_UGM3 = 2

blynk = blynklib.Blynk(BLYNK_AUTH_TOKEN)
timer = Timer()

GPIO.setmode(GPIO.BCM)
GPIO.setup(PIN, GPIO.IN)
GPIO.setwarnings(False)


@timer.register(vpin_num=0, interval=INTERVAL, run_once=False)
def get_sensor_data(vpin_num):
    th = threading.Thread(target=measure, args=(PIN,))
    th.start()


def measure(PIN):
    t0 = time.time()
    t = 0
    while True:
        # パルス幅計測
        duration = pulseIn(PIN, 0)
        # 計測時間を超えたら濃度などを計算して送信
        if ((time.time() - t0) > MEASURING_TIME):
            ratio = t/MEASURING_TIME * 100
            concentration = 1.1 * pow(ratio,3) - 3.8 * pow(ratio,2) + 520 * ratio + 0.62
            blynk.virtual_write(VPIN_RATIO, ratio)
            blynk.virtual_write(VPIN_CONCENT, concentration)
            blynk.virtual_write(VPIN_UGM3, calc_ugm(concentration))
            break


if __name__ == "__main__":
    while True:
        blynk.run()
        timer.run()

blynk.virtual_writeをつかえば、指定した番号のvpinにデータが保存されていくみたいです。

Blynkを使う

ここまでくればあとはアプリでぽちぽちすれば終わり

説明がめんd...長くなるので、結果だけのせると、こんな感じでグラフ表示が簡単にできました。

f:id:photosynth-inc:20201217213452j:plain

でも本当にUIよいので、困るところそんなに無いと思います。

考察

センサーは別途の横においてるんですが、だいたい朝起きたときから1時間くらいかけて高くなっているようにみえる

以上

結果あんまりわからずw 空気清浄機かけてみるのとみないので変わるかとか試してみたけど、あまり変化せず。

とりあえず布団干したり、シーツ洗ったりする頻度を上げるようにしようと思いました。

感想

身の回りに関するデータを時系列で可視化するっていうのはあまりやったことがなかったので、やってみると結構楽しくできてよかったです。

他にも温度とか湿度とかとって、ログとして残しておくと、特に意味はないけどニヤニヤできそうな気もするのでやってみたいですね。

あとは市販のものでもできるけど、センサーデータトリガーで家電操作したりとかもいいですね

何よりこういったことに興味もって動くことができたのはこの会社に入っていろんなエンジニアの方々と触れ合えたおかげだなぁとしみじみ思います。

ということで

株式会社フォトシンスでは、一緒にプロダクトを成長させる様々なレイヤのエンジニアを募集しています。 https://recruit.jobcan.jp/photosynth/listrecruit.jobcan.jp