DevOps 技術: コードの保守性

Google が構築するシステムを実行するには、多くのコードが必要です。Android オペレーティング システムは 1,200~1,500 万行のコードで実行されます。Google のモノリシック コード リポジトリには 10 億以上のコード行が含まれています。標準のスマートフォン アプリには 50,000 行のコードが含まれています。

DevOps Research and Assessment(DORA)の調査による 2019 年の State of DevOps Report では、チームのコードを効果的に維持できる能力は、継続的デリバリーによる成功のために積極的に貢献する多数の技術手法の 1 つあると示されています。

チームがコードの保守性により良好に機能していれば、次のことが実現します。

  • 必要に応じてコードベースでの例の検索、他の人のコードの再利用、他のチームが管理するコードの変更を簡単に行う。
  • プロジェクトへの新しい依存関係の追加や、依存関係の新バージョンへの移行を簡単に行う。
  • チームの依存関係が安定し、コードの破損が滅多に発生しない。

これらにより、デベロッパーによる組織のコードベース全体でのコードの検索、再利用、変更が容易になること、依存関係の管理に役立つプラクティスやツールの実装の重要性が明らかになります。

コードの保守性は、他のチームのコードの検索、再利用、変更を可能にする機能に依存しているため、組織全体の調整が必要な機能です。大規模なコードベースや大規模な組織では、依存関係を効果的に管理することが重要になります。依存関係の問題を回避できるツールや、コードの変更により生じる影響を明らかにするツールにより、すべてのエンジニアによる設計の決定やコードの品質が向上します。これにより、エンジニアは開発を迅速化し、安定性と信頼性の高いソフトウェアを作成できます。

コードの保守性の実装方法

実装の面では、ソースコードの管理と依存関係の管理は個別に扱うことが重要です。ただし、Google が使用するモノリシック リポジトリや monorepo パターンなどのソリューションでは、これらの両方に対応できます。

まず、ソースコードの管理です。誰でもコードベースの任意の部分の検索、再利用、変更を簡単に行えるようになります。

  • 配布の高速化: ソフトウェアを迅速に配布するには、チームが互いのコードを表示して変更を提案できる必要があります。これをできる限り簡単にすることで、組織全体で知識を伝達できます。また、作業遂行のためにコードベースの他の部分に変更が必要なメンバーのブロックを解除することもできます。
  • 安定性と可用性が向上: インシデントが発生した場合、コードベースのどの部分かを迅速に検出し、変更を行えることが非常に重要です。
  • コードの品質向上: コードのリファクタリングによって内部の品質を向上させるには、多くの場合、コードベースの複数の部分を変更する必要があります。これが難しい場合、リファクタリングを実行する可能性が減り、コストが増加します。Google を含めて一部の組織では、チーム間でのコードの保守プロジェクトを実行し、コードベースの保守レベルのアイテムを修正しています。このような修正は、組織全体のコードに簡単にアクセスして変更する機能に依存しています。

例の検索や他のユーザーのコードの再利用は、組織のソースコード全体に簡単にアクセスして検索できることに依存しています。この要件を実装する最も簡単な方法は、コードがプラットフォーム内の複数のリポジトリに分割されている場合でも、組織全体のコードに対して単一のバージョン管理プラットフォームを使用することです。使用するバージョン管理のプラットフォームが分散しているほど、コードを見つけるのが困難になります。

組織によっては、コードベースの一部をロックしたまま、そのコードベース部分を必要とするユーザーだけに対して表示するする必要が生じまる(Google はそのような組織の 1 つの例です)。これは、理想的にはルールではなく例外とし、ソフトウェアは機密のソースコードの表面領域を最小限に抑えるように設計されるべきです。多くの場合、バージョン管理システムのアクセス制御メカニズムを使用して、機密性の高いコードを論理的に分離するだけで十分です。

また、他のチームが管理するコードを変更することもできます。そのような変更には、通常、当該のコードを管理するチームからの承認を得る必要があります。pull リクエストなどのメカニズムでは、バージョン管理でブランチが作成され、承認によってブランチが統合されるため、他のチームが変更を提案する際の負担を最小限に抑えつつ、不正な変更を防止し、職掌分散などの情報セキュリティ管理を実行します。

次に、依存関係について考えてみます。チームが簡単に依存関係を追加および更新できるようにして、依存関係を安定させ、コードがほとんど破損しないようにすることで、次のようになります。

  • セキュリティの向上: 依存関係は存続するほど、その脆弱性が見つかる可能性が高くなります。特に脆弱性が発見されてパッチが付与された後に、依存関係を最新の状態に維持することが不可欠です。
  • 配布の迅速化: 他のチームや組織が開発したライブラリを使用すると、独自のコードを作成してその処理を行う必要はありません。依存関係が安定していてコードが滅多に破損しないようにするメカニズムがあれば、より多くの時間をコーディングに費やし、メンテナンスに費やす時間を短縮できます。

依存関係の管理は、ソフトウェア開発チームにとって一般的な課題です。依存関係の最新の状態を保ち、アプリ間で一貫性を保つことは、複雑で、時間と費用がかかります。多くの組織では、このタスクに適切なリソースを割り当てることができません。この問題はプロセスやツールが不十分なことによって悪化します。依存関係に明らかな脆弱性が発見され、それらを使用するアプリケーションを更新する必要がある場合、重大なセキュリティ リスクが発生する可能性があります。

新しいバージョンの依存関係に重要な変更が含まれるかどうかを確認し、このような依存関係が使用されているシステムで依存関係を迅速かつ簡単に関連付けるために、自動化された継続的インテグレーション(CI)やテストを含めて、チームが既知のバージョンの依存関係を利用して、すばやく容易にアップグレードするには、プロセスとツールを導入し、進化させることが重要です。

ソフトウェアへの依存関係の追加には、ベンダリングと宣言型マニフェストという 2 つのモデルが一般的に使用されています。ベンダリングでは、すべての依存関係のソースコードまたはバイナリが、アプリケーションとともにバージョン管理にチェックインされます。また、最近のプラットフォームには、バージョン管理にチェックインされた宣言型マニフェスト ファイルで指定されている依存関係を管理する依存関係管理ツールがあります(たとえば、Python の pip、Node.js の npm、R の CRAN、.NET の NuGet など)。

ベンダリングとマニフェストのどちらを使用する場合でも、最も重要な考慮事項は次のとおりです。

  • トレーサビリティ: 特定のパッケージやデプロイから、そのビルドに使用されたすべての依存関係の正確なバージョンにトレースできるようにします。そうしないと、依存関係への変更が原因で発生する問題のデバッグはできません。
  • 再現性: ビルドプロセスは可能な限り確定的にする必要があります。マシンによって動作が異なるビルドプロセスに起因する問題のデバッグは非常に困難です。

Google におけるコードの保守性の実装

Google では、比較的標準的なアプローチを通じてコードの保守性を実装しています。Google のアプローチはトレードオフを伴い、すべての人を対象としたものではありませんが、この記事で説明している目標をチームが効率的に達成できるようになります。

世界中の Google のソフトウェア デベロッパーの 95% が、トランクベース開発モデルを使用して、一元化されたソース管理システムを介して管理されるモノリシック共有コードベースを操作しています。2016 年の Google のコードベースには、「約 100 億個のファイルが含まれ、18 年間わたる約 3,500 万件の契約の履歴が含まれていました。リポジトリには 86 TB のデータが含まれ、約 900 万個のソースファイルに固有の約 20億 行のコードが含まれています。」

すべての Google のコードは 1 つのリポジトリに保存されるため、他のチームのコードを簡単に見つけて変更できます。Google は、コードベース全体を簡単に検索できる内部の検索エンジンを提供しています。デベロッパーはさまざまなツールを使用して、レビューと承認のための変更リストを作成できます。テストはすべての変更リストに対して実行され、変更リストは更新とコメントが可能です。他の多くのプラットフォームと同様に、Google のコードレビュー ツールを使用して、レビュー担当者に特定の変更を提案できます。Google リポジトリ内の各ディレクトリには、そのディレクトリ内のファイルに対する変更を承認できるユーザーまたはグループをリストした OWNERS ファイルがあります(同様の機能は GitHubGitLabBitbucket で使用できます)。すばやい検索、承認者の候補、自動テストによって、変更の提案、レビュー、共同編集が簡単かつ確実になります。

これらのツールを使用すると、コードベースの複数の部分を変更する大規模なリファクタリングは比較的簡単に実行でき、アトミックに実行できます。Google では、コードベースの重要なセクションに影響を与える変更を行うプロセスをさらに簡素化、自動化するツールを構築しています。Google ツールには、Blaze と呼ばれるビルドシステムが含まれており、これを使用して、依存関係を含むすべての変更をソースからビルドし、テストできます。(Blaze の一部は、オープンソース ツール Bazel の形式でリリースされました)。 Google のどの部門でも、変更を発見して提案できます。提案内容には、同じモノリシック リポジトリに保持されているインフラストラクチャ構成も含まれます。コードの共有と再利用は簡単です。

Google にはオープンソース ソフトウェアへの依存関係を管理するためのコントロールもあります。まず、Google で使用されるすべてのオープンソース ソフトウェアでソースを Google のモノリシック リポジトリにチェックインさせる必要があります。2 つめに、指定のライブラリの 1 つのバージョンのみが、ソース コントロールにいつでもチェックインできます。3 つめに、すべてのソフトウェアは静的にリンクされ、ソースからビルドされます。最後に、ライブラリのソースコードが変更されるといつでも、その依存関係を使用するすべてのソフトウェアの再ビルドと自動テストがトリガーされます。

これらの管理機能と Google の強力な CI インフラストラクチャを組み合わせることで、新しいバージョンの依存関係によって本番環境システムを簡単に最新に保つことができます。また、すべてのシステムで特定のライブラリの一貫したバージョンが使用されるようになります(プロダクトが 2 つのコンポーネントに依存し、次に共通のライブラリの複数のバージョンに依存することで、プロダクトをビルドできなくなる「ダイアモンド依存関係」の可能性をなくします。)。

外部コードで依存関係を管理するための Google のアプローチとのトレードオフは、新しい依存関係の追加が困難になることです(コードの保守性による主な成果の 1 つ)。新しい依存関係については、そのソースコードを Google のモノリシック リポジトリにチェックインする必要があります。つまり、初期段階とアップグレード時の両方で、コードをレビューしてテストする必要があります。ただし、このレベルの厳密性により、Google のプロダクトのコードにセキュリティの脆弱性が生じるのを防ぎ、すべての依存関係でメンテナンス担当者が明確に指定されるようになります。

コードの保守性に関する一般的な注意点

すべてのコードを誰でも検索して変更できるようにすることによる主な障害は、ツールのサポートと組織文化です。

最初の注意点は、複数のバージョン管理リポジトリか、アクセス設定が制限されているバージョン管理リポジトリです。可能であれば、すべてのコードを 1 つのバージョン管理プラットフォームにまとめておく必要があります。デフォルトのアクセス権は、組織内の誰でもソースファイルを表示できるようにし、機密ファイルへのアクセスを制限するのが理想的です。バージョン管理も検索できます。

これに対して、組織は通常、バージョン管理を変更できるユーザーを制限しています。これが 2 番目の注意点です。つまり、コードベース内の書き込みアクセス権を持っていない部分に変更を加えるユーザー向けのツールやプロセスがないことです。これは、(多くの場合、誤って)生じた問題の修正と、依存先のコードの所有者である他のチームによって生じた問題の修正の両方で、大きな障害となる可能性があります。また、これによって、コードベースの複数の部分にアクセスするリファクタリングも阻止されます。

この問題を軽減するために、一部の最近のバージョン管理ツールでは、ユーザーが書き込みアクセス権を持っていないコードベースの部分に対する変更リクエストの送信、レビュー、承認、監査を行う方法が用意されています。

ツールがサポートされている場合でも、組織内で(場合によってはベンダーや請負業者も対象とする)コードベースを利用可能にして、検索できるようにすることが必要になります。これは、バージョン管理リポジトリに大量の機密情報とチーム間で共有できないコードが含まれている組織では不可能な場合があります。

バイナリ依存関係の使用が最も一般的ですが、各言語にはバイナリ依存関係を管理するための独自のツールチェーンがあります。組織全体のソフトウェア ポートフォリオで依存関係を効率よく管理、追跡できるようにするための標準的な戦略と慣例を作成するには、複雑な作業が必要です。CI インフラストラクチャに多額の投資を行って、ライブラリの新しいバージョンに対する既存のシステムとの互換性のテストや、脆弱性のテストを容易にするには、サードパーティ ライブラリのアップグレード プロセスを定期的に実施する必要があります。

実際には、多くの組織が依存関係の管理をチームに委ねていますが、その結果は大幅に異なります。そのため、ライブラリで見つかった脆弱性に迅速に対応することや、事前に予測することは非常に困難です。影響を受けるサービスを検出することでさえ、通常、非常に古典的なプロジェクトです。

コードの保守性を測定する方法

ここでは、コードの保守性の測定を開始するためのシンプルなアイデアをいくつか紹介します。

  • 組織のコードベースのうち、検索可能な割合。
  • 書き込みアクセス権を持っていないコードベースの部分に変更する場合の平均リードタイム。
  • コードベースの重複しているコードの割合。未使用の割合。
  • 使用しているすべてのライブラリのうち、最新の安定しているバージョンが使用されていないアプリケーションの割合。
  • 本番環境での各ライブラリのバージョン数。中央値。適切な目標。2 年以上経過しているバージョンの数。
  • チームがライブラリをアップグレードする頻度。処理にかかる時間。

測定対象を検討する際に、次の 3 つのユースケースに焦点を当てます。

  • 技術的債務と設計上の債務の管理
  • チェンジ マネジメント(緊急の変更を含む)
  • 脆弱性に対するパッチ適用

コードベースが大きくなるにつれ、技術的負債が大きな懸念事項となります。組織とその顧客が進化に依存するプロダクトとして、コードをリファクタリングし、再設計できることが重要です。大規模なコードベースの場合、重要なツールがサポートされていないと、これが複雑になる可能性があります。また、未使用、重複している、テスト カバレッジが低い、または脆弱性が含まれているコードを特定できることも重要です。最初のステップとして、ツールを使用して、改善する領域を特定し、安全に対処することを容易にするための指標を確立して追跡します。

次のステップは、チェンジ マネジメントです。誰かがコードベースの一部に変更を加えた場合、お持ちのツールはその変更による影響を検出するのにどの程度役立っていますか?別のチームが影響を受ける場合、特にコードベースの別の領域で修正が行われる場合、問題を解決するためアクションにかかる時間。緊急の変更を行う必要がある場合、コードベースへの必要なコードの変更、テスト、リリースにかかる時間。

指標を確立して追跡すると、プロセスによって変更が反映されるまでにかかる時間を追跡できるようになります。次に、ボトルネックを特定し、必要に応じてツールのサポートを追加して、プロセスを改善します。速度の向上のために検証と承認をバイパスする「緊急」のプロセスには注意が必要です。目標は、通常のプロセスの信頼性と速度が、緊急時に効果的に機能することです。

脆弱性に対するパッチ適用は、特に重要な変更管理のシナリオです。ライブラリで脆弱性が見つかった場合、脆弱性のあるバージョンのライブラリを使用するソフトウェアの検出とパッチにどれくらいの時間がかかるでしょうか? 不明な場合は、定期的にテストすることをおすすめします。侵害やデータやコードの流出への対処には多大な費用がかかるため、このような攻撃を頻繁に受ける場合は、使用しているサードパーティ ソフトウェアを最新の状態に保ち、脆弱性が見つかった場合に簡単にアップグレードできるようにするために、多大なリソースを投入する価値があります。

次のステップ