(判断基準) ライブラリやSDKに依存するかしないか
はじめに
車輪の再発明という言葉を聞いたことはあるだろうか。Wikipediaによると「広く受け入れられ確立されている技術や解決法を(知らずに、または意図的に無視して)再び一から作ること」という意味だ。ソフトウェア開発では、一般的に車輪の再発明は避けるべきことだとされている。プログラマが知るべき97のこと でも 車輪の再発明の効用 というエッセイで以下のように書かれている。
「車輪の再発明」はどうしてそんなに忌み嫌われるのでしょうか。それはまず、新たにコードを書くより、既存のコードを流用する方が安全でコストが少なくて済むからです。既存のコードは、その多くが「正しく動作すると既に確認されたコード」です。厳しいテストによって品質を高められ、製品としても役立ってきた実績のあるコードが多いのです。既存の製品やコードベースに時間と労力を投資したのに、同様のものを再度作ってまた時間と労力を投資するのは無駄、という考えもあります。あえて車輪の再発明をするのだとしたら、それなりの理由は必要でしょう。
車輪の再発明の是非に関して既に世の中に多くの記事があるが、今日は筆者の考えを書いてみる。
まず、第一に、不必要な車輪の再発明は避けるべきだ。例えば、簡単なTODOアプリを作るために1からデータベースサーバーを実装するのは有効な時間の使い方ではないだろう (それ自体が目的でない限り)。一方、数行で実装できる padStart
関数を自前で実装するために left-pad ライブラリに依存するべきではない。
ライブラリに依存しない方がいい
ライブラリは私たちには制御できない
ES2017で String.prototype.padStart
が導入される以前は、多くのライブラリが left-pad
というライブラリに依存していた。2016年2月のダウンロード数は226万回もあった。しかし、2016/3/23 に npm がこのライブラリを削除したため、世界中のソフトウェアがビルドできなくなってしまった。この間、 left-pad
に依存しているライブラリは勿論、間接的にこのライブラリに依存しているアプリケーションもビルドできなくなった。
colors.js
は週に1,382万回ダウンロードされていた人気ライブラリだ。このライブラリの作者は、ある日意図的に無限ループを仕込んだバージョンをリリースした。このライブラリは aws-cdk でも使われていたため、JavaScriptユーザーに留まらず、AWSユーザーにも被害が及んだ。
引用: https://qiita.com/SnykSec/items/23bcd8dc873239d2bece
moment.js は人気のある日付ライブラリだ。しかし現在は非推奨で、他のライブラリに移行しなければならない。この記事 によると、Day.js に移行するのに3ヶ月かかったようだ。
このように、ライブラリを使用するということは、自分で制御できない部分を増やすということでもある。left-pad
の場合は、数行で実装できるのだから自分で実装したらビルドは壊れなかったし、colors.js
は使わないであるとか簡単な色付けだけ実装して妥協しておくこともできた。moment.js
の場合は日時の扱いを Unix Timestamp に統一しておけば非常に簡単なユーティリティだけ実装すれば、移行に3ヶ月かかることもなかったかもしれしない。
これらは確かに「車輪の再発明」だが、再発明すれば上記のリスクを回避できたのだ。そして再発明のコストがさほど高くない場合もあるだろう。
ライブラリはいつ使うと良いのか
再発明すべき時とライブラリを使用するべき時の具体的な境界についてだ。
結論から言うと、これは
- セキュリティに関係するか
- チーム
- 企業
に寄るだろう。
1. セキュリティに関係するか
例えば認証に関する処理は、自力で実装することでセキュリティ事故に繋がる可能性が高まる (たとえセキュリティの専門家であったとしても)。よって、そのリスクを受け入れてでも自社にメリットがあると判断できる状況でない限りはセキュリティに関連する処理は自力で実装しない方が良いだろう。
2. チーム
チームに関しては、実装できそうなメンバーがいるかどうかが判断基準になる。過去に類似の実装をしたことがあったり、少なくとも実装方法の目論見があるメンバーがいれば、ライブラリを使用しないことを第一選択肢にするべきだ。
3. 企業
次に企業に関して。スタートアップであれば、まずはお金を稼ぐ必要がある。なので車輪再発明に使える時間は少ないだろう。一方で大企業であれば、ある程度車輪の再発明に時間を割くことができるだろう。
参考までに筆者の例を書いてみようと思う。筆者は現在プレシリーズAのスタートアップで働いている。相対的に車輪再発明に使える時間は少ない。それでも2日以内に実装できそうなものはライブラリを使用せずに自力で実装することにしている。
例えばフロントエンドでは、必須に思えるstore管理ライブラリすら使用していないし、フォームライブラリも使用していない。一方でフレームワーク (Vue.js) やリッチエディタはライブラリを使用している。
バックエンドでは、JSONやCSV,YAMLを扱う処理はライブラリを使用している。こういうのは仕様を全て実装しなければならないので筆者の能力では2日では実装できなそうだからだ。一方でテストフレームワークは自力で実装した。850行で必要な機能を揃えられたからだ。
SDKに依存しない方がいい
SDKはサービス稼働率を下げる
ここでいうSDKとは、例えば Firebase や Algolia のようなSDKを通じて機能を提供するサービスやツールのことを指す。例えば認証基盤に Firebase Authentication を使用しているサービスの場合、サービスの稼働率は以下の式となる。
それぞれの稼働率が90%だった場合、サービス自体の稼働率は 81% となる。
SDKを使用すればするほど (少なくともサービス全体の) サービス稼働率は下がらざるを得ないのである。
SDKはサービス体験を損なうことがある
例えば、サービスを多言語対応したいとする。多言語対応と言えばまずは翻訳だろうということで、Google 翻訳ウィジェット を導入したとする。
例えば Google Search Central Blog をGoogle翻訳すると、タイトルのレイアウトがこのように崩れる。(「使用可能」の文字に何かが被っている)
これは少なくとも望ましい状態ではないし、もしユーザーが翻訳されたサイトであることを知らずにこのサイトを見たら不信感を抱いて利用しないだろう。少なくとも最高のユーザー体験とは言えない。
そもそも、多言語対応とは、翻訳以外にも例えば以下の作業が必要である。
- 言語単位でレイアウト崩れが起きていないかの確認・修正 (デザインシステム含む)
- 各言語でのレイアウト崩れを検知するスナップショットテスト
- タイムゾーンや時差の対応
- 日時や数値の表示方法の対応
- ユーザーが必要とする言語データのみを配信できるビルドシステムの構築
- バックエンドサーバーが返すメッセージの多言語対応 (コード値を返すようにするなど)
加えて、SDKを使用すると、ユーザーは毎回SDKをロードする必要がある。ロードするリソースが多ければ多いほど、サービスを利用できるまでの時間が長くなり、これも体験を毀損する原因の1つとなる。
SDKはいつ使うと良いのか
まず、例えば Datadog のようなサービスのコア機能とは関係のないSDKはある程度寛容に導入しても良いだろう。配信リソースやCPU、メモリを消費しすぎない限りはユーザーの体験を大きく毀損することはないだろうし、開発者にとってメリットがあるのであれば導入するべきだ。
一方で、サービスのコアに密接に関わるSDKに関してはトレードオフがある。
SDKの利用は素早く最初のリリースを迎えられる点がメリットだが、デメリットとして、サービス稼働率を下げる原因になるかもしれないし、将来SDKが廃止されたら移行に大きなコストがかかるかもしれない。そしてユーザー体験は多かれ少なかれ妥協が必要だ。
一方で自分たちで開発すると、最初のリリースは多かれ少なかれ遅くなるが、継続的に最高のユーザー体験を提供できるし、SDKの廃止のようなことを考える必要もない。
参考までに筆者の例を書いてみようと思う。
認証基盤の導入を検討した際はSDKを採用した。理由としては、認証基盤の実装は専門的な知識が必要だし、仮に専門知識があったとしても、社会からの信用のないお手製の認証基盤を使用しているサービスを利用したいユーザーはいないだろう。なので、認証基盤に関しては細かなユーザー体験の追求よりも社会的に信頼のあるサービスを利用する方が望ましいと考えた。
別の例は、外部サービス連携を短期間で実装できる iPaaS の導入である。結論としては導入しなかった。理由は以下だ。
- 機能仕様・ユーザー体験に大きな妥協が必要だったこと
- iPaaSを使いこなすための技術的な負担が増加すること
- システムの構成要素が増えることによるメンテナンスコストの増加を避けたかったこと
例えば、メンテインしたい場合に、自社サーバー1個であれば単にサーバーを停止すれば良いが、連携先サービスがある場合は連携先サービス側の手続きを考慮する必要がある。また、テストを記述するにも複数のサーバーに処理が分散しているため、モックに頼る部分が必然的に大きくなり、本質的なカバレッジが下がっていまう。
結局、通常のプログラミングを用いて実装した。
まとめ
基本的な考え方として、理想の世界では全てを自作することが望ましいが、時間やコストなどの実世界の制約に応じて、段階的にライブラリやSDKを使用する、と考えるとわかりやすいかもしれない。
Discussion
興味深い記事をありがとうございます!
1つ質問で、SDKを用いてサービス稼働率が下がってしまうことのデメリットってどんなことがあるのでしょうか?
私がFirebaseなどの認証基盤でSDKを使った経験がないため、ここの部分についてハッキリと理解できませんでした。教えていただけると幸いです!
コメントありがとうございます。
Firebase を用いてコメントをしますが、Firebase はあくまで例であることを念のため言及しておきます。
そして前提を確認させてください。
SDKを使用した場合でも Firebase と直接 HTTP 通信をした場合でも根本的には変わらないと考えています。Firebase の稼働率が 100% でない限りは、サービス全体の稼働率は記事で述べた式の通り下がると思います。
筆者は、Firebase を使用する際にSDKを使用するか否かに関わらず、外部サービスを使用することでサービス全体の稼働率が下がる、という点に言及したつもりでした。SDKという言葉を使用したことで若干の混乱を招いたかもしれません。この記事では以下のように捉えて頂けると幸いです。
この前提を元にご質問に回答させて頂きます。(もしかしたらもう不要かもですが)
サービス稼働率が下がることのデメリットとして最も大きな点は、得られるはずだった利益が得られなくなることではないでしょうか。
突然のサービス停止は、その時間にユーザーがサービスが使えないばかりか、不安定なサービスとみなされて競合サービスに乗り換えられるリスクがあります。よって、サービス稼働率は重要です。
また、Firebase などの認証基盤は、非常に有名なので流石に稼働率はほぼ100%に近いだろうと考えるかもしれません。確かにその通りです。Firebase はほぼ100%の稼働率です。なのでSDKを使用しても実際にはサービス稼働率はほとんど下がらないと考えるかもしれません。
しかし、SDKやライブラリは、メジャーバージョンアップデートで破壊的変更をすることがあります。破壊的変更とは、簡単に言えばSDKやライブラリを使用する部分のコードを何かしら変更しないとこれまでと同じように使えなくなることです。
破壊的変更に事前に気づいてコードを修正できれば何も問題はありませんが、破壊的変更に気づくためには人間による作業が必要な場合があります。 (ライブラリの場合は Renovate などを使用すれば機械的に気づけますがRSSで流れてくる情報を目で確認しなければならない、みたいな場合もありますよね) 人間はミスをするのでどこかで作業漏れが発生します。そしてその漏れは1度発生しただけで致命傷になる可能性があります。例えば、サービスが停止した後に問題に気づいたが、その破壊的変更に対応するには数日以上かかるかもしれません。数日間サービスが停止したらとんでもない問題ですね。
よって、このような人為的ミスの可能性も考慮して実際の稼働率を測るべきです。2年に1回人為的ミスが発生して5日間サービスが停止するとしたら稼働率は99.3%です。そして、SDKの数だけ指数関数的に稼働率は下がります。5個のSDKを使用していたら稼働率は 96.5% です。年間13日弱のサービス停止です。
極端な話、この認証基盤を自チームで実装していればこんなことには決してならないはずです。単純にCIで気づけるからです。なので、まずは自力で実装することを第一選択肢とし、自身やチーム、組織の能力、時間、コスト制約を考慮して場合によってはSDKを使用する、という考え方が良いのではないかというのが筆者の意見でした。
念の為補足しておきますが、この記事とコメントは、全部自前で実装するべきだ! みたいな過激な考え方ではなく、あくまでライブラリやSDKを導入するための検討基準のようなものを提示した、と捉えて頂けると筆者の意図を汲んで頂けて嬉しいです。
ここまでが筆者の主張でした。引き続きご質問などございましたら記載頂けると嬉しいです!
詳細な説明ありがとうございます!
正しく以下の前提部分の認識ずれていたため理解することができました!
人為的なミスによる稼働率の低下の部分も納得です!ありがとうございます!