重大な変更

.NET ライブラリでは既存のユーザーに向けた安定性と将来に向けたイノベーションとの間でバランスを取ることが重要です。 ライブラリ作成者にはコードが完璧になるまでコードを再構成し、再考する傾向がありますが、既存のユーザーを壊すと、特に低レベルのライブラリに対して、マイナスの影響が出ます。

プロジェクトの種類と破壊的変更

.NET コミュニティによるライブラリの使用方法によって、破壊的変更がエンドユーザー開発者に与える影響が変わります。

  • シリアライザー、HTML パーサー、データベース オブジェクトリレーショナル マッパー、Web フレームワークなど、低レベル/中レベルのライブラリが破壊的変更の影響を最も大きく受けます。

    ビルディング ブロック パッケージは、アプリケーションを構築する目的でエンドユーザー開発者が使用します。また、NuGet 依存関係として他のライブラリによって使用されます。 たとえば、アプリケーションを構築しているとき、オープン ソースのクライアントを使用して Web サービスを呼び出すとします。 クライアントで使用される依存関係に対する破壊的更新は自分で修正できるものではありません。 変更しなければならないのはオープンソースのクライアントであり、それに対しては何の権限もありません。 互換性のあるライブラリ バージョンを探すか、クライアント ライブラリの修正案を提出し、新しいバージョンが出るのを待つ必要があります。 最悪なケースは、使用する 2 つのライブラリが依存する 3 つ目のライブラリのバージョン間で互換性がない場合です。

  • UI コントロール スイートなど、高レベルのライブラリの場合、破壊的変更の影響が小さくなります。

    エンドユーザー アプリケーションでは、高レベルのライブラリは直接参照されます。 破壊的変更が行われた場合、開発者は最新版への更新を選択するか、破壊的変更に合わせてアプリケーションを変更できます。

✔️ ライブラリがどのように使用されるかについてよく考えてください。 破壊的変更はそれを使用するアプリケーションやライブラリにどのような影響を及ぼしますか。

✔️ 低レベルの .NET ライブラリを開発するときは、破壊的変更を最小限に抑えてください。

✔️ ライブラリを大幅に書き直した場合は、新しい NuGet パッケージとして公開することを検討してください。

破壊的変更の種類

破壊的変更はさまざまなカテゴリに属し、その影響力は一様ではありません。

ソースの破壊的変更

ソースの破壊的変更はプログラム実行に影響を与えませんが、次回、アプリケーションを再コンパイルしたとき、コンパイル エラーを引き起こします。 たとえば、新しいオーバーロードによって、以前はあいまいではなかったメソッド呼び出しに多義性が与えられることがあります。あるいは、パラメーターの名前が変更されると、名前付きパラメーターを使用する呼び出し元が壊れます。

public class Task
{
    // Adding a type called Task could conflict with System.Threading.Tasks.Task at compilation
}

ソースの破壊的変更は開発者がアプリケーションを再コンパイルするときにのみ害があります。そのため、破壊的変更の中では破壊性が最も少なくなります。 開発者は壊れた自分のソース コードを簡単に修正できます。

動作の破壊的変更

動作の変更は破壊的変更の中でも最も一般的な種類です。動作のいかなる変更も、消費者にロジック エラーを発生させる可能性があります。 メソッド シグネチャ、スローされた例外、入力/出力データ形式など、ライブラリの変更はすべて、ライブラリの利用者にマイナスの影響の与える可能性があります。 以前の壊れていた動作にユーザーが依存していた場合、バグの修正が破壊的変更になることがあります。

機能の追加や不適切動作の改善は良いことですが、慎重に行わない場合、既存ユーザーにとってアップグレードが非常に難しくなります。 動作の破壊的変更の前に "設定" を置くことが、変更に対処する開発者を助ける 1 つの方法です。 この "設定" で開発者はライブラリの最新版に更新し、同時に破壊的変更の採用を選択できます。 このような方法をとることで、開発者は最新の状態を維持しながら、時間をかけてコードを調整できます。

たとえば、ASP.NET Core MVC には、MvcOptions で有効/無効になっている機能を変更する互換性バージョンという概念があります。

✔️ 新しい機能が既存のユーザーに影響を与える場合は、新しい機能を既定でオフにし、開発者が設定によってその機能を選択できるようにすることを検討してください。

.NET API での動作の破壊的変更の詳細については、.NET の動作変更の互換性に関するセクションを参照してください。

バイナリの破壊的変更

バイナリの破壊的変更は、ライブラリのパブリック API を変更するときに発生します。そのため、旧バージョンのライブラリに対してコンパイルされたアセンブリはその API を呼び出せなくなります。 たとえば、新しいパラメーターを追加してメソッドのシグネチャを変更すると、旧バージョンのライブラリに対してコンパイルされたアセンブリから MissingMethodException がスローされます。

バイナリの破壊的変更はアセンブリ全体を壊すこともあります。 AssemblyName でアセンブリの名前を変更すると、アセンブリの ID が変更されます。アセンブリの厳密な名前付けキーを追加、削除、変更した場合も同様に ID が変更されます。 アセンブリの ID を変更すると、それを使用するすべてのコンパイル済みコードが壊れます。

❌ アセンブリ名を変更しないでください。

❌ 厳密な名前付けキーを追加、削除、または変更しないでください。

✔️ インターフェイスではなく、抽象基本クラスを使用することを検討してください。

インターフェイスに何かを追加すると、そのインターフェイスを実装する既定の型でエラーが出ます。 抽象基本クラスを使用すると、既定の仮想実装を追加できます。

✔️ 削除する予定の型とメンバーに ObsoleteAttribute を付けることを検討してください。 この属性によって、無効になった API を使用しないようにコードを更新するための指示が与えられます。

ObsoleteAttribute が与えられた型やメソッドをコードが呼び出すと、ビルドの警告が出され、属性にメッセージが与えられます。 この警告によって、無効になった API を使用している人に十分な移行時間が与えられます。無効になった API が削除されたとき、ほとんどの人がそれを使用しなくなっています。

public class Document
{
    [Obsolete("LoadDocument(string) is obsolete. Use LoadDocument(Uri) instead.")]
    public static Document LoadDocument(string uri)
    {
        return LoadDocument(new Uri(uri));
    }

    public static Document LoadDocument(Uri uri)
    {
        // Load the document
    }
}

✔️ 低レベルおよび中レベルのライブラリでは、ObsoleteAttribute が指定された型やメソッドを無期限に保持することを検討してください。

API を削除することはバイナリの破壊的変更です。 保守管理コストが低く、ライブラリに与える技術的な負荷が大きくなければ、無効になった型とメソッドを保持することを検討してください。 型やメソッドを削除しなければ、前述の最悪なケースを回避できる可能性があります。

バイナリの互換性が損なわれる .NET API の変更の詳細については、.NET パブリック コントラクトの互換性に関するセクションを参照してください。

関連項目