Простой пример, который доставлял бы мне дискомфорт в программировании если бы английский язык был бы для меня родной. Door->open() vs Open->door().
Да, в ООП предполагается такое незначительное нарушение грамматики, но из него никак не следует, что нужно идти в диаметрально противоположное направление и писать хелперы.
К сожалению, не могу подтвердить или опровергнуть это мнение. В основном наблюдаю пренебрежение к языку и словам, из-за которого код получается неопрятным, неаккуратным, но зато рабочим, как если бы его работоспособность была конечным продуктом.
Разве что, случается (ведь я сам не носитель): некоторые слова кажутся вычурными и неестественными, потому что редко их встречаешь или до этого не знал.
На мой взгляд, сама семантика вызова: ThreadPool.Execute предполагает, что действие не будет выполнено мгновенно, поскольку мы работаем в контексте ThreadPool.
Никогда бы не подумал что-то вызвать на тредпуле, не понимая, что это в действительности значит: что там есть потоки, они управляются, что не нужно их надолго задерживать. Тип ThreadPool сообщает вполне достаточно.
Но аргумент понятен. Как вам: ThreadPool.Queue(action)? Зачем WorkItem? Что это значит?
Как финтифлюшка в HelloWorld забавно смотрится, но не масштабируется для большого проекта.
Хм. Сперва постулируется: "не масштабируется для большого проекта", а затем как подтверждение — довольно частный пример, который может касаться лишь одной из подсистем этого самого большого проекта.
Кроме этого, надо понимать, 5.Minutes() работает там, где работает, а там, где не работает, ищется другое.
Теперь масштабируем эту строчку в десятки раз, и получаем вполне приличный, читаемый код. Система, которой нужны тики, а также иные, сложные, требующие технической сноровки, не пострадали.
Но при разработке обычно у каждого класса/модуля есть ответственный владелец (человек или команда). Было бы неприятно зайти в свой идеально причёсанный класс Directory и обнаружить там наваленную гору кода от метода Clean и его подчинённых, который написан другой командой и другим стилем, с другими соглашениями.
Поэтому методы расширений, которые есть в C#, или же т.н. Uniform Function Call Syntax (в некоторых других, хороших языках) позволяют добиться нужного уровня декомпозиции (разумеется, там, где не подходит объектная), сохраняя синтаксис (и семантику, надеюсь) вызова метода у экземпляра.
Передать класс в хелпер, который есть только в том проекте, где он нужен, намного элегантнее.
Поддержка хелпера хуже, чем перенасыщенный поведением класс. Когда мы говорим о Clean в Directory, то отражаем реально существующее действие очистки папки в коде, а вот в случае с хелпером — нет.
Да и раз уж говорить про статические хелперы, то зачем писать:
Не всегда понятно, когда работа идёт с этими методами внутри класса, где, скорее всего уже есть приватные или локальные переменные с такими названиями.
Да, существуют ситуации, когда уже есть какие-то переменные или поля с такими же именами. Но и их можно обойти, всё зависит от конкретного случая, главное понимать, зачем глагол, а зачем существительное. В случае с Calculate глагол уж точно ни к чему.
Как вы должно быть поняли — пишу с точки зрения джависта и джавовский гайдлайнов, где это вопрос именования расписан и стандартный вариант именно глагол-существительное (что и над чем).
В C# для методов существует такое же правило. Интересно, что если метод, то Get (или какой-нибудь Calculate) пишется, а вот если свойство (синтаксически это метод без параметров и без скобочек) — то нет. Как если бы от физического воплощения (свойство или метод) одного и того же понятия мы бы по разному говорили о нём между собой.
Думается мне, критерий всегда один: говорят "скидка", значит Discount, и т.д., тут в комментарии ниже описывал.
Код тогда строится не по действиям, императивно, а описательно: не "скидка считается считателем скидок по формуле", а "скидка — это...". Разумеется, там где действие, там остаётся царствие глагола.
Я хочу именно что указать, что мне нужно получить пользователя, разве нет?
В том то и дело, что вот это "получить" — лишнее, ненужное. Получить пользователя и пользователь подразумевают одно и то же (пользователя), только первое зачем-то что-то уточняет.
Я бы не хотел отходить от темы, размышляя "а что если". Боюсь, тогда мы тут надолго застрянем.
Совершенно точно, что простое IDiscount имеет свои пределы, но пределы эти — часть другого обсуждения, мне же оно нужно было для того, чтобы показать идею.
Кстати, если взять CalculateDiscount(), CalculateNDS() и CalculateTotalSum(), то проще — Discount(), NDS() и TotalSum(). Что оно Calculate, и так понятно.
Диалог сохранения показывает метод-обработчик кнопки сохранить.
Это ж какой годкласс на сколько тысяч строк получится? Да и зависимости левые получаются. Зачем документу знать о графической системе даже просто транзитом?
На мой взгляд, показать диалоговое окно с выбором пути для сохранения, а потом вызвать document.SaveTo(path) или document.SaveTo(stream) — вполне достаточно. Если нужно обобщить для Unit-тестов, то document.SaveTo(storage). Да и говорим мы: "Сохрани документ". Опять же, посредники и помощники — избыточны.
Попытки играться с зависимостями, перенося их туда, куда не требуется, приводит к ложному впечатлению, будто всяческие SOLID соблюдены, а код понятен и расширяем. Само наличие слова Helper — прямая дорога в технологическую сингулярность, и ваши слова про "экселевскую мусорку" это, к сожалению, подтверждают.
Там почти монаду сделали. (т.е. ExcelHelper.setWorkingDir(path)
Можно было бы и по другому сделать, согласен, но не надо вешать на документ, то что собственно документа не касается
Полностью согласен, но не вижу ExcelHelper как подходящее решение такой проблемы.
Мне кажется, выразительнее и понятнее:
var document = ExcelDocument.Of(path);
а не:
var document = ExcelHelper.LoadDocument(path);
Или можно ещё:
var document = file.AsFile().AsExcelDocument();
Написанное полно описывает происходящее, и не привлечены дополнительные не классы, но понятия: говоря о загрузке документа из файла, мы ограничились известными словами.
Разумеется, не берусь утверждать, что хорошо понимаю все сложности, которые возникли в вашем конкретном случае.
Ну тут больше вопрос архитектуры — как вы делите программу на куски: фасадами или фабриками?
На мой взгляд, такое разделение не совсем корректно. Возьмём выражение:
var document = new ExcelDocument();
var sheet = document.NewSheet();
Будет ли ExcelDocument фасадом или фабрикой, не имеет решительно никакого значения, поскольку главное — с помощью названия подвести к уже существующему опыту (о создании документов и таблиц в Excel многие имеют представление).
Как только мы согласились написать ExcelDocument вместо ExcelHelper, мы уже как бы предначертали его судьбу и что за методы в нём можно ожидать.
Если описываете "систему контроля версий", то VersionControlSystem; если "коммиты репозитория" — Commits; если "текстовый редактор" — TextEditor, если "тексты" (редкий какой-то случай) — Texts; если "веб-браузер" — WebBrowser, если "веб-страницы" — WebPages.
На мой взгляд, всё банально, но эту банальность часто избегают с помощью всяческих VCSManager, CommitsHelper, TextEditorUtils, WebHelper и т.д., как если бы простота была чем-то преступным и недостойным.
Что вы понимаете под уровнем абстракции в примере с Weapon, как он повысился и в какой момент?
Мы смотрим, как у нас обвешан загадочными гроздьями мета-штук Weapon
Вот это, кстати, понял. Возможно, не достаточно точно выразился, но если под "мета-штуками" вы имеете ввиду фразу про "метапрограммирование", то речь же была о простейшей библиотеке сериализации вроде Newtonsoft.Json.
Так что Weapon ничем, выходит, и не будет обвешан. Ответственность перенесли, как того требует S, но сохранили удобство, и никаких WeaponDamageGetter, WeaponJsonProvider, WeaponManager после себя не оставили.
Да, в ООП предполагается такое незначительное нарушение грамматики, но из него никак не следует, что нужно идти в диаметрально противоположное направление и писать хелперы.
К сожалению, не могу подтвердить или опровергнуть это мнение. В основном наблюдаю пренебрежение к языку и словам, из-за которого код получается неопрятным, неаккуратным, но зато рабочим, как если бы его работоспособность была конечным продуктом.
Разве что, случается (ведь я сам не носитель): некоторые слова кажутся вычурными и неестественными, потому что редко их встречаешь или до этого не знал.
На мой взгляд, сама семантика вызова:
ThreadPool.Execute
предполагает, что действие не будет выполнено мгновенно, поскольку мы работаем в контекстеThreadPool
.Никогда бы не подумал что-то вызвать на тредпуле, не понимая, что это в действительности значит: что там есть потоки, они управляются, что не нужно их надолго задерживать. Тип
ThreadPool
сообщает вполне достаточно.Но аргумент понятен. Как вам:
ThreadPool.Queue(action)
? ЗачемWorkItem
? Что это значит?Хм. Сперва постулируется: "не масштабируется для большого проекта", а затем как подтверждение — довольно частный пример, который может касаться лишь одной из подсистем этого самого большого проекта.
Кроме этого, надо понимать,
5.Minutes()
работает там, где работает, а там, где не работает, ищется другое.Положим, описываю я дебаффы в игре:
Теперь масштабируем эту строчку в десятки раз, и получаем вполне приличный, читаемый код. Система, которой нужны тики, а также иные, сложные, требующие технической сноровки, не пострадали.
Поэтому методы расширений, которые есть в C#, или же т.н. Uniform Function Call Syntax (в некоторых других, хороших языках) позволяют добиться нужного уровня декомпозиции (разумеется, там, где не подходит объектная), сохраняя синтаксис (и семантику, надеюсь) вызова метода у экземпляра.
Поддержка хелпера хуже, чем перенасыщенный поведением класс. Когда мы говорим о
Clean
вDirectory
, то отражаем реально существующее действие очистки папки в коде, а вот в случае с хелпером — нет.Да и раз уж говорить про статические хелперы, то зачем писать:
если можно хотя бы:
Ведь читается приятнее, проще, а перенасытить поведением такие штуки довольно сложно: возможно, в самой предметной области не найдётся столько слов.
Да, существуют ситуации, когда уже есть какие-то переменные или поля с такими же именами. Но и их можно обойти, всё зависит от конкретного случая, главное понимать, зачем глагол, а зачем существительное. В случае с
Calculate
глагол уж точно ни к чему.В C# для методов существует такое же правило. Интересно, что если метод, то
Get
(или какой-нибудьCalculate
) пишется, а вот если свойство (синтаксически это метод без параметров и без скобочек) — то нет. Как если бы от физического воплощения (свойство или метод) одного и того же понятия мы бы по разному говорили о нём между собой.Думается мне, критерий всегда один: говорят "скидка", значит
Discount
, и т.д., тут в комментарии ниже описывал.Код тогда строится не по действиям, императивно, а описательно: не "скидка считается считателем скидок по формуле", а "скидка — это...". Разумеется, там где действие, там остаётся царствие глагола.
В том то и дело, что вот это "получить" — лишнее, ненужное. Получить пользователя и пользователь подразумевают одно и то же (пользователя), только первое зачем-то что-то уточняет.
Совершаемое действие не имеет значения. Имеет значения результирующее понятие.
Я бы не хотел отходить от темы, размышляя "а что если". Боюсь, тогда мы тут надолго застрянем.
Совершенно точно, что простое
IDiscount
имеет свои пределы, но пределы эти — часть другого обсуждения, мне же оно нужно было для того, чтобы показать идею.Кстати, если взять
CalculateDiscount()
,CalculateNDS()
иCalculateTotalSum()
, то проще —Discount()
,NDS()
иTotalSum()
. Что оноCalculate
, и так понятно.Как раз об этом и будет заключительная статья!
Диалог сохранения показывает метод-обработчик кнопки сохранить.
На мой взгляд, показать диалоговое окно с выбором пути для сохранения, а потом вызвать
document.SaveTo(path)
илиdocument.SaveTo(stream)
— вполне достаточно. Если нужно обобщить для Unit-тестов, тоdocument.SaveTo(storage)
. Да и говорим мы: "Сохрани документ". Опять же, посредники и помощники — избыточны.Попытки играться с зависимостями, перенося их туда, куда не требуется, приводит к ложному впечатлению, будто всяческие SOLID соблюдены, а код понятен и расширяем. Само наличие слова
Helper
— прямая дорога в технологическую сингулярность, и ваши слова про "экселевскую мусорку" это, к сожалению, подтверждают.Боюсь, это не монада...
Это лёгкая шалость в сторону экзистенциализма, там есть идея с похожим узором.
"Глагол предшествует существительному" — примерно то же самое, вы всё верно указали.
Если нужно инжектить, то пожалуйста:
Вызов тогда:
Суть осталась та же, изменилась только форма.
Книга отличная!
Но этот цикл про несколько иную идею, хотя и пересекается с некоторыми разделами книги.
Потому что
Get
— это глагол, а никакого значимого действия мы указывать не хотим, только то, что вот пользователь с идентификатором ID.Но и
User
— не предел. Я улучшу это имя, развив ряд соображений из этой статьи, в статье следующей.Полностью согласен, но не вижу
ExcelHelper
как подходящее решение такой проблемы.Мне кажется, выразительнее и понятнее:
а не:
Или можно ещё:
Написанное полно описывает происходящее, и не привлечены дополнительные не классы, но понятия: говоря о загрузке документа из файла, мы ограничились известными словами.
Разумеется, не берусь утверждать, что хорошо понимаю все сложности, которые возникли в вашем конкретном случае.
На мой взгляд, такое разделение не совсем корректно. Возьмём выражение:
Будет ли
ExcelDocument
фасадом или фабрикой, не имеет решительно никакого значения, поскольку главное — с помощью названия подвести к уже существующему опыту (о создании документов и таблиц вExcel
многие имеют представление).Как только мы согласились написать
ExcelDocument
вместоExcelHelper
, мы уже как бы предначертали его судьбу и что за методы в нём можно ожидать.Если описываете "систему контроля версий", то
VersionControlSystem
; если "коммиты репозитория" —Commits
; если "текстовый редактор" —TextEditor
, если "тексты" (редкий какой-то случай) —Texts
; если "веб-браузер" —WebBrowser
, если "веб-страницы" —WebPages
.На мой взгляд, всё банально, но эту банальность часто избегают с помощью всяческих
VCSManager
,CommitsHelper
,TextEditorUtils
,WebHelper
и т.д., как если бы простота была чем-то преступным и недостойным.Боюсь, я не до конца понял вашу мысль.
Что вы понимаете под уровнем абстракции в примере с
Weapon
, как он повысился и в какой момент?Вот это, кстати, понял. Возможно, не достаточно точно выразился, но если под "мета-штуками" вы имеете ввиду фразу про "метапрограммирование", то речь же была о простейшей библиотеке сериализации вроде
Newtonsoft.Json
.Так что
Weapon
ничем, выходит, и не будет обвешан. Ответственность перенесли, как того требует S, но сохранили удобство, и никакихWeaponDamageGetter
,WeaponJsonProvider
,WeaponManager
после себя не оставили.Боюсь, мы тогда отдалимся от темы.
Мысль была вот какая: порой метафора, восходящая к опыту, понятнее и проще, чем низкоуровневая деталь.
ManualResetEvent
— наглядный пример.