こんにちは、今年の1月からCTOに就任した id:Yasaichi です。同月、推しだった欅坂46の平手友梨奈さんが同グループを脱退し、これでやっとオタ卒できそうと安堵(?)していたところ、うっかりハロプロの沼に落ちてしまいました。好きな色はアクアブルー*1です。
さて、今回はピクスタ開発部によるCodeZineでの連載「ピクスタでは何が起きている!? プラットフォーム事業開発の課題と解決法」のために私が執筆した内容をお送りします。
以下は、こちらの記事を翔泳社からの許可を得て転載したものです。
はじめに
本記事は、筆者が以前「Web現場Meetup」というイベントで発表した内容を記事として編集したものです。当日のスライドはこちらになります。
近年、何らかのオープンソースソフトウェア(以下、OSS)を利用せずにWebサービスを開発・運用することは不可能であると言っても過言ではないでしょう。こうした中で日々仕事をしていると、「お世話になっているOSSに対して何らかの貢献をしたい」という思いや「自分も何かしらのOSSを作ってみたい」といった気持ちが芽生えるのは自然な流れなのかもれません。かくいう私も例外ではなく、仕事で利用しているOSSにパッチを送ったり、自分でも10個ほどのOSSを作ってきました。これらのほとんどは、サービス開発の過程で直面した問題がきっかけで生まれたものです。
このような経験から、私は「サービス開発の現場はOSS開発のネタの宝庫であり、現場に立っているからこそ開発者の問題を解決するソフトウェアを生み出せる」と考えるようになりました。一方で、少なくない数の企業が募集要項に「OSSの開発経験」を歓迎条件として挙げている現状を見ると、誰しもができることではないというのが事実のようです。個人的には、これは技術的なスキルの問題というよりは、単に機会を見逃してしまっていることが多いように見えます。そして、機会を捉えられるか否かは、問題に対してどう向き合うかが鍵になると考えています。
そこで本記事では、私がサービス開発の過程で直面した問題からOSSの開発に至るまでの思考パターンと、これを実際の問題に適用した例をいくつか紹介します。
OSSを生み出す思考パターン
サービス開発の過程で直面した問題からOSSの開発に至る際、私は「要素分解」→「横展開」→「客観視」という思考の過程を経ることが多いです。以下では、各過程で行っていることとその目的を順に説明していきます。
1. 要素分解
問題に直面したらまず意識しているのは、すぐにその問題を解決するコードを書こうとしない、ということです。手を動かす前に問題を構成要素に分解して、各々に対して既存の解決策が適用できないかを検討することで、自分が解かないといけない問題を小さくするようにしています。具体的には、まず利用しているフレームワークの機能や既存のOSSを使って問題の一部を解決できないかを調べます。そして、これらを適用した後に残った問題を解決するコードを考えるようにしています。
このように、問題を小さくしようと意識しているのは、目の前の問題に特化したコードを書いてしまうことを避けるためです。既存の解決策が適用できない問題だけに取り組むことで、書くべきコードの量が減るだけでなく、汎用的な解決策も生まれやすいと感じています。
2. 横展開
最初の問題よりも小さな別の問題とそれに対する解決策が得られたら、次にこれを横展開できないかを考えるようにしています。具体的には、同じ解決策を適用できる問題が他にもありそうかを探します。これは、得られた解決策に一定の汎用性があるかどうかを確認するためです。ここで何も思いつかないのであれば、無理にOSS化せずに諦めています。
3. 客観視
得られた解決策の汎用性が確認できたら、最後にこれをOSSとして公開したと仮定して、提供する機能を説明する一文(英語)を作って客観視するようにしています。得られた解決策を他者が理解でき、有用性を感じるかどうかを確かめるためです。ここでしっくりくる一文が思いつかない場合、問題設定か解決策のどちらかに問題があると考え、1の要素分解フェーズに戻ります。
理想を言えばこのタイミングでREADMEまで書くべきなのですが*2、筆者の場合、READMEを書くことに気合いを入れ過ぎて実装に至るまでにモチベーションを使い果たしてしまうことが何度かありました。そのため、手軽なこちらの方法を採用しています。
実際の適用例
次に、私が過去に直面した問題と、これに対して前述の思考パターンを適用した例を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*3を利用して実現できることがわかりました。幸い、対象のエラーページでは通常のページと同じヘッダーやフッターを使いたいという要件はなかったので、ここでは潔く諦めることにしました。ただ、これで解決とはいかず、すぐに次の問題に気づきます。Asset Pipelineの処理結果はpublic
ディレクトリの直下に出力されないので、何らかの対処が必要なのです。この問題に関しては、変換処理の前後に任意の処理を差し込むAPIが見つかったことで、これを利用してエラーページをpublic
ディレクトリの直下に移す処理だけ書けばよいだろうという結論で落ち着きました。
この時点でさらに問題を小さくすることはできないと感じたので、次に、得られた解決策を適用できる問題が他にもありそうかを考えることにしました。今回の場合、「エラーページを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のリポジトリで、私はあるひとつの興味深いプルリクエストを見つけました。less-railsは、LessというCSSの代替言語をAsset Pipelineで利用できるようにするためのものです。そのプルリクエストでは、前述したSprocketsでのインターフェースの変更へ対処するためにgreaseが使われていました(なお、残念ながら最新のバージョンではgreaseを使わない形に書き換わっています)。
less-railsは非常に多くの方から利用されているため、この依存にgreaseが追加されたことで、私がこれまでに作った中で最も多くダウンロードされたOSSになりました*4。「問題を小さくしようと意識することで、汎用的な解決策が生まれる」ことを身をもって実感した出来事でした。
おわりに
本記事では、私がサービス開発の過程で直面した問題からOSSの開発に至るまでの思考パターンと、これを実際の問題に適用した例をいくつか紹介しました。実際の例を通じて、「要素分解」→「横展開」→「客観視」という思考の過程を経ることの有用性を示せたのではないでしょうか。
本記事の内容が、読者の方がより良いOSSを生み出すきっかけになれば幸いです。
* * * * *
ピクスタでは、「ユーザーに価値を速く継続的に届けるために技術をどう使うか」というテーマに興味のあるエンジニアを募集しています!
*1:アンジュルムの上國料萌衣さんのメンバーカラーになります。
*2:いわゆる「README駆動開発」のこと。
*3:複数のJavaScriptやCSSなどのアセットをひとつのファイルに連結し、最小化や圧縮などの最適化を行うRuby on Railsの機能のこと。ファイル連結と同時にCoffeeScriptやSassといった他の言語で書かれたアセットをブラウザが解釈できる形に変換することもでき、gakubuchiではこれを利用しています。
*4:2019年12月末までに約450万ダウンロードされています。