Comments 55
Действительно, хорошие имена — это всего лишь один, но зато огромный шаг к поддерживаемости кода.
А иногда что писать — знаешь, а как описать — нет. Это что же, долго перебирать варианты как описать, чтобы решить как назвать, чтобы наконец то написать? Да не, ерунда какая то, лучше очередной Utils\Manager зафигачить =)
ПС: есть на эту тему «шутка» о всего двух проблемах программирования — именовании и инвалидации кеша =)
Программирование в современном понимании это, по сути, это написание описания алгоритмов понятных как людям (специально обученным), так и вычислительным устройствам.
А это написание на 90% состоит как раз из придумывания имён (остальные 10 придумали за нас авторы языка и библиотек).
А иногда что писать — знаешь, а как описать — нет. Это что же, долго перебирать варианты как описать, чтобы решить как назвать, чтобы наконец то написать?
На мой взгляд, да, нужно перебирать. Не что писать, а как — вот что важно.
Если вдруг кому-нибудь интересно — Егор Бугаенко много пишет на эту тему. Например, https://www.yegor256.com/2015/03/09/objects-end-with-er.html
Разница между ListSorter
и SortedList
в том, что в первом случае мы говорим о списке, его сортировщике и отсортированном списке:
var list = new List<int>() { ... }; // Список.
var sortedList = ListSorter.Sort(list); // Сортировщик + отсортированный список.
Но ведь чувствуется, что сортировщик тут избыточен, нам важен не он и не его наличие, а результат — отсортированный список, потому что именно он представляет некоторое понятие из предметной области, скажем, рейтинг фильмов.
Например:
var list = new List<int>();
var sortedList = new SortedList<int>(list);
// или
var list = new List<int>();
var sortedList = list.Sort();
Смотрится элегантнее, на мой взгляд. Работа не перенаправляется на аутсорс какому-то третьему лицу.
Буква «S» в наборе «SOLID» прямым текстом говорит нам, что не должно быть так, чтобы объект сам себя менеджил, провайдил, гетил, сетил, ридил, райтил, валидэйтил, энкодил, декодил, диспетчил.
Боюсь, она ровно это и утверждает. Положим, JSON-сериализацию можно сделать так:
public class Weapon
{
public int Damage;
}
public static class WeaponJsonSerializer // Как будто бы Single Responsibility.
{
public static string Serialize(Weapon weapon) =>
$"\{\"Damage\": {weapon.Damage}\}";
}
Хотя мы и вынесли сериализацию в отдельную сущность, принцип S, как мне видится, был нарушен: теперь каждое изменение полей Weapon
потребует изменений в WeaponSerializer
.
Можно:
public class Weapon : IJson
{
public int Damage;
public string AsJson() => $"\{\"Damage\": {Damage}\}";
}
Теперь лучше, поскольку Weapon
ответственен за всё, что с ним происходит, но так работать попросту неудобно, да и, к тому же, иногда не так велика разница, идти ли вниз к методу AsJson()
или к лежащему недалеко WeaponJsonSerializer
.
Куда сильнее — использовать возможности метапрограммирования и реализовать обобщённую сериализацию с помощью рефлексии или кодогенерации. Библиотек полно.
Ну ОК, навернули. Дальше начинается прикольное:
1. Оказывается, вместе с Weapon в JSON полезно закатывать объекты, на которые weapon ссылается. Например, если Damage у нас не общий, а для каждого врага разный (огнемёт лавовому монстру только в радость), то в Weapon у нас массив объектов, в которых Damage и ссылка на тип врага. Кое-что из свойств врага, кстати, тоже оказывается полезно закатать в JSON. Как всегда происходит в подобных случаях, наш новый прекрасный уровень абстракции начинает усложняться, разрастаться, и в результате сам превращается в монстра хуже лавового.
2. Нежданчик. Оказывается, нам нужно иногда генерить разные JSONы. Ну то есть для хранения один, для сайта другой, для отправки в налоговую инспекцию по электронному документообороту третий, для годового отчёта Вельзевулу четвёртый. Будем усложнять уровень абстракции?
Это у нас только JSON. А есть ещё отображение, динамика, печать на бланке, контроль консистентности, репликация и штучки три интеграции с другими системами по ETL (как водится, в обе стороны, и совсем не через JSON). В какой-то момент времени мы титаническими усилиями приходим к тому, что со всем справились. Но тут вдруг возникает необходимость (не «хотелка», а именно суровая необходимость) добавить новую сущность. Какое-нибудь Remedy. По аналогии с Weapon. Мы смотрим, как у нас обвешан загадочными гроздьями мета-штук Weapon и понимаем, что зря пошли в программисты.
Боюсь, я не до конца понял вашу мысль.
То есть навернуть уровень абстракции
Что вы понимаете под уровнем абстракции в примере с Weapon
, как он повысился и в какой момент?
Мы смотрим, как у нас обвешан загадочными гроздьями мета-штук Weapon
Вот это, кстати, понял. Возможно, не достаточно точно выразился, но если под "мета-штуками" вы имеете ввиду фразу про "метапрограммирование", то речь же была о простейшей библиотеке сериализации вроде Newtonsoft.Json
.
Так что Weapon
ничем, выходит, и не будет обвешан. Ответственность перенесли, как того требует S, но сохранили удобство, и никаких WeaponDamageGetter
, WeaponJsonProvider
, WeaponManager
после себя не оставили.
Можно сделать же serializer не статичным и с ссылкой на объект. И asJson будет возвращать актуальное значение
Примеры с DirectoryCleaner/Directory.Clean и file.Changes.OnNext могут работать в некоторых случаях, но в других могут порождать God Object с кучей ответственностей и зависимостей. Хотя конечно, все зависит от конкретного случая. Я заметил, что такие упрощения имен допустимо делать только в зрелом коде, который уже не так часто меняется, при условии что изменение API не создаст проблем. Но если изначально писать код в таком стиле, то это создаст больше путаницы чем принесет пользы. Потому-что с ходу тяжело адекватно оценить восприятие кода. Тут конечно code review хорошо помогает, особенно если ревьювер знаком с кодом только поверхностно.
В примере orders вместо orderRepository есть и плюсы и минусы
Полностью согласен. Среди множества всех возможных сценариев, существуют такие, в которых _ordersRepository
смотрится лучше, чем _orders
, но в большинстве (95%), на мой взгляд, Repository
— избыточное уточнение.
Как минимум, сходу такое имя воспринимается как коллекция
Мне кажется, IOrdersRepository
как раз имеет семантику коллекции: получить заказы, добавить, удалить. Так что он вполне себе может представлять множество заказов (или заказы), а уж тип, если потребуется, дополнит происходящее.
Я заметил, что такие упрощения имен допустимо делать только в зрелом коде, который уже не так часто меняется, при условии что изменение API не создаст проблем
На моём опыте люди, как правило, напротив, всё слишком переусложняют и переуточняют: _currentSelectedSpecificItemIndex
. Превратить такую запись в _selectedIndex
(убираем Item
, поскольку, скорее всего, итак работаем в контексте какого-то типа SomeItem
) — не упростить, но убрать избыточность.
Мне кажется, IOrdersRepository как раз имеет семантику коллекции: получить заказы, добавить, удалить.
Вот только семантика коллекции тут ложная: она маскирует стоимость всех этих операций.
Иногда существование классов бывает вызвано необходимостью.
Так, большинство Params-, Args- и Options-классов нужны для того, чтобы уменьшить число параметров у метода, и иметь возможность добавлять в будущем новые параметры сохраняя совместимость со старым кодом.
А ещё персистентность всегда всё портит. Ради возможности положить объект в базу или прочитать его оттуда приходится либо держать открытыми все внутренности объекта, либо создавать те самые Data и Info.
Куда живее звучитISequence
, а неIEnumerable
;IBlueprint
, а неICreator
;IButton
, а неIButtonPainter
;IPredicate
, а неIFilter
;IGate
, а неIOpeneable
;IToggle
, а неIEnableable
.
Вот только интерфейсы — это не сущности. Интерфейсы — это качества и роли сущностей. Нет никаких проблем если класс Sequence будет реализовывать интерфейс IEnumerable
, класс Blueprint
будет ICreator
, а класс Gate
будет IOpeneable
.
Impl, Abstract, Custom, Base, Concrete, Internal, Raw — признак неустойчивости, расплывчатости архитектуры, который, как и ружье из первой сцены, позже обязательно выстрелит.
А как ещё разделять внешнее и внутреннее API? Если всё делать публичным — это и будет то самое ружье.
Например, поле типаIOrdersRepository
так и называют —_ordersRepository
. Но насколько важно сообщать о том, что заказы представлены репозиторием? Ведь куда проще —_orders
.
Проще-то проще, но когда рядом находятся IOrdersRepository
, List<Order>
и Dictionary<Guid, Order>
— приходится их хоть как-то различать.
Ещё, бывает, в LINQ-запросах пишут полные имена аргументов лямбда-выражений, например,Player.Items.Where(item => item.IsWeapon)
, хотя что это предмет (item) мы и без того понимаем, глядя наPlayer.Items
В таких простых ситуациях — да, понимаем. А вот в трехуровневых запросах на самом внутреннем уровне уже хотелось бы видеть item
вместо простого x
.
Лично мне каждый раз приходится вспоминать, чем отличаются Set от Reset и что вообще такое "вручную сбрасывающееся событие" в контексте работы с потоками.
А вот это уже просто незнание английского языка и стандартной терминологии. Надо выучить термины, а не подводить философию под своё незнание, иначе вы однажды будете сами вынуждены назвать монитор компьютером, а системный блок процессором.
Set
переводится как "установить", Reset
— как "сбросить". Название же вашего класса "ThreadGate"
ничего не говорит о том, будут ли ворота закрыты после выхода из WaitForOpen
или нет, а ведь это важная информация.
Так, большинство Params-, Args- и Options-классов нужны для того, чтобы уменьшить число параметров у метода, и иметь возможность добавлять в будущем новые параметры сохраняя совместимость со старым кодом.
Тут тогда, думается мне, есть проблема посерьёзнее: недостаточно разбили предметную область на сущности, поэтому есть методы с большим количеством параметров. Расширять это дело настоятельно не рекомендую. Спрятать много параметров в объект не поможет, ведь нагрузка на ум осталась!
А ещё персистентность всегда всё портит. Ради возможности положить объект в базу или прочитать его оттуда приходится либо держать открытыми все внутренности объекта, либо создавать те самые Data и Info.
Согласен, и такое бывает! Но это не оправдывает другие случаи, когда Info
и Data
— наспех сочинённая декомпозиция бизнес-логики.
Вот только интерфейсы — это не сущности. Интерфейсы — это качества и роли сущностей.
Речь не об интерфейсах и о том что они такое, а о словах, которые выбираются, чтобы отразить подразумевающееся интерфейсом. Поэтому:
Нет никаких проблем если класс Sequence будет реализовывать интерфейс IEnumerable
Проблемы есть, ведь как понятия Sequence
и Enumerable
очень схожи между собой, только одно основывается на уже известной последовательности, тогда как второе — на глаголе перечислять, из которого выводится, что перечисляем мы последовательность.
Идея вот какая: важно подобрать такое слово, чтобы оно как можно глубже вплеталось в опыт. IOpeneable
не восходит к картинкам, а IGate
— напротив.
А как ещё разделять внешнее и внутреннее API? Если всё делать публичным — это и будет то самое ружье.
Думаю, нет никакой связи между тем, что пишут HumanBase
(вместо Animal
, например), и публичностью и непубличностью. Речь об архитектурной сложности решения. С Base
, Impl
, Internal
, Raw
и прочим ничего понятного и простого, как правило, не получается.
Проще-то проще, но когда рядом находятсяIOrdersRepository
,List<Order>
иDictionary<Guid, Order>
— приходится их хоть как-то различать.
Полагаю, наличие частного случая никак не влияет на все остальные, где IOrdersRepository
— единственный объект, представляющий заказы.
В таких простых ситуациях — да, понимаем. А вот в трехуровневых запросах на самом внутреннем уровне уже хотелось бы видеть item вместо простого x.
Если вы имеете ввиду нечто вроде:
Inventory.Items
.Where(item => item.IsWeapon)
.Select(item => item.Damage)
.Where(damage => damage > 10);
То, мне кажется, куда просторнее смотрится:
Inventory.Items
.Where(x => x.IsWeapon)
.Select(x => x.Damage)
.Where(x => x > 10);
Что x > 10
— про урон, думаю, всё ещё понятно, а дышать стало легче.
Set переводится как "установить", Reset — как "сбросить". Название же вашего класса "ThreadGate" ничего не говорит о том, будут ли ворота закрыты после выхода из WaitForOpen или нет, а ведь это важная информация.
Понимаю ваш аргумент. Я думал над тем, чтобы повысить точность этого названия, но опыт подсказывает, от ворот в первую очередь ожидаешь, что они просто закрываются и открываются, а уж потом можно дополнить, автоматически закрываются или нет.
Например, можно убрать оттуда слово Thread
(блокировка потока как будто следует из WaitForOpen
) и получится два типа: Gate
и AutoCloseGate
. Всё ещё понятнее, чем стандартный аналог.
Проблемы есть, ведь как понятия Sequence и Enumerable очень схожи между собой, только одно основывается на уже известной последовательности, тогда как второе — на глаголе перечислять, из которого выводится, что перечисляем мы последовательность.
Вот только IEnumerable
— не последовательность. В математике в последовательности можно напрямую получить второй элемент (берем и пишем a
2). У IEnumerable
нельзя получить второй элемент иначе как перебором.
Думаю, нет никакой связи между тем, что пишут HumanBase (вместо Animal, например), и публичностью и непубличностью. Речь об архитектурной сложности решения. С Base, Impl, Internal, Raw и прочим ничего понятного и простого, как правило, не получается.
У вас странные правила.
Если вы имеете ввиду нечто вроде: [...]
Нет, я имел в виду что-то вроде
db.Projects.SelectMany(x =>
x.Tasks.SelectMany(y =>
y.Items.Select(z => new { x.Foo, y.Bar, z.Baz })
)
)
Если вам все еще понятно что там написано внутри — добавьте ещё пару уровней.
Например, можно убрать оттуда слово Thread (блокировка потока как будто следует из WaitForOpen) и получится два типа: Gate и AutoCloseGate. Всё ещё понятнее, чем стандартный аналог.
Было: есть событие наличия элементов в очереди, и мы ждём его наступления.
Стало: есть ворота наличия элементов в очереди, и мы ждём их открытия...
Ну да, ну да, можете называть это "понятнее" если вам так нравится.
Если вам все еще понятно что там написано внутри — добавьте ещё пару уровней.
И уже не будет иметь значения, x
там или orderOfSomeItemOfSomeTaskOfSomeProject
. Такой код исправляется другими средствами.
Было: есть событие наличия элементов в очереди, и мы ждём его наступления.
Хм. Ранее вы писали, что Set
— это "установить", а тут уже, кажется, "наступить". Также заметил несколько смутное "наличие элементов в очереди", которое никак не следует из ManualResetEvent
и того, что обсуждалось ранее.
Стало: есть ворота наличия элементов в очереди, и мы ждём их открытия...
Боюсь, мой пример был искажён. Не получится перенести низкоуровневую терминологию сигнальных состояний на повседневные ворота. Нет, тут всё гораздо проще: есть ворота; когда они закрыты, пройти дальше нельзя; когда открыты — можно.
Как выглядит ManualResetEvent
, являющийся EventWaitHandle
, неясно. А ворота выглядят так:
несколько смутное "наличие элементов в очереди", которое никак не следует из ManualResetEvent
"Наличие элементов в очереди" — это семантика переменной, а не её типа. Тот самый случай, когда имя переменной тоже важно.
Нет, тут всё гораздо проще: есть ворота; когда они закрыты, пройти дальше нельзя; когда открыты — можно.
Но что эти ворота означают?
Боюсь, мы тогда отдалимся от темы.
Мысль была вот какая: порой метафора, восходящая к опыту, понятнее и проще, чем низкоуровневая деталь. ManualResetEvent
— наглядный пример.
И ведь не GitUtils, а IRepository, ICommit, IBranch; не ExcelHelper, а ExcelDocument, ExcelSheet; не GoogleDocsService, а GoogleDocs.
С другой стороны, «Система контроля версий», а не «Коммиты». «Текстовый редактор», а не «Тексты». «Веб-браузер», а не «Веб-страницы».
«Кофемолка», «Холодильник», «Калькулятор», «Эскалатор», «Эвакуатор» — а не «Кофе», «Продукты», «Вычисления», «Ступеньки»,
«Система контроля версий», а не «Коммиты»
Если описываете "систему контроля версий", то VersionControlSystem
; если "коммиты репозитория" — Commits
; если "текстовый редактор" — TextEditor
, если "тексты" (редкий какой-то случай) — Texts
; если "веб-браузер" — WebBrowser
, если "веб-страницы" — WebPages
.
На мой взгляд, всё банально, но эту банальность часто избегают с помощью всяческих VCSManager
, CommitsHelper
, TextEditorUtils
, WebHelper
и т.д., как если бы простота была чем-то преступным и недостойным.
Ну тут больше вопрос архитектуры — как вы делите программу на куски: фасадами или фабриками?
Фасады — это именно ExcelHelper, ConnectionManager, "Текстовый редактор".
Фабрики (и всякие иные подставлялки CI) — это именно ExcelSheet, IPeer, "Текст" подними перо, опусти перо.
Как именно разбивать — дело вкуса и наследия.
Ну тут больше вопрос архитектуры — как вы делите программу на куски: фасадами или фабриками?
На мой взгляд, такое разделение не совсем корректно. Возьмём выражение:
var document = new ExcelDocument();
var sheet = document.NewSheet();
Будет ли ExcelDocument
фасадом или фабрикой, не имеет решительно никакого значения, поскольку главное — с помощью названия подвести к уже существующему опыту (о создании документов и таблиц в Excel
многие имеют представление).
Как только мы согласились написать ExcelDocument
вместо ExcelHelper
, мы уже как бы предначертали его судьбу и что за методы в нём можно ожидать.
Документ он не ищет сам себя в файловой системе, не выдаёт список документов в папке, не открывает диалоговых окон (для сохранения). Это то что реально делал у нас класс с таким названием (ExcelHelper).
Ну а для работы с самим документом уже, конечно же (разделяем ответственности) есть одновременно и класс НашExcelDocument (в реале, конечно, там их было несколько абстракций и много реализаций), с которыми опять-таки работа, в основном, ведётся через фасад. Вся экселевская мусорка в одном месте — все довольны.
Можно было бы и по другому сделать, согласен, но не надо вешать на документ, то что собственно документа не касается. Это только запутает всех. В конце концов документ (обычно) сам себя не пишет и не форматирует. Не надо нарушать субъект-объектные отношения реального мира, если вы уж так на него ориентируетесь.
Можно было бы и по другому сделать, согласен, но не надо вешать на документ, то что собственно документа не касается
Полностью согласен, но не вижу ExcelHelper
как подходящее решение такой проблемы.
Мне кажется, выразительнее и понятнее:
var document = ExcelDocument.Of(path);
а не:
var document = ExcelHelper.LoadDocument(path);
Или можно ещё:
var document = file.AsFile().AsExcelDocument();
Написанное полно описывает происходящее, и не привлечены дополнительные не классы, но понятия: говоря о загрузке документа из файла, мы ограничились известными словами.
Разумеется, не берусь утверждать, что хорошо понимаю все сложности, которые возникли в вашем конкретном случае.
И да — метода именно загрузки не было. Загрузка шла лениво, кешировано и абсолютно незаметно (логически) для другого кода. Там почти монаду сделали. (т.е. ExcelHelper.setWorkingDir(path) и дальше там уже пошло статистическое веселье, но, конечно можно было и сделать ExcelHelper.getDocument(...) в нескольких вариантах, если хотелось)
Ну а кто будет диалог сохранения показывать?
Диалог сохранения показывает метод-обработчик кнопки сохранить.
Это ж какой годкласс на сколько тысяч строк получится? Да и зависимости левые получаются. Зачем документу знать о графической системе даже просто транзитом?
На мой взгляд, показать диалоговое окно с выбором пути для сохранения, а потом вызвать document.SaveTo(path)
или document.SaveTo(stream)
— вполне достаточно. Если нужно обобщить для Unit-тестов, то document.SaveTo(storage)
. Да и говорим мы: "Сохрани документ". Опять же, посредники и помощники — избыточны.
Попытки играться с зависимостями, перенося их туда, куда не требуется, приводит к ложному впечатлению, будто всяческие SOLID соблюдены, а код понятен и расширяем. Само наличие слова Helper
— прямая дорога в технологическую сингулярность, и ваши слова про "экселевскую мусорку" это, к сожалению, подтверждают.
Там почти монаду сделали. (т.е. ExcelHelper.setWorkingDir(path)
Боюсь, это не монада...
И основная проблема именно в этом
Существование предшествует сущности
Не очень понял эту часть. Мне кажется, что можно было бы просто сказать «Действие (глагол) предшествует сущности (существительному)» и смысл был бы тот же, нет? Или я чего-то не замечаю?
Как пользователю API мне удобно было бы написать Directory, поставить точку и найти в автодополнении метод Clean().
Но при разработке обычно у каждого класса/модуля есть ответственный владелец (человек или команда). Было бы неприятно зайти в свой идеально причёсанный класс Directory и обнаружить там наваленную гору кода от метода Clean и его подчинённых, который написан другой командой и другим стилем, с другими соглашениями.
С другой стороны, чем меньше класс, тем легче его поддерживать. Обвешивать общий класс методами, которые нужны только для какой-то частной задачи, так себе удовольствие. Применять наследование для наращивания функциональности — CleanableDirectory=class(Directory) — ещё худший выход. Передать класс в хелпер, который есть только в том проекте, где он нужен, намного элегантнее.
Если это будет
void Clean() { DirectoryCleaner.process(this); }
а DirectoryCleaner это то чудо от другой команды — то почему бы нет? Просто отделегировали задачу…
проблема-то в основном остаётся, как именно переложить действие — на глаголы или существительные :)
Эта статья как раз описывает проблему.
Зато вынести её в отдельный модуль (что в таких языках делается через класс) устраняет описанную вами проблему «чужой код с чужим стилем в моём вылизанном садике», так что прогресс тут есть.
Радикально решить — чтобы и волки сыты (код никак не привязан и не должен быть даже в той же сборке, если мы продолжаем думать на примере C#), и овцы целы (можно звать как Directory.Clean) — можно решить, как я понимаю, за счёт пастуха (какой-то редирект на стадии компиляции — возможно, тут достаточно extension method, а может, и нет, если хочется даже без дополнительного using).
Ну а если реализация всё равно подключается через какую-нибудь DLL, где этот Cleaner подгрузится по необходимости — то и потери тут не большие. Всё равно ведь они будут. Например, стандартная сборка GNU libc подключает локализацию даже при main() { return 0; } потому, что часть инициализации самой библиотеки может жаловаться в stderr на проблемы старта ;(
Но при разработке обычно у каждого класса/модуля есть ответственный владелец (человек или команда). Было бы неприятно зайти в свой идеально причёсанный класс Directory и обнаружить там наваленную гору кода от метода Clean и его подчинённых, который написан другой командой и другим стилем, с другими соглашениями.
Поэтому методы расширений, которые есть в C#, или же т.н. Uniform Function Call Syntax (в некоторых других, хороших языках) позволяют добиться нужного уровня декомпозиции (разумеется, там, где не подходит объектная), сохраняя синтаксис (и семантику, надеюсь) вызова метода у экземпляра.
Передать класс в хелпер, который есть только в том проекте, где он нужен, намного элегантнее.
Поддержка хелпера хуже, чем перенасыщенный поведением класс. Когда мы говорим о Clean
в Directory
, то отражаем реально существующее действие очистки папки в коде, а вот в случае с хелпером — нет.
Да и раз уж говорить про статические хелперы, то зачем писать:
DirectoryHelper.CleanDirectory(path);
FileSystemUtil.CleanDirectory(path);
если можно хотя бы:
Directory.Clean(path);
Clean.Directory(path);
Ведь читается приятнее, проще, а перенасытить поведением такие штуки довольно сложно: возможно, в самой предметной области не найдётся столько слов.
Это тот же самый хелпер, просто с другим синтаксисом.
Это не совсем верно.
Во-первых, методы расширений ограничены одним типом (если это хорошие, аккуратные методы расширений, а не очередные мусорки).
Во-вторых, методы расширений семантически идентичны вызовам методов на объекте, тогда как хелперы — вызовам статических методов с передачей объекта как параметра.
семантически идентичны вызовам методов на объектеМысль не закончена. Какой из этого вывод?
В пользу хелперов скажу, что мне нравится правило «явное лучше неявного». Указывая имя хелпера, программист осознанно подключает какую-либо зависимость.
А вот это
@"C:\Windows".UpgradeOS();
выглядит как какая-то пышь-пышь магия ))Мысль не закончена. Какой из этого вывод?
Что методы расширений не являются "теми же самыми хелперами, просто с другим синтаксисом".
А вот это
@"C:\Windows".UpgradeOS();
выглядит как какая-то пышь-пышь магия ))
Всё верно, действительно, магия. В таких случаях, конечно, лучше привлекать чуть больше предметных сущностей, чем просто строка.
Например:
var windows = Windows.Of(path: @"C:\Windows");
windows.Upgrade();
Разве это хуже, чем:
WindowsUtils.UpgradeFromPath(@"C:\Windows");
Разве это хуже, чемРазумеется, хуже. Хотя бы тем, что аллоцируется объект в куче, в котором нет пока никакой необходимости.
вывод?Почему нет? В IL-коде всё то же самое, лишь синтаксис другой.
Что методы расширений не являются «теми же самыми хелперами
Но ведь читаем мы С#.
И что такого дают методы расширения?
var a = b.As<A>();
// Не то же самое, что:
var a = Represent.As<A>(b);
Методы расширений позволяют строить такие предложения, где расширяемый объект является субъектом действия.
Как видите, я рассматриваю их не с точки зрения деталей реализации, а с точки зрения кода как текста.
Код живой и мёртвый. Часть первая. Объекты