実際の適用例
次に、私が過去に直面した問題と、これに対して前述の思考パターンを適用した例を2つほど紹介します。なお、ピクスタでは主にWebアプリケーションフレームワークのRuby on Railsを利用して開発しているため、どちらの例も成果物はRubyのライブラリ(以下、gem)であることをご了承ください。
gakubuchi
最初に紹介するのは、gakubuchiというgemです。このgemは、私がサービスのメンテナンス作業のためにエラーページの内容を修正したことがきっかけで生まれたものです。
直面した問題
Ruby on Railsは、本番環境でエラーが発生するとその種類に応じてpublicディレクトリの直下にある404.htmlのようなページを表示する仕組みを標準で用意しています。技術的には、これらを静的ファイルとして事前に用意するのではなく、オンデマンドに生成することも可能です。しかし、全ての種類のエラーページでこの方法が利用できるわけではありません。例えば、サービスのメンテナンス時に表示する503ページのように、Railsアプリケーションを通さずに表示されるものは静的ファイルにせざるを得ません。
このような制約もあって、当時のエラーページは全て独立した静的ファイルとして作られていました。そのため、似たようなHTML/CSS/JavaScriptの記述がいくつもコピー・ペーストされていて見通しが悪く、修正にもひと手間かかる状態でした。そこで、RailsアプリケーションのViewのようにテンプレートエンジンや便利なヘルパー関数を使ってエラーページを記述できるようにすれば、状況を改善できるだろうと考えました。
思考パターンの適用
まず、今回の問題を構成要素に分解すると、次の3つになります。
- テンプレートエンジンの利用
- ヘルパー関数の利用(特に、外部CSS/JSファイルのパス解決)
- ヘッダーをはじめとした共通コンポーネントの再利用
検証の結果、最後の共通コンポーネントの再利用以外は、Ruby on Railsの機能の一つであるAsset Pipeline(注2)を利用して実現できることが分かりました。幸い、対象のエラーページでは通常のページと同じヘッダーやフッターを使いたいという要件はなかったので、ここでは潔く諦めることにしました。ただ、これで解決とはいかず、すぐに次の問題に気づきます。Asset Pipelineの処理結果はpublicディレクトリの直下に出力されないので、何らかの対処が必要なのです。この問題に関しては、変換処理の前後に任意の処理を差し込むAPIが見つかったことで、これを利用してエラーページをpublicディレクトリの直下に移す処理だけ書けばよいだろうという結論で落ち着きました。
注2
複数のJavaScriptやCSSなどのアセットを1つのファイルに連結し、最小化や圧縮などの最適化を行うRuby on Railsの機能のこと。ファイル連結と同時にCoffeeScriptやSassといった他の言語で書かれたアセットをブラウザが解釈できる形に変換することもでき、gakubuchiではこれを利用しています。
この時点でさらに問題を小さくすることはできないと感じたので、次に、得られた解決策を適用できる問題が他にもありそうかを考えることにしました。今回の場合、「エラーページをpublicディレクトリの直下に移す処理」の実装をエラーページ以外にも適用できるようにすれば、キャンペーンページのような任意の静的ページでも同じ仕組みが使えるだろうと考えました。
一定の汎用性があることを確認できたので、最後に、OSSとして公開した際の説明文を考えることにしました。前の「横展開」フェーズで静的ページ全般に利用できそうな見通しが立っていたので、”Static pages management with Asset Pipeline”という一文をすぐに思いつくことができました。客観的に見ても、「何をしたいのか理解できない」とはならないだろうと思ったので、gemとして作って公開することにしました。
grease
次の事例は、greaseというgemです。このgemはサービス開発で直面した問題から生まれたわけではないのですが、元の問題をうまく汎化して解くことができた良い例のため、紹介させてください。
直面した問題
このgemを開発したきっかけは、前述したgakubuchiのリポジトリにあるIssueが立ったことです。その内容は、「SprocketsというAsset Pipelineの実装に利用されているgemをアップグレードしてみたところ、エラーが出て動作しない」というものでした。
Sprocketsはあるバージョンまで、Tiltというgemが規定したインターフェースに依存していました。Tiltは、Rubyで実装されたさまざまなテンプレートエンジンを統一されたインターフェースで扱えるようにするためのもので、薄いラッパークラス群を提供しています。
報告していただいたエラーの原因は、新しいバージョンでは(機能拡張のために)Sprocketsがまったく別のインターフェースを用意してこれに依存するようになったことでした。gakubuchiは以前の実装を前提としていたので、新しいバージョンで動作しなくなってしまったのです。
思考パターンの適用
最も素朴な解決策は、gakubuchiが独自で対応している2つのテンプレートエンジンに対して、Sprocketsの新しいインターフェースに合わせるためのラッパークラスをそれぞれ書くことでした。しかし、この変更を取り込むと、gakubuchiの取り扱う問題が大きくなってしまいます。そのため、今回の問題の解決策を別のgemとして実装し、gakubuchi側ではこれを利用する形にしました。
次に、このラッパークラスの実装という問題をさらに小さくできないかを考えました。前述したように、RubyにはすでにTiltという統一インターフェースを提供するgemが存在します。このインターフェースをSprocketsの依存するそれに変換するアダプターを実装すれば、各テンプレートエンジンに対してラッパークラスを実装しなくて済むため、今回はこのアプローチをとることにしました。このアダプターは、Sprocketsと任意のテンプレートエンジンに依存するgemを開発している場合にも利用できるので、ニッチではありますが一定の汎用性があるだろうと考えました。
最後に、OSSとして公開した際の説明文を考えることにしました。前の「横展開」フェーズで開発者向けのgemになることは見えていたので、シンプルに”Tilt adapter for Sprockets 3 or later”とし、実装に着手しました。
後日談
greaseの公開から約半年後、less-railsというgemのリポジトリで、私はある1つの興味深いプルリクエストを見つけました。less-railsは、LessというCSSの代替言語をAsset Pipelineで利用できるようにするためのものです。そのプルリクエストでは、前述したSprocketsでのインターフェースの変更へ対処するためにgreaseが使われていました(なお、残念ながら最新のバージョンではgreaseを使わない形に書き換わっています)。
less-railsは非常に多くの方から利用されているため、この依存にgreaseが追加されたことで、私がこれまでに作った中で最も多くダウンロードされたOSSになりました(注3)。「問題を小さくしようと意識することで、汎用的な解決策が生まれる」ことを身をもって実感した出来事でした。
注3
2019年12月末現在で約450万ダウンロードされています。
おわりに
本記事では、私がサービス開発の過程で直面した問題からOSSの開発に至るまでの思考パターンと、これを実際の問題に適用した例をいくつか紹介しました。実際の例を通じて、「要素分解」→「横展開」→「客観視」という思考の過程を経ることの有用性を示せたのではないでしょうか。
本記事の内容が、読者の方がより良いOSSを生み出すきっかけになれば幸いです。
