Як оновити бібліотеку і потонути в цій задачі. Оновлення Roslyn та PVS-Studio 7.34

pic

Вступ

Чи траплялося вам, що через баг з часовими зонами зламався ваш додаток у продакшн? Або, можливо, ви стикалися з дивним питанням зміни літнього часу (DST), яке зламало функцію планування? Робота з датою та часом у розробці програмного забезпечення може бути справжнім кошмаром.

У .NET DateTime та DateTimeOffset надають базову функціональність, але часто виникають проблеми при роботі з такими складними аспектами, як часові зони, літній час і точні обчислення. Тут на допомогу приходить NodaTime — потужна і інтуїтивно зрозуміла бібліотека, яка значно спрощує роботу з датою та часом і зменшує ймовірність помилок.

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

Проблеми з DateTime: чому це болісно

Якщо ви колись стикалися з DateTime, то вам знайомі ці труднощі:

  1. Неясність: це UTC? Локальний час? Без вказівки? Хто знає!
  2. Плутанина з часовими зонамиDateTime не дуже добре працює з часовими зонами.
  3. Хаос з літнім часом — Перехід через межу літнього часу, і все ламається.
  4. Проблеми з точністю: нано секундна точність? Мабуть, ні.

Хоча DateTimeOffset допомагає з UTC зсувами, він все ж не вирішує проблему з часовими зонами.

Реальна проблема

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

  • Надійно зберігати часові мітки.
  • Перетворювати часи зустрічей у часові зони кожного учасника.
  • Автоматично враховувати зміни літнього часу.

Використовуючи тільки DateTime, ви, ймовірно, отримаєте здивованих користувачів, які пропустять зустрічі.

NodaTime на допомогу!

Як почати з NodaTime

Встановлення

Щоб почати використовувати NodaTime, інсталюйте пакет NodaTime через NuGet:

Install-Package NodaTime

Або, якщо ви використовуєте .NET CLI:

dotnet add package NodaTime

Після встановлення ви зможете почати використовувати її потужні API.

Основні функції NodaTime

1. Чітке розділення понять дати та часу

Замість одного перевантаженого типу DateTime, NodaTime надає окремі типи:

  • Instant — Фіксований момент часу (завжди UTC).
  • LocalDateTime — Дата і час без інформації про часову зону.
  • ZonedDateTime — Дата і час з часовою зоною.
  • OffsetDateTime — Дата і час з UTC зсувом.
  • Duration і Period — Для точних обчислень часу.

2. Робота з моментами

Потрібна мітка часу UTC? Використовуйте Instant.

using NodaTime;
var now = SystemClock.Instance.GetCurrentInstant();  
Console.WriteLine($"Current Instant: {now}");

Все чітко. Без неясностей.

3. Перетворення між часовими зонами

Робота з часовими зонами — це сильна сторона NodaTime:

using NodaTime;  
using NodaTime.TimeZones;  
var zone = DateTimeZoneProviders.Tzdb["America/New_York"];  
var zonedDateTime = now.InZone(zone);  
Console.WriteLine($"New York Time: {zonedDateTime}");

4. Обробка літнього часу

Припустимо, користувач планує зустріч у червні та ще одну в грудні. NodaTime автоматично враховує перехід на літній час:

var summerTime = new LocalDateTime(2024, 6, 1, 12, 0).InZoneStrictly(zone);  
var winterTime = new LocalDateTime(2024, 12, 1, 12, 0).InZoneStrictly(zone);  
Console.WriteLine($"Summer Time: {summerTime}");  
Console.WriteLine($"Winter Time: {winterTime}");

Ніяких ручних налаштувань. Ніяких головних болів.

5. Аріфметика та обчислення

NodaTime робить обчислення часу легкими:

Duration duration = Duration.FromHours(5);  
Instant later = now + duration;  
Console.WriteLine($"5 Hours Later: {later}");

Для календарних обчислень:

Period period = Period.FromYears(1);  
LocalDate nextYear = new LocalDate(2024, 1, 1).Plus(period);  
Console.WriteLine($"Next Year: {nextYear}");

Використання NodaTime в реальних умовах

1. Планування подій

Забезпечте, щоб зустрічі/події відбувалися у правильний час для користувачів по всьому світу.

2. Логування та аудиту

Використовуйте Instant для точних, надійних UTC міток часу у журналах.

3. Фінансові транзакції

Банківські та платіжні системи вимагають точних обчислень часу — Period гарантує правильність.

Міграція з DateTime на NodaTime

Перехід на NodaTime? Почніть з перетворень:

DateTime dt = DateTime.UtcNow;  
Instant instant = Instant.FromDateTimeUtc(dt);  
ZonedDateTime zoned = instant.InZone(DateTimeZoneProviders.Tzdb["Europe/London"]);  
Console.WriteLine(zoned);

Просто. Поступова міграція без зламу існуючого додатку.

Резюме та основні висновки

  • NodaTime усуває типові проблеми з DateTime.
  • Використовуйте Instant для надійних, UTC-базованих міток часу.
  • ZonedDateTime гарантує правильну обробку часових зон.
  • Використовуйте Period та Duration для точних обчислень.
  • Міграція проста з вбудованими перетвореннями.

Завдяки NodaTime ви уникнете кошмарів з часовими зонами, усунете неясності і забезпечите надійність вашого додатку на .NET.

🚀 Чи стикалися ви з проблемами при налагодженні питань з часовими зонами? Поділіться своїм досвідом у коментарях!
Оновити бібліотеки Roslyn та MSBuild, а також все, що вони потребують;

  1. Мати справу з деякими проблемами залежностей;

  2. Оновити наші BuildTools до останньої версії, зберігаючи зворотну сумісність з попередніми версіями тут і там;

  3. Виправити проблеми, що виникають під час оновлення (і вони безумовно з'являються, інакше цієї статті не було б).

Це дуже спрощена схема, і буде краще, якщо ви прочитаєте статтю за посиланням вище. У нас також є статті про підтримку Visual Studio 2019 та Visual Studio 2022, які також охоплюють ці проблеми. Після того, як ви прочитаєте це все, ви, ймовірно, подумаєте: "Мммм, смачна спадщина."

pic

Звісно, спадщина… Тепер у нас є рішення для MSBuild — це MSBuildLocator. Однак у нього є й свої обмеження: є окремі збірки для .NET Framework та .NET Core. Версія для .NET Framework може шукати та реєструвати MSBuild лише для .NET Framework, а версія для .NET Core може робити те ж саме лише для .NET Core. Думаю, це пов'язано з тим, що MSBuild також поділяється на MSBuild для .NET Framework та MSBuild для .NET Core.

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

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

Вийшов .NET 9 RC 1, і ми почали реалізовувати підтримку. Спочатку ми оновили бібліотеки та наші BuildTools. Під час оновлення ми помітили дві нові папки у пакеті Microsoft.CodeAnalysis (Roslyn): BuildHost-net472 та BuildHost-netcore. Спочатку ми зраділи, думаючи, що проблем буде менше. Але це не було так.

Оновлення бібліотек і BuildTools пройшло досить швидко, завдяки нашому попередньому досвіду. Після цього розробники запускають локальний набір тестів, щоб переконатися, що аналіз правильний. Ці тести включають велику кількість відкритих проєктів з відкритим кодом, на яких працює аналітизатор. Після того, як ми запустили тести, виникли проблеми: у деяких фрагментах коду аналітизатор почав повідомляти (через внутрішні логи) про помилки компіляції у вже скомпільованому коді, і тут і там почали виникати винятки. Ми почали виправляти і змогли довести локальні тести до повного завершення.

Наступний етап — це запуск оновленої версії на тестах, які запускають аналітизатор у шаблонних проєктах для різних версій Visual Studio та .NET. Ці тести виконуються в контейнерах з різними середовищами. Так от, ми бачимо, що аналіз ламається на машинах, де встановлені лише версії VS 2010, 2012, 2013, 2015. Він також не проходить для проєктів .NET Framework у стилі SDK з VS 2017, 2019, 2022, але без встановленого .NET SDK.

Чому попередні тести були успішними і не виявили проблем? Вони виконувалися локально на машині розробника, яка фактично має всі версії Visual Studio та багато версій .NET SDK.

Ми розібралися і знайшли проблему: оновлений Roslyn все ускладнив.

pic

Раніше Roslyn очікував, що користувачі використовуватимуть MSBuildLocator або подібне. У нас були власні BuildTools та власні інструментальні набори, які використовував Roslyn. Цей підхід дозволяв нам підтримувати C# проєкти навіть для Visual Studio 2010. Єдине, що залишалося складним — це підтримка нового .NET. Як я вже згадував, Microsoft.CodeAnalysis тепер включає дві додаткові папки. Roslyn перейшов на нову архітектуру з серверами побудови для незалежного пошуку та створення проєктів на етапі розробки. Тепер він використовує два BuildHost: один для .NET Framework і один для .NET Core. Залежно від типу проєкту, Roslyn викликає відповідний процес BuildHost для побудови проєкту.
MSBuildLocator використовується для пошуку MSBuild. І тут є кілька проблем.

Перша проблема

BuildHost для .NET Core вимагає наявності хоча б .NET 6 Runtime для запуску. Оскільки PVS-Studio використовує Roslyn, користувачі повинні мати встановлений .NET 6+ Runtime. Хоча це вимога зрозуміла і прийнятна, деякі можуть запитати: "Чому не випустити BuildHost для .NET Core як самодостатній додаток або навіть використовувати NativeAOT?" На жаль, це значно збільшить розмір файлу .exe. Однак основна проблема полягає в іншому: під капотом використовується Assembly.Load для реєстрації MSBuild, і це просто не працює в таких додатках.

Друга проблема

MSBuildLocator для .NET Framework може шукати тільки MSBuild 15, 16, 17 (Visual Studio 2017, 2019, 2022). Тому, якщо у користувача є досить старий проєкт і він використовує Visual Studio 2015, Roslyn не зможе знайти відповідний MSBuild. Навіть якщо проєкт повністю зібраний на системі, Roslyn просто не буде працювати. Ми повідомили про цю проблему на GitHub. Якщо коротко, розробники не надають цьому пріоритет, вказуючи, що VS 2015 і більш ранні версії просто застаріли. Однак з'ясувалося, що якщо у користувача є .NET SDK, Roslyn починає використовувати план резервного копіювання. Якщо у вас є старий проєкт .NET Framework, Roslyn спробує використовувати BuildHost для .NET Core. Більшість часу це працює нормально, але проблеми можуть виникнути, якщо є щось, що не підтримується MSBuild для .NET Core. Ви побачите це в описі третьої проблеми.

Все це призводить нас до висновку, що якщо ми хочемо зберегти підтримку старих проєктів, нам потрібно постачати .NET SDK. Для збереження цілісності продукту ми вирішили поставляти .NET 9 SDK з ним для Windows, як ми вже робимо для Linux і macOS. Ці операційні системи вимагають .NET SDK тієї версії, для якої він був побудований. Чому нам потрібен .NET 9 SDK для Linux і macOS — це інша розмова, але повірте, нам це потрібно.

Третя проблема

Для будь-яких проєктів у стилі SDK Roslyn автоматично використовує BuildHost для .NET Core, і не має значення, чи може MSBuild для .NET Core насправді зібрати проєкт. MSBuild для .NET Core не підтримує всі функції, які підтримує MSBuild для .NET Framework. Наприклад, MSBuild для .NET Core не підтримує COMReference. Ми також створили проблему для цього. Як ми це обробляємо на нашій стороні? Ну, ми цього не робимо. Такі рідкісні помилки призводять до невеликих недоліків у семантичній моделі, отриманій з Roslyn. Це ніяк не впливає на якість аналізу.

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

Декілька думок

Отже, який результат від вдосконалень Microsoft в Roslyn? Більш складний процес розповсюдження для аналітизатора, що вимагає від користувачів встановлення непотрібних .NET SDK, і десятки днів, витрачених на те, щоб усе працювало так, як повинно.

Для чого ж ця стаття? Я хотів поділитися маленькою історією про те, як оновлення та покращення інструментів інколи можуть ускладнити життя, замість того, щоб спростити його. Насправді, багато з цього можна було б уникнути, якби Roslyn більш обережно вибирав BuildHost. Я також хотів дати стислий пояснення для клієнтів, які запитують: "Чому так сталося?" Для цього я написав коротку версію.

Якщо вам здалося, що я надто критично ставлюсь до Roslyn, ви не помилились — хоча і лише трохи, і здебільшого тому, що я мав багато додаткової роботи 🙂 Я все одно вважаю, що Roslyn — це чудовий інструмент, який робить перевірку та аналіз коду значно легшими.

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

Дякую, що прочитали 🙂

Перекладено з: How to update library and get swamped with this task. Roslyn and PVS-Studio 7.34 update

Leave a Reply

Your email address will not be published. Required fields are marked *