Pull to refresh
30
0.5
Валерий @mvv-rus

Ненастоящий программист

Send message

В Википедии написано - что примерно так же, как пишется: "дэус экс", если по-русски. Это - часть довольно известного фразеологизма "deus ex machina" (который означает примерно то же, что и "рояль в кустах").

А те, кто видео записывает и те, кто с SQL Server профессионально работают - это одни и те же люди?

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

В книжках середины 90-х, по которым я SQL осваивал, настойчиво утверждалось, что правильно называть "сиквел" . Но как пришел я в конце тех же 90-х в группу, где работали с этим самыс SQL, услышал "эскуэль", углубился в конкретный Interbase - и больше это "сиквел" не видекл и не слышал.

PS А по теме - как услышал примерно тогда же про "постгрес" (хотя ЕМНИП он уже официально назывался PostgreSQL), так и до сих пор не вижу смысла называть его как-то по-другому.

По-моему, основная проблема, примера с Early return - это всего лишь догматическое следование правилам стиля кода. Потому что если переписать тот же самый код таким образом

std::string applySpell(Spell* spell)
{
  	if (!spell) return "No spell";
	if (!spell->isValid()) return "Invalid spell";
	if (this->isImmuneToSpell(spell)) return "Immune to spell";
	if (this->appliedSpells.constains(spell)) return "Spell already applied";
	
	appliedSpells.append(spell);
	applyEffects(spell->getEffects());
	return "Spell applied";
}

то стандарта вполне хватает (причем - даже не стандарта, а древних ещё обычаев языка C, даже не C++, когда и стандарта-то никакого не было). Код получается не менее компактным, чем по предложениям автора статьи. И - вполне читаемым (на мой взгяд, по крайней мере, ибо читаемость кода - она, вообще-то, субъективна). Что до модифицируемости, которая, как считается, пропадает при отказе от фигурных скобок (типа, ещё один оператор так просто не добавишь), то ничто не мешает в одном нужном месте эти скобки вернуть. А если нужно добавить оператор везде, то место ему - в функции, результат которой, вызванной с прежним возвращаемым значением,.и будет возвращать в return. Случайно добавить оператор без скобок в такой код IMHO тоже не то, чтобы невозможно, но незаметно и не задумываясь - сложно.

Единственная практическая сложность, которую я вижу с этим кодом - это как настроить средства контроля стиля кода (IDE и т.п.), чтобы они не проявляли излишний фанатизм и разрешали писать код так, как удобно, а не так, как положено ;-)

Ваша статья вызывает у меня ряд замечаний.

  1. По учету затрат памяти. В C# затраты памяти конвертируются, прежде всего, в затраты времени на сборку мусора. И я не увидел, что вы не только пытаетесь их учесть,. ни явным образом - я понимаю - это непросто, особенно - на коротких методах: "естественная" (по решению GC) сборка мусора начне вносить непредсказуемость, принудительная - утопит производительность в накладных расходах - ни даже хотя бы упомянуть о них: может даже, бенчмарк их учитывает как-то сам, но об . этом упомянуть надо IMHO.

  2. По выбору алгоритма, с которым вы сравниваете. (UPD: Да, я понял, что это - из обсуждаемой статьи, но IMHO контекст обсуждения в вашей статье шире). Вы взяли какой-то сильно оптимизированный вариант, пытающийся сэкономить количество просматриваемых символов, который, будучи оптимизированным, естественно, непонятен. Если писать алгоритм по-простому, не упарываясь преждевременной оптимизацией, то решение можно написать далеко не такое длинное и запутанное, хотя с той же, линейной по числу элементов, асимптотикой и без лишних выделений памяти. Например, так:

public string GetLongestWordMemory(string s, SearchValues<char> separators)
{
    int ipos=0,maxlen=0;
    string maxstr="";
    do {
        iposprev=ipos;
        ipos=str.IndexOfAny(AnyOf,iposprev);
        curlen=(ipos>=0?ipos:str.Length)-iposprev;
        if(curlnen>maxlen) {
          maxlen=curlen;
          maxstr=str.Substring(iposprev,curlen);
        }
    } while(ipos++>=0);
    return maxstr;
}

Достаточно компактно, почти линейно (один очевидный цикл) и вполне понятно, где модифицировать на проверку того, является ли слово годным и на сохранение нескольких слов - там, где if стоит . И да, никаих хитрых "алгоритмов" тут тоже нет.

И последнее. Сложность читать и модифицировать код - это, вообще говоря, субъективная оценка: кого чему учили. Ибо если человека не учили специально функциональному подходу (или, хотя бы, работе с множествами, на математике), то для него элементы функционального подхода будут вызывать затруднения сами по себе. Тогда как от императивного подхода таких затруднений не будет: он - пошаговый рецепт получения результата - в обыденной жизни куда чаще встречается. И это усложнение самих элементов может запросто съесть упрощение от снижения их числа.

После прочтения статьи у меня сложилось впечатление, что простой, но точный перевод статьи Тауба лучше бы способствовал пониманию async/await, чем данная статья. Бльно уж много в ней мыслей о том, как это всё сложно, которые IMHO только задерживают читателя.

Но, возможно, это мое впечатление обусловлено тем, что я давно знаю, как реализован async/await.

Реализацию оператора await можно понять зная лишь Task.

Да, понять реализацию на уровне концепции можно. Но реальная реализация там сделана сложнее. И по факту использует подход duck typing, в целом для C# нетипичный.

И это позволяет использовать кое-какие интересные трюки. У меня, например, в одном проекте есть класс, в одном из методов которого есть конструкция await this. Ревнителям чистоты кода сразу сообщу, что это - пет-проект, так что никто посторонний от такого трюка не пострадает.

Таки нет: Task Parallel Library (Task и пр.) - в .NET Framework 4.0, async/await, с его ключевым методом GetAwaiter - в 4.5. Всё это несложно проверить и сейчас, по документации на сайте MS.

Но за MVC с Razor таки не скажу: не следил за перипетиями появления MVC, перехода с aspx на cshtml и пр.

Нет не может. И зря вы так пренебрежительно относитесь к теории.

Я к ней отношусь с высоты своего опыта, не пренебрегая ей, но и не поклоняясь ей слепо. Потому как про многие положения теоретиков я узнал, попробовав их перед тем на практике, на своей, так сказать, шкуре. Эта теория - она ведь не более, чем обобщение опыта, на так ли. Потому подхожу к ее утверждениям со скептицизмом. Иногда он даже оправдывается. Например, в годы моей молодости теоретики требовали писать комментарии фактически на каждой строчке, а мне это не нравилось - ибо я не видел пользы к комментариям типа "удобная единица" после команды загрузки 1 в регистр (запомнился мне такой в коде IBM VM/SP). И писал комментарии в разумном количестве, благо мог себе позволить. А сейчас писать комментарии, наоборот, считается плохой практикой. А я иногда пишу, потому что не колеблюсь вместе с "линией партии" - когда чувствую, что они нужны.

В реальной действительности, DI уничтожает все объекты по окончанию Scope. Уничтожает, значит вызывает метод Dispose() у объектов.

Это делает не некий волшебный DI, а контейнер сервисов, по вызову IServiceScope.Dispose() к которой он принадлежит. А так как контейнер сервисов с равным успехом может быть использован обоими шаблонами - и SL, и DI - то и объекты с временем жизни области, полученные из контейнера ограниченной области, точно так же будут освобождены, как и полученные через DI.

Я не первый раз слышу от .NET разработчиков, что они путают GC и Disposable, искренне не понимая в чём разница. Это печально.

Согласен. Но я-то тут причем? Я не путаю и даже никого не учу путать.

Это крайне важно, это ключевое отличие.

По-моему, это отличие - всего лишь в понимании, что такое шаблон SL: вашем и моем (и как я вижу из комментариев - не только моем). Давайте либо договоримся, что понимать под SL, либо закончим дискуссию как беспредметную. Кстати, если брать SL в вашем понимании, это тоже - конец дискуссии, положительно оценивать такой убогий шаблон я не буду.

Я надеюсь, у вас получится осознать данный факт, тем самым углубить свои знания и понимание :)

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

https://habr.com/ru/companies/ruvds/articles/776768/comments/#comment_26335860

Не является SL, так как RequestServices это внедрённый через DI IServiceProvider, созданный с помощью IServiceScopeFactory. Это чистый DI.

Таки является: В моем понимании (т.е. как я его использую) шаблон SL не ограничивает источник, из которого он может брать сервисы. Но раз у вас другое понимание этого шаблона, то нам с вами спорить, наверное, не о чем.

RequestServices это внедрённый через DI IServiceProvider, созданный с помощью IServiceScopeFactory. Это чистый DI. В контекст запроса (HttpContext) добавлен RequestServices ...

Чаво?! В той части кода ASP.NET Core, где создается контекст запроса, нет никакого DI. В частности - в получении IServiceScopeFactory. Конкретно в стандартной конфигурации (в которой в качестве HttpContext используется его наследник DefaultHttpContext), в конструктор DefaultHttpContextFactory - класса, реализующего в стандартной конфигурации фабрику контекстов запроса IHttpContextFactory, который и создает этот DefaultHttpContext - передается только контейнер сервисов приложения (типа IServiceProvider, естественно), а конструктор сам вытягивает из него чисто по шаблону SL этот самый IServiceScopeFactory, запоминает в своем внутреннем поле, а потом, при вызове метода Initialize() (а не просто конструктора, потому что DefaultHttpContext может быть взят и из пула) содержимое этого поля копируется в содержимое свойства ServiceScopeFactory в DefaultHttpContext. А DefaultHttpContext, в свою очередь, передает (не напрямую, там ещё есть путь с выборкой из кэша) это свойство в конструктор RequestServiceFeature (это - реализация IServiceProviderFeature, тип которой, кстати, закодирован явно, без всякого IoC), который уже, наконец, создает контейнер сервисов для ограниченной области запроса и делает его доступным через свойство упомянутого интерфейса. А свойство DefaultHttpContext.RequestService в реальности получает доступ к ссылке на этот контейнер через этот интерфейс.
Короче, "Use the Source, Luke": правда - она в исходниках.
А DI обеспечивается уже фреймворком, реализующим обработчик соответствующей конечной точки маршрутизации, через этот самый контейнер сервисов запроса. Нет фреймворка (например MapGet и пр. до появления Minimal API)- нет DI.

Анти-паттерн, это означает паттерн, который применять не рекомендуется, так как его использование может нанести вред

Дык, ровно это я написал, упомянув инструкцию по ТБ: применять на практике можно, но с пониманием зачем это делать и каковы могут быть последствия. Но, собственно, это так практически со всеми шаблонами: область применимости любого из них ограничена.

Вообще демагогия это великолепный инструмент

Это очень удобная позиция: назвать то, что вам не нравится, демагогией. Неконструктивная она, однако. И это - еще один довод за прекращение дискуссию с вами.

Так это же ваша личная картина мира :) Соотносится ли это с практикой? Я пока этого не наблюдаю.

Да. Но чтобы не наблюдать, вы специально взяли компонент, рассчитанный на такое вот использование, с заточенным на это сервисом фабрики. А как насчет других компонентов - для которых такие замечательные фабрики MS не написала? А я ведь писал (не вам, кстати) про декорирование именно таких компонентов, которые можно использовать путем вызова из него декоратора декорируемого компонента с подменой нужных параметров. IMHO это дает достаточный простор для написания самых разных костылей, не так ли?
А ещё вы сравнили ваш навороченный DI с самым убогим способом использования объекта SL - подключением по статичной ссылке. А если, к примеру, вы в обработчике запроса HTTP возьмете для использования в SL ссылку на контейнер сервисов из объекта запроса - пресловутый RequestServices, то по крайней мере с временем жизни у вас будет все в порядке. Ибо напоминаю ещё раз, что DI в C#/.NET - это замаскированный SL. И по той же причине есть подозрение (но это мне проверять лень - кода там больно много наворочено), что и клиент придет нужный и с нужной конфигурацией.

Получается, вы не знаете теории, и это выходит вам боком, так как вы IoC противопоставляете SL, что выглядит по меньшей мере странно :)

Учите теорию. Иначе ваши рассуждения это сплошная беда.

Ну вот, вы с чего то решили, что я противопоставляю SL и IoC. А я там противопоставил этот новомодный (ну как новомодный - ноги у него растут из 90-х и нулевых) IoC и традиционный подход с явно прописанными чисто конкретными зависимостями - не от интерфейсов, а от реализаций. Да, это затрудняет модификацию кода, но ведь и достоинство у него есть: всегда видно, с чем именно имеешь дело, IDE подскажет.
Думал, из контекста вы это поймете, но вы предпочли сделать соломенное чучело и поучать его.

А причем тут рекомендации МС на тему "что лучше"? Вопрос-то был в том, можно ли, используя шаблон SL вместо DI, получить сервис с нужным временем жизни. Ответ - можно, если использовать правильный объект контейнера сервисов для SL. В том числе, получить правильный объект можно и там, где никакого DI нет. Например - в обработчике конечной точки маршрутизации базового ASP.NET Core, который принимает в качестве параметра строго HttpContext и ничего больше.
То есть, претензия к SL, названная @qw1, по факту оказывается несостоятельной. Ответ был ровно про это. И он никак не касался выбора, какой шаблон использовать, при условии что такой выбор есть (а есть он не всегда).

Не буду ничего говорить за парадигму, ибо это - теория, чистая и высокая. А на практике, если взять правильный объект SL (типа IServiceProvider), то от него вы получите сервис с правильным временем жизни ("в скопе", как вам надо). Например, если вы вызовете в контроллере Context.RequestServices.GetService<YourDBContext>() (DbContext обычно используют специфичеcкого типа, в данном случае путь это будет YourDBContext), то получите DbContext годный как раз на время обработки запроса. И через параметр конструктора контроллера вы получите его же - просто за вас это чуть раньше сделает фреймворк.

PS Хотел дополнить в самом комментарии, но время истекло, поэтому - здесь.

Вообще IMHO все разговоры на тему "smth - антипаттерн" отдают теоретизированием на уровне споров остроконечников с тупоконечниками. А на практике все "антипаттерны" - тоже паттерны, шаблоны проектирования. И как у всех других шаблонов у них есть свои недостатки и свои достоинства, а следовательно - область применимости, выходить за которую небезопасно. Впрочем, тут IMHO - как с инструкциями по ТБ: нарушать можно, но при этом следует четко отдавать себе отчет, зачем ты это делаешь и чем это может тебе грозить. А вообще "И терпентин goto на что-нибудь полезен!" (почти по Козьме Пруткову) ;-)

Стандартный DI контейнер от MS явно показывает, что все зависимости надо настраивать ДО их использования. SL же таким не ограничен и поэтому я видел очень много разных костылей

Программиста вообще сложно ограничить в возможности писать плохой код. И DI этому не помеха, и даже кабы не подспорье. Вон колега @hVostt, упомянул чуть ниже в качестве преимуществ DI возможность легко и просто декорировать использующие его компоненты. Как по мне, это дает ничуть не меньше (как бы не больше) возможностей накостылить: декорировать компонент IMHO куда проще, чем изменять SL.

При чём тут какой-то RequestServices, понятия не имею.

Я о своем, о наболевшем. Впрочем, как я понимаю разработка на ASP.NET Core - это очень большая часть разработки на C#/.NET вообще, так что эту мою печаль разделят, полагаю, многие.

Ни в коем случае. SL не управляет временем жизни объектов. Он этого делать не в состоянии, DI напротив, всегда знает весь граф объектов, которые создал, и когда они подлежат уничтожению. DI может обеспечить такие жизненные циклы, как Scoped и Owned. SL этого сделать не может, так как понятия не имеет в каком контексте была запрошена зависимость. Максимум Transient и Singleton.

Вы это с точки зрения чистой высокой теории написали? Или все же - применительно к нашей низкой практической теме C#/.NET?
За чистую теорию ничего говорить не собираюсь, но вот в реальной действительности C#/.NET временем жизни объектов управляет сборщик мусора. А DI может влиять на время жизни объекта сервиса только так, как позволяет контейнер сервисов. И SL может в точности то же самое.
То есть, если вы берете Scoped-сервис из контейнера сервисов ограниченной области, то жить он будет столько, сколько будет жить эта область. И не важно, как именно вы берете этот сервис: получаете через IServiceProvider.GetService или же указываете фреймворку как зависимость класса/метода - которую получит через IServiceProvider.GetService из того же контейнера и подставит на место уже фреймворк.

Принцип IoC разрушается, так как часть управления созданием зависимости переносится в реализацию.

Если рассуждать так, то при добавлении параметра в конструктор этот принцип нарушается точно так же: вы инициируете создание зависимости. Т.е., в контексте обсуждения (.NET и C#, напоминаю) делаете ровно то же, что и вызывая IServiceProvider.GetService: инициируете создание объекта зависимости, не указывая его реализацию и не передавая ему никаких дополнительных параметров.

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

Звучит страшно, но как я уже написал выше, это страшное "детальное построчное изучение" в обсуждаемом контексте в реальности сводится к поиску в исходном тексте обращения к SL, конкретно в наиболее интересном случае обработчиков в веб-приложении - свойства RequestServices. И это делается даже более механически, нежели поиск внедренных через параметры зависимостей.
Но если у вас вдруг нет исходного текста программы, тогда сложнее - придется таки прочитать документацию.
А вообще, сторонникам IoC по поводу "явное лучше неявного" стоило бы промолчать: сам этот принцип направлен на то, чтобы заменить явное неявным.

Появляется дополнительная зависимость от SL. Зависимость, объявленная в конструкторе, на самом деле не требует никакого DI, и сконструировать класс можно обычным new, или мок-контейнером -- что крайне полезно для изолированного юнит-тестирования.

Конкретно в обсуждаемом контексте .NET и C#, SL - это не какая-то высшая сущность, а ещё один тип параметра-интерйеса. И он точно так же поддается имитации, как и любой другой параметр. А ещё он в .NET и C# присутствует везде, где есть DI - потому что DI в .NET и C# - это SL, спрятанный где-то выше по стеку вызовов.

Компонент, использующий SL невозможно задекорировать, со специфичными реализациями зависимостей.

Этот компонент откуда ссылку на SL получает? Если через параметр - задекорируйте этот самый SL - и будет вам счастье.
Что интересно, сам .NET именно этим и занимается, когда использует сервисы с временем жизни Scoped: фактически он создает декоратор для основного контейнера, который держит ссылки на созданные реализации сервисов на время работы в ограниченной области (в ASP.NET Core - в рамках обработки одного запроса HTTP). И если вам вдруг понадобится передать такую зависимость (не важно, полученную через SL или DI) в код, выполняющийся вне этой области (между запросами, например), то весьма вероятно у вас будут проблемы.
Но таки да, если компонент получает ссылку на SL не через параметры, откуда-то ещё (к примеру из HttpContext.RequestServices), то с декорированием сложно.

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

Теоретически - да. На практике в .NET если использовать только возможности контейнера по умолчанию, то компонент переносится в любой проект, где есть DI и контейнер сервисов - потому что в .NET DI реализуется через SL "где-то там". А в том же ASP.NET Core контейнер сервисов есть всегда, так что проблем не будет.

То есть, получается, что недостатки SL конкретно в .NET нивелируются. Это с одной стороны. А с другой стороны, DI в .NET (который, например, в ASP.NET Core практически обязателен к использованию) обеспечивается фреймворками, а потому без SL далеко не везде можно обойтись без серьезных жертв, а недостатки SL - это, в большинстве случаев, и недостатки DI

Все разговоры о теоретических преимуществах DI над SL в контексте C#/.NET упираются в непреложный факт: DI здесь - это SL где-то выше по стеку, возможно - "заметенный под ковер" в коде фреймворка. А потому главынй теоретический недостаток SL - невозможность проверок на то, что зависимость реализаована (и как именно), на стадии компиляции (и редактирования исходного кода в IDE, где IDE реализацию вам не подскажет) - никуда здесь не девается. И точно так же как и в SL, в DI для C#/.NET ничто не препятствует разработчику забыть реализовать сервис или зацепить сервис из логически другой части программы, создав совершенно необязательную зависимость.

Что до заметности зависимости, то это - вопрос привычки. Если есть исходный текст программы, то наметанный глаз отловит использование RequestServices из контекста запроса (обычное место ссылки на контейнер сервисов) в обработчике конечной точки (или в действии контроллера MVC, или в обработчике Razor Pages) не с меньшей легкостью, чем параметр метода или конструктора. Особенно - при использовании поиска (а методы и конструкторы поиском искать сложнее).
А если исходного текста нет, то должна быть документация.

Ну, а компоновка по ходу работы (иначе говоря, подключаемые модули - plug-ins) - это, вообще-то, архитектурное решение. И, как известно, есть программы, для которых такое решение вполне обосновано. Но архитектурное решение должно приниматься осознаннно. И, желательно - уполномоченными на это людьми. А неуполномоченным нужно давать по рукам. Но это все, конечно - в том же самом недостижимом идеале в котором DI - не SL.

Information

Rating
1,534-th
Registered
Activity