Топ-20 помилок .NET-розробників, які я зустрічаю щотижня (і які вам варто виправити вже сьогодні)

💡 Усі статті, обговорення, новини про .NET — в одному місці. Приєднуйтесь до .NET спільноти!

Всім привіт! Мене звати Едуард, я .NET Engineer в компанії Infinity Technologies. Протягом останніх років я бачив, як одні й ті самі помилки повторюються знову і знову як у власному коді, так і в роботах колег. Часто ці помилки здаються дрібницями, але саме вони стають причиною непередбачуваних багів, поганої продуктивності і «технічного боргу», який потім боляче повертати.

Тож ловіть мій топ-20 помилок, які потрібно прибрати зі свого коду, щоб стати сильнішим .NET-інженером.

1. Використання DateTime.Now замість абстракції годинника (IClock)

Класика жанру. Здається, що DateTime.Now це безпечний спосіб отримати поточний час. Але проблема в тому, що цей час залежить від системної часової зони.
Уявіть, що у вас мікросервісна архітектура, де частина сервісів живе в UTC, а інші в локальному часі (бо хтось колись так налаштував сервер). Результат баги з часовими зміщеннями, які неможливо стабільно відловити в тестах.

Кейс із практики: Логіка нарахування підписок, яка працювала чудово у нас в Києві, почала «здвигати» дати для клієнтів з Нью-Йорка. Причина DateTime.Now підхоплював локальний час сервера. Рішення винести час у IClock і контролювати джерело в тестах та продакшні.

2. Працюєте на .NET Framework замість LTS версії (.NET 8)?

Так, міграція може бути болючою, особливо якщо проекту 10+ років. Але чим довше ви тягнете з переходом, тим більше втрачаєте:

  • Сучасні оптимізації продуктивності (особливо з .NET 6-8).
  • Безпекові патчі (Framework більше не оновлюється).
  • Нові можливості екосистеми (тулінг, бібліотеки).

Кейс із практики: Один з клієнтів Infinity Technologies роками тримав CRM-систему на .NET Framework 4.6. Після переходу на .NET 7 продуктивність API виросла на 30% «з коробки» просто через нові оптимізації рантайму.

3. Логування простим текстом замість Serilog/Seq

У невеликих проєктах логування через Console.WriteLine або прості текстові файли здається нормальним рішенням. Але коли у вас продакшн, де летить 10 000+ запитів на хвилину знайти в логах проблему без структурованого підходу стає майже неможливо.

Кейс із практики: У нас був сервіс, де команда логувала все у текстовий файл. Під час розслідування проблеми з продуктивністю ми втратили пів дня, просто шукаючи потрібні записи. Після переходу на Serilog з вивантаженням у Seq аналітика логів стала займати хвилини.

4. Важка робота в request-потоці ASP.NET

Якщо у вас на request thread’і йде обробка важкої аналітики, генерація PDF чи масивний SQL-запит ви автоматично блокуєте цей потік на весь час виконання задачі. Результат високий latency, зниження RPS, і користувачі бачать спінери замість відповідей.

Кейс із практики: Ми працювали над системою генерації звітів, яка синхронно будувала PDF одразу після кліку користувача. Коли навантаження зросло, сайт почав зависати. Перенесення генерації в background queue (через Hangfire) знизило навантаження в 5 разів.

5. Забуваєте Dispose() для DbContext, HttpClient, Stream?

Це одна з найпідступніших помилок, бо наслідки не завжди проявляються одразу. Забутий Stream може залишити відкритий файл, не звільнений DbContext тримати підключення до бази, а новий HttpClient на кожен запит швидко вичерпати доступні порти.

Кейс із практики: В одному з проєктів команда створювала новий HttpClient для кожного запиту до стороннього API. На тестах все працювало чудово, але на проді почали отримувати помилки «Address already in use». Вирішення використовувати Singleton HttpClient або IHttpClientFactory.

6. Блокуєте async через .Result або .Wait()

У .NET async/await працює так, що якщо ви блокуєте асинхронний виклик через .Result або .Wait(), це може призвести до дедлоків або жорсткого блокування потоків. Особливо небезпечно в ASP.NET, де кожен потік на вагу золота.

Кейс із практики: В одному проєкті був сервіс, який викликав async-метод отримання токена авторизації через .Result. Все працювало, поки навантаження було мінімальним. Але коли зросла кількість запитів, частина потоків почала «залипати» в очікуванні. Після рефакторингу на await проблема зникла, а продуктивність виросла на 20%.

7. Бізнес-логіка всередині LINQ-запитів до EF

Коли ви починаєте писати умовні конструкції, цикли або розгалуження прямо всередині LINQ-запитів до Entity Framework це погано з двох причин:

  1. Ви блокуєте тестування цієї логіки.
  2. Ви стаєте заручником того, як EF транслює ваш код у SQL.

Кейс із практики: Один з розробників у нас реалізував логіку ціноутворення (з урахуванням акцій, типів клієнтів і т.д.) прямо у Where-запиті до бази. В результаті, запит до бази став монстром, який важко оптимізувати і неможливо покрити юніт-тестами. Вирішення логіку винесли окремим сервісом, а запити до бази зробили максимально простими.

8. Жорстке прописування конфігів замість IOptions<T> і секретів

Hardcoded налаштування (URLs, connection strings, API-ключі) прямо в коді це не лише погана практика з точки зору безпеки, але й велика проблема для масштабованості проєкту.

Кейс із практики: Під час міграції сервісу на staging середовище команда виявила, що всі URL-адреси були захардкожені прямо в сервісах. Зміна оточення перетворилася на пекло. Перехід на IOptions<T> і винос секретів у Azure Key Vault вирішив проблему раз і назавжди.

9. Service Locator замість нормальної DI-ін’єкції

Service Locator дає ілюзію зручності, бо «можна дістати будь-який сервіс звідки завгодно». Але насправді це приховує залежності класів і робить тести майже неможливими без хака.

Кейс із практики: В одному з проєктів розробники масово тягнули сервіси через Service Locator (ServiceProvider.GetService<T>), і коли прийшов час писати юніт-тести половина класів вимагала моків на 10+ залежностей, які навіть не проглядалися у конструкторі. Після рефакторингу на constructor injection тести стали значно простішими.

10. Вимкнення аналізаторів і автоперевірки стилю коду

«Воно ж мені заважає кодити швидко, ці попередження.» Результат через пару місяців проєкт перетворюється на стилістичний хаос, а баги, які можна було б впіймати на етапі розробки, потрапляють у прод.

Кейс із практики: У невеликій команді вимкнули Roslyn-аналітик «бо заважали працювати». Через два місяці отримали production-багу з null reference, яка могла б бути попереджена nullable-аналізатором. Виправили? Так. Але втратили день продакшн-тайму.

11. God-класи і контролери по 500+ рядків

Коли один клас (особливо контролер) розростається до сотень рядків, це означає лише одне: ви зібрали в ньому все, що могли. Такі «Бог-класи» важко тестувати, важко читати і неможливо ефективно рев’ювити.

Кейс із практики: В одному з проєктів контролер замовлень виріс до 1 200 рядків, обробляючи весь життєвий цикл замовлення, валідацію, платежі і навіть бізнес-правила. Під час чергового рев’ю ми витратили 2 години на обговорення одного контролера. Після розбиття на окремі сервіси (OrderService, PaymentService, ValidationService) рев’ю таких змін почало займати 10-15 хвилин.

12. Відсутність ConfigureAwait(false) у бібліотеках

Якщо ви пишете бібліотеки або SDK для інших проектів, не використання ConfigureAwait(false) в асинхронних викликах може призводити до дедлоків, особливо у WPF/WinForms додатках.

Кейс із практики: Ми розробляли бібліотеку для інтеграції з платіжним шлюзом. Усі async-методи не мали ConfigureAwait(false). В результаті, коли клієнт інтегрував бібліотеку в WinForms-додаток, частина інтерфейсу почала «вмирати» через дедлоки. Виправили це, додавши ConfigureAwait(false) у всіх внутрішніх викликах бібліотеки.

13. Ловля System.Exception без повторного викидання

Коли ви ловите System.Exception і нічого з ним не робите (а тим більше не прокидуєте далі), ви маскуєте реальну проблему. Це як бачити, що димить, але заклеїти лампочку індикатора.

Кейс із практики: Один сервіс час від часу падав, але у логах нічого не було. Виявилось, що у catch(Exception ex) був пустий catch-блок без логування і rethrow. Після додавання логування ми знайшли баг у сторонній бібліотеці, який роками «ховався» у цій чорній дірі.

14. Закоментований код у репозиторії

Закоментований код це шум. Він відволікає розробників, ускладнює merge-конфлікти і створює ілюзію «потрібності». Git і так зберігає історію змін.

Кейс із практики: В одному проекті в репозиторії залишались закоментовані фрагменти старої логіки, яка вже не використовувалась, але «раптом ще стане в нагоді». Під час міграції на нову архітектуру merge-конфлікти з цим «мертвим» кодом затягнули процес об’єднання гілок на кілька днів.

15. Ігнорування CancellationToken у асинхронних завданнях

Без CancellationToken користувачі не зможуть скасувати довгі або непотрібні операції. Ресурси витрачаються даремно, ви ризикуєте накопиченням «завислих» тасків і виснаженням системи.

Кейс із практики: У проекті з імпортом великих Excel-файлів не було підтримки CancellationToken. Коли користувачі завантажували файли по 500 000 рядків і розуміли, що обрали не той файл, вони не мали можливості зупинити обробку. В результаті сервер просто зжер усі потоки. Після додавання підтримки відміни завдання через CancellationToken, система почала вести себе набагато стабільніше під навантаженням.

16. Відсутність #nullable enable

Nullable Reference Types у C# це не просто нова фіча, це діагностичний інструмент, який попереджає вас про потенційні NullReferenceException ще на етапі компіляції. Вимкнувши цю опцію, ви добровільно відмовляєтесь від захисту.

Кейс із практики: В одному з проєктів працювали з API, яке іноді повертало null у деяких полях JSON. Без включеного Nullable Reference Types ми зловили NRE у продакшені. Після включення #nullable enable компілятор одразу показав, де саме ми «довіряли» даним без перевірок.

17. Ініціалізація HttpClient через new замість Singleton/IHttpClientFactory

Кожного разу, коли ви створюєте новий екземпляр HttpClient через new, ви відкриваєте нове TCP-з’єднання. Якщо це відбувається часто, ваші порти швидко вичерпуються, а продуктивність падає.

Кейс із практики: У нас був сервіс, який викликав стороннє API з new HttpClient() для кожного запиту. При навантаженні понад 1000 RPS сервер почав «падати» через портове виснаження. Після рефакторингу на IHttpClientFactory проблема зникла повністю, а середній час відповіді знизився вдвічі.

18. Всі сервіси в одному проєкті без модульності

Коли у вас всі сервіси, ентіті і логіка живе в одному великому проекті без поділу на модулі/шари ви отримуєте архітектурний моноліт, який важко підтримувати. Не плутайте це з монолітною системою це саме про хаос у проєкті.

Кейс із практики: В одному проекті не було окремих шарів для Business Logic, Data Access і Presentation. Будь-яка зміна вимагала реверс-інженерії всього рішення. Після впровадження модульної архітектури (чіткий поділ на Core, Infrastructure, API) швидкість розробки зросла на 30%.

19. Надмірне використання dynamic «бо так простіше»

Dynamic спокушає швидко вирішувати проблему з типами, але як тільки dynamic заходить у ваш код ви втрачаєте типобезпеку, інтелісенс і компілятор більше не ваш друг. Це short-term рішення, яке породжує long-term хаос.

Кейс із практики: Розробник використав dynamic для роботи з JSON, аби «не писати зайві класи». Все працювало, поки структура JSON не змінилась. Замість компіляторної помилки ми отримали runtime-баг на продакшні. Виправили, перейшовши на Typed Models з валідацією.

20. «Мені не потрібні Unit-тести, я і так все перевірив руками»

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

Кейс із практики: В одному проекті розробник вважав, що «юніт-тести це для слабаків», поки не зробив рефакторинг обробки знижок, який поламав ціну для преміум-клієнтів. Помітили це лише через тиждень. Якби були юніт-тести баг зловили б одразу.

Замість висновку

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

А, може, у вас є власний пункт № 21?

👍ПодобаєтьсяСподобалось9
До обраногоВ обраному3
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
If you can’t do the little things right, you’ll never be able to do the big things right. So if you want to change the world, start off by making your bed.

www.youtube.com/watch?v=sBAqF00gBGk
Насправді писати «чистий» код — це така сама звичка як заправляти ліжко, чи мити посуд, чи бути охайним. Це не важко — але в той самий час ніби і не обов’язково. Тому дуже багато людей залишають брудний посуд «на потім» чи не заправляють ліжко бо усе одно лягати спати.
Багатовіковий досвід довів що дисципліна має величезне значення, а починається вона саме з таких дрібниць. Самодисципліна та звичка робити речі повністю і правильно — не тільки допомагає одній людині. Вона створює середовище де панує охайність і лад — а отже навіть нові люди, які у нього потрапляють вимушені підтримувати цей рівень. І так само навпаки: якщо більшість людей не переймаються ладом — то матимемо студентську общагу.
Коли я дивлюся код чужого проєкту я у першу чергу звертаю увагу на ці прості речі, яким часто нехтують:
1) Розділяти інтерфейси та імплементацію. Це ніби просто і очевидно. Зробити інтерфейс — хвилини роботи. Особливо якщо використати рефакторинг тули «Extract Interface» чи тим більше AI. Інтерфейсів можна зробити скільки завгодно, їх можна комбінувати і саме інтерфейси — це основа вашої архітектури! Наприклад ви можете віддати read-only інтерфейс усім, хто не має права міняти данні. Можете віддати мінімальну версію інтерфейса де є тільки ключ та ім’я для усяких комбо-боксів чи лукап полів. Можете розділити інтерфейс для звичайного юзера — і інтерфейс для адміністратора ... На мою думку інтерфейсів у коді має бути десь у 2 рази більше, ніж класів. Якщо це не так — то у вас просто ніхто не вміє мислити абстракціями і думати за архітектуру.
2) Не використовувати new. Просто один раз прийміть концепцію Dependency Injection, задекларуйте Lazy екземпляри потрібним вам інтерфейсів(!) і уявіть що вони магічним чином з’являться у потрібний момент.
3) Не використовуйте static і не зловживайте const. У вас вже є попередня концепція DI. Потрібні налаштування — вашому класу нададуть ISettings, потрібні константи — нададуть IConstants, потрібен кеш — нададуть ICache. Те саме стосується статичних класів і методів типу DataTime.Now. Не зав’язуйтесь на стандартні класи — краще зробіть свій IClock (як тут написали). Просто замислитесь — наскільки гнучкою є така система! Різні мови, різні таймзони, різні країни, різні юзери, різні формати? Не проблема — просто налаштовуємо потрібний набір налаштувань та констант у DI контейнері. Хочемо уникнути «Meltdown Vulnerability» коли один юзер прочитає чужі данні з кеша? Просто робимо різні екземпляри кеша під кожного юзера. Ну і ніколи більше не хвилюємось за потонко-безпечність, Dispose і час життя об’єктів — це робота DI контейнера.
4) Не пишіть багато коду. Пам’ятаєте лекції по математиці чи фізиці де за 90 хвилин професор встигає заповнити формулами 4 дошки? А більшість студентів загубилися ще на другій.
Коли на програміста находить натхнення, він занурюється у потік, у голові у нього вже повністю склався алгоритм — він готовий написати код який буде робити усе потрібне: від початку до кінця. Це буде один метод на 500 строк з купою змінних, циклів, гілок і формул. Так мислить звичайний кодер: усе намагається тримати у голові і усе відтворити у коді. Потім, коли прийде час написати юніт тести для такого кода — додадуться ще тести по 1000 строк. Навіть сам автор через пів-року вже у цьому не розбереться. Досвідчений девелопер розуміє що неможливо усе тримати у голові. «Слона треба їсти по шматках». І тут допомагає абстрактне мислення. Нам треба зробити конвертацію валют? Чудово — напишемо клас який перемножить і поверне потрібне значення. Де він візьме курс валют? А хай йому «гноміки принесуть»: а ми просто опишемо у залежностях що він потрібен. Уявіть що кожен маленький «гномік» може робити тільки дуже прости дії. І ваша задача зібрати їх у «бригади», навчити «бригадира» у якому порядку що робити, потім результат роботи бригад віддати іншим, які додадуть свою частку. На цьому побудовано майже увесь інженерний світ: електронні схеми, конвеєри збірки авто, мости, будинки, механічні годинники — менші компоненти збираються у більші — а ті збираються між собою у частини готової конструкції. Навчиться створювати маленькі класи і об’єднувати їх за допомогою інших маленьких класів — і ви вже ніколи не побачите «монстра» на 500+ строк. Ось для натхнення:
www.youtube.com/watch?v=mNOGcRANTOw

DateTime.Now

UtcNow — індастрі стандарт.

Якщо у вас на request thread’і йде обробка важкої аналітики, генерація PDF чи масивний SQL-запит ви автоматично блокуєте цей потік на весь час виконання задачі

Почитайте якось як працює async/await в контролерах

Якби були юніт-тести баг зловили б одразу.

Юніт тести в 99% випадків — звичайне марнування часу розробників, особливо в дефолтних круд помийках. Інтеграційні та e2e тести вирішують.

Хотів би для себе зрозуміти, чому описані речі ви називаєте помилками?

Використання DateTime.Now замість абстракції годинника (IClock)

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

Працюєте на .NET Framework замість LTS версії (.NET 8)

З яких це дев вирішує коли переходити на LTS версію? Хіба це не задача бізнесу виришіти треба йому перехід чи ні? В чому помилка не дуже зрозуміло?

Логування простим текстом замість Serilog/Seq

Якщо у вас маленький проект, навіщо вам Serilog/Seq? Це має сенс тільки якщо проект маштабується і потрібно працювати зі структурованими логами. Але це не є помилкою.

Бізнес-логіка всередині LINQ-запитів до EF
Ви блокуєте тестування цієї логіки.
Ви стаєте заручником того, як EF транслює ваш код у SQL

Ви стаєте заручником того як EF транслює ваш код у SQL в будь-якому випадку. Часто, має сенс залишити саме цю бізнес логіку в Where запиті, або навіть викликати стару добру сторку яка все прокалькулює і буде оптимізована. Чим більше ти працюєш з EF, тим краще треба знати і SQL і сам EF. Винесення логіки як ви описали в окремий сервіс — не вирішує проблему.

God-класи і контролери по 500+ рядків

God-клас або контролер який виконував сто років свою функцію і не потребував переписування — може ще стільки же і прожити. До будь-якого рефаторингу треба підходити з розумом. Якщо вартість підтримки перевищує його переписування — час змінювати. Це впринципі не є помилкою. ASP.NET/Win Forms/WPF проекти часто пишуться так що вся логіка в одному місці. Але не завжди це погано.

Закоментований код у репозиторії

Так нікого цей код не парить просто. Тобто помилка що залишений закоментований код? Це більше про сміття в коді, а не про помилки.

Всі сервіси в одному проєкті без модульності

З яких пір, використання моноліта вважається помилкою? Просто для себе хочу зрозуміти.

Надмірне використання dynamic «бо так простіше»
Замість компіляторної помилки ми отримали runtime-баг на продакшні. Виправили, перейшовши на Typed Models з валідацією.

Тобто у вас немає тестів? Тут помилка не в тому що ви отримали проблему на проді, тому що у вас немає ні тестів ні мануального тестівання. Тут проблема не з dynamic, а тому що у вас біда з тестуванням і ви не відловили це раніше. Якби у вас були тести на ту ж валідацію JSON файла, то вам би не довелось паритись з тим використав розробник dynamic для цієї задачі чи ні.

Тут помилка не в тому що ви отримали проблему на проді, тому що у вас немає ні тестів ні мануального тестівання.

на хрена брать статически типизированный язык, чтобы писать на нем как на JS?

Є декілька сценаріїв коли динамічна типізація дуже гарно виграє і коди виходить доволі лаконічний код в порівнянні з типізованим. Одним із прикладів це генерація XML\JSON документів learn.microsoft.com/...​oducing-the-expandoobject WPF data binding чудово працює як з dynamic\ExpandoObject і можна створювати форми на льоту з нормальною прив’язкою.
Ще приклад використання.

private bool DeepEquals<U>(U x, U y) where U : class =>
    new GenericComparer<U>().Equals(x, y);

private bool DeepEqualsFromObj(object x, object y, Type type)
{
    dynamic a = Convert.ChangeType(x, type);
    dynamic b = Convert.ChangeType(y, type);
    return DeepEquals(a, b);
}
Та ось такий
dynamic fileInfo = jsonSerializer.Deserialize(jsonTextReader);
Далі граєшся з JSON як хочеш. Тут же не проблема з dynamic, а більше з тим що згенеровані JSON/XML файли як у автора статті, можна валідувати за допомогою різних схем (приклад test.schema.json).
{
	"$schema": "https://json.schemastore.org/global.json",
	"$id": "https://www.test.com/test.schema.json",
	"title": "Test File",
	"description": "Schema for validating Test objects",
	"type": "array",
	"default": [],
	"items": [
		{
			"title": "Items",
			"additionalProperties": false,
			"type": "object",
			"properties": {
				"id": {
					"type": "integer",
					"minimum": 1,
					"example": 1
				},
				"createdDateTime": {
					"type": "string",
					"format": "date-time",
					"example": "2022-01-03T16:04:52.8985036+01:00"
				},
				"testNumber": {
					"type": "string",
					"maxLength": 10
				},
			},
			"required": [
				"id",
				"createdDateTime"
			]
		}
	]
}
Та валідуємо за допомогою NJsonSchema. І тепер неважливо типізацію використовуєте ви чи ні, ви перевіряєте чи файл JSON згенерований правильно чи ні.
var directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var schemaDefFilePath = $"{directoryName}\\test.schema.json";
var data = File.ReadAllText($"{directoryName}\\test_data.json");
object account = JsonConvert.DeserializeObject<object>(data);

var (isValid, errors) = await IsDataValidAsync(account, schemaDefFilePath);

if (isValid)
{
    Console.WriteLine("File is valid");
}

public static async Task<(bool IsValid, ICollection<ValidationError> Errors)> IsDataValidAsync(object data, string jsonSchemaFilePath)
{
    var json = ToString(data);
    var errors = (await JsonSchema.FromFileAsync(jsonSchemaFilePath)).Validate(json);

    return (errors.IsNullOrEmptyCollection(), errors);
}

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

очевидно, что в статье речь идет о других кейсах

Так це і є кейс автора. Вони змінили структуру файла і ніде не було її перевірки, а крайнім зробили розробника який типу використав dynamic. Там тести повинні були бути на структуру файла і валідувати її потрібно було.

Кейс із практики: Розробник використав dynamic для роботи з JSON, аби «не писати зайві класи». Все працювало, поки структура JSON не змінилась. Замість компіляторної помилки ми отримали runtime-баг на продакшні.

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

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

через Service Locator (ServiceProvider.GetService)

Какой еще валидный способ потребления scoped-сервисов из singleton-сервисов? Например ресолвить dbcontext из backgroundservice?

В Singleton сервісі (наприклад в мене це бекграунд сервіс)

IServiceProvider serviceProvider — отримуємо через DI

using var scope = serviceProvider.CreateScope(); // Створюємо скоуп
var notifyService = scope.ServiceProvider.GetRequiredService(); // отримуємо наш scoped-сервіс

var notifyService = scope.ServiceProvider.GetRequiredService(); /

Так, но эта строчка и есть пример сервис локатора, в этом и смысл коммента, что не всегда это антипаттерн, а иногда необходимость

Використання DateTime.Now замість абстракції годинника (IClock)

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

Якщо у вас на request thread’і йде обробка важкої аналітики, генерація PDF чи масивний SQL-запит ви автоматично блокуєте цей потік на весь час виконання задачі

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

Працюєте на .NET Framework замість LTS версії (.NET 8)?

якщо бізнесу ок працювати на старій версії і нема ресурсів на переписування то чому ні ?

God-класи і контролери по 500+ рядків

у чому проблема мати всю логіку в одному місці і не робити купу go to implementation щоб дізнатися як працює роут ?

Підписатись на коментарі