Шаблон C # для чистой обработки «свободных функций», избегая статических классов «вспомогательного пакета» в стиле Helper

9

Недавно я рассматривал несколько статических классов «вспомогательных пакетов» в стиле Хелпера, плавающих вокруг больших кодовых баз C #, с которыми я работаю, в основном, как следующий очень сжатый фрагмент:

// Helpers.cs

public static class Helpers
{
    public static void DoSomething() {}
    public static void DoSomethingElse() {}
}

Конкретные методы, которые я рассмотрел,

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

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

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

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

  • Статический класс используется исключительно как пространство имен.
  • Статический идентификатор класса в принципе не имеет смысла.
  • Когда добавляется новый GLUM, либо (a) к этому классу «bag» прикасаются без веской причины, либо (b) создается новый класс «bag» (который сам по себе обычно не является проблемой; что плохо, так это то, что новый статические классы часто просто повторяют проблему несвязанности, но с меньшим количеством методов).
  • Мета-именование неотвратимо ужасно, нестандартное, и , как правило , внутренне противоречива, будь то Helpers, Utilitiesили что угодно.

Каков достаточно хороший и простой способ для рефакторинга этого, предпочтительно для решения вышеупомянутых проблем, и, предпочтительно, с как можно более легким прикосновением?

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

Уильям
источник
1
О GLUMs ™: я думаю, что аббревиатуру для «облегченного» можно было бы удалить, так как методы должны быть облегченными, следуя передовым методам;)
Фабио
@ Фабио Я согласен. Я включил этот термин в качестве (возможно, не очень смешной и, возможно, запутанной) шутки, которая служит сокращением для написания этого вопроса. Я думаю, что «облегченный» показался здесь уместным, потому что рассматриваемые кодовые базы являются долгоживущими, устаревшими и немного грязными, то есть есть много других методов (обычно это не GUM ;-), которые являются «тяжеловесными». Если бы у меня был больший (ага, любой) бюджет на данный момент, я бы определенно выбрал более агрессивный подход и занялся бы последним.
Уильям

Ответы:

17

Я думаю, что у вас есть две проблемы, тесно связанные друг с другом.

  • Статические классы содержат логически не связанные функции
  • Имена статических классов не предоставляют полезную информацию о значении / контексте содержащихся функций.

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

Проблемы и возможные решения

Методы в основном не связаны друг с другом - проблема. Объедините связанные методы в одном статическом классе и переместите несвязанные методы в их собственные статические классы.

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

Методы маленькие - не проблема. - Методы должны быть небольшими.

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

Статический класс используется исключительно как пространство имен - не проблема. Вот как используются статические методы. Если этот стиль вызова методов вас беспокоит, попробуйте использовать методы расширения или using staticобъявление.

Статический идентификатор класса в принципе не имеет смысла - проблема . Это бессмысленно, потому что разработчики вкладывают в него несвязанные методы. Поместите несвязанные методы в их собственные статические классы и дайте им конкретные и понятные имена.

Когда добавляется новый GLUM, либо (a) этот класс «bag» затрагивается (грусть SRP) - не проблема, если вы придерживаетесь идеи, что в одном статическом классе должны быть только логически связанные методы. SRPздесь не нарушается, потому что принцип должен применяться для классов или методов. Единственная ответственность статических классов означает содержать разные методы для одной общей «идеи». Эта идея может быть функцией или преобразованием, или итерациями (LINQ), или нормализацией данных, или проверкой ...

или реже (б) создается новый класс «сумок» (больше сумок) - не проблема. Классы / статические классы / функции - это наши (разработчики) инструменты, которые мы используем для разработки программного обеспечения. Есть ли в вашей команде ограничения на использование классов? Каковы максимальные ограничения для «больше сумок»? Программирование - все о контексте, если для решения в вашем конкретном контексте вы в конечном итоге получите 200 статических классов с понятным именем, логически размещенных в пространствах имен / папках с логической иерархией - это не проблема.

Мета-наименование неизбежно ужасно, нестандартно и, как правило, внутренне несовместимо, будь то хелперы, утилиты или что-то еще - проблема. Укажите лучшие имена, которые описывают ожидаемое поведение. Храните только связанные методы в одном классе, которые помогают лучше именовать.

Fabio
источник
Это не нарушение SRP, но довольно явно нарушение принципа открытого / закрытого принципа. Тем не менее, я обычно обнаружил, что Ocp доставляет больше хлопот, чем стоит
Пол
2
@Paul, принцип Open / Close нельзя применять для статических классов. Это была моя точка зрения, что принципы SRP или OCP не могут применяться к статическим классам. Вы можете применить его для статических методов.
Фабио
Хорошо, здесь была некоторая путаница. Первый список вопросов вопроса не является списком предполагаемых проблем. Это список общих характеристик конкретных методов, которые я рассматриваю. Это контекст, чтобы помочь ответам предоставить более применимые решения. Я отредактирую, чтобы уточнить.
Уильям
1
«Статический класс как пространство имен», тот факт, что это обычный шаблон, не означает, что он не является анти-шаблоном. Метод (который по сути является «свободной функцией» для заимствования термина из C / C ++) в этом конкретном статическом классе с низкой когезией является в данном случае значимым объектом. В этом конкретном контексте структурно лучше иметь подпространство имен Aсо 100 статическими классами с одним методом (в 100 файлах), чем статический класс Aсо 100 методами в одном файле.
Уильям
1
Я сделал довольно серьезную очистку вашего ответа, в основном косметическую. Я не уверен, о чем ваш последний абзац; никто не беспокоится о том, как они тестируют код, который вызывает Mathстатический класс.
Роберт Харви
5

Просто примите соглашение, что статические классы используются как пространства имен в таких ситуациях, как в C #. Пространства имен получают хорошие имена, как и эти классы. using staticОсобенность C # 6 делает жизнь немного легче.

Вонючие имена классов, такие как Helpersсигнал о том, что методы должны быть разделены на статические классы с более подходящими именами, если это вообще возможно, но одно из ваших подчеркнутых предположений заключается в том, что методы, с которыми вы имеете дело, «попарно не связаны», что подразумевает разделение на один новый статический класс для каждого существующего метода. Это, наверное, хорошо, если

  • Есть хорошие имена для новых статических классов и
  • вы судите об этом, чтобы реально улучшить ремонтопригодность вашего проекта.

Как надуманный пример, Helpers.LoadImageможет стать что-то вроде FileSystemInteractions.LoadImage.

Тем не менее, вы можете получить статические классные методики. Вот как это может произойти:

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

Важно помнить, что эти методические пакеты статического класса не являются необычными в реальных кодовых базах C #. Вероятно, не патологично иметь несколько маленьких .


Если вы действительно видите преимущество в том, что каждый метод, похожий на «свободную функцию», находится в своем собственном файле (что хорошо и целесообразно, если вы честно осознаете, что это на самом деле повышает удобство сопровождения вашего проекта), вы можете рассмотреть возможность сделать такой статический класс вместо статическим частичный класс, использующий статическое имя класса аналогично тому, как вы бы использовали пространство имен, и затем использующий его через using static. Например:

структура фиктивного проекта с использованием этого шаблона

Program.cs

namespace ConsoleApp1
{
    using static Foo;
    using static Flob;

    class Program
    {
        static void Main(string[] args)
        {
            Bar();
            Baz();
            Wibble();
            Wobble();
        }
    }
}

Foo / Bar.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Bar() { }
    }
}

Foo / Baz.cs

namespace ConsoleApp1
{
    static partial class Foo
    {
        public static void Baz() { }
    }
}

Flob / Wibble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wibble() { }
    }
}

Flob / Wobble.cs

namespace ConsoleApp1
{
    static partial class Flob
    {
        public static void Wobble() { }
    }
}
Уильям
источник
-6

Как правило, вы не должны иметь или требовать каких-либо «общих служебных методов». В вашей бизнес-логике. Посмотрите еще раз и поместите их в подходящее место.

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

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

Например, допустим, у меня есть общая функция MySort () вместо Helpers.MySort или добавление нового ListOfMyThings.MySort. Я могу добавить его в IEnumerable.

Ewan
источник
Дело в том, чтобы не делать еще один "жесткий взгляд". Дело в том, чтобы иметь шаблон многократного использования, который может легко очистить «пакет» утилит. Я знаю, что утилиты - это запахи. Вопрос гласит это. Цель состоит в том, чтобы разумно изолировать эти независимые (но слишком тесно взаимодействовал расположено) объекты домена на данный момент и идти как можно проще по бюджету проекта.
Уильям
1
Если у вас нет «общих служебных методов», вы, вероятно, недостаточно изолируете части логики от остальной части вашей логики. Например, насколько я знаю, в .NET нет функции объединения путей URL общего назначения, поэтому я часто заканчиваю тем, что пишу ее. Такой тип бесполезен как часть стандартной библиотеки, но в каждой стандартной библиотеке чего-то не хватает . Альтернатива, оставляя логику, повторенную непосредственно внутри вашего другого кода, делает его значительно менее читабельным.
jpmc26
1
@ Иван, это не функция, это подпись делегата общего назначения ...
TheCatWhisperer
1
@Ewan В этом и заключалась вся суть TheCatWhisperer: служебные классы являются препятствием для того, чтобы помещать методы в класс. Рад, что ты согласен.
jpmc26