FluctSDKのドキュメントをリニューアルした話

こんにちは。新卒2年目エンジニアのchocovayashiです。 普段は、fluctでiOS / Android / Unity向けに広告SDKの開発をしています。

広告SDKはSDK自体の機能開発も大事ですが、使い方をわかりやすく説明するためのドキュメントの存在も大事です。 今回はFluctSDKのドキュメントを新しく作り変えたので、そのアプローチやどういうツールを使ったのかご紹介します。

新しいドキュメントはこちらです。

https://voyagegroup.github.io/FluctSDK-Doc

f:id:chocovayashi:20200716110520p:plain

目次

ドキュメントが抱える課題

作り変える前のドキュメントは、各プラットフォーム(iOS, Android, Unity)ごとにGitHubのWikiで管理していました。

iOS, Android, Unity (Wikiでの記述は既に消しているので、現在はHomeだけしか見れません。リンクは過去の編集履歴へのリンクです。)

1. 商材が増え、見たいドキュメントに辿り着くことが面倒になった

iOSの例を挙げると、私が入社した1年ほど前は、動画リワード広告JavaScriptタグによるバナー広告しか商材がなく、見たいドキュメントをすぐ見つけることができました。しかし、ここ1年で商材が増え全て画像のように箇条書きにしていたので、見たいドキュメントを探し出すのに苦労するようになりました。画像はiOSの商材で、iOSだけでも画像の倍の商材があり、またAndroid, Unityにもほぼ同じ数の商材がありました。

f:id:chocovayashi:20200716110331p:plain

視覚的に商材を選べるUIにできれば簡単にたどり着けるようになりそうだなと思いました。

2. 同じ記述になる部分を重複して管理している

FluctSDKのUnity Pluginの実態は、iOS, Androidの各プラットフォームへのbridge機能を有しているだけで、ロジック部分はiOS, AndroidのSDKがそれぞれ有しています。 なので、FluctSDKのUnity PluginにおけるiOSやAndroidの最低要求OSバージョンは、FluctSDKの要求するiOS,Androidの最低要求OSバージョンと同じになります。 各プラットフォームごとにドキュメントを公開している(内部的にも各プラットフォームごとにドキュメントを管理していました)制約上、各プラットフォームを跨いで情報を共有できなかったので、共通の情報であっても重複した記述をする必要がありました。その影響で、変更漏れが発生することがありました。

3. おしゃれじゃない

個人的にはこれが最も重大な課題でした。デザイナーではないのでおしゃれの細かい定義はしていませんが、左に目次があったり、複数の言語の実装例をタブで選択できるようにしたいなと思っていました。

他に意識したところ

  • gitなどでバージョン管理ができる
    • チーム内でのレビューをしやすくしたい
    • 変更履歴がわかるようにしたい
  • ドキュメントの構成をツールに依存させない
    • ツールのサポート終了などで将来他のツールに移行する可能性は十分にあるので、ドキュメントの書き方がツールに依存しすぎないようにする

ツールの選定

まずは選択肢を挙げ、それぞれについて考えました。

GitBook

f:id:chocovayashi:20200716110420p:plain

トップページから溢れ出るおしゃれ感には心を揺さぶられました。また、Analyticsも見れるのでとても魅力的でした。ただ、localでの開発が非推奨であったり(https://github.com/GitbookIO/gitbook )、お金がかかるというのはデメリットだと考えました。

HUGO

f:id:chocovayashi:20200716110440p:plain

HUGOはタイトルからもわかるように、staticなsiteを作れるオープンソースのframeworkです。テーマが多彩でその1つにドキュメントっぽいテーマもあったので十分活用できそうでした。また、hot-reloadもあるので、快適に開発できそうだなと印象を受けました。

中身はgoで書かれているので、デプロイする時は例えばCIでhugoコマンドを使い、MarkdownをHTMLへ変換し、その HTMLをデプロイする必要がありました。またドキュメントの公開という観点からすると、少しオーバースペックな気がしました。

docsify

f:id:chocovayashi:20200716110502p:plain

まず見た目がとても可愛いので良いですね。docsifyはJavaScriptで書かれたドキュメント生成ツールです。静的ファイルとして置いたMarkdownをdocsifyが表示時にHTMLへ変換するので、build不要で公開できるのが魅力的でした。HUGOと同様にhot-reloadもあり開発には困ることはなさそうです。また、pluginの開発もできるので、独自拡張もしやすそうでした。

ただ、テーマが4種類しかないので、もし凝ったデザインを作りたくなったら0から考えないといけないのが大変だなと思いました。

ツールの選定結果

候補の中から、docsifyを採用することにしました。docsifyはCover pageという機能があり、トップページをHTMLで自由にカスタマイズすることができます。今回は見たい商材へすぐに辿り着けるようなデザインへとカスタマイズしました。

f:id:chocovayashi:20200716110520p:plain

ドキュメントの埋め込み機能がありコンテンツを再利用できるので、重複する情報の管理を1箇所で行うことができるようになりました。例えば、iOSの最低要求OSバージョンをiOSのドキュメントに書いていてもそれをFluctSDK Unity Pluginで再利用することが可能になりました。

デザインの面では、Tab機能で見た目を簡単にリッチにできる点も採用の決め手になりました。

f:id:chocovayashi:20200716110542p:plain

docsifyを使う上でちょっとした工夫

SDK本体のリリースサイクルの弊害にならないようにしたい

ドキュメント内にSDKのversionを記述してる箇所があり、それはSDK本体のリリース毎に修正する必要がありました。これを手動で毎回変更する運用にすると、変更を忘れたり、変更をするのが面倒で細かくリリースを行う弊害になりかねませんでした。なので、GitHub Actionを使い自動でversion upする仕組みを作りました。

GitHub Actionには特定のリポジトリにjsonの情報付きで通知を送る仕組みがあるので、それを利用しSDK本体がリリースされるタイミングで、ドキュメントを更新する仕組みを作りました。

ただ、version情報は各所に点在していて、GitHub Actionがその全ての場所を修正しようとすると、ドキュメントの可用性が低下してしまいます。なので、version情報などをyamlファイルで一元管理できるpluginの独自開発を行いました。plugin自体はMarkdownの埋め込みとほぼ同じ実装かつ後述するので、ここでは省略します。

低コストでデプロイを自動化する

ドキュメントの中身以外のメンテナンスは極力したくないのでCIは使いたくありませんでした。というのも、実際に価値を生むのはSDK本体なのでそこの開発の邪魔にならない立ち位置で運用したかったからです。またドキュメントの性質上、大量の同時アクセスなどを考慮する必要もありませんでした。なので、今回はGitHub Pagesを採用しました。さらに、buildする必要もないので、masterへmergeするだけでデプロイすることが可能になりました。

GitHub Pagesをホスティング先にすることにより、CIを使わずにデプロイすることが実現できました。

docsifyを使う上でちょっと困ったこと

Tab機能とドキュメントの埋め込み機能を併用で使えない

docsifyはMarkdownをHTMLに変換してドキュメントを表示しています。ドキュメントの埋め込み機能はHTMLへ変換する前に別のMarkdownを読み込み、展開させる機能です。一方でTab機能はドキュメントの埋め込み機能がMarkdownの展開をする前に処理が行われており、2つの機能を共存させることができませんでした。

なので、Tab機能とドキュメントの埋め込み機能を共存させるために、ドキュメントの埋め込み機能をpluginとして独自で開発し、タイミングを制御できるようにしました。簡単にpluginが作れるところもdocsifyの良いところですね。

まずはdocsifyにPluginを登録します。

window.$docsify.plugins = [].concat(
  embedMarkDownPlugin,
  window.$docsify.plugins || []
);

window.$docsify.pluginsへpluginを追加します。embedMarkDownPluginはpluginの中身です。

function embedMarkDownPlugin(hook, vm) {
  hook.beforeEach(function (content, next) {
    embedMarkdown(content, next);
  });
}

hook.beforeEachはdocsifyで定義されたライフサイクルのコールバックです。他にも様々なコールバックが用意されています。 embedMardownはMarkdownを埋め込む実態の関数です。

function embedMarkdown (content, next) {
  const regex = '\\[embedded-markdown\\]\\(([a-zA-Z0-9!-/:-@¥[-`{-~]+)\\)';
  const embeddedDescriptions = content.match (new RegExp (regex, 'gim'));
  if (!embeddedDescriptions) {
    next (content);
    return;
  }
  const embeddedFiles = embeddedDescriptions.map (desc => {
    return desc.match (new RegExp (regex, 'im'))[1];
  });

  Promise.resolve ()
    .then (function () {
      return Promise.all (
        embeddedFiles.map (desc => {
          return fetch (desc).then (res => (res.ok ? res.text () : ''));
        })
      );
    })
    .then (responses => {
      responses.forEach ((res, index) => {
        const escapedEmbeddedDescription = embeddedDescriptions[index].replace (
          /[\\^$.*+?()[\]{}|]/g,
          '\\$&'
        );
        const regex = new RegExp (escapedEmbeddedDescription);

        // 指定されたmarkdownが存在しない場合
        if (!res) {
          content = content.replace (regex, '');
          return;
        }

        // markdownを入れ替え
        content = content.replace (regex, res);
      });
      embedMarkdown (content, next);
    });
}

このように定義し、Markdownに以下のような記述をすると、指定したMarkdownを埋め込むことが可能になりました。

[embedded-markdown](path/want-to-embed-markdown.md)

まとめ

いくつかのツールを検討して、最初に挙げた課題を最短で解決してくれそうなdocsifyを採用しました。

広告SDKの開発においては、どうしても広告の機能開発に目を向けがちですが、お客さん目線に立つとドキュメントの見易さも非常に重要になってきます。地味な作り変えかもしれませんが、少しでもお客さんの開発の助けになればなと思います。