Структура C # для чистої обробки "вільних функцій", уникаючи статичних класів у стилі "помічник"


9

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

// Helpers.cs

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

Я розглядав конкретні методи

  • переважно не пов'язані між собою,
  • без явного стану, що зберігається через виклики,
  • малий, і
  • кожне споживається різними непов'язаними типами.

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

Тільки для цього питання я буду називати цей метод як GLUM (загальний легкий корисний метод). Частково призначена негативна конотація "похмурості". Вибачте, якщо це трапляється як тупий каламбур.

Навіть відкладаючи власний скептицизм щодо GLUM-файлів за замовчуванням, мені не подобаються такі речі з цього приводу:

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

Що є досить хорошим та простим шаблоном для рефакторингу цього, бажано вирішити вищезазначені проблеми та, бажано, як можна легше торкнутися?

Я, мабуть, повинен підкреслити: Усі методи, з якими я маю справу, попарно не пов'язані один з одним. Здається, не існує розумного способу розбити їх на тонкозернисті, але все ще багатомісні статичні методи-пакети.


1
Про GLUMs ™: Я думаю, що абревіатуру для "легкої ваги" можна видалити, оскільки методи мають бути легкими, дотримуючись кращих практик;)
Фабіо

@ Фабіо Я згоден. Я включив цей термін як (мабуть, не дуже смішний і, можливо, заплутаний) жарт, який слугує абревіатурою для написання цього питання. Я думаю, що тут "легка вага" здавалася доречною, тому що кодові бази, про які йдеться, є довговічними, застарілими і трохи безладними, тобто трапляється багато інших методів (як правило, ці GUM ;-), які є "важкою вагою". Якби у мене зараз було більше (так, будь-якого) бюджету, я б напевно підходив до більш агресивного підходу і звертався до останнього.
Вільям

Відповіді:


17

Я думаю, у вас є дві проблеми, тісно пов'язані між собою.

  • Статичні класи містять логічно не пов’язані між собою функції
  • Назви статичних класів не дають корисної інформації про значення / контекст функцій, що містяться.

Ці проблеми можна виправити, поєднуючи лише логічно пов’язані методи в один статичний клас і переміщаючи інші у власний статичний клас. Виходячи з вашої проблемної області, ви та ваша команда повинні вирішити, які критерії ви будете використовувати для поділу методів на споріднені статичні класи.

Побоювання та можливі рішення

Методи здебільшого не пов'язані один з одним - проблема. Поєднайте споріднені методи під одним статичним класом і перемістіть неспоріднені методи до власних статичних класів.

Методи без явного стану зберігаються через виклики - не проблема. Методи без громадянства - це особливість, а не помилка. Статичний стан важко перевірити в будь-якому випадку, і його слід використовувати лише в тому випадку, якщо вам потрібно підтримувати стан у викликах методу, але є кращі способи зробити це, наприклад, машини машини та yieldключове слово.

Методів мало - не проблема. - Методи повинні бути невеликими.

Кожні методи споживаються різними непов'язаними типами - не проблема. Ви створили загальний метод, який можна повторно використовувати у багатьох непов'язаних місцях.

Статичний клас використовується виключно як простір імен - не проблема. Ось як застосовуються статичні методи. Якщо цей спосіб виклику викликає занепокоєння, спробуйте скористатися методами розширення або using staticдекларацією.

Статичний ідентифікатор класу в основному безглуздий - проблема . Це безглуздо, оскільки розробники вкладають у нього непов'язані методи. Поставте непов'язані методи у власні статичні класи та дайте їм конкретні та зрозумілі імена.

Коли додається новий GLUM, будь-який (а) цей "мішковий" клас буде торкатися (смуток SRP) - не проблема, якщо ви дотримуєтесь ідеї про те, що в одному статичному класі повинні бути лише логічно пов'язані методи. SRPтут не порушується, тому що принцип повинен застосовуватися для класів або для методів. Однозначна відповідальність статичних класів означає містити різні методи для однієї загальної "ідеї". Ця ідея може бути функцією або перетворенням, або ітераціями (LINQ), або нормалізацією даних, або валідацією ...

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

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


Це не порушення SRP, а досить явно порушення відкритого / закритого принципу. Це сказало, що я, як правило, вважаю, що у Ocp більше проблем, ніж це варто
Пол

2
@Paul, принцип відкриття / закриття не можна застосовувати для статичних класів. Це було моєю думкою, що принципи 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


Сенс у тому, щоб не сприймати ще один «жорсткий погляд». Сенс у тому, щоб мати багаторазовий малюнок, який може легко очистити «мішок» комунальних послуг. Я знаю, що комунальні послуги - це запахи. Питання стверджує це. Метою є розумне виділення цих незалежних (але занадто тісно розташованих) доменних об'єктів на даний момент і якнайпростіше переходити до бюджету проекту.
Уїльям

1
Якщо у вас немає "загальних корисних методів", ви, ймовірно, недостатньо відокремлюєте фрагменти логіки від решти вашої логіки. Наприклад, наскільки я знаю, в .NET функція приєднання URL-адреси загального призначення в .NET, тому я часто закінчую її написання. Такий тип суглобів краще би був частиною стандартної бібліотеки, але в кожному стандартному контенті щось не вистачає . Альтернатива, залишаючи логіку повтореною безпосередньо у вашому іншому коді, робить її значно менш читаною.
jpmc26


1
@Ewan, що не є функцією, це підпис делегата загального призначення ...
TheCatWhisperer

1
@Ewan У цьому і полягає вся суть TheCatWhisperer: класи класів - це хитрість навколо необхідності розміщення методів у класі. Радий, що ви згодні.
jpmc26
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.