GameWith Developer Blog

GameWith のエンジニア、デザイナーが技術について日々発信していきます。

エンジニアの採用情報をまとめた GitHub Repository を公開しました #GameWith #TechWith

ごぶさたしています。@serima です。

このたび GameWith では採用情報をまとめた GitHub Repository を公開しました。

github.com

採用に関する情報の分散化問題

GameWith ではエンジニアを積極採用中ですが、いざ採用候補者の立場にたってみると情報が各所に分散しており、せっかくの情報発信が十分に届いていないのではないかと感じていました。

たとえば年単位で運用している技術ブログの記事のなかで、どれが現在の会社の雰囲気や開発スタイルを適切に伝えているものなのかは外からでは分かりません。

さらに昨今は、採用にまつわる情報があらゆる媒体に分散して掲載される傾向があり、候補者視点では情報の収集も一苦労です。 特に、なかの人のインタビュー記事は自社運用の Wantedly や外部の求人媒体などそれぞれで掲載されたりする傾向が強いように思います。

f:id:serimaryo:20210528105105p:plainf:id:serimaryo:20210528104808p:plain
さまざまな媒体に掲載された採用候補者向けの情報

そこで、GameWith でエンジニアとして働くイメージを持ってもらえるような記事や資料を選定し、下記ジャンルに振り分けとりまとめることにしました。

  • 社内の取り組み
    • 組織としての取り組みを紹介したり社内の雰囲気やメンバーを伝えるもの
  • サービス開発
    • 実際にサービス開発が行われている雰囲気を伝えるもの
  • エンジニアリング
    • おもにアーキテクチャや技術的なアプローチを伝えるもの

recruitment/articles.md at master · GameWith/recruitment · GitHub

これを定期的に見直しをおこなうことで「いま読んでもらいたい」記事や情報などを集約できると良いなと考えています。

また、どのようなプロセスで選考が進むかなどざっくりでも全体像が分かるようにし、安心して選考に臨んでもらえるようにしました。

recruitment/interview_guide_engineer.md at master · GameWith/recruitment · GitHub

ほかにもカジュアル面談の申込みの導線を整えたり、最新の技術スタックについてアップデートしたりと細かいところも情報を更新しましたので、ご興味ある方はぜひご覧ください。

エンジニア組織のブランディング

他方で、エンジニア採用やエンジニア組織のブランディングを GameWith で働くエンジニアみんなのものにしていきたいという思いもあります。

組織ブランディングやカルチャーは決してひとりで作れるものではなく、全員の意思とアクションの積み重ねが結果的にブランドやカルチャーとなっていくと考えています。

エンジニアにとっては馴染み深い GitHub を使うことで、普段の業務の一環として各々が Pull Request を作成し Contribution してもらえると良いなと思っています。

採用のためのウェブサイトを運用・更新していく場合は少なからずデザインのことを考慮する必要がでてくる場合もあり、更新ハードルが上がってしまいます。

こうしたことを避けるため、マークダウンのみで簡潔に書けることも大事なポイントだと考えました。

そんな折に、Kyash さんがまさにそのようなことを取り組まれていることを知り、弊社でもこの取り組みを踏襲させていただくことにしました。

Kyash さんは素敵なことに Creative Commons by 4.0 のライセンスを適用していたため、それに従い弊社の取り組みも同様のライセンス適用としています。

GitHub Actions で文章校正を自動化

今はまだ自分ひとりしか commit していませんが、今後は複数のメンバでリポジトリを運用していく想定でいます。表現のゆらぎをなるべくなくし、読みやすい文章を保つことを目的として、GitHub Actions を利用した textlint を試験的に導入しています。

詳細は Repository をご覧いただければと思いますが、肝は action-textlint を利用することで、プルリクエストの行単位に自動でレビューコメントをポストしてくれるところです。

f:id:serimaryo:20210528105517p:plainf:id:serimaryo:20210528105450p:plain
コードレビューコメントの自動投稿のイメージ(action-textlint より転載)

textlint にはさまざまなルールプリセットが用意されていますが、さらにどの程度の強度で文章を校正するかなどもカスタマイズが可能です。

表記ゆれを防ぐための校正ツールとして prh (proofreading helper) という仕組みもあり、これらを組み合わせることで一貫性のある文章をコンピュータの手を借りながら書くことができるようになります。

カジュアル面談資料のアップデート

あわせてこのタイミングでカジュアル面談用の資料のアップデートをおこないました。 業務では Notion を利用してドキュメンテーションを行っているため、面談用の資料もスライドではなく Notion で作成することにしました。

見出しだけチラ見せすると、このような感じになっています。

f:id:serimaryo:20210528100709p:plain

一方的にこちらから話すのではなく、候補者の方が聞きたいことなどもカジュアルに聞いてもらえる場となっているのでご安心ください。

さいごに

GameWith の会社のミッションは「ゲームをより楽しめる世界を創る」というものです。

最近は、ウマ娘をはじめとした攻略関連のツールを提供したり、市況に合わせてゲーム紹介の場をととのえたりといろいろなプロジェクトが並行して走っています。

一方、裏側ではインフラ費用削減や運用コスト削減をおこなったり、フロントエンドのエコシステムを整えたりと地道なシステム改善も着実に実施しています。

こういった守りの部分も最終的には攻めに転じることになると考えており、ゲームを楽しむみなさまが、よりゲームを楽しめるような価値づくりへ繋げていければと思います。

Twitter

Twitter にてテックブログの投稿をツイートしていますので、よろしければフォローをお願いします!

twitter.com

Wanted!

一緒に働く仲間(特にサーバサイドエンジニア)を絶賛募集中です! 以下 Wantedly のページからぜひカジュアル面談へお申し込みください!

www.wantedly.com

ウマ娘フレンド募集掲示板の Firestore 設計と検索 #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの tiwu です!

自分が所属しているチームではいくつか攻略ツールを実装しており、去年はあつ森交換掲示板をリリースしたりしました!

その際のブログはこちらになります!

tech.gamewith.co.jp

今回はウマ娘フレンド募集掲示板を実装した際に得た Firestore の知見や工夫について書いていこうと思います!

ウマ娘フレンド募集掲示板

ウマ娘フレンド募集掲示板は、自分のプロフィールを投稿したり、いろいろな条件で他のプレイヤーを検索したりすることができます。

gamewith.jp

このツールもあつ森交換掲示板と同じく GameWith Design System を利用し Vue + TypeScript で実装を行っています。

tech.gamewith.co.jp

Firestore の設計

登録する内容は自分のフレンドコードに加えて、代表ウマ娘 + 親(継承元)1 + 親(継承元)2 の情報となっています。

代表ウマ娘, 親(継承元)1, 親(継承元)2 に関しては登録するデータの構造はほぼ同じになっているため、当初はサブコレクションを利用した設計を考えていました。

f:id:tiwu:20210507120943p:plain

サブコレクションは、親になっているドキュメントを取得した際に一緒に取得できるわけではなく、親のドキュメントとは別に取得する必要があります。

そのため1つの募集を表示するために、募集ドキュメント + 代表ウマ娘ドキュメント + 親(継承元)1ドキュメント + 親(継承元)2ドキュメント = 4回ドキュメントを取得することになります。

Firestore は取得するドキュメントの数で課金されるため、1つの募集を表示するために4回ドキュメントの課金が発生します。

firebase.google.com

今回コスト削減を考えサブコレクションでデータを持つのではなく、一覧で利用するデータは全て募集ドキュメントで持つようにし、1つの募集を表示するのに1回のドキュメント取得で済むようにしています。

検索について

サブコレクションの検索

一覧のコストを考えサブコレクションでデータを持たず、募集ドキュメントでデータを持っていますが、検索面の理由もあり募集ドキュメントでデータを持つようにしています。

例えば代表ウマ娘が「スピード」因子を持つ募集を検索する場合、サブコレクションでデータを持っていると

  1. ウマ娘サブコレクションから代表ウマ娘が「スピード」因子を持つ、代表ウマ娘ドキュメントを取得
  2. parent を利用して、親ドキュメントの documentId を取得
  3. 親ドキュメントを documentId を利用して取得する

とったフローで検索をすることになります。

const parentDocIds = [];

const subCollectionQuerySnapshot = await firestore.collectionGroup('SUB_COLLECTION_NAME').where('因子', '==', 'スピード').get();
subCollectionQuerySnapshot.forEach((doc) => {
  // 1つ上の親はドキュメントではなくコレクション自体を指すので、2つ上を参照する
  if (doc.ref.parent.parent) {
    parentDocIds.push(doc.ref.parent.parent.id);
  }
});

// in が 10件しか受け付けないので分割して検索
for (let i = 0; i <= parentDocIds.length; i += 10) {
  const splitParentDocIds = parentDocIds.slice(i, i + 10);
  const querySnapshot = await firestore.collection('COLLECTION_NAME').where(firebase.firestore.FieldPath.documentId(), 'in', splitParentDocIds).get();
}

firebase.google.com

firebase.google.com

表示と同じくドキュメント取得数が多くなるため、募集ドキュメントに検索に利用するデータをもたせています。

OR 検索の AND 検索

Firestore では (A or B or C) and (D or E or F) といった where-in の AND 検索はできません。

cloud.google.com

// できない
const querySnapshot = await firestore.collection('COLLECTION_NAME').where('サポートカード', 'in', ['1','2','3']).where('ウマ娘', 'in', ['A','B','C']).get();

// できる
const querySnapshot1 = await firestore.collection('COLLECTION_NAME').where('サポートカード', 'in', ['1','2','3']).get();
const querySnapshot2 = await firestore.collection('COLLECTION_NAME').where('ウマ娘', 'in', ['A','B','C']).get();

ウマ娘フレンド募集掲示板では、A or B or C で100件取得、D or E or F で100件取得し、重複を抽出をするようなロジックを組んで検索を実現しています。

ページング

ウマ娘フレンド募集掲示板では SNS の一覧のように、続きを読み込むというページング機能を実装しています(指定したページを表示する機能はありません)

firebase.google.com

シンプルな AND 検索と、OR 検索の AND 検索で実装方法を変えているのでそれぞれ紹介します。

AND 検索

AND 検索の場合は公式の例のようにシンプルな実装になっています。

startAt, startAfter を利用することでクエリの開始点を指定することができるため、前回の末尾のスナップショットを保持して利用することで実現しています。

class Api {
  lastSnapShot: firebase.firestore.QueryDocumentSnapshot | null;

  get() {
    let query = firestore.collection('COLLECTION_NAME');

    if (this.lastSnapShot) {
      query = query.startAfter(this.lastSnapShot);
    }

    const querySnapshot = await query.get();

    this.lastSnapShot = querySnapshot.docs[querySnapshot.docs.length - 1];
  }
}

OR 検索の AND 検索

前述したように、 (A or B or C) and (D or E or F) といった where-in の AND 検索はできないため、A or B or C で100件取得、D or E or F で100件取得し、重複を抽出をするようなロジックを組んで検索を実現しています。

AND 検索のページングのように1回のクエリで取得できないため、各クエリ毎にスナップショットを保持する設計で実現しています。

また、取得後の結果で重複を判定するため、過去に取得したデータも保持してします(下記サンプルでは細かいところは省略しています)

class Api {
  lastSnapShotMap: Map<'サポートカード' | 'ウマ娘', firebase.firestore.QueryDocumentSnapshot | null>;
  documents: any[];

  search(key: 'サポートカード' | 'ウマ娘', value: string[]) {
    let query = firestore.collection('COLLECTION_NAME').where(key, 'in', value);

    const lastSnapShot = this.lastSnapShotMap.get(key);
    if (lastSnapShot) {
      query = query.startAfter(lastSnapShot);
    }

    const querySnapshot = await query.get();

    this.lastSnapShotMap.set(key, querySnapshot.docs[querySnapshot.docs.length - 1]);

    querySnapshot.forEach((doc) => {
      this.documents.push(doc.data());
    });
  }

  get() {
    search('サポートカード', ['1','2','3']);
    search('ウマ娘', ['A','B','C']);
  }
}

終わりに

今回の知見をまとめると

  • ドキュメント毎の課金なので、一覧を実装する際に可能な限り一覧のドキュメントでデータを持つ
  • (A or B or C) and (D or E or F) といった where-in の AND 検索はできない
  • シンプルなページングであれば簡単に実現ができる

上記3点がポイントとなります。

(A or B or C) and (D or E or F) といった where-in の AND 検索ができないのは、多くの人が詰まるポイントかなと今回感じました。

これからもチャレンジしていくので、よろしくおねがいします!

Twitter

Twitter にてテックブログの投稿をツイートしていますので、よろしければフォローをお願いします!

twitter.com

Wanted!

一緒に働く仲間(特にサーバサイドエンジニア)を絶賛募集中です! 以下 Wantedly のページからぜひカジュアル面談へお申し込みください!

www.wantedly.com

GameWith 開発部で取り組んでいる「業務アピール会」について #GameWith #TechWith

はじめに

ご無沙汰しております。GameWith の開発マネージャー @serima です。

最近は 5 歳の息子と一緒にフォートナイトの実況動画をリビングのテレビに映しながら夕飯を食べるというエキサイティングな私生活を過ごしています。

さて、今回のブログは開発部で取り組んでいる「業務アピール会」について書いていきたいと思います。

背景と課題

GameWith は 2020 年の 4 月ごろからフルリモートワークに振り切ったという話を以前ブログで書きました。

tech.gamewith.co.jp

フルリモートワーク化によって通勤時間などの削減などワークライフバランスに寄与するなど良い一面はありましたが、他チームのお互いの仕事が見えづらくなり部署としての一体感が薄くなるなどデメリットの部分も出てきていました。

もともと毎月開発部の全体会は行ってはいましたが、どちらかというとマネージャーや部長からの情報共有という側面が強く、チーム外のメンバー間コミュニケーションを促す施策はとくに行ってきませんでした。

一時期には全体会の前の時間を少しだけ使ってシャッフル雑談などの取り組みにトライしていましたが、より業務内容にフォーカスした施策のほうが良いのではという声が課題としてあがってきました。

業務アピール会

Spatial Chat の試験導入をはじめフルリモートワーク化されたことでの課題を解消すべくいろいろな取り組みをしてきましたが、今も継続している取り組みのひとつに業務アピール会というものがあります。

課題のところで述べたようにチームをまたいだメンバー同士のコミュニケーションを積極的に促す方向性ではなく、個人がどのような仕事をしたかをアピールしていくことで、個々人の仕事が GameWith というサービスに寄与していることを伝えたくこのような会を発足することになりました。

発起人となったごーさんとどのような形にしていくのが良いかという議論をする中で盛り上がったのが会のネーミングでした。

最初は LT(Lightning Talk) という名前を使おうとしていたのですが、言葉のニュアンス的にフリーテーマ(技術にほとんど関係がなくても発表テーマとして取り扱ってよい)よりの意味合いが強いと感じていました。

より業務にフォーカスした話をしてほしいという考えから明示的に業務というワードを入れ、紆余曲折を経て業務アピール会となりました。

会が終わったあと数日以内には運営メンバーでかならず KPT を毎回おこない、会のブラッシュアップを行ってきました。

f:id:tiwu:20210422111537p:plain
Keep

f:id:tiwu:20210422111552p:plain
Problem / Try

そのおかげで、いまでは実施フローも固めることができ、マニュアルに沿うだけで比較的低コストに毎月運営できるようになりました。 また、GameWith として初の新卒社員(2020年4月入社)のディレクターの方も交えて運営を行っています。

過去の発表事例

現時点ですでに 7 回開催しており、今までの業務アピール会では下記のような発表がありました! (他にも面白いものがたくさんあります…)

  • Core Web Vitals の取り組みについて
  • 数値分析のダッシュボード作成について
  • テストケースの話
  • SEO 改善の話
  • iOS アプリで複数動画を扱うテクニック
  • 文章から感情分析を行う取り組みについて

Core Web Vitals に関してはブログとしてもアウトプットしていますので、こちらもご覧ください。

tech.gamewith.co.jp

終わりに

とはいえ、この形がベストと考えているわけではなく、そのときそのときに応じて柔軟にやり方を変えながら進めていきたいと思っています。

サービス施策だけでなく、組織施策としても様々な取り組みにチャレンジし、より良いサービスを提供していきますのでこれからもよろしくおねがいします。

Twitter

Twitter にてテックブログの投稿をツイートしていますので、よろしければフォローをお願いします!

twitter.com

Wanted!

一緒に働く仲間(特にサーバサイドエンジニア)を絶賛募集中です! 以下 Wantedly のページからぜひカジュアル面談へお申し込みください!

www.wantedly.com

GameWith の Core Web Vitals(Cumulative Layout Shift) 改善! #GameWith #TechWith

初めに

こんにちは。GameWith のエンジニアの tiwu です!

今回のブログは GameWith の Core Web Vitals(Cumulative Layout Shift) 改善について書いていこうと思います!

Core Web Vitals

developers-jp.googleblog.com

Core Web Vitals とは優れたユーザーエクスペリエンスを提供するための3つの指標を指します。

  • Largest Contentful Paint (LCP)
    • ページの主要コンテンツが読み込まれるまでの時間
  • First Input Delay (FID)
    • 最初の入力までの遅延時間
  • Cumulative Layout Shift (CLS)
    • 表示されるページコンテンツにおける予期しないレイアウトのずれの量

Core Web Vitals の詳細についてはこちらを御覧ください。

web.dev

Core Web Vitals は 2021年5月から SEO の要因に追加されことが発表されています。

developers.google.com

計測

GameWith では Core Web Vitals を以下のツールを利用して計測をしています

Search Console

Search Console の「ウェブに関する主な指標」から Core Web Vitals の改善が必要な URL や件数が確認できます。

Search Console のレポートのデータは Chrome UX Report から取得しており、実際にユーザーがページにアクセスをした際のデータになります。

support.google.com

f:id:tiwu:20210122155956p:plain

f:id:tiwu:20210122155920p:plain

なんと、今年の1月時点では17万以上の記事が不良 URL として判定されています!(多い!)

WebAutoPerf

Search Console ではサイトの全体的な件数などは確認できますが、指定の URL の日毎の推移などは確認できません。

GameWith では WebAutoPerf というツールを利用し、日毎の推移を計測しています。

web.dev

Chrome UX Report からデータを取得し、Spreadsheet に保存後、DataStudio で可視化しています。

f:id:tiwu:20210226191736p:plain

調査

GameWith では Cumulative Layout Shift のスコアが基準に達していないことがわかったので、Cumulative Layout Shift の改善を行っていくことにしました。

実際に改善をしていく際は Chrome DevTools を利用して、修正すべき箇所を調査していきます。

Performance タブでページを計測し、Experience 行が赤くなっているところを確認します。

f:id:tiwu:20210122162544p:plain

スクショのように赤くなっているところをクリックすると、実際にページのどの部分で問題が起こっているか青色でハイライトしてくれます。

このスクショの例では画像が読み込まれるまで高さを確保していないので、読み込み後テキストが下にずれてしまうという状態でした。

f:id:tiwu:20210122163642p:plainf:id:tiwu:20210122163651p:plain

改善

修正箇所がわかったので改善をしていきます。

今回の箇所は画像が読み込まれるまで高さを確保していなかったのが問題なので、読み込まれる前から高さを確保しておきます。

ここの画像はサイズが固定(600 * 315)なので、高さを計算して設定します。

.article-ogimage {
    width: 100%;
    height: calc(100vw * 315 / 600);
}

vw は viewport's width といって、1vw = ビューポート幅の 1% となります。

developer.mozilla.org

このように予め高さを指定して置くことで、Cumulative Layout Shift が改善されます。

f:id:tiwu:20210122165726p:plainf:id:tiwu:20210122163651p:plain

結果

Cumulative Layout Shift を改善したことで視覚的なズレが無くなり、ユーザー目線で見た時に体験がとても良くなりました!

また、いろいろな箇所を改善したことによって、ほとんどのページで良好 URL になりました 🎉

f:id:tiwu:20210304105904p:plain

f:id:tiwu:20210304105917p:plain

17万以上あった不良URLはほぼなくなり、WebAutoPerf のグラフもしきい値の 0.1 を大きく下回っています!

f:id:tiwu:20210304105928p:plain

終わりに

Core Web Vitals の改善を行う際に、Yahoo! さんの下記記事がとても参考になりました。ありがとうございます!

techblog.yahoo.co.jp

引き続きより良い体験を提供していくので、これからもよろしくおねがいします!

Twitter

Twitter にてテックブログの投稿など呟いていますので、よろしければフォローをお願いします!

twitter.com

Wanted!

一緒に働く仲間を募集しています!

www.wantedly.com

ダッシュボードに ESLint を導入した話 #GameWith #TechWith

はじめに

こんにちは!Incremental Stream Team の @53able です!

今回はダッシュボードに ESLint を導入した話を書いていこうと思います!

ESLint について

ESLint は JavaScript の構文チェックツールです。

eslint.org

導入の背景

GameWith のサービス側には2年前に ESLint が導入されています。

GameWith のダッシュボード側には導入されていなかったです。

ダッシュボードの開発が複数人で活発に行い、振り返りをしたところ構文チェックツールがないせいで、人によって微妙な書き方の差異が発生しており、開発速度が遅くなっているので、今回導入することにしました

コードレビューで、レビュワーが構文チェックをしてコメントするのはかなりコストが高く、この課題も解決したかったです 例:var 使っているので let, const 使って欲しいなど

やりたいのは「構文チェック」と「自動整形」だったため、ESLint を導入しました

ESLint の設定

ESLint の設定を最初に紹介したいと思います!

{
    "extends": [
        "eslint:recommended",
        "plugin:prettier/recommended"
    ],
    "env": {
        "browser": true,
        "jquery": true,
        "es2021": true
    },
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "prettier/prettier": ["error", {
            // prettierのルールをカスタマイズ
        }]
    }
}

Prettier

Prettier 関連のプラグインについていくつか紹介します。

eslint-plugin-prettier

Prettier のルールを ESLint のルールとして実行できるようにするプラグインです。

www.npmjs.com

eslint-config-prettier

ESLint にもコードの整形についてのルールがいくつかあります(例えば、インデントは半角スペース2つにするか4つにするか)

こういった Prettier とコンフリクトするルールについて、Prettier の設定で ESLint を上書きするようにするプラグインです。

www.npmjs.com

導入作業

1. 現在のエラーの調査

まずはじめに、GameWith のサービス側に既に ESLint が導入されているので、まず最初にダッシュボードの JavaScript に ESLint を実行し、どんなエラーがどれくらい出るのか確認しました。

確認する際はダッシュボードの JS のディレクトリにはライブラリの JS を直接ダウンロードして配置してあったので、上記 ESLint 実行時には無視するように設定をしました。

また、今回のダッシュボードへの導入のタイミングで、ESLint の enves6 から es2021 に引き上げました(es2021 は Chrome 85 から対応しており、問題ないと判断をしました)

ESLint を実行すると、ファイルごとにエラーが表示されます。

今回知りたかったのは、ファイルごとのエラーではなく、全体を通してどんな種類のエラーが出ているかを知りたかったので eslint-report-by-rule というモジュールを利用しました。

www.npmjs.com

eslint-report-by-rule は下記のように実行します。

eslint-report-by-rule 'eslint dashboard -f json'

実行すると下記のように ESLint のルール別のエラー数が表示されます(下記は実際に図った数値で合計 4000 over のエラーが出ています!)

※ダッシュボードの JavaScript は量が多く、このコマンドは15分ほどかかりました笑

{
    "no-unused-vars": 141,
    "prettier/prettier": 3646,
    "no-undef": 545,
    "no-redeclare": 5,
    "no-useless-escape": 53,
    "no-const-assign": 1,
    "no-empty": 11,
    "no-prototype-builtins": 5,
}

2. エラーの修正(自動修正)

修正作業としてはまず ESLint の fix オプションを利用し修正をしました。

eslint --fix dashboard

この fix オプションよって、4000 件以上あったエラーが約 800 件になりました!

3500 件以上あった prettier/prettier のエラーは全てなくなりました(すごく助かります💦)

{
    "no-unused-vars": 141,
    "no-undef": 545,
    "no-redeclare": 5,
    "no-useless-escape": 41,
    "no-const-assign": 1,
    "no-empty": 11,
    "no-prototype-builtins": 5,
}

3. エラーの修正(手動修正)

fix オプションでも修正できなかったエラーに関しては手動で修正しました。

  • no-unused-vars
    • 使われていない変数は、削除したりコメントアウトしたりして修正をしました
  • no-undef
    • var, let, const が書かれてない変数に関しては、手動で let, const を付与しました
  • no-redeclare
    • 無駄な再代入は修正しました
  • no-useless-escape
    • 正規表現内の不要なエスケープは削除しました
  • no-const-assign
    • const 宣言している変数に代入をしていたので let 宣言に修正しました
  • no-empty
    • catch 文の中が空だったので修正しました
  • no-prototype-builtins
    • hasOwnProperty の実行方法について修正しました

苦労した点

関数から受け取った変数が利用されていなかったのでその変数と関数の実行を削除したのですが、テスト時にバグが発生し、調査すると消した関数が原因でした。

理由としては、削除した関数が副作用のある関数だったためです(具体的には関数内で DOM の操作を行っていました)

サンプルコード

if (hoge) {
  const piyo = fuga(elementId);
}

また、宣言子なしで定義されている変数はグローバルスコープになりどんな箇所からも参照することができるので、let, const の付与でスコープが変わってしまい参照できなくなる可能性もあったので、注意深く作業しました。

ダッシュボードには webpack や gulp といったバンドラーを利用していないため、ライブラリなど依存関係は <script> の読み込み順で解決していました。

そのため、ライブラリなどの別ファイル定義の関数やクラスなどを利用している箇所で no-undef エラーが発生していました。

今回はバンドラーなどは使わず import/export を利用し no-undef エラーを修正しました。

現代の主要なブラウザは import/export に対応しているので、バンドラーの導入など不要で修正できるため採用しました 👍

サンプルコード

import hoge from 'hoge.js';

const piyo = new hoge(elementId);

import が書かれている JavaScript は type=module で読み込む必要があるため、ダッシュボードに記述されている <script> を修正しました。

import/export を書く場合、ESLint で下記オプションを設定する必要があります。

"parserOptions": {
  "sourceType": "module"
},

import を使ってるので、jQuery の getScript の処理は全て import に書き換えました。

4. 完了

手動で修正したことによって、エラーは 0 になりました🎉

終わりに

今回はダッシュボードへの導入だったため、import/export はバンドラーを通さずに採用しています。

ダッシュボードは Chrome で開かれることが多いため、採用もしやすかったです!

導入は完了したのですが、実は PHP Template の <script> に直書きされた JavaScript が存在しているため ESLint による構文チェックができていません。

ちなみに直接書かれた JavaScript の先頭には下記コメントアウトを書き残してあります笑(約100箇所)

<?php //TODO このスクリプトに関連する作業を行う場合は、JSファイルにして ESLint を通すようにしてください。 ?>

現在はテストツールを導入しているので、次回はテストツールの導入について紹介できればと思います!

Twitter

Twitter にて GameWith のエンジニア向け情報発信をしています!

twitter.com

GoogleAnalyticsの 目標到達プロセスの分析 によって課題発見、改善できた話 #GameWith #TechWith

あいさつ

こんばんは、 @peka3 と申します。

今回は、以前の記事で紹介した、あつ森交換掲示板の改善についての話になります!

課題の発見から実装、その後の効果検証まで通して書きたいと思います。

以前の記事は↓↓↓からどうぞ。

tech.gamewith.co.jp

課題の発見

あつ森交換掲示板をリリース後、認知度が増すにつれて各数値は順調に伸びていったのですが、思ったほど投稿数が増えませんでした。

投稿の部分は入力項目が多く、ユーザーに操作を強いる部分です。

今回は、投稿画面に入って、離脱せずに投稿完了した人 の数値を調べてみました。

このような特定の手順で操作をした人を調べるにはGoogleAnalyticsの「目標到達プロセスの分析」が便利です。(アナリティクス360左メニューの「分析」から行けます)

f:id:peka3:20201217175931p:plain:h300

投稿画面から投稿完了に行くには、絶対にアイテムの選択をしなければいけません。

どこで離脱していったかを調べるために、 投稿画面に来たあと、アイテムを選択し、投稿完了した。 というイベントの流れをたどった率を分析します。

結果は以下のようになりました。

f:id:peka3:20201218200023p:plain
投稿完了率 改修前

(一部数値はふせさせていただきました!!)

投稿画面に来た時点で、投稿したいモチベがあるはずにも関わらず、投稿完了したユーザーは40%程度 でした。

この数値が多いか少ないかは比較対象がありませんが、感覚としてまだまだ伸ばせそうな手応えのある数値です。

課題へのアプローチ

投稿完了率がどうやら低そうというのはわかりましたが、一体なぜ低くなってしまっているのかがわかりません。

そこで次に打つ手として2つの選択肢がありました。

  • ユーザーにアンケートを取る
  • デザイナーにUI/UX観点で改善案がないか相談してみる

今回は後者のデザイナー相談をすることにしました。

実装を急いだこともあり、まだ改善の余地がありそうだったこと、こちらのほうがアンケートを取るより早く施策実行に移れるからという理由からです。

改修内容

相談後、以下のような改善案を実装することにしました。

  • アイテム選択のモーダルを全画面にする
    • アイテム選択画面ではアイテム選択に集中させたいから
  • アイテム選択したのを分かるように、トースト/snackbar 表示
    • 選択した際のインタラクションがなく、選択済みアイテム欄を見ないとわからなかった
  • アイテム保存ボタンを追従させる
    • 一番下までスクロールしないと保存ボタンが押せなかったため
  • 選択済みアイテム欄の追従
    • スクロールしてしまうと隠れてしまっていたため

f:id:peka3:20201217182033p:plain:w200 トースト追加

f:id:peka3:20201217182113p:plain:w200 選択枠の固定、保存ボタンの固定

どれも数日あれば実装できそうな内容です。

実際に一週間程度の稼働で全ての実装が終わりました!

今回は、ABテストなどはせずに一気に実装しました。 ABテストはもっとわずかな変化を検証したいときや、余計な要素を完全に排除して効果検証をしたいときに使うべきであり、今回はそれに適さないと思ったからです。

その後の変化

改修後、どのような変化があったのかの効果検証もGoogleAnalyticsの「目標到達プロセスの分析」が便利です!

新規ユーザーだけに絞っての調査もできるので「既存のUIで学習してしまったユーザー」を弾くことができるのです。

f:id:peka3:20201218201836p:plain
投稿完了率 UI改修後

各イベントでの離脱率が如実に現象しました!

新規ユーザーの投稿完了率も 65%を超えています

改修前に比べて20%以上の投稿完了率向上となりました!

まとめ

一週間ほどの改修作業で、投稿率20%改善という大きな効果が得られました。

今回は以下の要素がうまく噛み合って速やかに改善できたと思います。

  • 計測
  • 課題発見
  • デザイナーさんの改善案の素晴らしさ

特に「計測」がなければ課題発見も出来ませんでした。

プロダクトはリリースだけが大事ではなく、ちゃんとログを仕込んで正しく計測することも大事だと改めて思いました!

以上です。

  • TwitterにてGameWithのエンジニア向け情報発信をしています! twitter.com

Google Analytics 4 property(GA4)でできるようになったこととTips #GameWith #TechWith

はじめに

こんにちは。GameWith のエンジニアの m です!

このブログはアドベントカレンダーの22日目のブログになります!

qiita.com

今回は Google Analytics 4 property について記事を書きます!

GA4とは

2020年10月14日に正式リリースされた次世代版の Google アナリティクスです。

support.google.com

Firebase向け Google アナリティクス(FA)をもとに、イベントベース測定モデルに変更されています。

個人的に大きく変わったと思う点と、GA4利用上のTipsを紹介します。

従来の Google アナリティクスは、ユニバーサルアナリティクス(UA)と記載して区別します。

変わった点

ウェブとアプリのデータを一つのプロパティでみられるようになった

FAのデータストリームを連携することで、相互にデータを同期することができます。

後述するBigQueryでも同じテーブルとして扱うことができるので、プラットフォームを跨いだ分析が容易になります。

イベントのデータ構造が柔軟になった

UAではカテゴリ、アクションのように予め大きな項目が決められており、カスタムディメンションも事前に定義が必要でした。

GA4では事前定義を必要とせず、各イベントごとにパラメータを自由に設定することができます。

UAのデータ構造

  • イベントカテゴリ
  • イベントアクション
  • イベントラベル
  • カスタムディメンション
    • (プロパティに事前定義が必要な)ユーザー定義のkey value

GA4のデータ構造

  • イベント名
  • イベントパラメータ
    • ユーザー定義のkey value
  • ユーザープロパティ
    • ユーザー定義のkey value

大規模サービスでも無料で利用しやすくなった

UAはヒット数(ページビューやイベントの発火)に制限があり、 月のヒット数が1,000万以上になると、有償版へのアップグレードが必要でした。

GA4にはイベント数(ヒット数)にあたる制限がありません。

今現在、有償版がないので確定ではありませんが、イベント単位のリッチさ(パラメータの定義数、文字数など)が課金のポイントになりそうです。

BigQueryとの連携が簡単になった

UAでは有償版のGA360でしか提供されていなかったBigQueryへのエクスポート機能が利用できます。(BigQueryへの転送やストレージの料金は発生します)

support.google.com

GAのデータはAPI経由でアクセスすると値が丸められる影響で実数値の分析が難しいですが、 BigQueryでは各イベントがレコード単位で分かれているので、加工や分析が容易になります。

スケジュールタスクを作成し、自動でイベント名ごとに別テーブルに分割して参照しやすくすることもできます。

Tips

user idについて

GA4のデータをBigQueryにエクスポートした際のテーブルスキーマには、トップレベルに user_id カラムが存在します。

ウェブでイベントパラメータのキー名に user_id を設定しておくと、自動でトップレベルにも値が入るようになります。

また、user idがない場合でも、 user_pseudo_id というカラムに自動でCookie管理のIDが振られるので、 サービス側に識別IDがない場合は、こちらをもとに行動分析が可能です。

イベントパラメータのBigQueryテーブルスキーマについて

イベントパラメータは event_params という RECORD型(REPEATEDモード) のカラムに格納されます。

support.google.com

クエリをもとにどんなデータ構造になっているかを紹介します。

event_name と event_paramsだけのテストテーブルを用意

イベントパラメータのvalueは内部的に STRING型 INTEGER型 FLOOT型 に分かれています。

今回は簡略化して以下のようにスキーマを定義しました。

f:id:takuya_minami373:20201222161435p:plain

INSERT

RECORD型のデータを挿入する場合は、STRUCT を使って構造を明示する必要があります。

REPEATEDモード なので ARRAY の記載で複数の値の格納が可能です。

INSERT
  `PROJECT_ID.DATASET_NAME.TABLE_NAME` (event_name, event_params)
VALUES
  ("event_name1", ARRAY<STRUCT<key STRING, value STRUCT<string_value STRING, int_value INT64>>>[
    ("key1",STRUCT<string_value STRING,int_value INT64>('value1', NULL)),
    ("key2",STRUCT<string_value STRING,int_value INT64>('value2', NULL))
  ]);

SELECT

SELECT
  *
FROM
  `PROJECT_ID.DATASET_NAME.TABLE_NAME`
WHERE
  event_name = 'event_name1';

普通に抽出すると画像のように1レコードに複数のイベントパラメータが存在することがわかります。 f:id:takuya_minami373:20201222161344p:plain

イベントパラメータのネストを解除するには以下のようにクエリを発行します。

SELECT
  event_name,
  (SELECT value.string_value FROM UNNEST(event_params) where key = "key1") as key1,
  (SELECT value.string_value FROM UNNEST(event_params) where key = "key2") as key2,
FROM
  `PROJECT_ID.DATASET_NAME.TABLE_NAME`
WHERE
  event_name = 'event_name1';

これでフラットなレコードに整形されるので扱いやすくなりました。

サブクエリがある分、参照データが多くなってしまうように見えますが、 1クエリ内であればよしなにキャッシュしてくれるので処理されるデータ容量は変わりません。

f:id:takuya_minami373:20201222161453p:plain

終わりに

GA4がデフォルトのプロパティとなり、今後は集中して機能拡張されていくみたいです。

UAの終了日はまだアナウンスされていないですが、早めに並行稼働を進めていくことが推奨されています。

スムーズに移行できるよう一緒に準備していきましょう!

Twitter

GameWithのDeveloper向けTwitterアカウントも開設しました。

ブログの更新情報などを発信するので良かったらフォロー宜しくお願いします!

twitter.com