コネヒト開発者ブログ

コネヒト開発者ブログ

リモートワークについて考える勉強会(オンライン開催)でもらった質問にすべて答えます

こんにちは、エンジニアの@dachi_023です。03/11(水)にリモートワークを考えよう、というテーマでオンラインの勉強会を開催しました。発表・参加していただいた皆さん、ありがとうございました!発表資料や雰囲気などは下記の弊社広報ブログにまとまっていますのでこちらもぜひご覧ください。

www.wantedly.com

近い内に録画した内容に字幕等つけたものをアーカイブ配信する予定ですので途中からしか見れなかった方や気になった方はそちらをお待ちください(というのを気軽にできるのもオンライン配信のいいところだな、と思いました)。

チャットでもらった質問について

今回はZoomのウェビナー機能を使って発表&その場でQAをしたんですが、時間の関係ですべての質問にはお答えすることができなかったので本投稿ですべてお答えしていきます。

※ 発表時に回答した質問についても再掲(もしくはより詳細に回答)しています。

職種によってリモート率とか変わったりしますか?

「この部は全然リモートしてないね」とかはないです。webサービスの会社なので基本はリモートワークでも働けます。ただ、会社に用事がある場合等は各々の判断で通勤のピークタイムを避けつつ出社しています。

sneekで背景見られたくない方とかはどうしてるんでしょうか?

(Zoomにはバーチャル背景があるが、他のサービスにはない。どうするか?という話)

Snap Cameraのフィルターには、顔の加工だけではなく背景も加工してくれるものがあるのでそういうのを使うといいかもしれません。

f:id:dachi023:20200312212651p:plain
例えばこれはナゲットなんですが、背景付きなので部屋が映りません

リモートワークで不便はありますか

導入直後はツールの使い方に慣れていないため、通話中に一斉に喋ってしまったりとかしてワチャワチャしてしまうことが多かったです。あとは子供が仕事中に絡んできたりするので最初は大変でした。

リモートワーク推進メンバーは、どうやって選定されましたか?

立候補した人で構成されています。そのメンバーで使えそうなツール候補のリストを作り、検証しながらどれを正式採用しましょうかね?という話を進めていきました。

リモートワークでも勤務時間ってあるんですか?

コアタイムがあるのでその時間内は確実に働いているようにしています。それ以外は家庭の状況などに合わせて働いています。なので、勤務時間の管理はオフィスワークしていた頃と変わらずやっています(打刻はSlack上からやっています)。

リモートで使用するツールの導入判断で、意外と重要なポイントってなにかありましたか?

リモートワークに限らないのですが「安定しているか」は大事だと思いました。例えば、Slackが頻繁に落ちるサービスだったらコミュニケーションが全然取れなくなってしまうリスクがあるので選びません、といった感じです。あとはツールを増やしすぎないように気をつけました。もちろん必要なら入れるんですが、複数のツールを組み合わせないといけないとなると単純に面倒なので・・・。

リモートワークを導入するのに大変だったことはありますか?

どういう心持ちでリモートワークすればいいだろうね、とかどういうツール使うと仕事が捗りそうかな、とかそういうドキュメントを沢山作ったのでその辺は結構時間がかかったかもしれません。あとは、私が対応したわけではないのですが、セキュリティまわりの対応とかは大変そうだなあと思って見ていました。

各自、在宅での誘惑はどのように向き合うようにしていますか?

私の場合はテレビが絶対見ることのできない方向にデスクを置いてます。今は目の前に壁があって、壁を見るかディスプレイを見るか、くらいしか選択肢がありません。あとは環境音とかがなるべく聞こえないように&通話したい時にすぐ繋げられるようにワイヤレスイヤホンを付けっぱなしにしています。

Zoom慣れない方へのレクチャーで工夫したことはありますか?

ミーティングの始め方はどうやるか?というレクチャーは厚めにやりました。Zoomはミーティングの設置だけでも設定する項目が多く面倒なので、ZoomのSlack App使ってミーティング開始するとログイン以外は不要で簡単ですよ!とかGoogle Calendarの予定にZoomのURLを紐付けると探しやすいよ!とかそういう話をしました。簡単に始められそうだな!という感覚を持ってもらうのを大事にしました。

初めてリモートワークをする人や、社内の人の反応はどうですか?

そこまで不便はしてなさそうかな?と思っています。不便だなと思った点などは共有してもらってなるべく早めに改善するようにして、ずっと不便な状態にはならないように気をつけています。

Zoom のホスト権限の管理などはうまく回ってらっしゃるのでしょうか

現時点だと推進メンバー、マネージャー以上のメンバー、人事や営業など外部の方とミーティングを設定する必要がある人に付与しています。マネージャー以上のメンバーとしているのはマネージャーに付与することで各チームに1人以上はホストがいる状況が作れるからです。

リモートのタスク管理とかで、問題とかありますか?

Asana, Trello, GitHub Project, ZenHubなど、チームごとに使っているものは異なりますがオンライン上で管理しているので特に困ることはありませんでした。あとはデイリースクラムや朝会などをZoomで開催し、タスクの確認をしてチーム内の認識を揃えるようにしています。

リモートで仕事外のコミュニケーションはどう取っていますか??(リモートランチや飲み会とか)

飲み会はやったことないですがランチはやりました(ちょうどこの発表当日の昼休みでZoomで繋いでランチしました)。最初は億劫になりがちなんですが慣れてくると良いものです。

管理が行き届かない場合のトラブル例とかはありますか?

コネヒトではトラブルとかは発生していないんですが、例えばメンバーの管理という観点で見ると「サボらないんですか?」はよく聞かれます。個人の意見としては、そこを管理する必要はない(= サボった分あとで頑張らないといけないのはサボった人なので・・・)と思います。モノの管理の話だと、リモートワークのために自宅に会社の備品などを持ち帰る必要があったりするのでその辺の管理は各自お願いしますね、とかでしょうか。

さいごに

以上となります!予想以上に多くの質問をいただくことができました。また、皆さんのリモートワークへの関心の高さも伺えたような気がしています。今後も引き続き参考になりそうな情報を発信していきます💪

PR

先月書いたブログでもまとめていますのでこちらも良ければ読んでみてください。

tech.connehito.com

社内向けにまとめていたリモートワークの知見をすべて公開します

こんにちは、エンジニアの@dachi_023です。先週くらいから新型コロナウイルスの感染拡大に伴って在宅リモートが導入された、という内容のプレスやニュースをよく見かけるようになりました。コネヒトでも同様に在宅リモートの推奨、ラッシュ時の電車通勤は原則禁止、などのアナウンスがあり、これまでリモートワークをしていなかったメンバーもリモートワークを導入して自宅から働いています。

ですが、いきなり「皆さんそれではリモートワークしてください!はいどうぞ!」と言われても家からどのように働けば良いのか・・・となり、ただただ生産性が落ちてしまうだけです。もちろん健康第一で導入されている今回のリモートワークではありますが、出来ることなら効率をなるべく落とすことなく働きたいです。

コネヒト開発部では今年の頭からリモートワークの試験導入を行っており、エンジニアとデザイナーが週に2日ほどリモートワークしてどういったツールを使うと良いかとか、ビデオ会議やチャットでのコミュニケーションを改善して業務が円滑に進むようにするためにはどうしたら良いかなどの調査や検証をしていました。本記事ではその検証結果を公開し、今回の件で初めてリモートワークを導入した企業の方々の参考になれればと思っています。

リモートワーク導入にあたり理解しておきたいこと

在宅で働くことはオフィスに来て働くことの上位互換ではないです。通勤がなくなるのでそこに関しては最高なんですが、チャットはやり取りに少し時間がかかったり、ビデオ通話は多少とはいえ遅延があり最初のうちは会話しづらく、オフラインで対面で話すのに比べてコストがかかることもあります。などなど、それぞれで得意なこと、不得意なことがあります。そうした状況をリモートワークするメンバー、オフィスにいるメンバーがそれぞれ理解して、協力しながら進めていく必要があります。

リモートワークはノリだけで始めると辛い

色んな記事で散々書かれていることですが、リモートワークは「スキル」です。どうやったら物理的に離れているメンバー同士でうまいこと協力していけるか、を考えて実践し、改善していかなければいけません。個人で改善できることもありますし、会社としてリモートワークが快適に行えるような土台を作ることも必要になってきます。会社にいる時と同じやり方では上手くいかないことも多々発生するのでそういった点を解決していくのが導入後すぐに発生する仕事になると思います。

チェックリストを作成する

全社向けに自宅の環境やリモート時の意識についてのチェックリストを作成しました。これによって何があった方がいいのか、在宅で仕事をするためにやっておくべきことをある程度把握してもらいたい、という狙いがあります。

自宅環境の整備について

必須なもの

  • ✅ インターネット回線は契約してあるか
    • モバイルルーターなどでも可 (ただし光回線の方が速い・安定する)
    • 自身の業務を行うのにストレスのない回線速度が出ているか
  • ✅ 作業スペースは確保できているか
    • 近くにPC等を充電できるようコンセントがある
  • ✅ ビデオ会議用のイヤホンはあるか
    • 自分が使いやすいものであれば何でもOK
    • 移動する際とか面倒なのでワイヤレスがオススメ
  • ✅ カメラはあるか
    • カメラがついているノートPCであればそれを利用する
    • ついていないモデルの場合は購入することをおすすめします
  • ✅ 誘惑を断ち切れるか
    • テレビなどのコンテンツに意識を持っていかれていないか
  • ✅ 残業時間を気にする意識はあるか
    • いつでも働ける状態なので気をつける
    • 適切な業務量と時間を維持する
  • ✅ 家族、パートナーに在宅で働くことが理解されているか
    • 業務時間中に家事をお願いされる
    • 話しかけられる頻度が多い
    • 働かないと・・・でも家の事も・・・みたいになって辛い

あればなお良い

  • ✅ Wi-Fiが使えるか
  • ✅ 長時間座っていても身体が痛くなりにくいデスク、椅子などはあるか
  • ✅ 外付けディスプレイはあるか

オフィスにいるメンバー向け

リモートワークしているメンバーと一緒に働くためには以下に注意しましょう。

  • ✅ オフラインでしか得られない情報を無くす
    • 会社来い!って言ってるのと変わらなくなっちゃうので
    • 必ずオンラインのどこかで確認できるようにすること (Google Drive, 情報共有サービス, など)
  • ✅ 勤怠などについて監視しすぎない
    • 所詮カメラで映している部分しか動きは見れないし、互いに疲弊するのでやめる
    • プロセスよりも最終的なアウトプットを評価する
    • ただし、今働いているかどうかは知れたほうがいいので始業・休憩・終業くらいはルール化する

使用しているツール

リモートワーク時に使用しているツールは下記の通りです。

2つ書いてあるところはまだ検証している途中のもので、部署やチームによって使っているツールがバラバラな状態です。最終的にはどちらかに統一される予定になっています。

以下、必須ではないものの使ってみたら意外と良かったものを紹介します。

ゆるい繋がりを生み出すツール

SneekとPukkaTeamは一定間隔で写真を撮ってチームメンバーの現在の状況を共有するためのツールです。どちらもモザイク機能があり顔や部屋を映したくない場合にも利用できます。効果は人による、といった感じで特にメリットを感じない人もいますが、見られている感があって仕事をやる気になる、という人もいました。どちらのツールも機能はほとんど一緒なのですが、Sneekはウィンドウサイズに合わせて写真のサイズが変わるので小さくして画面端に置いておくのに便利でした。

ノイズキャンセリング

Krispをインストール後にZoomやHangoutsの入力側として設定すると環境音などを軽減してくれます。今のところ軽減が確認できたのは以下です。

  • テレビの音
  • 外を走っている車の音
  • 強風時の風の音

テレビに関しては結構な音量で流れていたにも関わらず実際に通話した人に聞いてみると「たまにうっすらと聞こえる程度で差し障りはない」とのことだったのでそれなりに効果がありそうでした。

f:id:dachi023:20200221210936p:plain
Zoomの例。krisp microphoneを選択することで有効になる

スピーカー側の設定もあるのですが、普段使っているイヤホンでノイズキャンセリングを有効にしたら音声がブツブツと切れるようになってしまったため私は無効にしています。

チャット上のコミュニケーション

普段から利用しているSlackでのコミュニケーションにおいても、今一度意識してみてねということで社内に展開したものが以下になります。なのでリモートワークだからやってくださいね、という内容ではないです。

利用頻度を落としすぎない

  • ひとりでいるとチャット画面と向き合う時間が少なくなりがち
    • 周りに人がおらず、ずっと集中しているので大事な用がある時くらいしか開かなくなりがち
    • リモート解禁でただでさえコミュニケーションコストが上がるので更に上がらないようにする
    • いつでも話しかけられる、話しかけられやすい状態を作れるよう意識する
  • また、通話やビデオ会議の際によそよそしくならないようにしておく
    • この人と久々に話したかも、となると気まずい感じになったりするのでチャットコミュニケーションも大事にする

とりあえず反応する

  • メンションなど来た時にとりあえず反応だけしておくと相手が見てくれていることを確認できる
    • 「後でちゃんと返します!」でもいいのでやると良い
    • 都度ちゃんと返す、をやり始めるとずっとチャットに張り付くことになる
      • 非同期コミュニケーションの良さがなくなるためそれは避けたい
  • 共有だけの連絡などに関してもEmojiでリアクションするなりして見たよ、が分かるようにする

雑談も大事にする

  • 対面で話す機会が減るのでそこを補うようなイメージ
  • コネヒトのSlack上には趣味などに関するチャンネルがあるのでそういったものも活用していく

不安を抱えておかない

  • 誰も気づかないまま自分だけがモヤモヤしていってしまうため
  • 書けばだいたい誰かが拾ってくれるので積極的に書く

察してくれは無理

  • 物理的に離れているため表情や雰囲気はほとんど分からない
    • ビデオ通話してたとしても見えている範囲は限られるのでオフラインに比べるとやはり弱い
  • 書いたことしか伝わらない、くらいの感覚でいたほうが良い
  • テキストのやり取りは含められる情報(感情とか)が少ないのでなるべく伝わりやすい文章にする

やりとりの数を減らす

  • 1回で伝えられる量を意識しないと何度も質問をすることになり時間がかかる
  • Yes / Noで答えられるような投げかけを意識するなど、工夫してコミュニケーションを取るようにする
  • また、話が長引きそうだなと思った時に速やかに通話に切り替えるなどができればそれでも良い

チャットだけに頼らない

  • チャットでのコミュニケーションは気軽だが前述したとおり情報が他の手段に比べ少なくなりやすい
  • より深く話し合いたい内容がある時などは通話等の手段を使えるようにしておく
    • = 通話に対しての苦手意識を減らしておく
    • 通話もオフィスで対面で話すのも同じだとイメージすると良い

ビデオ会議中のコミュニケーション

リモートワーク解禁によって初めて利用する人も多いであろうビデオ会議に関しても意識しておきたいことをまとめて展開しました。

1人1台PCを用意して通話を繋ぐ

  • オフィスにいる人達が1台だけ接続して通話するのは良くない
  • 3人以上だともう無理。2人なら全員映るからまだ許せる
  • 1台に複数人張り付いている場合
    • 死角で喋っている人が誰なのかがリモート側からだと分からない
    • マイクからの距離が遠くなりがちなので声が聞こえなかったりする
  • 誰が発言しているのか、が分かりやすい状態にしておく

オフィスメンバー、1箇所に集まる必要なし

  • 全員イヤホンして自席やフリスペでそれぞれ話せば良い
  • 集まるとどうしても物理的に近い側が優位になりリモート側が隔離されるタイミングがある
  • あと、会議室をいちいち取らなくて良くなる
    • 自席などで周りに人がいる状況で独り言的に話すのは最初恥ずかしいと思うけどそのうち慣れます

喋らない時はミュートにする

  • イヤホンせずにスピーカーから音声を流している場合は特にハウリング、山彦するので常に意識してミュートを忘れないようにする
  • Zoomでは音が出ている人が分かる機能があり、それの活用にもなる

同時に喋らない

  • オフラインでの会話と違い映像・音声共に遅延が発生するので非常にコミュニケーションしづらくなる
  • 誰かが喋っている時、他メンバーはミュートにして聞くことに徹すると会話がしやすくなる
  • 交代制にしやすくするために喋り終わったら「以上です」などを付けるようにすると分かりやすい

会場は通話部屋

  • 会議室やオフィスは会場ではない
  • HangoutsやZoomに作成した通話部屋が会議の会場である、という認識を持つ
  • 画面越しの人たちも会議に参加していることを意識してコミュニケーションをとる

なるべく物理ツールを使わない

  • 共有がしづらい、デジタル化しづらいため極力避ける
  • ホワイトボードが使いたいならMiroなどを使うと良い
    • オンライン上で共同編集が可能、閲覧もそのまま出来る

共有できるものはする

  • 議事録や資料など、URLを先に伝えておいたり、画面共有したりする
  • 「聞き取れなかった」「さっきの資料を再確認したい」といったメンバーがいることを考慮する

大人数での議論は避ける

  • ROM専(聞いているだけの人)が増えるだけなので大人数集まるのは非常に効率が悪い
  • 肌感だが頑張っても8人くらいが限界だと思う
    • 少なければ少ない方が楽ではある
    • 8人に収まらないような会議はそもそも会議の内容もメンバーも分割した方がいい

喋りますの合図を決めておく

  • あると便利くらいの感じで別に必須ではない
  • 手を挙げるとか、Zoomであればリアクション機能を使うなどして合図を決めておくと誰が喋るかが分かりやすい
  • ファシリテーターがいればそれで良い場合もある

さいごに

たまたま開発部がリモートワークの検証期間中であったため、そこまで大きな混乱もなく導入、良いスタートダッシュが切れています。ですが、改善できる箇所はまだ沢山残っているので、今後も改善を続けていき今よりも高いパフォーマンスを発揮していけるようにしたいです。もし皆さんの会社でも導入しようか考えている、導入したが上手く機能していないと感じた時、この記事を読んでいただいて何か参考になるものがあれば良いなと思っています。

*1:GSuite版のHangoutsです

*2:Slack上から打刻する機能を利用しています

Swift製CLIツールをMintを使わずSwiftPMで管理する

こんにちは!エンジニアの柳村です。

SwiftLintやSwiftFormat,XcodeGenといったSwift製のコマンドラインツールを管理するために、同じくSwift製であるMintを使っているのを割とよく見かけます。 Mintは便利ですが、Mint自体をなにかしらの手段でインストールしなければならないという問題がでてきます。

そこでMintは使わずSwift Package ManagerでSwift製のコマンドラインツールを管理する方法をご紹介します。

方法

Package.swiftを用意します。 nameを適当に設定し、dependenciesに利用したいコマンドラインツールのリポジトリのurlと必要であればバージョンを指定します。

// swift-tools-version:5.1
import PackageDescription

let package = Package(
    name: "Tools",
    dependencies: [
        .package(url: "https://github.com/apple/swift-format", .branch("master")),
        .package(url: "https://github.com/yonaskolb/XcodeGen", from: "2.13.0"),
        .package(url: "https://github.com/mac-cain13/R.swift", .exact("5.1.0")),
    ]
)

あとはswift runするだけです。

$ swift run -c release swift-format --help

最初の実行時はコマンドラインツールのビルドが行われるため結構時間がかかりますが、2回目以降は.build/release以下にビルド結果が残っているのでビルドなしで実行できます。

Swift Package Managerでツール以外のパッケージも管理している場合

Swift Package Managerを使っていないプロジェクトの場合は、なにも考えずにプロジェクトのルート直下にPackage.swiftを配置しても特に問題ないと思いますが、既にSwift Package Managerを使ってる場合は対処が必要です。

なぜなら、Swift Package Managerはdebugビルドやtestのときのみのdependenciesを設定する機能がないため、dependenciesに書いたものは必ずダウンロードとビルドが実行されてしまうからです。そのためリリースビルドなどが無駄に遅くなってしまったりします。

対処方法

その1. ディレクトリを分ける

コマンドラインツール系は別のディレクトリにおいてしまうのが手っ取り早い方法です。

例えば、cliというディレクトリをプロジェクト内につくって、そのディレクトリ内にコマンドラインツールのdependenciesを書いたPackage.swiftを配置しswift runを実行すればよいです。swift run --package-path cli xcodegenというふうに--package-pathでPackage.swiftのあるディレクトリを指定すればcliディレクトリに移動せずにswift runすることができます。

その2. 別の名前のPackage.swiftを用意して実行時にPackage.swiftに置き換える

Swift Package Manager製のコマンドラインツールの開発で使われたりするやり方で、例えばPackage.test.swiftというテスト用のファイルを用意しておいて、テストするときだけPackage.test.swiftをPackage.swiftに置き換えます。

普通のiOSアプリ開発の場合は、Package.release.swiftというファイルを用意しておいて、CIなどでリリースビルドするときだけPackage.release.swiftをPackage.swiftに置き換えてやるとよいのではないかと思います。

ただ、2つのファイルをメンテしなければならないといったデメリットがあります。

Mintとの違い

最後にMintとの違いについて触れておきます。

Mintはglobalに保存

Swift Package Managerの場合はPackage.swiftのあるディレクトリの.build以下にcloneやビルド結果が保存されますが、 Mintの場合は/usr/local/lib/mint/以下に保存されます。複数のプロジェクトがあり、他のプロジェクトと同じバージョンのパッケージを使っていた場合はキャッシュが効くメリットがあります。

Mintはglobal linkつくるのが楽

MintはCLIを実行するrunだけでなく、globalにinstallするinstallという機能があります。 どいういうことかというと、例えばmint install yonaskolb/XcodeGenを実行しておくと、/usr/local/bin以下に実行ファイルのリンクがつくられるので、mint run xcodegenとしなくても、xcodegenだけで実行できるようになります。

Mintはconfig fileなしで実行できる

Swift Package Managerの場合Package.swiftを用意しなければなりませんが、Mintの場合はMintfileなしでも使えます。

まとめ

基本的にはMint使わなくてもSwift Package Managerで事足りるのではないかなと思っています。

ただ、SwiftLintの0.38.xはビルド通らないようでした(https://github.com/realm/SwiftLint/issues/2867)のでご注意ください。(0.39.0で治りました)

そしてコネヒトではエンジニアを募集しておりますので少しでも興味のある人はお話だけでも聞きにきてください! www.wantedly.com

コネヒトはPHPerKaigi 2020にプラチナスポンサーとして協賛いたします!

こんにちは。エンジニアの 高野 (@fortkle) です。
さて、今日はイベント協賛の告知をさせてください!

PHPerKaigi 2020に協賛いたします

タイトルにもある通り、PHPerKaigi 2020 にプラチナスポンサーとして協賛いたします。
コネヒトではメインプロダクトである「ママリ」を始めとして開発のメイン言語としてPHPを活用しており、フレームワークとしてはCakePHPを採用しています。

イベント概要

PHPerKaigiはどんなイベントか、公式サイトから拝借すると

PHPerKaigi(ペチパーカイギ)は、PHPer、つまり、現在PHPを使用している方、過去にPHPを使用していた方、これからPHPを使いたいと思っている方、そしてPHPが大好きな方たちが、技術的なノウハウとPHP愛を共有するためのイベントです。

という 過去・現在・未来PHPerに向けたお祭り のようです! 他のカンファレンスと同様に公募トークがありつつ、数人で特定のテーマについてディスカッションするInteractive Round Tableや、アンカンファレンスなどちょっと違ったことも企画されているとのこと!

タイムテーブルもすでに発表されているので興味がある方は要チェックです!

タイムテーブル | PHPerKaigi 2020 #phperkaigi - fortee.jp

コネヒトからは2名登壇します

@itosho 2020/02/09 16:55〜 Track A 15分トーク
登壇タイトル:「Deep Module in PHP」

fortee.jp

@fortkle 2020/02/11 15:00〜 Track A 15分トーク
登壇タイトル:「GitHub Actionsで始めるPHPアプリケーションのCI実践入門」

fortee.jp

ご参加される方はぜひ発表を聞いていただければ幸いです!

チケット購入はこちらから

phperkaigi.jp

最後に

ここまで読んでくださった皆様、ありがとうございました。
そして、 PHPerチャレンジ中の皆様、お目当てのPHPerトークンはこちらです!

#EnjoyPHPWithConnehito

それでは!

コネヒトではPHPerを積極採用中です!

Connehito Image PHPとAWSで開発!生活領域における課題を解決したいエンジニア募集!

2019年の忘年会を機械学習APIを使って盛り上げた話

こんにちは!
忘年会エンジニアのたかぱい(@takapy)です。

コネヒトの忘年会は例年気合いが入っており、有志メンバーが運営となり1年の締めくくりを最高のものにすべく、尽力しています。

今回は2019年の忘年会を技術で盛り上げるべく、同じく忘年会エンジニアのあぼ氏(@abo)と奮闘したことをご紹介できればと思います。


目次


パシャりんピック(既存構成)の紹介

前回(2018年)忘年会時に、弊社エンジニアにより「パシャりんピック」という歓談タイムを盛り上げるコンテンツが爆誕しました。

ざっくり説明すると、

  • 社員同士でツーショット写真を取る
  • 社員1人1人に点数が初期設定されており、写真に写る人物によってその写真のスコアが算出される
  • 最終的に獲得スコアの一番高い人の勝ち(たくさん撮った人が有利)

というものです。(詳しくは下記参照)

tech.connehito.com

このコンテンツの評判がとても良かったので、今回はこれをアップデートさせる形で忘年会を盛り上げてやろうと画策しました。

今回のコンセプト

そもそものコンセプトが明確だったので、今回はこれに「笑顔」要素と順位と写真の見える化をプラスすることで、より盛り上がるコンテンツに仕上げようと決めました。

アップデート内容

大別して下記2点です。

  • リーダーボードの実装(主にあぼ担当)
  • スコア算出方法のアップデート(主にたかぱい担当)

それぞれについて、全体の構成図も交えながら説明していきます。

アーキテクチャ

以下のようなアーキテクチャを構築しました。

f:id:taxa_program:20200117180243p:plain
システムアーキテクチャ

使用した技術は下記です。

  • GCP
  • Azure
  • Python
  • Firebase
  • Flutter

リーダーボードについて(byあぼ)

今回は"人"と"写真"の2つに対して点数がつくので、点数上位の人と写真がリアルタイムで見られるようにリーダーボード(スコアボード)を作りました。

f:id:aboy_perry:20200120113657j:plain
リーダーボード

使用技術ですが、今回はFlutterをweb上で動かしました。Flutter on the webは現状beta版ではありますが、

  • 私がFlutterを趣味で使っているので知見があり、好きなので開発するやる気が出る
  • 保守性を考えなくても良いアプリケーションである
  • 最悪ローカルの環境で特定のブラウザでさえ動けば良い

という理由で採用しました。

基本的にリーダーボード側はFirestoreから購読した値を画面に表示するだけなので、そこまでやることは多くありません。Firestoreの購読にはStreamBuilderを使って、UIはRowとColumnを駆使して組み立てていきました。

また、リーダーボードのメインのUIとは別に、当日の忘年会進行スライドとの統一感を出すために、画面右上に忘年会のロゴを表示させました。今回はStackという、Widgetを重ねられるWidgetを使って実現しました。Stackを使うことで"ロゴ"表示とメインコンテンツの"スコア"表示部分のレイアウトが分割されるので、個人的にはこっちのほうが好みです。

class BoardPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Positioned(
            top: -20,
            right: -50,
            child: Image.asset(
              'logo.png',
            ),
          ),
          // メインコンテンツのスコア表示UIを組み立てる
          Column(
            ~~~
          ),
        ],
      ),
    );
  }
}

Firebaseの機能を使えるようにする

それから、Firebaseの機能を使うのに一工夫必要だったので紹介します。まず、Flutter on the webはFirebase Hostingで動かしました。設定はfirebaseコマンドを使ってサクサクと行えます。firebase.jsonではpublicにbuild/webを指定します。これはflutter build webでweb用のリリースビルドが生成される場所です。

{
  "hosting": {
    "site": "year-end-party-2019-board",
    "public": "build/web",
    "ignore": [
      "firebase.json"
    ]
  }
}

次に、Dart packagesからFirebaseパッケージをインストールするのに加えてweb/index.htmlのscriptタグでjsファイルを読み込ませるようにしました。この辺はFirebaseをJavaScriptアプリケーションにインストールするドキュメントを参考にしました。これでFirebaseの機能が使えるようになりました。

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  firebase: ^7.1.0

web/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>year-end-party-2019-board</title>
</head>
<body>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/6.2.0/firebase-firestore.js"></script>
  <script src="main.dart.js" type="application/javascript"></script>
</body>
</html>

スコア計算について (byたかぱい)

忘年会エンジニア兼・機械学習エンジニアなので、スコア算出に機械学習の要素を取り入れました。

「笑顔」という時点で察している方もいらっしゃると思いますが、写真に写る人物の笑顔度合いを数値化してスコア算出を行いました。

本来であれば、複数の写真(学習データ)を用意して、笑顔度のアノテーションをして、学習させて・・・というフローが必要なところですが、今のご時世APIを1つ叩くだけでやりたいことができます。

という訳で、笑顔の数値化にはAzure Face APIを使用しました。

Azure Face APIには、Python SDKがあり、普段Pythonを触っている人であれば比較的スムーズに使用できるのではないかと思います。

Azure Face APIの使用例

例えば、画像をpostしてレスポンスを受け取る場合は下記のように記述します。

FACE_API_URL = 'https://japaneast.api.cognitive.microsoft.com/face/v1.0/detect'
KEY = 'hogehoge'
HEADER = {'Ocp-Apim-Subscription-Key': KEY}

def call_api(image_url):
    params = {
        'returnFaceId': 'false',
        'returnFaceLandmarks': 'true',
        'returnFaceAttributes': 'smile,emotion'
    }

    response = requests.post(
                            FACE_API_URL,
                            params=params,
                            headers=HEADER,
                            json={"url": image_url}
                            )

    return response


# image_urlには画像のURLを設定
response = call_api(image_url)

ここから笑顔の値を取得したい場合は下記のように記述します。

smile = response.json()[0]['faceAttributes']['smile']

これで笑顔度(0〜1)を取得することができます。

Azure Face APIが笑顔に寛大すぎる問題

試しに何枚か写真を撮って笑顔度を算出してみると、ある問題に気付きました。。。

「割と簡単に最高値が取得できてしまう🤔」

これでは盛り上がるコンテンツにならないのでは?と思い、とあるロジックを追加しました。

それは「写っている人物の鼻端が近ければ近いほど、高得点になる」というもので、これを「仲の良さ」と銘打ってスコア計算に組み込みました。
(近い位置で写真撮ってる方が仲良さそうですよね?そうでしょ?)

f:id:taxa_program:20200115104828p:plain
当日のルール説明に使用した資料

鼻端のx座標、y座標に関しては上記のAPIを使用すれば取得できるので、「近さ」の定義だけ考えました。

「近さ」の定義

通常であればユークリッド距離を計算すれば良いのですが、ハックできる要素を含ませた方がコンテンツとして面白いのでは?と思い、x座標の差分の絶対値を「近さ」の定義として計算することにしました。

どうすればハックできるかはもちろん伝えていなかったのですが、開始10分くらいで気付かれはじめ、最終的には高得点の写真ばかりになっていました・・・笑(詳細は後述)

「近さ」を考慮したスコア計算

以下が「近さ」を考慮したスコア計算例です。(スコアの幅は0点〜1000点にしています)

def calc_score(response, width):
    score = 0
    nosetip = 0
    
    count = 0
    if len(response.json()) > 2:
        count = 2
    else:
        count = len(response.json())
    
    for i in range(count):
        smile = response.json()[i]['faceAttributes']['smile']  # 笑顔度 0 ~ 1
        sadness = response.json()[i]['faceAttributes']['emotion']['sadness']  # 悲しみ 0 ~ 1
        score += (smile - sadness) * 100

        # 鼻端の近さ
        if nosetip == 0:
            nosetip = response.json()[i]['faceLandmarks']['noseTip']['x']
        else:
            nosetip -= response.json()[i]['faceLandmarks']['noseTip']['x']

    # 鼻端の距離スコアを計算
    nosetip = 1 - (abs(nosetip) / width)
    
    if score == 0: return 0
    
    return max(0, int(round(((score / 2) * nosetip), 1)*10))

# responseは前項で取得したFace APIのレスポンス、widthは写真の横幅
score = calc_score(response, width)

このようにすることで、隣り合う2人の距離(鼻端のx座標)が近ければ近いほど高得点が出るようにしました。

やってみてどうだったか

「笑顔」という条件を提示することで、いろんな人の様々な表情を垣間見ることができました!
と同時に、今回はリーダーボードがほぼリアルタイム*1で更新されたので、リーダーボードに映っている、スコアの高い写真を参考にどのように写真を取れば高得点が叩き出せるか試行錯誤するという一味違った体験を提供することができ、大盛況に終わることができたのではないかと思っています!

想定外だったことといえば、先にも述べた通り開始10分くらいでハックされたことです(汗)
今回はx座標のみスコアに寄与するため、横並びではなく、縦並びで写真を撮るとかなり高いスコアを叩き出すことができます。*2

これにより、忘年会後半はほぼ全員が縦並びで写真を撮るという見慣れない光景が広がっていました(笑)が、結果的に仲良さそうに写真を撮っていたので運営側としても大成功&嬉しかったです!

最後に

告知です!

2月6日にMeetUpを開催します!

ママ向けNo1アプリ「ママリ」を運営している社員とざっくばらんにお話ししてみませか?

当日はCEO、CTO、デザイナー、新規事業開発責任者、人事責任者など様々な職種の社員が参加します!ちょっと話聞きたいなくらいの温度感でOKなのでご参加お待ちしてます!
(忘年会の様子とか聞いてみたいな〜という方も気軽にお越しください(笑))

connehito.connpass.com

*1:1分間隔でスコア計算をしていた

*2:鼻端のx座標の差分を限りなく0にすることができるため

JSConf JP(Day2)に行ってきました!

こんにちわ!フロントエンドエンジニアの黒田です!

2019年12月1日に行われたJSConf JP(Day2)のレポート記事で、コネヒト Advent Calendar 2019 22日目の記事です。
ちなみに、弊社コネヒト株式会社はJSConf JPにシルバースポンサーとして協賛しました。

さて、ここからはトークセッションの概要になりますが、会場では3Roomに別れて同時に別々のセッションが開かれており、実際に私が視聴したセッションに限りのレポートになることをご了承ください。

InversifyJSを用いたレイヤードアーキテクチャの構築

speakerdeck.com

レイヤードアーキテクチャやDDDなどアーキテクチャのおさらいとDIコンテナを実装すべき話、またそれらを実現するライブラリについての話でした。
当初はInversifyJSがこれらを解決するライブラリとして紹介する予定だったが、TSyringeというMicrosoft製のライブラリを使った方がいいよということらしいです。

Build and scale multiple Voice application by using TypeScript

speakerdeck.com

CMでおなじみのAlexaの開発の話でした。
リクエスト/レスポンス処理はask-sdkを使ってTypeScriptベースで開発できるようです。
ただ、機能を意味するスキルの設計にやや難があるらしくいい感じに作れるFWが今後作られることを期待しているらしいです。
若干専門的な事柄がありつつも、基本的に音声・言語処理系の知識がなくても開発可能なところに興味が沸きました。

正攻法はあるのか !? 泥臭く戦った Node.js バージョンアップ一部始終

speakerdeck.com

Nodeを6系から一気に10系にバージョンアップした話でした。
ノウハウがない中で泥臭くリリースノートの差分確認や全機能テストを繰り返して、無事リリースまで行ったのは単純にお疲れ様でしたとしか言いようがありません。
泥臭いと書くとネガティブに捉えがちですが、シンプルに正攻法とも言えるわけでこれはこれで貴重な経験談を聞けたなと思います。
ただ、ライブラリのバージョンメンテナンスはこまめにやっていこうなとしみじみ思いました。

Migration from React Native to PWA

speakerdeck.com

React Nativeへの移行やフルNativeへの移行はちらほら聞いたことはあるが、React NativeからPWAへの移行は聞いたことがないので驚きでした。
マルチプラットフォーム対応やReact Nativeのライブラリ管理、ビルド/デプロイ周りはweb中心のエンジニアだと馴染みがなくて大変なんだろうなという所感。
あとは開発リソース不足の問題も含めて改めて「開発 ≠ 運用」という言葉が印象に残りました。

GraphQLを用いたECサイトにおけるパフォーマンス改善

speakerdeck.com

パフォーマンス改善のアプローチをフロント、サーバー、GraphQL/Schemaの3つに分けて分かりやすく説明していました。
GraphQLでN+1問題を回避したり、over-fetchingをなくしたり、実践的なTipsを多く挙げられていました。

JavaScriptのままでTypeScriptを始める

speakerdeck.com

TypeScriptを導入したいんだけど諸々な事情で取り入れられないよーというアルアルな方向けに、JavaScriptで相応の恩恵が得られるTyped-JavaScriptというライブラリの話でした。
ややエッジが効いているものの、規約に沿ったコメントを書いておくだけで導入可能なようです。素晴らしい!

Your benchmark may not guide a real application performance

speakerdeck.com

パフォーマンス計測指標の設定の仕方について、よく出てくる指標はあくまで一般的な指標であって盲目的にそれらを採用するのはNGだよという話でした。
言うは易しではありますが、最適な計測指標を見つけるには自分のプロダクトのドメイン知識をよく理解して設定しようそうしましょう。

JavaScriptとSwift/JavaをつなげるCapacitorと、これからのWeb Frontned.

speakerdeck.com

クロスプラットフォーム開発で使われるCapacitorの話でした。
ionicは触れたことがなく個人的には理解に難ありなセッションでしたが、簡単にネイティブアクセスできるコードがJavaScriptで書けるようです。

大規模アプリケーション開発でのElm実践

UniposはElmを使っているというのは知っていたけど、実際どんな設計になるのかをElmという言語の特徴を元に解説する話でした。
Elm食わず嫌いで今まできてしまっていましたが、少しだけ触ってみようかなと、それほど登壇者のElm愛が伝わってきました。

最新のWeb技術でIoT開発をする

speakerdeck.com

webエンジニアだとなかなかIoTの世界は触れる機会は少ないけれど、JavaScriptで動くようにできるIoTデバイスがあるんだよという話でした。
obnizはJavaScriptがかければ、あとはセンサーやモーターの知識を補完するだけでIoTができるらしいです。
事例もふまえてかなり分かりやすく説明されていました。価格もお手頃価格なので機会があれば試してみたいですね。

Sponsor talk

恒例のスポンサートークです。
Yahoo! Japan, リクルートテクノロジーズ, ドワンゴ, DMM, Twilioといった有名どころの企業が登壇していました。
中でもtwilioの自社サービスを使った対話型のセッションが聞き手を巻き込んでいて特に印象に残りました。

points at random

f:id:Naomichi-Kuroda:20200106184201j:plain
points at random

最後のクロージングセッションです。
登壇者はMariko Kosakaさんで、実は11年前にアートイベントのインターンとして会場に来ていたそうです。

本題では、Sol Lewitt氏のアートクリエイティブの過程がコードによく似ていること。
また、Marikoさんのキャリアの中でとても多くの人と出会い、その出会いが重要なものばかりであったこと。
それらの出会いは偶然、ランダムのように見えて実はランダムではなく、皆がそれぞれ持っているprivilege(特権)のおかげなのだそうです。
特に後半のコミュニティに関する内容はすごくエモくて素敵なセッションでした!

Closing

f:id:Naomichi-Kuroda:20200106184306j:plain
closing

代表の古川さんの挨拶でイベントの締め括りです。
JSConf Jpができるまでの経緯や関係者への感謝の言葉を述べていました。
最後にみんなで記念撮影で終幕でした。

最後に

以上、JSConf JP 2日目のレポートでした。
1日目から参加してきましたが、海外の方も結構参加されていて国際色豊かなイベントだったなという一方、会場が真冬の屋上だったり、教室のようで狭かったり運営の方は大変だろうなーと思いつつも、1回目でこれだけやれれば大成功だったのではないかなーと思いました。
次回は9月頃に開催予定だそうですので、JavaScriptを使われる方、スポンサーの方は次回以降も参加してどんどん盛り上がっていったらいいなと思います。

コネヒトでは職域を問わず幅広くWebエンジニアを積極採用中です!

  • フロントエンドエンジニア

www.wantedly.com

  • サーバーサイドエンジニア

www.wantedly.com

  • インフラエンジニア

www.wantedly.com

サービス内のトレンドを把握するために、テキストデータを可視化・通知してくれるslack botを作った話

本記事はコネヒト Advent Calendar 2019 17日目の記事です。

こんにちは!MLエンジニアの野澤(@takapy0210)です!

今回は、ママリ内に日々蓄積されているテキストデータを良い感じに可視化して、定期的にslack通知する仕組みを実装したお話です。

※下記で使用している画像やデータに関しては、あくまでママリ内での傾向を算出しており、個人を特定できるものではございません。また、画像に関しては一部加工しております。

目次

どんなもの?

毎朝9時頃に、前日投稿されたテキストデータを元に、word cloudやN-gramによる単語の出現頻度を可視化してslackに投稿しています。

下記は12月のとある日の投稿ですが、インフルエンザやワクチンといった単語が目立っていることが分かります。

f:id:taxa_program:20191216143918p:plain
word cloudの例

thread内には、uni-gram、bi-gram、tri-gramの画像が投稿されます。

f:id:taxa_program:20191216144041p:plainf:id:taxa_program:20191216144621p:plainf:id:taxa_program:20191216144742p:plain
N-gramの例

なぜやったのか

現在のコネヒトではデータの民主化が進んでおり、多くの社員がredashなどのBIツールを用いて、各々にデータ分析をすることができます。

しかし、テキストデータに関しては、曖昧検索や投稿に紐づくタグ*1などでしか検索することができず、日々蓄積されるデータに対して有効なインサイトを得るのはなかなか難しい状態です。
ママリはママがお互いに悩みを相談し合うことのできるQ&Aサービスなので、日々蓄積されるテキストデータの量も多く、そこから得られるインサイトはとても大切です)

そこで、定期的にママリ内のテキストデータを可視化・共有することで、誰かが何かに気づいてポジティブな行動が生まれたり、有効なインサイトが見つかれば良さそう!ということで実装しました。

社内の方からも嬉しいお言葉が....!

f:id:taxa_program:20191216160324p:plain
社内の方のお声

アーキテクチャ

アーキテクチャは下記のようになっています。

f:id:taxa_program:20191216153950p:plain
アーキテクチャ

各サービスでどんなことをやっているのか簡単に触れます。

CloudWatch

  • StepFunctionsを毎朝9時に起動するためのトリガーとして使用しています。

StepFunctions

  • 「GlueでのETL」と「Fargateでのバッチ処理」を管理しています。

Glue

  • DBから対象のデータを取得し、簡単な前処理を行ったあと、S3に日付ごとのディレクトリを作成して保存しています。

Fargate

  • 分かち書き、word cloud作成、N-gram作成、slack投稿までを行っています。(pythonを使用)

word cloudは例えば下記のように記述することで画像ファイルとして保存することができます。
詳細は公式API Referenceをご参照ください。

# 保存
def save_image(img, file_name):
    Image.fromarray(img).save(file_name)


# wordcloudの作成
def create_wordcloud(text, file, mask=None, max_words=100, max_font_size=80, colormap=None):

    # 日本語に対応させるためにフォントのパスを指定
    f_path = TTF_FILE_NAME

    # wordcloudの生成
    wordcloud = WordCloud(
                    background_color='white',
                    font_step=1,
                    contour_width=0,
                    contour_color='steelblue',
                    font_path=f_path,  # 日本語対応
                    stopwords=stopwords,  # ストップワード(set型)
                    max_words=max_words,
                    max_font_size=max_font_size,
                    random_state=42,
                    width=800,  # 幅
                    height=600,  # 高さ
                    mask=mask,
                    collocations=False,  # bigramを考慮するかどうか
                    prefer_horizontal=1,  # 文字の向き 1だと全部横向き、0だと全部縦向き
                    colormap=colormap
    )
    wordcloud.generate(str(text))

    img = wordcloud.to_array()
    save_image(img, file)


# 呼び出し
create_wordcloud(text=df['text'], file=file_name, mask=ellipse_mask, colormap='tab20_r')

また、slack botで画像を投稿する際は下記のように記述することで実現可能です。
こちらも詳細はAPI Referenceをご参照ください。

# slack通知
def slack_notify(file, comment, ts=None):

    # ファイル名
    filename = file.split('.')[0]

    # 実際に投稿したい画像ファイル
    files = {'file': open(file, 'rb')}

    param = {
        'token': TOKEN,
        'channels': CHANNEL,
        'initial_comment': comment,
        'title': filename,
        'thread_ts': ts
    }

    res = requests.post(url="https://slack.com/api/files.upload", params=param, files=files)
    json_data = res.json()
    return json_data

thread内に続けて投稿したい場合は、tsパラメーターに値を指定することで実現可能です。

# 上記slack通知関数を呼ぶ(初回)
res = slack_notify(file_name, '初回投稿')

# 初回投稿のtsを取得
channel = res['file']['channels'][0]
ts = res['file']['shares']['public'][channel][0]['ts']

# 初回投稿のthread内に投稿する
_ = slack_notify(file_name, 'thread内投稿', ts)

終わりに

今回は、なるべくコストを掛けずにサクッと実装してみました。
このような取り組みを行いデータをより身近におくことで、全社的なデータ活用の促進にも繋がるのではないかと考えております。

今後は社員からのフィードバックをもらいつつ、より良いものにアップデートしていければと思っています!


【ちょっと宣伝】

コネヒトではサービス改善を行うエンジニアを募集しています!
少しでも興味もたれた方は、是非気軽にオフィスに遊びにきてください!

www.wantedly.com

*1:正規表現によって保存される特定のワード