コネヒト開発者ブログ

コネヒト開発者ブログ

サービス内のトレンドを把握するために、テキストデータを可視化・通知してくれる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:正規表現によって保存される特定のワード

データ分析N本ノックをやった結果

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

はじめに

こんにちは!ママリ開発チームでアプリケーションエンジニアをやってるaboです。10日目に書いたばかりですが気にせず投稿します!

今回は、コネヒトに入社してからデータ分析ツールに慣れるためにやったことのお話です。

前提として、コネヒトではRedash、Mixpanel、Google Analytics、Reproといったデータ分析ができるツールを導入しており、誰もがデータに触れられる状態になっています。これらのツールはフル活用していて、例えばRedashには現在60ダッシュボードと約3,000クエリ*1が存在しています。この「データの民主化」あたりについては、これらの記事で詳しく触れられているので興味がありましたらご覧ください。

tech.connehito.com

tech.connehito.com

このようにデータが民主化され活用されている組織ですが、私自身はSQLがわかるくらいで、それらのツールの利用経験はほとんどありませんでした。そこで私が「データ分析ツールを駆使して施策の効果検証ができ、施策の次の手がデータから導き出せるような状態」になるべく*2行ったのがデータ分析N本ノックです。

データ分析N本ノックとは

f:id:aboy_perry:20191211200943p:plain:w300
小学生の時は野球部でした

データ分析N本ノック*3は、(だいたい)1日ひとつお題となるデータを決めて、RedashやMixpanelを使ってそのデータを出してみるというシンプルなものです。最初はとりあえず複数のデータソースを扱えるからという理由でRedashに絞って行いました。

お題は自分で考えたものや、社内で「これ出せると嬉しい !」と依頼されたもので、例えば次のような感じです。

  1. ◯◯タブのユーザー属性別スクリーンビュー数の推移
  2. ◯◯タブのユーザー属性別記事クリック数の推移
  3. ◯◯カテゴリで2019年5月1日~2019年6月30日までに投稿された質問
  4. ネイティブアプリのエラーイベント数の推移
  5. 購読開始画面を見たのに購読しなかった人数

お題には、すでに他の誰かが出していてRedashにクエリが存在しているものや、Redash以外のツールを使ってより簡単でビジュアルに見れるデータも含まれている可能性がありましたが、当時はそんなことを気にしていると進まないし、最初はとにかく慣れるのが重要なので、気にせずにノックしていきました。

続けるうちに起こった変化

最初はどのデータがどのデータソースに存在するのか、Redashでデータソースごとにどうクエリを書けばいいか、複数のデータソースを組み合わせたい場合どうすればいいかなど、無限にわからないことがありました。当時のチームには数値サポート大臣と呼ばれる、ファクト出しや効果検証のサポートをしてissueを生む速度をあげる役割の人が一人いたので、ちょうどいいのでその人に聞きながら進めていきました。

あとは、「こういうことやってます!」というのをドキュメントに書いて社内公開したり、チーム内で宣言したおかげか、「◯◯さんがこういうデータ欲しがってる」というニーズを私に教えてくれる人も現れました。

そして、だいたい10本ほどノックを捌いたあたりで、業務で必要なデータ出しにはさほど困らなくなっていました。例えば施策のファクト集めだったり、効果検証などです。また他の人が書いたクエリも読めるようになるので、既存クエリを活用してさらに効率的にデータ分析ができるようになっていきます。こうなってくると楽しくなってきて、データ分析したいマンになっていきます。それからはノックのためにお題を決めるというよりも、実際の業務で出したいデータを自分で出すようになりました。

ノック数が20本になった段階で、「issueを考える過程で自然とMixpanelやRedashを使ってデータ分析を行うようになってきたため、純粋に訓練としてのノックの必要性が低くなった」「issueを考える過程で使ったクエリの方が価値があるため、Redashにこだわる必要もなければ、ノックをするために無理にクエリを考える必要性も低くなった」と感じ、ノックを終了することにしました。

というわけで結果はN=20、自分の想定よりだいぶ少ない数で終わらせることにしたわけですが、当初の目的である「データ分析ツールを駆使して施策の効果検証ができ、施策の次の手がデータから導き出せるような状態」になることは一定達成できたので、もうノックという手段は必要無くなったのです。

f:id:aboy_perry:20191212181845j:plain
RedashのMy Queriesに残っているノックの面影

おわりに

少し抽象化するとこのノックによる学びは次の2点になります。

  • 自分がやっている/やりたいこととその意図を伝えると、理解者や協力者があらわれる
  • やればできるようになるしやらないとできるようにならない

前者は、もう一歩踏み込むと、自分が伝える側の話だけでなく、自分が他の人の意図を汲み取って理解・協力する側になれる可能性があると思っています。相互理解が深まると、メンバー同士の相互作用が進み、より良い仕事ができるはずです。自分の行動やその意図は伝えていきましょう。*4

後者は至極当たり前なことを書いてますが、結構大事だと思っていて、まずは「続けたらできるようになった」という成功体験が積めたことが自信に繋がり次の行動への支えになってくれますし、「何かができるようになりたかったらやるしかないんだ」と腹を括る気持ちも湧いてきます。

ちなみに、やったらできるようになった私は、今「チームのデータ分析支援」というのを行動目標においていて、私と同じようにツールの利用経験が無い人の支援や、ディレクターが考える施策のファクト集め、効果検証を一部手伝ったりしています!今回私ができるようになったのはサポートしてくれた人たちのおかげですし、個人でできることが増えた=チームに還元できることが増えた、という認識です。これからも相互に助け合って最高のチームを目指します!


というわけで、コネヒトではチーム開発やデータドリブンな開発が好きなエンジニアを大募集中です! www.wantedly.com

*1:unpublishedや練習用のクエリも含みます

*2:加えて前職まで全くデータドリブンな開発を行わなかったのでできるようになりたかったのです

*3:最初はN=100で、営業日換算で約5ヶ月続けるつもりでした...

*4:もちろんあえて意図を隠す場合もあると思いますが

Extends 新米マネージャーが1on1で実践していること

こんにちは。2017年11月にAndroidエンジニアとしてjoinした@katsutomu です。

7日目に引き続きの登場です。以前のエントリーで金髪から緑髪にすると宣言しましたが、依然、完遂出来ておりません。可及的速やかに対処いたします。

さて、今回は、1on1についてのお話をしたいと思います。10月から一部のマネジメント業務を担当し、4人のメンバーの1on1を実施するようになりましたので、わたしなりに試行錯誤した2つの事例を、ここまでの振り返りをかねて紹介します。 以前、本ブログにおいて@itoshoが投稿した1on1のエントリーがありますが、こちらの内容を踏襲したので、その内容にも触れながら紹介していきます。

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

1. 期待することと場のあり方を一緒に考える

さて、1on1を実施する上で、よく聞く声として、何の為に1on1を実施するのかわからないということや1on1の場がマネージャーへの共有の場になり話したいことが話せないというようなものがありますが、これは個人的には目的や期待値の不一致を起因としたミスコミニュケーションによるバグであると捉えています。

継承元では以下のように対処していました。

まず、初回の1on1では目的の共有を行いました。一口に1on1と言っても、社長が実施する1on1と直属の上長が実施する1on1は役割が違います。また、個々人が1on1に対して抱いているイメージや期待値も様々です。ですので、最初にそのギャップを埋めるために僕が想定している1on1の目的やイメージなどを伝えるようにしました。

この課題に対して、わたしがとったアプローチは、初回の1on1でIntroduction Sessionを実施することでした。 やったことは以下の3点です

  1. わたしから1on1での約束ごとをお話しする
  2. 1on1に期待することについて一緒に考える
  3. 期待を叶える為にはどういう場であるべきかを一緒に考える

具体的には下記のようなアジェンダを用意してメンバーそれぞれと議論しながら決めていきました。

せきねとあなたが1on1のやり方を相談するアジェンダです
徐々に最高の1on1を目指していきましょう

# アジェンダ
1. 1on1のあり方について確認
2. グラウンドルールについて
2. やりかたについて相談

### 1. 1on1のあり方について確認
- 1on1に何を期待しますか
- どういう場にしていきたいですか
- 決めたい約束事はありますか
- etc


### 2. グラウンドルールについて

#### HRTでいきましょう
- 謙虚(Humility):お互いに弱さを見せられる
- 敬意(Respect):お互いに敬意を持っている
- 信頼(Trust):お互いがお互いの成長期待を持っている

#### 1on1の始めと終わりの気持ちを教えてください
- 始めと終わりの変化を知りたいです
- 忖度なく正直に答えてほしいな
- ファイブフィンガーとかお天気とか

#### 最後に話した内容を確認しましょう
- 認識の齟齬はないか
- NextActionの有無
- やり方でカイゼンしたいことはないか

前半で、1on1に何を期待するか?、どういう場にしたいか?、グラウンドルール以外で決めたい約束事はあるか?の3つの問いをベースに1on1の骨格を一緒に決めていき、後半で、1on1を実施する上でのルールの確認を行い、毎回の1on1への姿勢について共通の理解を得るようにしています。

この手法を採用し、メンバーそれぞれの期待と場のあり方を決めたことで、2回目以降の1on1がスムーズに実施できました。また1on1の終わりに話した内容の同期とカイゼンしたいことを確認し、決めたやり方に固執しすぎないようにしています。

また、継承元で以下に抜粋した様に実施していた1on1シートも、このタイミングで作成しています。

目的の共有と併せて、面談シートのような1on1シートを作成しました。個人的に1on1では雑談も大切だと思っているので、あまりガチガチにやりたくないと思っています。とは言え、やってる感だけ出して、目標の達成支援が出来ていないなら、それはメンバーの貴重な時間をいたずらに奪っているだけなので、トラッキングも兼ねて1on1の数日前に1on1シートを用意して事前に話したいことなどを書いてもらうようにしていました。

ただ、Introduction Sessionを実施したことにより、1on1に期待することと場のあり方が、それぞれで違ってきていたため、フォーマットもそれぞれに合わせチューニングしています。

2. 1on1に共に向かう

Introduction Sessionを経て、2ヶ月ほどそれぞれのメンバーと1on1を実施していきましたが、話す内容、一回の時間、シートのフォーマットの調整など、少しずつ個別に最適化されていきました。ところが同時に、1on1という場の経験値が個々の場に埋もれてしまい、組織やチームへフィードバックされないことに、一種の属人化のような、もったいなさを感じ始めました。そのため、それぞれが1on1で実施している内容について、情報交換する場を用意してみました。

やったこと

わたしと1on1を実施している4人のメンバーでグループワークを実践しました。 いくつかワークの手法を検討しましたが、メンバーそれぞれが、お互いの1on1の内容を知らないため、やったこと(Y)、わかったこと(W)から次にすること(T)を考えるのが相応しい方法であると考えて、YWTを選択しました。

www.kikakulabo.com

具体的には以下のアジェンダにそって進行しました。

## このメモ is 何?
- 2019/12/10に実施する**ぽじね1on1 YWT**の資料ダヨ
- 前半はバーバラさんにファシリテートを任せ、わたしは不在ですのでこのメモを元にバーバラさんに進行してもらいます!
- わたしが不在なのは、なるべくノイズを減らす為だよ!

## 背景
- 1on1の場は、メンバーそれぞれでやり方や話すことが固定化されていくが、それぞれのやり方にPros/Consがある
    - 個別最適されること自体はOK!
- 情報交換をすれば良い刺激になりそうだが、そもそも1on1のことを語らう場や機会は中々ない
- 場を用意してみて、どのような変化が起きるかを試してみましょう〜

## ねらい
- それぞれの1onやり方の情報交換をすることで、改善点が見つかたり、相乗効果をうむことが可能 

## 期待する効果
1. 他のメンバーの1on1を知って気づきを得る
2. 自分や他のメンバーの1on1のpros/consを明確にする
3. 自分の1on1の改善点を見つける

## やること

### YWT
ぽじねの1on1に関係しての**やったこと(Y)、わかったこと(W)、次にすること(T)**を元に振り返り&情報交換をします!

### タイムライン
- アイスブレイク[5min]
- YWの記入[5min]
- YW共有タイム[8min]
- QAタイム[10min]
- Tを付箋に好きなだけ書く[5min]
- T共有タイム[8min]
- TのQAアンドバッファ[4min]
- ぽじねからの確認&お返事タイム[15分]

今回は、わたしがグループワークに参加するのは、タイムラインの最後にあるぽじね*1からの確認&お返事タイムからとし、なるべく、1on1受ける側のメンバーだけで闊達に意見を交わしてもらい、お互いのFBやわたしへのFBが自由に生まれることを期待しました。

やってみてどうだったか

f:id:katsutomu0124:20191211200814p:plain それぞれの1on1へ持ちかえる様な情報交換が生まれることを期待していましたが、それだけでなく、元々1on1が持っていた役割を別のミーティングで解決するTが出たことが印象的でした。抜粋すると以下の2つになります。

  • せきねからのフィードバックだけでなく、チームでお互いにフィードバックをし合えるようにする
  • 目標の進捗管理はチームのミーティングでも可視化できるようにする

それぞれの1on1を話し合った副次効果として、課題へのアプローチ方法が整理され、1on1以外の方法で解決する動きにつながったこは非常に良い変化だと捉えています。

また、今回は目的が違ったため、同様のアプローチはとりませんでしたが、継承元では以下のワークを実施していました。

コネヒトではOKRによる目標設定をしているのですが、その過程で中間振り返りを実施するタイミングがあります。そこで目標達成のためにいつも1on1より長めに時間を確保し、目標の振り返りと併せてワークショップ形式でフィードバックを行いました。

1on1の場に限らずですが、協働体制を築き、成果に向かう為に、それぞれで試行錯誤を繰り返しています。

まとめ

今回は、私の実施した2つの取り組みについて、紹介させて頂きました。ただ、当たり前ではありますが、初回のセッションで目的の認識を十分に揃えていたとしても、1on1の場で話を聞く、わたしの姿勢やスキルが不十分であれば、良い時間にはなっていかないと考えています。実際、1on1の場では、まだまだ実力不足を痛感することが多く、日々猛省をしています。 1on1が、その人がその人らしく、成果に向かいつつ、お互いの成長を促進するためのツールであり時間であることを目指して日々精進していこうと思います。

また今回、実施した1on1 Introduction Sessionは以前参加したエンジニアのためのコーチング入門という勉強会で知った手法です。その節は貴重なお話をありがとうございました! speakerdeck.com

PR

今回紹介したように、マネジメント観点でもエンジニア組織として今よりもよくしていきます!一緒に試行錯誤をしてくれる方を募集中です。もし少しでも興味がありましたが、ぜひ一度遊びに来てください!!! Connehito Image 家族の課題を解決するサービスのMAUを増やすAndroidエンジニア募集!

*1:社内ではぽじねという愛称で呼ばれています

iOSアプリエンジニアから担当領域を拡大するためにやっていること

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

はじめに

こんにちは!ママリ開発チームでアプリケーションエンジニアをやってるaboです。

5月に入社した私はママリ開発チームに所属し、Swiftを使ったiOS版ママリアプリの開発を担当してきました。ママリ開発チームというのは、iOS/AndroidアプリとWeb、アプリから呼ばれるAPIやWebView、管理画面やバッチなど、とにかくママリに関連するシステム開発全般を担当するチームです。

そんなチームでiOSアプリ開発を担当していましたが、最近は少しずつサーバーサイド側の開発に手を出しています。今回はその「サーバーサイド開発への担当領域拡大」するためにやっていることを書こうと思います。

担当領域を拡大することを決めた背景

ママリ開発チームで半年弱過ごしましたが、サーバーサイドが絡む優先度の高いissueが多く存在するもののリソースが足らず後回しになってしまったり、チームがリソース効率を気にしながらプロダクトバックログ整理およびスプリントプランニングを行う状況を経験し、課題に感じていました。チーム内には、iOSとWebフロントエンドが書ける人やiOSとAndroidが書ける人が、普段から横断的に動いてくれています。私も前職でPHPやJavaScriptの経験があるので、この課題解決に貢献できるのではないかと考えていました。

チームとしての理想は、ママリに関連するシステム開発全般を担うチームとして、なるべく優先度の高い=KPIに効くissueにスウォーミングして捌ける状態だと思っています。

というわけで、まずは私ができることからということで、10月から「担当領域を拡大してチームの役に立つ」をテーマに、iOSアプリ開発も続けながら、サーバーサイド開発にも手を広げています。スプリントバックログに積まれたiOS側のissueが終わったらサーバーサイド側のissueを手伝ったり、優先度が高く対応工数が多くかかるサーバーサイド側のissueに最初から参加したり、といった感じです。

目的はあくまでチームの役に立つ=チームのKPI達成に貢献すべく優先度順にissueを捌けるようにすることであり、そのためなら一口に「担当領域の拡大」と言っても拡大先はいくつか存在します。例えばAndroidアプリエンジニアや、スクラムマスターなどが考えられます。とはいえそこは選択と集中で、前述した課題感も踏まえ、今はサーバーサイド開発に絞っています。

以上の目的を達成すべく、サーバーサイド開発をキャッチアップするために私がやっていることをご紹介します。

やっていること

それは、社内にあるたくさんのお宝を取りにいくことです。ここでは三つのお宝をご紹介します。

f:id:aboy_perry:20191205210132p:plain
社内にはお宝がいっぱい!

社内のドキュメントやプルリクエストを見る

一つめのお宝は社内に蓄積したドキュメントや、過去のPRです。

ドキュメントはたくさんありますが、まず最初に読んでおくといいものを読み、その後は辞書みたいに使います。過去のPRには実装の背景や、実装時の議論が書いてありますし、リファクタコミットはBefore/Afterで何がどう良くなったのかが差分で確認できます。

試しにslackで聞いてみたところ、最初に読むと良いドキュメントや、その人自身が「これ勉強になった!」と思ったリファクタissue等がすぐに集まりました。感謝!

f:id:aboy_perry:20191209145822j:plain
slackで聞くやいなやシュバババっと協力してくれてる様子

※画像内の「観光案内」とは、開発フロー等PJ全般で取り入れていることや、コーディングするうえで気をつけるべきことなどが書かれてあるドキュメントのこと。

過去に起きた障害の振り返り資料と対応タイムラインを読む

二つめのお宝は障害の振り返り資料や対応タイムラインです。

社内ではFTS(Fail Teaches Success)という名称で呼ばれている、障害ごとに検知〜対応、原因などをまとめた資料を過去に遡って読んでいます。また、障害時にはslackにテンポラリーチャンネルを作ってそこでやりとりをして情報を集約しているので、そのチャンネルのやりとりも読んでいます。

振り返り資料を読むことで、PHPを書くときに気をつけたほうが良さそうなポイントや、システム間の関連性・影響範囲の情報が集まります。障害対応のタイムラインを読むことで、その人たちの思考が追体験できます。

実は入社して数ヶ月たったくらいに全てに目を通したのですが、その時はiOSアプリ起因の障害の場合は理解できたものの、サーバーサイド起因の障害の場合なんのこっちゃわからなかったというのが正直なところです...。

今でもひとつひとつが重厚なのには変わりありませんが、サーバーサイド開発に触れ始めたことと、「担当領域を拡大するぞ」という意識のせいか前より理解できるようになってきているので、今後もじっくり続けたいことのひとつです。

ペアプロ/モブプロを活用する

三つめのお宝は人です。

ドキュメントを見て完璧に理解しその瞬間から実践できるようになったら幸せなんですが...私は手を動かさないとなかなかできるようになりません。実際にサーバーサイド側のissueを実装していると、悩んだり、つまずくことがたくさんあります。そのときにはペアプロ(ないしはペア作業)をお願いするようにしています。

また、毎週金曜日にママリ開発チームのサーバーサイドエンジニアみんなで半日ほどモブプロをしてみています。これは熟練者がいなくても品質と実装スピードが担保されるようにするためで、その熟練者がみんなを集めて始めてくれました。

私のとってモブプロやペアプロの大きなメリットはすぐ聞けることです。物理的に近い距離にいて同じ問題に向き合っていることと、「疑問点や不明点があれば、理解出来るまで質問したり意見したりする」のを是としている前提というのが大きく働いていると思います。これらはオンボーディングにもとても有効だと感じています。私自身はPHPの経験があるので「PHP?なにそれ(・ω・`)」とまではいかないものの、コネヒトで採用しているCakePHPやDockerなどの知識と経験は無いため、ちょっとしたことでも聞けるという雰囲気はとても助かっています。

ペアプロとモブプロを初めてみてまだ日は浅いですが、すぐ聞けることによって主に次のような効果を実感しています。

  • 熟練者が使っているIDEの便利な機能を知ることができる
  • 環境系のトラブルがすぐ解決できる
  • 手が止まらない

熟練者が使っているIDEの便利な機能を知ることができる

モブプロやペアプロだと実際に開発する様子を目で見れるので、"使える"機能を知ることができます。とにかく分からない操作に対して「今のなんですか?!」「どうやったんですか?!」と聞くようにしています。

例えば初回のモブプロでは、次のようなPhpStormの機能と使い方を知ることができました。

  • Shift 2回 どこでも検索
  • ⌘ + E 最近開いたファイル
  • ⌘ + l 行:列移動
  • Presentation Mode
  • どこでも検索でGithubのリンクを貼っても行番号つきでジャンプできる
  • 逆にコードからハッシュ付きでGithubを開ける
  • 範囲選択してReplace
  • 可愛いGopherプラグイン

f:id:aboy_perry:20191205164830p:plain
ローディングがGopherになるプラグイン。かわいい。

plugins.jetbrains.com

IDEの効果的な活用は生産性向上に繋がるので、例えばチーム立ち上げ時に一度モブプロをチームで行えば、知識共有によってチームの生産性の水準をあげる効果が期待できるのではないかと思います。

環境系のトラブルがすぐ解決できる

実装そのものではなく周辺技術のトラブルは一人で作業していると沼にハマりがちですし、ましてや普段とは違う領域の開発環境ならなおさらだと思います。

実際、モブプロで私がタイピストの番に「composer installすると依存関係が原因で失敗する」というトラブルが起きたものの、モブの知恵を総動員して爆速で原因を探り、今根本解決すべきでないと判断しissueを別でたてて、その場は回避策を実行して開発を進める、といった一幕がありました。

環境系のトラブルにハマったときに、それは再現性のあるものなのか、原因は何で、回避策が取れそうか今根本解決すべき問題か、といった原因究明と対応方針決めが一緒に一気にできるのは効率的に感じました。

手が止まらない

いや実際には手が止まることはあるんですが、その時は誰かが知識を共有しているか、どう実装するかの方針決めの議論が進んでいるということです。設計や書き方に悩んでもその場でモブやナビゲーターと対面で議論できますし、議論中も実際にコードを書いてみながら進められる点がいい方向に働いているのかなーと思いました。

もうひとつ理由があるとすれば、集中力があがるためだと思います。私自身は複数人の時間を一気に使って行うので「集中しないと...!」という危機感に近い気持ちもあり、自分の役割がなんであれ自ずと集中力が増した実感があります。

f:id:aboy_perry:20191205204731j:plain
楽しいモブプロの様子

おわりに

今回「やっていること」で書いたお宝は、コネヒトに限らず多くの組織に存在するはずですので、新しい組織や領域に加わる際のキャッチアップの参考に少しでもなれば幸いです。

この取り組みはまだ始まったばかりですので、「やっていること」であげた項目は今後も引き続き継続していこうと思っています。ただし、同時に「これが"チーム"の成果に繋がっているか?」という視点は忘れずに持って進みたいと思っています。チームがリソース効率を気にせずに優先度順にissueを捌けるようになり、チームのKPI達成に貢献できるように頑張ります!


コネヒトではチーム開発が好きなエンジニアを募集しています!家族向けサービスをつくりたいエンジニアの皆さん、お待ちしています。

www.wantedly.com

会議量を見える化する「カレンダー調太郎」を作りました

本記事はコネヒト Advent Calendar 2019の9日目のエントリーになります。

こんにちは!CTOの @itosho です。Jリーグでは横浜F・マリノスが15年ぶりに優勝しましたね⚽おめでとうございます!僕は鹿島アントラーズのサポーターなので、天皇杯に気持ちを切り替えています。

早速ですが、皆さん、会議は好きですか?僕もそうなのですが、しなくていいならしたくないという方が多いのではないでしょうか?というわけで、今日は会議の話をしたいと思います。

カレンダー調太郎とは?

カレンダー調太郎(しらべたろう、と読みます)は開発部*1メンバーの1週間の会議時間を見える化するSlackアプリです。毎週金曜日にみんなの1週間の会議時間をSlackに通知します。また、前週比や来週の見込みも分かるようにしています。

f:id:itosho525:20191208015804p:plain
カレンダー調太郎の通知の一部

こんな感じで通知され、僕の会議時間が21時間だったことが分かります。

何故つくったか?

過去にこんなツイートをしている通り、僕はエンジニアたるものコードを書いてなんぼという考えがあります。特にCTOになってから会議の時間が増え、「これじゃあChief Technology OfficerじゃなくてChief Meeting Officerやで」という課題意識がありました。また、僕以外のメンバーでも時期によって(例えば、目標設定や評価の期間など)、会議の時間が増えることがあります。

ただ「会議が多いな〜」と思っても、それが可視化されていないと危機感が持ち辛く、惰性で過ごしてしまいがちです。そこで定期的に会議量をみんながいる場所で知らせることで改善が進むのでは?と考え、カレンダー調太郎を作りました。

例えば上述の例で言うと、僕の場合週の半分以上の時間が会議に費やされていることが分かります。これは明らかに健全ではないと思うので、減らそうという力学が働きます。実際、見える化してから定例で入っている会議の棚卸しを実施し、頻度や時間を変えるなどのアクションを行いました。

どうやってつくったか?

前提として弊社はG Suiteを利用しているのですが、特別なことはしておらず、スプレッドシートにGoogleカレンダーID(弊社の場合はメールアドレス)とニックネームのリストを作成して、そのリストを元にGASからGoogleカレンダーのAPIを使って、各メンバーの予定を取得して、それをゴニョゴニョして、Slackに投稿しているだけです。シートはこんな感じです。

f:id:itosho525:20191208022314p:plain
スプレッドシートのメンバー管理

GASでやっている処理はコードを読んだ方が早いと思うので、1時間くらいで書いたコードを晒しておきます。NYSL的な感じで自由に使ってください。

// GASの定期実行で呼ばれるメイン関数
function main() {
  var thisMonday = getTargetDay(0);
  var thisSaturday = getTargetDay(5);
  var lastMonday = getTargetDay(-7);
  var lastSaturday = getTargetDay(-2);
  var nextMonday = getTargetDay(7);
  var nextSaturday = getTargetDay(12);
  
  var members = getMembers();
  var message = 'こんにちは〜カレンダー調太郎です!今週のMTG時間をお知らせするよ〜\n';
  message = message + '```\n';
  
  for each(var member in members) {
    var calendar = CalendarApp.getCalendarById(member[0]);
    var thisTotalHours = getTotalHours(calendar, thisMonday, thisSaturday);
    var lastTotalHours = getTotalHours(calendar, lastMonday, lastSaturday);
    var nextTotalHours = getTotalHours(calendar, nextMonday, nextSaturday);
    var diff =  thisTotalHours - lastTotalHours;
    
    message = message + '## ' + member[1] + '\n';
    message = message + '- ' + thisTotalHours + '時間';
    
    var compared = '先週比:';
    if (diff > 0) {
      compared = compared + '+' + diff + '時間';
    } else if (diff < 0) {
      compared = compared + diff + '時間';
    } else {
      compared = compared + '増減なし';
    }
    var forecast = '来週の見込み:' + nextTotalHours + '時間';
    
    message = message + '(' + compared + ' / ' + forecast + ')\n';
  }
  
   message = message + '```\n今週もおつかれさまでした〜';
  
  postSlack(message);
}

// add=0にすると今週の月曜日を返す
function getTargetDay(add) {
  var today = new Date();
  var date = today.getDate();
  var day = today.getDay();
  today.setDate(date - day + 1 + add);
  
  return new Date(Utilities.formatDate(today, "JST", "yyyy/MM/dd")); 
}

function getMembers() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheetByName('foo'); // シート名は適宜変更してください
  var lastRow = sheet.getLastRow();
  var members  = sheet.getRange(2, 1, lastRow - 1, 2).getValues();
  
  return members;
}

function getTotalHours(calendar, from, to) {
  var events = calendar.getEvents(from, to);
  var totalDiff = 0;
  
  for each(var event in events) {
    var startTime = event.getStartTime();
    var endTime = event.getEndTime();
    var diff = endTime - startTime;
    
    var hour = Math.floor(diff / 1000 / 60 / 60);
    // 終日の予定などを除外する
    if (hour >= 8) {
      continue;
    }
    var title = event.getTitle();
    // フレックスの予定を除外する
    if (title.indexOf('フレックス') != -1) {
        continue;
    }
    // 退社の予定を除外する
    if (title.indexOf('退社') != -1) {
        continue;
    }
    // 勉強会の予定を除外する
    if (title.indexOf('勉強会') != -1) {
        continue;
    }
    var guests = event.getGuestList(true); // trueの場合イベントオーナーを含む
    // 参加者が2人未満の場合は除外する
    if (guests.length < 2) {
      continue;
    }
    
    totalDiff = totalDiff + diff;
  }
  
  var totalHours = Math.round(totalDiff / 1000 / 60 / 60);
  
  return totalHours;
}

function postSlack(message) {
  var payload = JSON.stringify({'text': message});
  var options = {'method': 'post', 'contentType': 'application/json', 'payload': payload};
  UrlFetchApp.fetch('https://hooks.slack.com/services/foo', options); // URLは適宜変更してください
}

1点、当たり前ですが「カレンダーに登録されている予定 = 会議」とは限らないので、ご注意ください。コネヒト社ではフレックス制を導入しているのでフレックス退社っぽい予定や勉強会っぽい予定、個人のToDoとして登録してるような予定は除外しています。カレンダーの使い方は人それぞれですので、あくまで相対的な指標としてみるのが良いかもしれません。

まとめ

カレンダー調太郎自体はまだ作ったばかりなので、今後継続的なチューニングが必要ですが、まずは見える化が出来たことは良かったと思います。ただ、見える化と言うからにはきちんと改善のアクションまで持っていかなくてはいけないので、そこはもう少しデザインしていく必要があるかなと思います。例えば、定期で入っている予定があれば「これは本当に必要な会議なのか?」的なリマインドを時々するといった機能を入れるといったことを考えています。

もちろん、会議をすること自体は悪ではありません。しかし、やはりエンジニアはコードで社会に対して価値を生み出していくのが生業だと思うので、会議の量は開発組織の健全性を測る指標の一つになるのではないかと考えています。というわけで、今後もコネヒトでは会議の減らして、生産性を上げていく取り組みを積極的に行っていきたいと思います!

PR

というわけで、弊社では自分の書いたコードで社会を良くしていきたいエンジニアを大大大募集しております! 1mmでも興味がありましたら、下記のWantedlyから「話を聞きに行きたい」ボタンをポチっと押していただくか、僕にDMを雑に送っていただいても構いませんので、まずはカジュアルにお話させていただければと思います!

www.wantedly.com

*1:弊社のエンジニアやデザイナーが所属する部署

Github Actionsを使ってリリース作業をちょっと楽にした話

こんにちは!2017年11月にAndroidエンジニアとしてJoinした関根です。 最近、髪の毛を金髪にしました。近々Android Studioへの敬意を評して緑色に染め直そうかなーと考えております。

今回は、コネヒトのAndroidチームでGitHub Actionsを利用して実践している事例を紹介したいと思います。

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

GitHub Actionsとは

本題に入る前に、簡単にGitHub Actionsについて紹介します。

GitHub Actionsは2019年11月に正式版となったGitHubの機能の一つです。 特定のイベントをトリガーにして、自動で一覧のプロセスを実行してくれます。 例えば、GitHubにブランチが作られた際にプルリクエストを自動で作成するというようなプロセスを実現できます。GitHubでは一連のプロセスをワークフローと呼び、個々のタスクのことをアクションと呼んでいます。 それぞれを解説すると以下の通りです。

ワークフロー

どのGithubイベント発生時に何を実行するかのプロセスが定義されたもので、GitHub Actionsの起点となる概念です。ビルド、テスト、パッ ケージ、リリースなどの用途にリポジトリ毎にセットアップが可能で、YAMLで記述を行います。例えば、Push時に特定のプロセスを実行する場合には下記のような記述をします。

f:id:katsutomu0124:20191206005909p:plain

ジョブと呼ばれるステップを設定することが可能で、このジョブの中でアクションを実行していきます。

アクション

ワークフロー内で実行される個々のタスクのことで、GitHub Actionsの最小構成要素で中核的な概念の一つです。

アクションには現時点でJavaScriptとDocker コンテナの2種類の作成方法が用意されており、ワークフロー内で下記の3つの方法でアクションを参照し、利用が可能です。

  • 同じリポジトリで定義されているアクション
  • GitHubのOSSのアクション
  • 公開されているDockerコンテナイメージ

f:id:katsutomu0124:20191206011920p:plain

このようにワークフローを作成し、ステップごとにアクションが呼び出されることで、一連のプロセスが実行されていきます。 コネヒトではAndroidアプリのレポジトリ内にワークフローとアクションを用意し、利用しています。 より詳しい利用方法は公式ドキュメントを読んでみてください。

リリース作業とその手間

さて、タイトルにも書かせて頂いた通り、Androidチームでは現在リリース作業でGitHub Actionsを利用しています。コネヒトでのAndroidアプリのリリース作業は下記の通りです。

  1. developブランチからreleaseブランチを作成する
  2. releaseブランチからmasterブランチへPull Requestを作成する
  3. releaseブランチでテストを行い、社内へのベータ配布を行う
  4. ストアへの段階リリースへを開始する
  5. releaseブランチをmasterブランチにmergeする
  6. GitHubにリリースノートを作成する

履歴の可視化や障害発生時の切り戻しのために上記のような手順を踏んでおりますが、これらの手順は地味ながら週に一度のリリースでは中々に手間のかかる作業でもありました。そういった作業は自動化した方が効率的に業務を進められるので、GitHub Actionsを利用して自動化に臨みました。

そして自動化へ

GitHubの操作を伴うものを自動化を行う箇所として選択し、現在は下記の3つのアクションを利用しています。

  1. リリースプルリクエストの自動作成のアクション
  2. リリースノートの自動作成のアクション
  3. Slack通知用のOSSのアクション

この3つのアクションで、先述したリリースフローの2、5、6のステップを自動化しています。

リリースプルリクエストの自動作成

まずはリリースプルリクエストの自動作成から紹介いたします。 コネヒトではリリースブランチを作る際に、履歴の可視化のために、そのバージョンに追加される機能やバグ修正のリストを、プルリクエストの本文に残すようにしております。この作業を自動化するために、以下のアプローチをとりました。

  1. releaseブランチが作られたことをトリガーとするワークフローの作成
  2. 前回リリースからの差分を元にしたプルリクエストを作成するアクション

ワークフローとアクションはそれぞれ以下のような実装です。

ワークフロー

f:id:katsutomu0124:20191206020842p:plain

①の設定でGitHubにPushし、ブランチが出来上がることでワークフローが実行されます。 今回はリリースブランチのみでアクションを実行したいので、②でif構文を利用してブランチ名のプレフィックスでフィルターをかけています。 ①と②の両方の条件をみたされると、指定のアクションを実行され、プルリクエストを作成しています。

アクション

プルリクエストの作成は、Docker コンテナのアクションで、下記のようなスクリプトを実行しています。

f:id:katsutomu0124:20191206021517p:plain

①で前回のリリースタグとの差分をリストアップして、プルリクエストの本文を作成しています。現段階ではDraftの状態でプルリクエストを作り、手動で読みやすく体裁を整えた上でReady For Reviewとするようにしています。

これらの実行後には下記のようなプルリクエストが作成されます。

f:id:katsutomu0124:20191206024044p:plain

これで手順がreleaseブランチをpushするだけで、プルリクエストの作成まで、半自動的に行われるようになりました。

リリースノートの自動作成

続いてリリースノートの作成です。こちらはストア公開後に履歴を残すために実施しております。 リリースノートを手動で作成すると、タグの設定、タイトルと本文の明記などの細かな作業に加えて、typoの有無やタグを作成するブランチの向き先が正しいかなど、目視確認をすることも多く、面倒な作業となっていました。

これらを解決するためにGithub Actionsで以下のアプローチを行いました。

  1. masterブランチへのプルリクエストがマージされたことをトリガーにするワークフローの作成
  2. プルリクエストの内容を元にGitHub Releaseを作成するアクション

ワークフローとアクションはそれぞれ以下のような実装です。

ワークフロー

f:id:katsutomu0124:20191206022626p:plain

masterブランチへのプルリクエストがマージされたことを達成するために①、②、③との箇所でそれぞれトリガーの設定とフィルター設定をしています。記述箇所が離れてしまっているのが、たまに傷ですね・・・。もし、よりシンプルな記述方法があれば教えてください!

アクション

f:id:katsutomu0124:20191206022857p:plain

①で元のプルリクエストの本文を取得し、リリースノートに利用しています。このようにワークフローの起点となったGitHubイベントのペイロードが$GITHUB_EVENT_PATHにjsonファイルで保存されているので、必要な情報を読み出し、アクション内で利用することが可能となっています。

これらが実行されると下記のようなリリースノートが作成されます。

f:id:katsutomu0124:20191206024728p:plain

OSSを利用したアクション

またコネヒトでは、社内へのベータ配布の前にPlayStoreのこのリリースの新機能に掲載する文章をディレクターに考えてもらっています。プルリクエストの内容を元に文章を考えるのですが、GitHub Actionsの導入前までは手動で共有を行なっていました。現在はOSSのアクションを利用してSlackへの通知を行なっています。

f:id:katsutomu0124:20191206024944p:plain

OSSのアクションを利用するだけであれば、ワークフローの定義を用意するだけで一連のプロセスを実行してくれます。

公式のアクションOSSのアクションにも注目しておくと便利なActionsを見つけられるかもしれませんね。

終わりに

以上が、AndroidチームでGitHub Actionsでリリース作業をちょっと楽にした話でした。今後は、iOSへの横展開やFirebase App Distributionへのテスト用バイナリをアップロードなどもGitHub Actionsで実践していこうかなと考えております。また進展があり次第、本ブログで発信していければと思います。 ここまでお読み頂きありがとうございました。

なお、コネヒトではAndroidエンジニアを積極募集しています、是非気軽にオフィスに遊びにきていただけるとうれしいです。 一緒に家族の課題を解決していきましょう!

Connehito Image 家族の課題を解決するサービスのMAUを増やすAndroidエンジニア募集!

JSConf JP に行ってきました!(1日目)

こんにちは!
フロントエンドエンジニアのもりやです。
ここはコネヒト開発ブログの投稿は初めてです。よろしくお願いします!

2019/11/30 に行われた JSConf JP の1日目に行ってきました。
実は、こういうカンファレンスへ行くのは初めてだったんですが、色々なお話を聞けてとても楽しめました!

私の聞いた内容を簡単に紹介させていただきます!

Opening talk (13:00 - 13:30)

f:id:hyirm:20191204172745p:plain

会場の 3331 Arts Chiyoda は、元々中学校だったところを改装した場所みたいですね。
Room A は普通の体育館で、なんか懐かしい雰囲気でした。

印象に残ったのは、いかなるハラスメントも認めない、ちゃんと報告用のURLも用意されているという点でした。
そういう配慮がちゃんとなされているのはすごく好印象でした。
あと、左のディスプレイにほぼリアルタイムで英語字幕が表示されます。すごい!

続きを読む