Кардинальные изменения

Важно для библиотеки .NET найти баланс между стабильностью для существующих пользователей и инноваций в будущем. Авторы библиотек склонны к рефакторингу и переосмыслению кода, пока он не станет идеальным, но нарушение работы существующих пользователей негативно сказывается, особенно для низкоуровневых библиотек.

Типы проектов и критические изменения

Как использование библиотеки сообществом .NET изменяет влияние нарушающих изменений на разработчиков конечных пользователей.

  • Библиотеки низкого и среднего уровня , такие как сериализатор, средство синтаксического анализа HTML, объектно-реляционная карта базы данных или веб-платформа, наиболее затронуты критическими изменениями.

    Пакеты стандартных блоков используются разработчиками конечных пользователей для создания приложений и другими библиотеками в качестве зависимостей NuGet. Например, вы создаете приложение и используете клиент с открытым кодом для вызова веб-службы. Критическое обновление зависимости, которую использует клиент, не является тем, что вы можете исправить. Это клиент с открытым исходным кодом, который должен быть изменен, и у вас нет контроля над ним. Необходимо найти совместимые версии библиотек или отправить исправление в клиентную библиотеку и дождаться новой версии. Наихудшая ситуация заключается в том, чтобы использовать две библиотеки, зависящие от несовместимых версий третьей библиотеки.

  • Высокоуровневые библиотеки , такие как набор элементов управления пользовательским интерфейсом, менее чувствительны к критическим изменениям.

    Высокоуровневые библиотеки являются непосредственными ссылками в приложении для конечного пользователя. При возникновении критических изменений разработчик может обновить до последней версии или изменить свое приложение для работы с критическим изменением.

✔️ Подумайте о том, как будет использоваться библиотека. Какие последствия будут иметь критические изменения в приложениях и библиотеках, которые используют его?

✔️ Минимизируйте изменения, нарушающие совместимость, при разработке низкоуровневой библиотеки .NET.

✔️ Рассмотрите возможность публикации основной перезаписи библиотеки в виде нового пакета NuGet.

Типы критических изменений

Критические изменения попадают в разные категории и не оказывают одинаковое влияние.

Нарушающее изменение исходного кода

Разрушающее изменение исходного кода не влияет на выполнение программы, но приведет к ошибкам компиляции при следующей попытке перекомпиляции приложения. Например, новая перегрузка может создать неоднозначность в вызовах методов, которые ранее были однозначными, или переименованный параметр нарушит вызовы, использующие именованные параметры.

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

Поскольку изменение, разрушающее совместимость с исходным кодом, вредно только при повторной компиляции приложений разработчиками, это наименее разрушительное из всех критических изменений. Разработчики могут легко исправить свой собственный исходный код.

Существенное изменение поведения

Изменения функционирования являются наиболее распространенным типом разрушающего изменения: почти любое изменение в функционировании может вызвать ошибку логики для пользователя. Изменения в вашей библиотеке, такие как сигнатуры методов, вызываемые исключения или входные и выходные форматы данных, могут негативно повлиять на пользователей библиотеки. Даже исправление ошибки может квалифицироваться как критическое изменение, если пользователи полагались на ранее нарушенное поведение.

Добавление функций и корректировка нежелательного поведения — это хорошее дело, но без должного внимания это может значительно усложнить обновления для существующих пользователей. Одним из подходов к тому, чтобы помочь разработчикам справиться с разрушительными изменениями в поведении, является управление ими через настройки. Параметры позволяют разработчикам обновлять самую последнюю версию вашей библиотеки, одновременно выбирая принять участие или отказаться от серьёзных изменений. Эта стратегия позволяет разработчикам быть в курсе последних тенденций, в то время как их потребляемый код адаптируется с течением времени.

Например, ASP.NET Core MVC имеет концепцию версии совместимости , которая изменяет включенные и отключенные MvcOptionsфункции.

✔️ Рекомендуется оставить новые функции отключенными по умолчанию, если они влияют на существующих пользователей, и позволить разработчикам принять участие в этой функции с параметром.

Дополнительные сведения о критических изменениях поведения в API .NET см. в статье о совместимости изменений поведения .NET.

Двоичное изменение, нарушающее совместимость

Изменение бинарного интерфейса происходит при изменении публичного API вашей библиотеки, так что сборки, скомпилированные против старых версий вашей библиотеки, больше не могут вызывать этот API. Например, изменение сигнатуры метода путем добавления нового параметра приведет к тому, что сборки, скомпилированные для старой версии библиотеки, вызовут исключение MissingMethodException.

Двоичное критическое изменение также может нарушить всю сборку. Переименование сборки с помощью AssemblyName изменит её идентичность, как и добавление, удаление или изменение строгого ключа именования сборки. Изменение идентификатора сборки приведет к разрыву всего скомпилированного кода, который его использует.

❌ Не изменяйте имя сборки.

❌ НЕ добавляйте, удаляйте или изменяйте ключ строгого именования.

✔️ Рекомендуется использовать абстрактные базовые классы вместо интерфейсов.

Добавление всех элементов в интерфейс приведет к сбою существующих типов, реализующих его. Абстрактный базовый класс позволяет добавить виртуальную реализацию по умолчанию.

✔️ Разместите 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 из системы является изменением, нарушающим бинарную совместимость. Учитывайте сохранение устаревших типов и методов, если их поддержка обходится недорого и не приводит к значительному техническому долгу вашей библиотеки. Не удаляя типы и методы, можно избежать наиболее плохих сценариев, упомянутых выше.

Дополнительные сведения о том, какие изменения API .NET нарушают двоичную совместимость, см. в разделе Совместимость общедоступных контрактов .NET.

См. также