Мысли и лучшие практики по статическим классам и членам [закрыто]

11

Мне очень любопытны мысли и лучшие отраслевые практики, касающиеся статических членов или целых статических классов. Есть ли какие-либо недостатки в этом, или это участвует в каких-либо анти-паттернах?

Я рассматриваю эти сущности как «служебные классы / члены» в том смысле, что они не требуют или не требуют создания экземпляров класса для обеспечения функциональности.

Каковы общие мысли и лучшие отраслевые практики по этому поводу?

Посмотрите приведенные ниже примеры классов и членов, чтобы проиллюстрировать, на что я ссылаюсь.

// non-static class with static members
//
public class Class1
{
    // ... other non-static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

// static class
//
public static class Class2
{
    // ... other static members ...

    public static string GetSomeString()
    {
        // do something
    }
}

Заранее спасибо!

Томас Стрингер
источник
1
Статье MSDN на статических классов и статических методов дает довольно хорошее лечение.
Роберт Харви
1
@ Роберт Харви: Несмотря на то, что статья, на которую вы ссылались, полезна, в ней мало что говорится о передовой практике и о некоторых подводных камнях при использовании статических классов.
Бернард

Ответы:

18

В общем, избегайте статики - особенно любого статического состояния.

Зачем?

  1. Статика вызывает проблемы с параллелизмом. Поскольку существует только один экземпляр, он, естественно, распределяется между параллельным выполнением. Эти общие ресурсы - проклятие параллельного программирования, и часто их не нужно совместно использовать.

  2. Статика вызывает проблемы с юнит-тестированием. Любой фреймворк для модульного тестирования, достойный солевой проверки, запускается одновременно. Тогда вы столкнетесь с # 1. Хуже того, вы усложняете свои тесты всем, что связано с настройкой / разборкой, и хакерством, с которым вы столкнетесь, пытаясь «поделиться» кодом установки.

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

Конечно, есть статика: постоянные значения работают отлично. Чистые методы, которые не вписываются в один класс, могут отлично работать здесь.

Но в целом избегайте их.

Telastyn
источник
Мне любопытно, насколько вы неспособны к параллелизму. Вы можете расширить это? Если вы имеете в виду, что к членам можно получить доступ без разделения, это имеет смысл. Ваши сценарии использования для статики - это то, для чего я обычно их использую: постоянные значения и чистые / служебные методы. Если это лучший и 99% сценарий использования статики, то это дает мне уровень комфорта. Также +1 за ваш ответ. Отличная информация.
Томас Стрингер
@ThomasStringer - только то, что больше точек соприкосновения между потоками / задачами означает больше возможностей для проблем параллелизма и / или большего снижения производительности при синхронизации.
Теластин
Так, если один поток в настоящее время обращается к статическому члену, тогда другие потоки должны ждать, пока поток-владелец не освободит ресурс?
Томас Стрингер
@ThomasStringer - может быть, а может и нет. В этом отношении статика (почти в большинстве языков) ничем не отличается от любого другого общего ресурса.
Теластин
@ThomasStringer: К сожалению, на самом деле это еще хуже, если вы не отметите участника volatile. Вы просто не получаете никаких гарантий от модели памяти без изменчивых данных, поэтому изменение переменной в одном потоке может не отражаться сразу или вообще.
Phoshi
17

Если функция "чистая", я не вижу проблем. Чистая функция действует только во входных параметрах и обеспечивает результат на основе этого. Это не зависит от какого-либо глобального состояния или внешнего контекста.

Если я посмотрю на ваш собственный пример кода:

public class Class1
{
    public static string GetSomeString()
    {
        // do something
    }
}

Эта функция не принимает никаких параметров. Таким образом, это, вероятно, не чисто (единственной чистой реализацией этой функции будет возвращение константы). Я предполагаю, что этот пример не является представителем вашей реальной проблемы, я просто указываю, что это, вероятно, не чистая функция.

Давайте возьмем другой пример:

public static bool IsOdd(int number) { return (number % 2) == 1; }

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

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

В .NET Framework методы расширения существуют уже довольно давно. LINQ содержит множество функций расширения (например, Enumerable.Where () , Enumerable.First () , Enumerable.Single () и т. Д.). Мы не считаем это плохим, не так ли?

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

Однако при написании теста для объекта, который ведет себя по-разному, в зависимости от того, является ли какое-то число нечетным или четным, нам на самом деле не нужно иметь возможность заменить IsOdd()функцию альтернативной реализацией. Кроме того, я не вижу, когда нам нужно предоставить другую Enumerable.Where()реализацию для тестирования.

Итак, давайте проверим читабельность клиентского кода для этой функции:

Вариант а (с функцией, объявленной как метод расширения):

public void Execute(int number) {
    if (number.IsOdd())
        // Do something
}

Вариант б:

public void Execute(int number) {
    var helper = new NumberHelper();
    if (helper.IsOdd(number))
        // Do something
}

Функция static (extension) делает первый фрагмент кода намного более читабельным, а удобочитаемость очень важна, поэтому используйте статические функции там, где это необходимо.

Пит
источник