A/B テストの基盤を構築した話

こんにちは。エンジニアの濱田( @hamakou108 )です。

先日M&Aクラウドのサイト上で A/B テストを本格的に導入していこうという話になり、そのシステム基盤を構築する機会がありました。 今回は A/B テストの基盤構築に至った経緯とどのように実装したのかについて紹介したいと思います。

A/B テストの基盤構築に至った経緯

M&Aクラウドの開発チームでは事業上の目標到達に向けた施策実施とその効果測定を継続的に行っています。

tech.macloud.jp

A/B テストの導入によって一度に複数の案を検証することが可能となるのに加え、外的要因に左右されにくいデータが得られるため、より効果的に検証サイクルを回すことができると考えています。

さて A/B テストを実施する方法として、 Google Optimize などのツールを利用することも選択肢として挙げられます。 しかしクライアントサイドでページを差し替えるパターンではページフリックのような UX 低下を招く可能性があること、振り分けたユーザー集団が特異性を持たないように振り分けロジックを自分たちで開発・保守したいというニーズもありました。 このような理由により、ユーザーの振り分けやページの差し替えといったロジック基盤を自前で開発することになりました。

設計と実装

A/B テスト基盤の開発において工夫した主なポイントは次のようなものです。

  • セッションの特定、および偏りのないユーザー振り分け
  • Laravel と Nuxt.js の両方で同じように機能すること
  • A/B テストの開始と終了のイベントの集計

これらについて順番に解説します。

セッションの特定、および偏りのないユーザー振り分け

あるユーザーに対して行ったページの出し分けとアクションの結果を紐付けるために、そのセッションを一意に特定する必要があります。 これを実現するために A/B テスト用の Cookie を発行して、ユーザー(クライアントのブラウザ)ごとに一意な ID を割り当てています。

テストパターンの振り分けを行う際は、まずこのユーザー ID ClinetId とテスト名 TestName を元に 0 から 1 までのランダムな実数値を出力し、その値を元に以下のようなロジックでテストパターン Varinat のどれかに振り分けています。

<?php
function chooseVariant(
    TestName $testName,
    ClientId $clientId,
    array $variantList
): Variant {
    // TestName と ClientId を元に 0 から 1 までの実数値に変換する。
    $frequencyPercentage = FrequencyPercentage::makeFromString(
        $testName->rawValue() . $clientId->rawValue()
    );

    $max = new VariantPercentage(0);

    foreach ($variantList as $variant) {
        /** @var Variant $variant */
        $min = new VariantPercentage($max->rawValue());
        $max = $max->add($variant->getPercentage());

        // どの Variant を何%の確率で出現させるかあらかじめ定義してある。
        // 例えば VariantA と VariantB にそれぞれ50%を設定した場合、
        // 数値が 0-0.5 なら VariantA に、 0.5-1 なら VarinatB に振り分けられる。
        if (
            $frequencyPercentage->isGreaterThanOrEqualTo($min)
            && $frequencyPercentage->isLessThanOrEqualTo($max)
        ) {
            return $variant;
        }
    }

    throw new LogicException('A/B テストのパターンを一意に決定できませんでした。');
}

任意の文字列を 0 から 1 までの数値に変換するロジックの実装(上記コードの FrequencyPercentage::makeFromString の中身)に関しては Qiita に記事を投稿しているので、是非のぞいてみてください。

qiita.com

Laravel と Nuxt.js の両方で同じように機能すること

M&Aクラウドは大部分が Laravel で実装されていますが、一部のフロントエンドは Nuxt.js への移行が進んでいます。 Nuxt.js 移行の詳細に関しては次の記事をご覧いただければと思います。

tech.macloud.jp

tech.macloud.jp

tech.macloud.jp

プロジェクトは今も進行中のため、現時点ではフロントエンドに Laravel と Nuxt.js が混在する状態となっています。 したがって例えば出し分け対象のページを Laravel 、ユーザーアクション後に訪れるページを Nuxt.js が生成しているといったケースも考えられ、このようなケースでも A/B テストが機能するように配慮する必要がありました。 このためどちらのシステムが発行した Cookie であるかに依らず、それぞれのシステムで同じように Cookie を取り扱えるような工夫をしています。 こちらの実装の詳細に関する記事も Qiita に投稿しているので、是非のぞいてみてください。

qiita.com

A/B テストの開始と終了のイベントの集計

A/B テストの結果の集計は Google Tag Manager (以下 GTM と表記)と Google Analytics (以下 GA と表記)を用いて行っています。

GTM の方には特定のイベントを受け取ったらそのデータを GA のイベントデータの形式にフォーマットして送信するように設定しておきます。 クライアントサイドの JS からは以下のようにして GTM にイベントを飛ばすことができます。

<script>
  window.dataLayer.push({
    event: 'ABTesting',
    testCaseDefinition: testName,
    valiant: variant,
    testState: 'start'
  })
</script>

開始(対象ページへのアクセス)または終了(アクションの実行)のタイミングで GTM にイベントを飛ばすことで、 A/B テストの試行回数とアクションの結果を GA 上で確認することができるようになります。

まとめ

M&Aクラウドにおける A/B テストの基盤作成の方法について紹介しました。 A/B テストを自前で実装することを検討している方の参考になれば幸いです。

PHPカンファレンス2020に弊社のエンジニアが4名登壇します🎉

こんにちは、M&Aクラウドの津崎(@820zacky)です。

弊社エンジニアの4名がPHPカンファレンスというイベントに登壇することとなりました🎉

今回は、登壇が決定するまでの流れと、どんな発表をするか?というところを書きます。

PHPカンファレンス2020とは?

phpcon.php.gr.jp

PHPカンファレンスは日本最大のPHPのカンファレンスです。

今年開催されるPHPカンファレンス2020は、12月12日にオンラインにて開催されます。

PHPカンファレンスでは、登壇したい人がプロポーザルを提出し、それを運営者が選定することでスピーカーを決めています。

今年は、9月16日から10月5日にかけて募集され、10月18日に採択が発表されました。

いいチームの作り方 と 全員プロポーザル宣言

https://cdn-ak.f.st-hatena.com/images/fotolife/k/kazuhei0108/20191025/20191025190323.png

今回、弊社では全エンジニアがプロポーザルの提出を行いました。 これは、我々エンジニアチームの「いいチームの作り方」に基づいて、「発信」に力を入れているためです。 我々が考える「いいチームの作り方」については、ブログ記事に説明があるのでよろしければご覧ください。

tech.macloud.jp

そういう背景があり、CTOの命に依り「全員プロポーザル宣言*1」が発令されました。

プロポーザル紹介

弊社のエンジニアが提出したプロポーザルを紹介します。

[採択]再コンパイル不要! core dump さえ吐ければ gdb デバッグできます

弊社インフラの神、鈴木さん( @yamotuki ) による発表です。

segment fault怖いですか? 私は怖いです。 でも大丈夫。鈴木さんが悪魔の倒し方を教えてくれます。 悪魔との格闘の末、銀の弾丸を手にした鈴木さん。彼の目にsegment faultはどう映るか。

fortee.jp

[採択]Laravelで運用しているサービスをNuxt.jsにリプレイスする

Nuxt.jsへのリプレイスを先導する弊社のフロントエンドモンスター久保田さん ( @kubotak_public ) による発表です。

リプレイス大臣がNuxt.jsへのリプレイスの奇蹟を爆笑を交えてお届けします。

fortee.jp

[採択]PHPer のための Vim 実践入門

弊社のタイピングマスター濱田さん( @hamakou108 ) による発表です。

弊社でもっともタイピングが早い男が愛用するVim。 みんながちょっと憧れて、みんながちょっと怖がってる。 そんなVimを愛し、Vimに愛された濱田さんによる、VimIDE的なことを実現する方法の紹介です!

fortee.jp

[採択]めざせブレークポイントマスター

私(津崎 @820zacky)の発表です。

あなたはXdebugしてますか?

Xdebugを使いこなすために便利な、ブレークポイントの知られざる機能を発見したので共有します。

デバッグでみなさんを幸せにしたい気持ちで溢れたやさしいプロポーザルです。

fortee.jp

[不採択]LaravelとAWSでサービスとともに設計を成長させる

弊社で一番テーブルフットボールが強いエンジニア、CTOのかずへいさん( @kazuhei__ ) によるプロポーザルです。

弊社サービスについて、成長に伴ってどのように設計が変わっていったのか熱く語ってくれるはずでしたが、残念ながら不採択となってしまいました。

fortee.jp

[不採択]テストを設計する

こちらもCTOによるプロポーザルです。

弊社サービスのテスト設計については、エンジニア内で多くの時間を使って議論しました。テストについて考え尽くした男の発表に乞うご期待。と言いたいところでしたが、こちらも残念ながら不採択でした。

fortee.jp

終わりに

弊社では全エンジニアが頑張ってプロポーザルを出しました。 5人中4人も登壇できるのは中々の快挙ではないかなと思います。

弊社では、PHP以外にもLaravelやVueのイベントなんかにも積極的に参加していますので、 弊社のエンジニアを見かけた際はお気軽にお声がけください。

また、弊社ではエンジニアの採用を行なっています。 ベンチャー企業でのWeb開発に興味のある方は、ぜひカジュアルにご応募ください🤝

www.wantedly.com

www.wantedly.com

*1:全エンジニアを対象とし、プロポーザル最低限一個、絶対に出しましょうというキャンペーン

SEOの観点からの高速化の取り組み(募集詳細ページの高速化編)

こんにちは。エンジニアの鈴木(@yamotuki)です。

以前、こちらの記事 で速度改善の前の測定のためにSpeedCurveを導入したという話を書きました。 今回はSEOの観点から Google Search Console で警告が出たページについて速度改善を試みた話を書いていきます。

ページ速度とSEOの関係

Google Search Console のなかの「ウェブに関する主な指標」というところでヘルプに書かれている指標は以下の3種類でした。以下の3つはCore Web Vitals とも呼ばれています。

LCP(Largest Contentful Paint): ページが最大の視認可能な要素をレンダリングするまでの時間
FID(First Input Delay: 初回入力遅延): ユーザーの操作に対してページがレスポンスを開始するまでの時間
CLS(Cumulative Layout Shift): ページの読み込み中にページの UI 要素がどの程度移動するかを示します。

ページ速度でいうとLCPとFIDが関わりますが、今回の記事での話はLCPの値について焦点を当てて対応したところについてです。

この指標のいずれかに問題があると「不良」や「改善が必要」となるようです。 具体的には、LCPだと閾値が4.0秒以上で不良、2.5秒以上で「改善が必要」と表示されていました。

具体的に速度の問題がなくなるとどれくらい検索順位が上がるかは明確には分かりませんがこちらのドキュメントgoogleの考え方が示されています。

取り組みの結果

先に、今回の記事で取り上げる改善だけでなく、一連の改善を行なった結果について記載しておきます。

LCPの改善だけではなく他の指標に対する改善も合わせてチームで取り組みました。その結果、以下のように大幅に指標の改善を行うことができました。

f:id:yamotuki:20201009174908p:plain
Google Search Console での経時での状態

LCP改善への取り組み

取り組む対象の特定

Google Search Console では問題があったページがどこなのか教えてくれます。 今回は、募集詳細ページ()のLCPに問題があることが分かったので取り組むことにしました。

LCPを分解する

LCP は以下の定義でした(再掲)。

LCP(Largest Contentful Paint): ページが最大の視認可能な要素をレンダリングするまでの時間

要するに、ファーストビューの最大の要素が見える状態になるまでの時間です。目立つのでそれが読み込まれると「大体表示されたな」と感じるユーザも多くなるラインです。

今回の対象のページでいうとファーストビューの大きな画像の部分です。

f:id:yamotuki:20201009180322p:plain
ファーストビューの画像

LCPを本当にざっくり分解すると以下のようになりそうです

  • バックエンドの応答を受け取る
  • HTMLの内容からJavaScriptCSS, 画像などダウンロードし始める
  • JavaScriptが差し込むHTMLも含めて解釈する(ダウンロードする時の設定による。詳細については、私はまだ理解が甘いですし長くなるので省きます)
  • まだダウンロード完了していない画像など待つ
  • 表示する

どこが遅かった?

今回のケースだとバックエンドが明らかに遅く、TTFB(Time To First Byte)が3~4秒でした。表示している内容からすると1秒切っても良さそうなものなので、これを改善することにしました。他にも沢山改善ポイントはありますが、速度改善は改善効果の大きいものから行うのが原則です。

f:id:yamotuki:20201009181049p:plain
左のほうが改善前。3~4秒かかっている

対応

そうと分かればバックエンドの改善です。 TTFBを遅くしていた原因は、ページ下部にあった「類似の募集」の項目でした。画面表示に必要な情報を集めるためにSQLを多く発行するN+1問題が発生していました。 今回はちょうど機能改善のタスクもあり、速度改善と合わせて行うことになりました(実際には、バックエンド部分の実装自体は同僚の@kubotakさんが行いました。私は事前の速度測定とマークアップなど担当したタスクでした。) そこまで複雑な処理でもなかったので問題の詳細箇所特定は Laravel Debugbar で適当に挟んで計測したと聞いています。

qiita.com

改善の結果、バックエンドのレスポンスが3~4秒程度だったものが1秒切るくらいになりました。

終わりに

LCPを改善しろ、となって「画像が重いから改善しよう」など場当たり的に対処するとなかなか目標を達成することは難しいと思います。 推測するな計測しろ、の原則に則ってSpeedCurveにはこれからもお世話になっていきます。

弊社では一緒にエンジニア活動してくれる仲間を探しています。

www.wantedly.com

社内勉強会で「JavaScript Promiseの本」を読了しました。

こんにちは!M&Aクラウドの荒井です。

弊社では週に2回社内で勉強会を開いています。

水曜日の夜は「フロントエンド勉強会」、金曜日の夜は「アプリケーション設計勉強会」と題しまして、お題に合う教材を選定し、みんなで輪読しています。

今年の6月はじめ頃から9月末にかけて、フロントエンド勉強会にて、こちらの「JavaScript Promiseの本」を教材にさせていただきました。

azu.github.io

選定の理由

弊社ではPHP+JavaScript+Vue.jsで作られたアプリケーションをTypeScript+Nuxt.jsで置き換えるということを行っています。

PHPで吐き出したページ上でJavaScriptを実行するという形式だと、大半のロジックはPHP側で制御されるので、ユーザーのフォーム投稿といった一部の操作くらいでしかJavaScriptからサーバーにリクエストするということがありません。

しかしNuxt.jsに書き換えるとデータの取得は全てHTTP API経由になるので、APIでログインしているか確認して、ログインしていたらまたリクエストして、というような複雑なAPIリクエストがありえます。

そこで、Nuxt.js化をすすめる上で、メンバー内である程度のPromiseの知識、async、awaitの知識が必要だと考えました。

学びがあったところ

Promiseの作り方

https://azu.github.io/promises-book/#how-to-write-promise

開発をする上では、大体の非同期ライブラリがPromiseで結果を返してくれるようになりつつあるので、Promiseを作る機会よりPromiseを使うことの方が多いとは思います。

しかし、今でも場合によっては自分でPromiseオブジェクトを作りresolve、rejectを呼ぶ機会はあると思うので、それを難なく理解できるようになったことは良かったと思います。

qiita.com

非同期と例外周りの理解

非同期的な処理のcallback関数内での例外はtry構文でcatchできないことや

techblog.yahoo.co.jp

Async Function内で例外が発生したときはRejectedなPromiseが返るなど、非同期処理時の例外の扱いについて詳しくなることができました。

これにより開発時のデバッグがしやすくなったのではないかと思います。

おまけ

勉強会の途中で見つけたタイポを が修正してPRしたところmergeされました😉

github.com

まとめ

社内で勉強会をやることによって、メンバー内で最低限の知識を共有し、共通言語が生まれることによってコミュニケーションが簡単になります。 また、勉強会はきっかけづくりであり、個別のメンバーがさらに技術を深掘って身につけていってくれればと思います。

社内ハッカソンを通してCS部の業務効率化に挑戦しました

こんにちはkubotak(@kubotak_public)です

連載になってる社内ハッカソンシリーズです。 今回はついに社内優勝したチームの改善についてです。(自慢)

以前の記事をまだ読んでない方はこちら

ハッカソンの大まかな概要は以前の記事から引用

  • 目的は、「エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること」
  • テーマは「チームの業務効率化」
  • 10:00~17:00でヒアリングと実装を行う
  • 各チーム5分で発表

CSチームについて

さて、CSチームですが一般的にはカスタマーサポートかと思われるかもしれませんが、弊社ではカスタマーサクセスの略称の部署です。 CSは弊社サービスのM&Aクラウドの利用者をサポートして円滑にM&Aを行えるようにしています。

今回私kubotakはこのCSチームの業務改善をエンジニアとして行いました。

おことわり

残念ながら今回のハッカソンで行った業務改善の内容が社外秘を多く含む業務のため詳細に紹介することができません。 ですので、技術的視点での解説をしたいと思います。

改善すること

詳細をお話することはできませんが、以下のような作業がありました。

  1. とある部署Aがとある部署Bに依頼をする
  2. 依頼を受けた部署Bはとあるサービスにログインして検索機能を利用、その検索結果をもとに判断
  3. 依頼を出した部署Aに判断した結果を提供する

このとある部署Bの作業を自動化してみようという試みです。
CS部のみならず、他の部署でもこの依頼は行っていました。
つまり、この業務が改善されることはCS部だけでなく、会社全体を効率化できるアイデアということです。

作業を自動化する

ハッカソンでは(2)と(3)について自動化を試みました。
アーキテクチャは以下のようになります。

f:id:kubotak:20200923184249p:plain
アーキテクチャ

特定の操作を行った場合にJobを作成し、非同期的にJobでAWS Lambdaを起動します。
AWS Lambdaでは※Puppeteerを利用して(2)で人力作業していたものを自動化します。
その結果を受け取ってSlackに通知される仕組みです。

※Puppeteer
PuppeteerはHeadlessChromeを利用して機械的にブラウザを操作できるNode.jsライブラリです。

プレゼン時の嘘

実はこの構成はすべて完成していませんでした。
時間の都合上すべてを実装する時間がなかったので、プレゼンで見栄えよく騙せるできる機能のみ実装しました。
できている箇所とできていない箇所は次のようになってました

できている

  • LambdaとPuppeteerによる自動化
  • Slack通知

できていない

  • Job発火

プレゼン時は特定の操作の後、即座にSlack通知が行われるように細工しました。
Slack通知が行われたということは、裏でどんな処理が行われているのか・・・
これをローカル環境のPuppeteerでChromeが立ち上がって自動で処理される様子を見せて全体の流れを説明します。
オーディエンスには、この一連の処理が本当に自動化されたかのように見えていますが、実際にはダミーのSlack通知が行なわれているだけという訳です。

この嘘はバレることなく、無事プレゼンは終了し社内優勝を頂きました。
とはいえ機能自体は完成しているので本番転用も可能です。

本番への投入

若干アーキテクチャは変更になりましたが現在このプログラムは本番運用されています。
変更になったのはSlackからAWS Lambdaを起動するようになり、結果をSlack通知する処理もAWS Lambda側になったところです。
つまり、Laravelを一切介さないアーキテクチャになりました。
完全自動化ではなく、(1)の依頼ベースで起動して自動返答される様になっています。
実はハッカソンの時はこの依頼ベースもなくして完全に自動化できないかという観点で作っていましたが業務上は依頼ベースが良いということでSlackからの起動に変わりました。
AWS Lambda自体がSlackへ返答する仕組みを追加しましたが、機能は独立しているのでAWS Lambdaを起動するものを変えるだけで簡単に本番転用できたのは良かった点でした。

まとめ

CS部は他の部署と違い、所属しているメンバーのITリテラシーが特に高く、「GoogleAppsScriptなどを利用してスプレッドシートに一工夫してみようという改善は自分たちでもできそうなのでこのハッカソンではやらない。」と言ったように手間のかからない改善案は却下となりました。
今回ブラウザを自動で動かす、というのをPuppeteerで実現しましたが一緒になってコードを読んで1行1行の行ってる処理を説明するとすんなり理解してもらえたので一緒になってのコーディングが楽しかったです。 また、JavaScriptで実装したことから同じ言語で記述できるGoogleAppsScriptへの興味も高まったようです。

引き続き、無駄な作業は機械にやらせる。をモットーに業務改善をサポートしていきたいと思います。

社内ハッカソンを通してライティング部の業務効率化に挑戦しました

こんにちは。エンジニアの濱田( @hamakou108 )です。

前回の記事に引き続き、社内ハッカソンの模様をレポートしたいと思います。

tech.macloud.jp

社内ハッカソンの概要については前回の記事の説明を引用します。

  • 目的は、「エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること」
  • テーマは「チームの業務効率化」
  • 10:00~17:00でヒアリングと実装を行う
  • 各チーム5分で発表

ライティングチームについて

今回のハッカソンではライティングチームとして、ライティング部のメンバーと共に業務効率化を図るための作品を制作しました。 ライティング部では弊社サービスM&Aクラウドに買収・出資を行いたい企業様の募集掲載ページを作成するため、取材やライティング、募集公開に向けたスケジュール管理などを行っています。

macloud.jp

チーム名は横浜エクセレント。偶然にもチームメンバーの名字の頭文字をすべて含む住所を開始5分で発見し、その住所付近にあるエクセレントなビル名を拝借しました。

チームメンバーの頭文字から横浜エクセレントと命名
チームメンバーの頭文字から横浜エクセレントと命名

ハッカソン当日の流れ

課題の洗い出し

まず最初にライティング部の業務フローを教えていただき、それぞれのフェーズで抱えている課題の洗い出しを行いました。

業務フローの各フェーズに紐づく課題
業務フローの各フェーズに紐づく課題

図中で丸で囲まれた箇所が作業のフェーズを表しており、矢印に従って次のフェーズへと進んでいきます。 実際には担当者の移り変わりなどを考慮したより細かいフェーズが存在するのですが、課題の洗い出しに注力するため単純化しています。

この時間では各フェーズについて一つ一つ議論し、時間が掛かる作業、合理的でない作業、ミスが発生しやすい作業などをマインドマップのようにメモしていきました。 一通り課題が出揃ってから全体を俯瞰し、どのペインが大きいかライティング部のメンバーは意見を出し、自分はエンジニアの立場で制限時間内でどのような解決手段が取れそうか意見を出し、協力しながらフォーカスする課題を絞っていきました。

最終的に対象となったのは一番目のフェーズ「取材日程調整」に関わる課題です。

何を制作するか決める

取材日程調整における課題には以下のようなものがありました。

  • 取材関係者のスケジュールが変わりやすいため、日程調整がしづらい
  • スケジュール調整を目検で行っているため、工数が掛かる
  • 上記などが要因で日程調整が長引きやすいため、掲載までのスピードが落ちたり、掲載に至らなかったりする

課題を分析していくことで細かい問題点が募集掲載数という重要な KPI に影響を与えている可能性があるということが見えてきました。 これらの課題を解決できる以下のような特徴を備えた日程調整システムを作ることを今回のハッカソンでは目標としました。

  • 取材関係者のスケジュールを一括管理すること
  • 日程の調整を効率よくスピーディーに行うこと

制作

大きな目標を掲げたものの、このような日程調整システムを一日でスクラッチで開発するのは目に見えて困難です。 そこで今回はスケコンというサービスをベースにして、弊社のライティング業務にフィットしない部分や不足している部分を Zapier というサービスで調整・補完する方針で制作を進めました。

schecon.com

zapier.com

スケコンでは Google Calender と連携して自動的に参加者の空き時間に合わせて候補日程を決定することができます。

また Zapier は複数のサービス連携を GUI から簡単に行うことができるいわゆる iPaaS です。 スケコン経由で Google Calender に予定が追加されると、それをトリガーにして日程調整の後続作業の一部を自動的に行う Zap ( Zapier 上の一連の自動化タスクの通称)を作成しました。

  1. 日程が確定したことを部内の関係者に通知する必要がある。 -> Slack のライティング部のチャンネルに日程および Google Calender の予定の URL を載せたメッセージを自動送信!
  2. 先方に確定した日程および取材にあたっての注意点などをメールで知らせる必要がある。 -> Gmail 上で日程や注意事項が記された下書きメール 1 を自動作成!
  3. 取材先企業様との契約の進捗管理表を更新する必要がある。 -> Google Sheet の契約管理表の取材先企業の列を探して自動更新!

Zapier は他にも様々なサービスと連携が可能で、例えば契約管理が Salesforce に移行した場合に連携先を Salesforce に変更するといったことも簡単にできるので非常に便利です。

結果とその後

残念ながら最優秀賞を勝ち取ることはできませんでした。 しかし作成した Zap について他の部署から真似してみたいという声が上がるなど評価していただける点もあり、有意義な成果物になったと思います。

また課題の洗い出しの際に挙がったもののハッカソンでは解決できなかった項目の一部は Issue としてまとめられ、後々の開発で実装されました。 ハッカソンの目的の一つ「エンジニアが他のチームの業務を理解すること」をより進んだレベルで達成できたように感じています。

ハッカソンのような普段あまりプロダクトチームに届かない声を拾う機会が事業の成長の芽になりうるのだなということを実感しました。 引き続き One Team のバリューに沿って全社一丸となって成長していきたいと思います!


  1. 取引先企業様に合わせて文面を微調整する可能性があるため、送信は行わずに下書きの作成のみに留めています。

社内ハッカソンを通じてコーポレート部の業務効率化に挑戦しました

こんにちは、M&Aクラウドの津崎です。

今回は、前回の記事に引き続き、社内ハッカソンについての記事を書いていきます。

tech.macloud.jp

社内ハッカソンがどんな風に行われたかについての詳細は、前回の記事をご参照ください。

ハッカソンの内容をざっくり説明すると以下のような内容です。

  • 目的は、「エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること」
  • テーマは「チームの業務効率化」
  • 10:00~17:00でヒアリングと実装を行う
  • 各チーム5分で発表

私は、コーポレートチームにエンジニアとして参加しました。 今回はコーポレートチームがどのような活動をしたのかについて書いていきます。

チーム概要

コーポレートチームは、コーポレート部2名 + 社外取締役1名 + エンジニア(私)の4名です。 コーポレート部は、経理労務・総務などの業務を行う部門です。 コーポレート部2名のうち1名はM&Aのアドバイザーや上場準備などとの兼務のため、コーポレート部は実質1人で成り立っています。

ヒヤリング

コーポレート部の状況

  最近は社員数が一年で倍以上になっており、入社手続きや給与の処理など業務は増えていく一方で、コーポレート部の負担が増加していました。 これからも社員数が増加することを考えると、コーポレート部の業務効率化は重要なミッションです。 ヒヤリングをしたところ、真っ先に出てきたのが、契約書の管理 についてでした。

これまでの契約書の管理

コーポレート部で日常的に取扱う契約書は、プラットフォームへの掲載をする企業様と弊社との契約書です。 以下のような問題がありました。

  • SalesforceとSpreadSheetの2重管理
  • 契約締結からファイリング・保管までの細かな作業が多い
  • 多部署から口頭で契約状態や契約の内容の問い合わせを受ける
    • SalesforceもSpreadSheetも必要な人が必要な情報を気軽に見られるように整備されていなかった

f:id:zacky2:20200905170231p:plain

効率化の試み

話し合いの結果、以下のような仕組みの開発を行えばかなりの効率化が計れることがわかりました。

コーポレート部が契約書PDFをSalesforceにアップロード
  ⬇️
文章中の重要事項(契約の種類や締結日、金額など)を自動で抽出
  ⬇️
Salesforce上に自動入力

この仕組みができれば、

  • 契約書ファイルを開いてて入力する時間の削減
  • 多部署のメンバーはSalesforceですぐに契約状態を確認できるため、問い合わせ対応の時間削減

を実現できます。

試算では、年間720分(12時間) の時間を削減できることがわかりました。 これは、現状の契約数をベースに算出したもので、弊社がグロースすれば、その恩恵は積み上がっていきます。 2年後に現在のクロージング数の4倍まで成長すると仮定すると 年272時間 もの時間を削減できる見通しです。

そんな感じで、やることは決まりました。

実装

エンジニアがまず最初にすべきことは、やりたいことが実現可能か判断することです。

  • 技術的に実現可能か?
  • 与えられた時間・リソースで開発可能か?
  • 実現できないリスクはどの程度あるか?

今回はSalesforce上の機能の開発が必要そうですので、おそらくはApexという未知の言語での開発になることは予想していました。

私「ApexはJavaみたいって聞いたことあるしJavaは何となくわかる」
私「Javaならライブラリもいっぱいある」
私「やれるかどうかわからないけど、とりあえずやってみよう」

ということで、「実現可能かをいつまでに判断するか?」「実現できなかったらどうするか?」など考えずにヌルッと技術調査を開始しました。

私「ApexってJavaっぽいけど結構ちがう 全然ワカラナイ」
私「ApexからSalesforceにアップロードしたファイルにアクセスするにはどのAPIを使えばいいの?全然情報が見つからない」
私「ApexってJavaみたいにいろんな便利なライブラリ使えいないの? PDFを文字列に変換するにはバイナリにして自前でガチャガチャしなきゃいけないの? 」

こんな具合で、限られた時間で落とし所を見つけるのすら難しそうなくらい実現が厳しそうだとわかりました。

私「えっもうこんな時間! 今から作る内容変えるの無理だ・・・」

作業にのめり込みすぎて実現不能だと判断するのに時間がかかりすぎたため、作る内容を変える時間は残されていませんでした。 最初から未知の技術を選ぶのがミスってるし、「できないかもしれない」と思った時に、作業に没頭するのではなく手を止めて、「できません」というべきだったなと反省しています。

プレゼン

契約書の管理方法を変えて業務効率化を行うというアイデアと、業務時間が多く削減されるという点から社内からの反応はいい感じでした。 また、実装はうまくいきませんでしたが、なぜうまくいかなかったかを素直に話したら結構盛り上がりました。

まとめ

コーポレートチームの開発はうまく着地できませんでしたし、エンジニアとしてもっとうまくやれた点もあって反省している部分もあります。 ハッカソンは失敗せず限られた時間でいいものを作れるのが理想ですが、失敗しても大丈夫なところに魅力があるなと感じました。

他のチームでのハッカソンの様子については、また次回あたりで紹介されると思いますので、お楽しみに!

社内ハッカソンを通して営業チーム業務効率化を試みました

こんにちは。M&Aクラウドの鈴木(@yamotuki)です。

先日、第1回となる社内ハッカソンを行いました。 私は営業チームに入り込んで業務改善を試みたので、その記録をここに残します。他のチームにおけるハッカソンの成果については後続のブログで紹介されるかと思います。

こちらの写真はハッカソンにおける発表風景です。

f:id:yamotuki:20200828183850j:plain
ハッカソン発表風景

M&Aクラウドにおけるハッカソン

目的は、エンジニアが他のチームの業務を理解すること & 他のチームがエンジニアの仕事を理解すること。
初回のテーマは「チームの業務効率化」でした。
10:00~17:00でヒアリングと実装を行います。

以下のようなルールの下で行いました。

* それぞれのチームで日頃の業務から課題を抽出してください
* 発表時間は1チーム5分です。発表では資料に加え、必ず何らかの動くデモを発表してください。
* プログラムを書くことを必須にはしません
* 最優秀賞は皆さんの投票を一番集めたチームとします
* 評価基準は以下です
  * 勝つべくして勝つ - インパクトが大きいアイディアか?
  * 全員UX - チームメンバーが使いやすいかどうか?
  * One Team - 全員で協力して作ったか?

弊社には以下のような部署があるのですが、エンジニアがそれぞれの部署に入り込んで業務改善を目指しました。

  • 営業チーム
  • CSチーム
  • FAチーム
  • ライティングチーム
  • コーポレートチーム
  • 社長室チーム(このチームだけデザイナの長竹さんが担当し、プロダクトの案とXDでのモックと動画作成がされました)

事前準備

エンジニアの中で使えそうなツールと効率化についての意識合わせを行いました。

意識合わせについての資料

qiita.com

便利ツールについて

以下はチーム内で話した内容のメモです。裏を取っていないものも多いため、正確性には欠けるかもしれません。

  • Zapier
    • Slack とか G Suite などAPI提供しているツールを連携させるツール
    • エンジニアの濱田さん「github issue -> asana のタスク追加 をやったことがある」
    • だいたいIFTTTと同じ。Enterprise系のツールへの連携が強い。連携先が多い。IFTTTはtoC向け。
    • お値段: 月100実行まで無料。750タスクで2000円。 2000タスク/5000円。1タスク1.5~3円くらい
  • UiPath
    • RPA(robotic process automation)のツール。定番らしい
    • 画面上で入力して、クリックして、みたいなのを自動化できる。E2Eテストに近いイメージ
    • ブラウザだけじゃなくて画面のどこでも自動化できる
    • 画像をみて、画像の種類によって分岐させて次のタスクを実行
    • テキストでの分岐もできるっぽい?
    • お値段:不明。Trialがある。
  • studio
    • コーディングがいらないLP作成ツール
    • 動的なものは難しいかも?メルカリは作れない可能性が高い。
    • 普通にプロダクト部の業務改善に使えそう
  • FormRun
    • お問い合わせフォームが簡単に作れる
    • iframe で問い合わせフォームを組み込む
    • 問い合わせをこれで一元管理すればダッシュボードでまとめらていいかもしれない
    • studioで作ったLPにとりあえず組み込むとか、ありかもしれない
  • Bubble
    • Webアプリが作れる。ガチ目なNoCodeツール
    • イデア:営業専用アプリ、FA専用アプリなんかを作って日々の作業を隙間時間に気軽にできるようできるとか?
  • Glide
    • https://note.com/a_n_do/n/n7b569c62770c
    • GoogleスプレッドシートをDBに使い、今流行りのPWAが制作出来るツール。用意されたテンプレートを使うだけで、簡単にWebアプリが制作出来るらしい。
    • 料金: 500行のデータは無料。25000行/2000円。会社で使うなら$8/人 で無制限
    • データ集めて一覧を綺麗に出す用途で良さそう

特に Zapier はすぐに使えそうだったのでその場でみんなで使ってみて、結構いい感じに動いたので後述するハッカソン本番でも活躍しました。
また、Glide についても、エンジニアの津崎さんが弊社サイトの管理ツールについている機能の一部機能の移植を試し、盛り上がりを見せました。

営業チームにおけるハッカソン

チーム概要

営業チームはインサイドセールスチームとフィールドセールスチームに分かれ、合計6人(当日は人数バランス調整のため5人)のチームです。 ざっくり言うと、前者は営業のアポを獲得するチーム、後者は実際に営業かけたりルート営業をやるチームになります。

エンジニア視点でいうと、インサイドセールスチームはテレアポを中心に繰り返しのタスクが多いため、エンジニアの力で改善の余地が多いのではないか、と感じました。
フィールドセールスは客先に伺って話をするのが中心ではあるので改善の余地は少ないのでは、と感じていましたが、実際に話を聞くと顧客にメールを送付したり日程調整したり、社内連携のためのslack入力など効率化できそうなところがそれなりにありました。

成果物

最終的に発表をした主な成果物としては以下の形になりました。

フィールドセールスの成果物 

1. 社内連携のためにSlackワークフローを適切に使う

  • before: 掲載契約が獲得できたときに社内の契約やライティングなど各担当者にやってほしい業務を全てSlackに手動で入力していた
  • after: Slackワークフローを使うことで最初にフォームに従って入力したらその連絡が終わるようにした

2. 掲載営業の後に即座に説明資料添付したメールを送付

  • before: 掲載営業の後に、移動や次の営業の準備で顧客に説明資料添付したお礼メールを送ることができない。それにより鉄を熱いうちに打てていない。原因は送付先メールアドレスや名前を外出先で手動で転記しなければいけないという思い込みでした
  • after: Slack上だけで写真を撮って会社にいるインサイドセールスチームに送信。代わりに送信してもらう。誰がどこの営業に行っているかは明らかなので言語コミュニケーションは不要でした

インサイドセールスの成果物

Zapier を使ってSalesforce と他のツールを連携して手間を減らす。

  • before: アポ獲得した時にやること3つ。
    • Googleカレンダーに予定を追加
    • Slack にアポ設定内容を手動で書き込み
    • Slack を見て Salesforce に「商談」を作成
    • アポ先に確認メール

アポ設定数は月間100件単位なので、それぞれのタスクが1分程度でもチリツモでかなりの時間がかかっていました。

  • after: 業務ステップを見直してNoCodeツールの Zapier を使う
    • Salesforce に「商談」を追加
    • 買い手商談だけフィルターし、以下の処理が自動で行われる
    • Slack に通知
    • カレンダーに登録
    • Gmail下書きを作成(当日は未実装だったが、他チームの発表も聞いてできることに気がつきました)

担当者が複数にまたがる業務ステップは、案外無駄な処理が隠れているものなのだなあと感じました。

結果

全くコードは書いていないですが、今後も営業チームの中だけでも運用しやすい優しい解決策になったかなと思います。
評価基準として以下のものが挙げられていました(再掲)。

  * 勝つべくして勝つ - インパクトが大きいアイディアか?
  * 全員UX - チームメンバーが使いやすいかどうか?
  * One Team - 全員で協力して作ったか?

営業チームは、このうちOne Teamの「全員で協力して作ったか?」でいうと良い結果になったと思います。

  • フィールドセールスチームはSlackワークフローの使い方をエンジニアの私が教えた後は、フィールドセールスチームのメンバーだけで詳細の処理を詰めて完成させてくれました
  • インサイドセールスチームは、Zapier 自体は私がいじることが多かったですが、Salesforce のレコードの構造やフィルターなどはチームメンバーによって詳細を詰めてもらいました

成果物のインパクトがやや薄く、他のチームの実装・発表が素晴らしかったこともあり、投票の結果はそこまで良いものではなかったでしたが、私も営業チームも学び多い1日となりました。

その後の話

営業チームでも使用した Zapier ですが、ハッカソンではライティングチームも使用し、さらにCSチームで兼ねてからNoCodeツールの検討をしていたこともあり、晴れて全社導入が決まりました。
今後も One Team のバリューに沿って、エンジニアと他チームが一丸となって会社を大きくしていくと思います。

Nuxt.js化計画vol.3

Nuxt.js化計画vol.3

こんにちは、こんばんは、kubotak(@kubotak_public)です。

前回の記事は以下 tech.macloud.jp

シリーズ第3段
今回は売り手向けマイページトップと売り手向け会員登録ページのNuxt.js化を紹介します。

売り手向けマイページトップ

売り手向けマイページトップ

売り手向けマイページトップは、会社や事業を売却したい、または資金調達をしたいユーザーがM&Aクラウドにログインした場合に遷移するページです。 現在のアクティビティやステータス、次に行うアクションなどがこのページで見ることが出来ます。

今回のリニューアルでは成約までのTODOリストが追加されました。

成約までのTODOリストとは

成約までのTODOリスト

売却や資金調達の際してどのような事をする必要があり、どんな資料が必要になるかなどをTODOリストにまとめています。 ユーザーはこのTODOリストを参考にして買い手企業との連絡を行い、完了したものはチェックをつけることで進捗を確認することができます。

モーダルから詳細を表示することもできます。 成約までのTODOリスト モーダル

Nuxt.js及びVue.jsを利用することでこのようにインタラクティブなUIも苦労せず構築することが出来ました。

売り手向け会員登録ページ

売り手向け会員登録ページ

会員登録ページでは、JavaScriptならではの即時バリデーションによりユーザーのストレスを減らすことを目的にリニューアルしました。

従来の会員登録ページではLaravelによるMPAで作られていたこともあり登録ボタンを押した後にLaravelのFormValidationによって登録画面に戻されて入力不備の項目を表示していました。 この問題として、ユーザーが登録ボタンを押すまで入力不備が分かりづらいという問題と、登録ボタンを押して次へのアクションを行うモチベーションを挫いてしまうことがあります。

この問題を解決するために、弊社ではVue.jsのバリデーションライブラリのVeeValidateを利用してインタラクティブなバリデーションを実装しています。
QiitaにVeeValidate関連の記事も書いていますので興味のある方は参照ください。

qiita.com

qiita.com

VeeValidateを利用し、画面の右下に入力不備のある項目の残数を出すことでユーザーに入力達成率を把握しやすく工夫しています。

f:id:kubotak:20200819140345g:plain

input要素一つ一つをコンポーネント化し、Storybookでまとめているので再利用も簡単に行なえますし、デザインの統一性も高まります。

f:id:kubotak:20200818165413g:plain

これはemail入力欄のStorybookです。入力不備があれば赤枠になり、不備がなければチェックマークがインタラクティブにつきます。

会員登録ページリニューアル効果

リニューアルは連休前の8/7にリリース致しました。
以降の登録率はリニューアル前と比較し20%ほど改善しています!💪

最後に

M&Aクラウドでは着々とフロントエンドをNuxt.jsに移行しています。
今後もリニューアル・リプレイスしたページを随時紹介していきます。

リリースタグとリリースノート作成の半自動化でリリースサイクルを改善した話

こんにちは。エンジニアの濱田( @hamakou108 )です。

近年 DevOps の文脈で開発生産性の指標としてリリース頻度が注目されています。 DevOps Research and Assessment が提供している State of DevOps Report 2019 ではハイパフォーマンスな開発チームに顕著なメトリクスとして、リードタイムや復旧時間などと並びリリース頻度が紹介されています。 またリリース頻度の向上や付随する障害リスクの低下を図るためのプラクティスについて多くの議論が行われています。

M&Aクラウドでもリリースを高頻度かつスピーディーに実施するため、リリース作業をできるだけ自動化するように工夫しています。 以前にデプロイの半自動化の事例について鈴木から紹介しましたが、今回はリリースタグおよびリリースノートの作成などのリリース準備作業の半自動化を行った事例について紹介したいと思います。

リリース準備作業で抱えていた課題

M&Aクラウドの開発チームにはリリース準備の作業効率に関わる二つの問題がありました。

手作業に伴うミス

弊社ではブランチ管理の手法として Git Flow を採用しています。 Git Flow は gitflow コマンドを利用することで比較的簡単に実践することができますが、それでも作業時のミスは発生しがちです。 例えばローカルの develop ブランチの内容が古い状態で release ブランチを切ってしまったり、 release と hotfix の作業手順を混同してしまったり、タグ名の形式を誤ったり1など、数え上げたらキリがありません。。

また初めてリリース作業を行うメンバーの場合、ローカル環境で gitflow のインストールや初期化が済んでいないといった別の問題も発生していました。

このようにリリースの準備作業は全体的に注意が必要な心理的ハードルの高い作業になっていました。

作業時間

リリースブランチの作成、リリースに含まれる PR のリスト化、リリースブランチのマージ、タグ作成までの一連の作業をマニュアルで実施するとそれなりに時間が掛かります。 特にリリースノートの作成に関してはどの PR が含まれているかコミット履歴を辿らなければならず、面倒な作業でした。 これらの作業によってリリース作業の担当者は普段の開発に掛けられる時間が大幅に減ってしまうことも問題でした。

スクリプトによる作業自動化

これらの作業手順はほぼ定型化されていたため、大半の作業をシェルスクリプトで自動化することで課題解決を図りました。 幾つかキーポイントとなる部分をコードスニペットと共に見ていきたいと思います。

事前要件のチェック

function checkRequirements() {
  if [[ ! "$OSTYPE" == "darwin"* ]]; then
    echo "MacOS を利用してください。" >&2; exit 1
  fi

  if ! type git > /dev/null; then
    echo "Git をインストールしてください。" >&2; exit 1
  fi

  if ! brew ls --versions git-flow > /dev/null; then
    echo "Git Flow をインストールしてください。" >&2; exit 1
  fi
}

function checkoutAndPull() {
  git checkout master
  git pull upstream master
  git checkout develop
  git pull upstream develop
  git fetch upstream
}

checkRequirements() では Git や gitflow など必要なツールがインストールされているか確認しています。 見て分かる通り MacOS に限定した実装にしていますが、これは現時点で開発者全員が Mac ユーザーであるためです。 複数の OS に対応した汎用性の高い実装にすることも可能でしたが、直接的に事業価値に貢献するコードではないので少ないコストで実装できる仕様を採用しました。 YAGNI です。

さらに checkoutAndPull() ではリモートリポジトリの pull を行い、ローカルリポジトリの内容が古いまま作業してしまうことを防止しています。

リリースに含まれる PR のタイトルとリンクの抽出

function printPullRequestToBeReleased() {
  local repositoryName
  local prevRelease

  repositoryName=${1}

  read -r -p "${repositoryName} の前回のリリースタグを指定してください: " prevRelease
  echo "${prevRelease}"
  git log "${prevRelease}".. --merges --first-parent --reverse --pretty=format:"* %b %s" | \
    REPO="${repositoryName}" perl -ple 's/Merge.*#(\d*).*$/(https:\/\/github.com\/your-organization-name\/$ENV{REPO}\/pull\/$1)/' | \
    perl -ple 's/\*\s(.*)\(h/\* [$1]\(h/'
  printf "\n\n"
  read -r -p "上記のリストをコピーしておいてください"
}

printPullRequestToBeReleased() では前回のリリースタグ名を指定するとそこから最新のコミットまでに含まれる PR のタイトルと URL をコミット本文から抽出して Markdown のリスト形式で出力します。 出力されたリストをそのまま利用するだけで簡単にリリースノートが作れます。

release ブランチの作成、マージ、タグ作成

function startRelease() {
  local repositoryName
  local currentRelease

  repositoryName=${1}

  read -r -p "${repositoryName} の「今回の」リリースタグを指定してください。空なら今日の日付が使われます: " currentRelease
  if test -z "${currentRelease}"; then
    currentRelease=$(date "+%Y-%m-%d")
  fi
  echo "${currentRelease}"

  git flow release start "${currentRelease}"
  git push upstream "release/${currentRelease}"
}

function finishRelease() {
  local repositoryName
  local releaseBranch

  repositoryName=${1}
  releaseBranch=$(git branch | egrep -o "release\/[0-9]{4}-[0-9]{2}-[0-9]{2}.*")

  if [[ -z "${releaseBranch}" ]]; then
    echo "リリースブランチが存在しません。" >&2; exit 1
  fi

  git checkout "${releaseBranch}"
  git pull upstream "${releaseBranch}"
  git flow release finish "$(echo "${releaseBranch}" | perl -ple 's/release\/(.*)/$1/')"
  git push upstream master
  git push upstream --tags
  git push upstream develop
  git push upstream --delete "${releaseBranch}"
}

startRelease() で release ブランチの作成を行います。 特に指定がなければブランチ名は release/YYYY-MM-DD のような形式で作成され、忘れがちなリモートリポジトリへの push も自動化しています 2

また finishRelease() で release ブランチのマージ、タグ作成もスクリプト化しました。

スクリプト導入の結果

スクリプトを作成したことで前述した課題を次のように解決することができました。

  • 事前要件を満たせていない場合はスクリプト実行不可とすることで環境依存の問題を防止。
  • Git 操作をスクリプト内で完結させることで手順の誤りによるミスを解消。
  • リリースノートの作成なども含め、自動化した部分の作業時間は1-2分にまで短縮。

まとめ

リリースタグおよびリリースノートの作成をスクリプトで半自動化することで、環境依存や手作業によるミスを軽減し、作業時間も短縮することができました。 作業効率に加えて DX も向上させることができましたが、まだ幾つかマニュアル作業も残っているので今後もリリースサイクルの効率化を適宜進めていきたいと思います。


  1. 弊社ではタグ名を特定のフォーマットとした場合に CircleCI のリリース専用ワークフローが実行されるようにしているため、名前を間違えるとタグを切り直さなければならない場合があります。

  2. ちなみに hotfix のフローに関してもほぼ同様にスクリプト化してあり、リリースフローによって使い分けています。