Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

レガシーシステムをこえて

Sansanプロダクト開発部・基盤チームの加畑です。法人向け名刺管理サービスSansan(以下、Sansan)の開発をしています。

これまでに、レガシーシステムの改善を主題とした記事を2つ書きました。レガシーシステムのおそうじでは、レガシーシステムを改善するプロジェクトの担当者としての経験をご紹介しました。レガシーシステムとつきあうでは、少し視点を広げ、ソフトウェアや開発組織の拡大に応じたレガシー化の変遷について考察し、組織としてどのように取り組むべきかの一考察を紹介しました。

ソフトウェア(システム)は本質的に複雑性をはらんでおり、変更が生じるかぎりにおいて、メンテナンス性の低下、いわゆるレガシー化と呼ばれる現象は避けられません。レガシーなシステムは事業の視点からみても技術的負債としてメンテナンスコストとして「利息」を払い続けることになります。それを避けるためには継続的に改善していく必要がありますが、レガシー改善プロジェクトはそれ自体が直接的な利益やユーザ価値を生まないにもかかわらず、コストが生じます。このコストを払わない最良の方法のひとつは、レガシー化してからなおすのではなく、開発の過程で生じるレガシー化の程度を最小限に抑えることです。

そこで今回は、「そもそもレガシーシステムを作り込まない」ことを目的に、開発プロセスについて考えてみます。

レガシーコードからの脱却

レガシーを作り込まない方法を考えるにあたり、指針として、書籍『レガシーコードからの脱却』を参考にします*1

www.oreilly.co.jp

レガシーシステムやコードに関する他の書籍としては、『リファクタリング*2』や『レガシーコード改善ガイド*3』などが有名です。それらに対する本書の特徴は、そもそもレガシーコードを生み出さないというところに焦点をあてている点、またその方法としてアジャイルという思想に基づき、実践的なプラクティスを紹介している点にあります。

レガシーシステムの定義は様々ですが、ここでは「メンテナンス性の低いシステム」と考えます。メンテナンス性の低いコードのわかりやすい例をあげると、「テストのないコード」があります*4。あるコードに対して機能の変更やリファクタなどをしたいと思ったとき、そのコードの正しい動作が把握できれば、それを維持したままリファクタすることができます。テストがあればそれが結果を保証してくれますが、テストがなければ変更のリスクが大きく、メンテナンス性が相対的に低い状態といえます。テストが書かれるか書かれないかが、メンテナンス性を左右するひとつの基準になります。

レガシー改善とアジャイル

先のとおり、書籍『レガシーコードからの脱却』では、アジャイルに基づきレガシーコードを生み出さないことに主眼を置いてます。アジャイルというとスクラムやXPが想起され、新たなシステムや機能を開発するプロジェクトでの適用事例や、書籍は世の中に無数に存在し、その価値が広く知られています。

ソフトウェアがレガシー化する要因は状況により様々ですが、それらの根幹には、人間の不確実性があると考えています。ウォーターフォール型の開発プロセスを考えたとき、設計を終え、実装をしている段階で要件変更があると、設計と実装に消費したぶんのコストが成果物に対して余分に発生します。そのつじつまをあわせるために設計が不十分になり、結果としてメンテナンス性の低いシステムになってしまいます。あるいは、実装を進める中で仕様の考慮漏れや既存コードの複雑さが想定以上であることに気づき、計画が崩れることもあります。アジャイルは、これらの問題へ対処します。アジャイルとは何かをここで説明することはしませんが、その起点となるアジャイルソフトウェア宣言では、以下の価値基準を示しています。開発を発見的なプロセスとして構築することにより、システムの継続的改善を可能にします。

Individuals and interactions over processes and tools
Working software over comprehensive documentation
Customer collaboration over contract negotiation
Responding to change over following a plan

翻って、我々は新規開発のみでなく、現実問題として「すでに存在しているレガシーシステム」に対処する必要があります。そして、旧来の開発プロセスにおける先述のリスクは、むしろ新規開発よりもレガシー改善のプロジェクトのほうが相対的に高くなるケースがあります。明確に機能が増えたりするわけではないため、「何を持って完成か」という要件を定義することが難しくブレやすいですし、そもそもメンテナンス性の低いシステムを対象とする以上、見積りが困難です。レガシーシステムを改善するプロジェクトのはずなのに、その結果がさらなるレガシー化を生み出す、ということが往々にしてあります。以上より、アジャイルは新規開発のみならず、レガシーシステムの改善においても価値を発揮すると考えられます。

そこで今回は、「そもそもレガシーシステムを作り込まない」という目的に対して、アジャイルというアプローチから考えます。その際、書籍『レガシーコードからの脱却』で紹介されている9つのプラクティスのうち、「小さなバッチで作る」「まずテストを書く」「レガシーコードをリファクタリングする」の3つをピックアップし、直近Sansanで私が関係したレガシー改善プロジェクトの事例ともに紹介してみます。

小さなバッチで作る

QCDSという、製品評価の指標があります。これは、品質(Quality)、予算(Cost)、納品(Delivery)、スコープ(Scope)を意味します。QCDは旧来の評価においても用いられてきましたが、アジャイル的な発想としてはスコープが可変なパラメータとして追加されています。スコープを調整することにより、全体の品質を最適化することができるという考え方です。

我々がやろうとすることはたいてい複雑であり、複数のことが混ざり合っています。それをそのまま実現させようとすると、単一のプロジェクトでは制御が難しくなりがちです。そこで、既知のことと未知のことを分離し、制御可能な単位にまでなったら、その単位ごとに独立な問題として解決します。

レガシーシステムの改善の文脈では、大きく「リファクタ」と「リライト」という戦略があります。前者はコードを段階的に修正していく戦略、後者は現行のシステムを捨て、同等の機能を持つ別のシステムを新たに作り、置き換える戦略です。一般に、リライトはリファクタが難しい状況における最終手段とされています。しかし、この2つは不可分ではなくスコープの問題と捉えることもできます。以下では、リファクタの積み重ねによりリライトを実現する事例を紹介します。

事例: アーキテクチャ移行

Sansanでは、歴史的な経緯により、主に業務ドメインにあたるアーキテクチャ部分に、複数世代のコードが存在します。新たに書かれるコードは最新世代のアーキテクチャに則りますが、旧世代のコードも残っており、一定のメンテナンスコストがかかる状態です。できることなら完全に移行したいのですが、アーキテクチャ全体を置き換える、いわゆるリライトのプロジェクトとして実行するには規模が大きく、リソースを一括で投入することは難しいです。また、事業要求に答えるため他の通常のプロジェクトも並行して進める必要があり、コードのコンフリクトが発生しえます。

そこで、「アーキテクチャ移行」という単位ではなく、子プロジェクトとして切り分け、それぞれを独立したプロジェクトとして移行を進めています。そのために、「小プロジェクトに分ける作業」にも相応にコストを払い精度を上げるべきととらえ、それ自体をひとつのプロジェクトとしています。

f:id:kyabatalian:20200725211656p:plain
アーキテクチャ移行の事前プロジェクト(v2は旧アーキテクチャの通称)

これにより、アサインされたチームはそのプロジェクトのスコープ内に責任を持ち、全体として徐々にアーキテクチャ移行を進めることが可能になりました。アーキテクチャ移行という最終的な目的をそのままプロジェクトのスコープにしてしまうと、そのための計画やリソースの確保が難しくなります。部分的な移行であっても十分に価値があり、その集積として結果的に「リライト」を実現することも可能です。「小さなバッチで作る」は必ずしも新規機能を分割してリリースするということに限らず、レガシーシステムの改善においても有効なプラクティスであることがわかります。

「CLEAN」コードを作る

CLEANとは、Cohesive(凝集性)、Loosely Coupled(疎結合)、Encapsulated(カプセル化)、Assertive(断定的)、Nonredundant(非冗長)の頭文字をとった略語です。ソフトウェアの業界はまだ成熟段階にありませんが、その中でも「良いコード」の指標となりうるとして、広く知られた観点です。

『レガシーコードからの脱却』にある9つのプラクティスの中でも、特にコードの品質を評価する指標であり、ソフトウェアの技術的な知識や能力、いわゆる技術的卓越性に直結するプラクティスです。良いコードは読みやすく整理されており、テストしやすく、したがって不具合を生みにくいです。本質的には関心事を分離し明確にしている、つまりモデル化を適切に行うということです。

事例: APIのサーバレス化

SansanではいくつかのAPIを開発・運用しています。そのうちのひとつに、「名刺のデータ化結果を受け取るAPI」があります。Sansanでスキャナや名刺取り込まれた名刺画像は、社内の別部門であるDSOC*5に送られ、データ化された結果がSansanに返ってきます。そのデータ化結果を受け取り、Sansanのデータベースに格納する役割をもつAPIがあります。

f:id:kyabatalian:20200725214544j:plain
データ化結果を受け取るAPI(移行前)

名刺画像の取り込みやデータ化のリクエストは時間帯によって波があります。また、サービスの成長に伴い、取り込まれる名刺の量が増え、APIに対するリクエストも全体として増加傾向にありました。これらのことから、従来どおりEC2インスタンスを増やしてスケールさせていく方法だと、メンテナンスコストの効率が悪かったため、以下のアーキテクチャに移行しました。

f:id:kyabatalian:20200725213620j:plain
データ化結果を受け取るAPI(移行後)

API Gatewayでリクエストを受け、Lambdaを介して「データベースに書き込むメッセージ」をSQSに送信し、内製のメッセージング処理基盤を用いてデータベースに書き込みます。これにより、リクエスト数に応じたスケーラビリティを確保し、かつ「データ化の結果を受け取る役割」と「データベースに書き込む役割」を分離することで、CLEANなアーキテクチャを実現しています。

これにより、DSOCとの結合が疎になり、メンテナンス性も向上します。例えばSansanでデータベースのメンテナンスのため一時的に書き込みを止めたいといった場合に、従来だとDSOC側からAPIへのリクエストを止めてもらう必要がありましたが、新たなアーキテクチャの場合はメッセージングサーバのみを停止させればよくなります。DSOCからのリクエストによりSQSのキューにメッセージが蓄積されていきますが、メッセージングサーバを再開させたときにそれらを処理していくので、DSOC側はSansan側のメンテナンスを意識する必要がなくなります。

また、Sansanにはメール配信の機能があり、配信基盤としてはSendGrid*6を利用しています。SendGridは配信結果をWebhook経由で送信する機能があり、Sansanではそれを受け取りデータベースに保存するAPIを運用しています。このAPIについてもほぼ同様のアーキテクチャで再設計しました。実装時には、これらの間のインターフェイスとなるメッセージクラスを先に設計することで、それぞれの役割を独立したタスクとし、実際に分担して進めることができました。

f:id:kyabatalian:20200725212024p:plain
メッセージクラスを実装したプルリクエスト

メッセージクラスは、インターフェイスを明示します。例えば、配信対象のメールアドレスを保持するプロパティに対しては、メールアドレスのフォーマットやSansanとして許容している文字列長の制約を設けます。また、配信結果のステータスを保持するプロパティに対しては、SendGridの仕様に従ってとりうる値の制約を設けます。メッセージを送信するLambdaのコードや、メッセージを処理するメッセージングサーバのコードは、この制約を前提とすることができます。このように、クラスあるいはプロセスに対する役割を明確にし、CLEANな設計にすることで、レガシー改善に伴う不確実性のリスクを抑えつつ、中長期的にスケールするシステムを構築することができることがわかります。

まずテストを書く

先述のとおり、テストの有無はわかりやすくメンテナンス性を左右します。本節はプラクティス6を引用していますが、続くプラクティス7は「テストでふるまいを明示する」とあり、アジャイルにおいてテストが重要な位置を占めていることがわかります。ここで、レガシー改善の文脈でとらえたとき、自動テストの実行環境自体もまたソフトウェアであり、メンテナンスの対象であり、レガシー化するものとみる必要があります。最たる例として、組織が拡大するフェーズにおいて実行速度がボトルネックになり、しばしば「テスト実行待ち」が発生し、開発を阻害します。

事例: 自動テストの並列化

Sansanでは、GitHubに対してpushされたブランチに対して、Jenkinsを用いて自動テストを実行しています。このテストは直列で実行されていたため、組織の拡大に伴い処理速度が追いつかず、「テスト実行待ち」の時間が開発効率を下げるひとつの要因になりつつありました。

f:id:kyabatalian:20200725212632p:plain
実行を待つテストジョブたち

そこで、自動テストを並列化するプロジェクトを進めました。課題は、自動テスト間の依存です。データベースやその他リソースへの依存関係があり、並列実行するとタイミングによって結果が変わる、という状態になっていました。

テストに限らず、一般に並列実行されたときの挙動は再現性の低いケースが多いです。「並列処理を想定してリソースに依存しないテストを書く」というのは当然ですし理想ですが、レガシーシステムが対象である場合、あるいは組織が拡大しその品質を制御しきれない場合には、その前提が保証されないケースもあります。現状のテストを別環境で並列実行してみる、ということがひとつの有効な手段になります。

f:id:kyabatalian:20200725213008p:plain
並列化テストのリリース

また、テストと呼ばれているのは、必ずしも自動テストには限りません。このプラクティスは、結合テスト・受け入れテストなどにも同様に適用することができます。設計の段階で結合テストが作成されることにより、いまから作ろうとしているものは何なのかをより明確にし、また自分や他メンバーの間で理解を深めることができます。あるいは、開発者とプロダクトオーナーの間のインターフェイスととらえ、満たされるべき仕様を合意することができ、「意図しないものを作っていた」という最もコストの高くもったいない手戻りを予防することができます。

まとめ

レガシーシステムを作り込まないという目的に対する、アジャイルの適用事例とその有効性を考察しました。書籍『レガシーコードからの脱却』で紹介されている9つのプラクティスのうち、「小さなバッチで作る」「CLEANコードを作る」「まずテストを書く」の3つをピックアップし、それぞれSansanにおける事例として、開発プロセス・設計・環境と、毛色の違う3つの領域に対する改善プロジェクトを紹介しました。新規機能の開発のみならず、レガシー改善の文脈においてもアジャイルが有効であることを、多少なりとも示せたのではないかと思います。

ここで紹介したとおり、アジャイルかウォーターフォールかといった二者択一ではなく、開発効率の最大化、ひいては事業価値を最大化を目指してよりよい開発を模索することが重要であり、それ自体がアジャイルと呼ばれる思想の根幹であると考えています。長く価値をもたらし続けるシステムを構築したい方、また既存のレガシーシステムを改善し、サービス・ビジネスのさらなる発展を切り開きたい方にとって、本記事が少しでも参考になれば幸いです。

宣伝

書籍『レガシーコードからの脱却』の著者及び訳者による講演を、8月30日(日)にオンラインイベントとして開催します。

buildersbox-online.com

以下が講演の概要です。

本セッションは、モダンアジャイルソフトウエア開発に欠かすことができないエクストリームプログラミング(XP)における、5つのプラクティス(CIのためのビルドの自動化、ペアプログラミングを通してのチームメンバーとのコラボレーション、テストの実行を容易にするための設計スキルの実践、設計を進めるテストファースト開発の使用、技術的負債を軽減させるためのリファクタリングコード)について紹介します。 これらの5つの技術領域はモダンアジャイルとともに持続的な成功のために不可欠なものとなっています。しかし多くのチームはこれらのプラクティスから恩恵を受けられておらず、効果的にも使用できていません。 本セッションでは、なぜこれらのエンジニアリング手法がアジャイルソフトウエア開発において重要であるかとともに、リスクを減らし、あらゆる開発過程において品質を確保する方法について考察します。

今回触れた内容に限らず、またレガシーに限定せず広くソフトウェア開発におけるプラクティスが、著者本人の言葉で語られる、貴重な機会です。本記事に興味をもたれた方にとってはもちろん、そうでない方も、ご参加をお待ちしています。

*1:本記事のタイトルは、この本の原題『Beyond Legacy Code』をリスペクトしています

*2:https://www.ohmsha.co.jp/book/9784274224546/

*3:https://www.shoeisha.co.jp/book/detail/9784798116839

*4:『レガシーコード改善ガイド』ではこれをレガシーコードの定義としています

*5:Data Strategy & Operation Centorの略。 https://sansan-dsoc.com/

*6:https://sendgrid.com/

© Sansan, Inc.