.NET Разработчик
6.51K subscribers
427 photos
2 videos
14 files
2.04K links
Дневник сертифицированного .NET разработчика.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 2031. #Оффтоп
«У Нас Кончились Столбцы» — Лучшая Худшая Кодовая База. Окончание

Начало
Продолжение

Баг в доставке
Одна особенно неприятная ошибка всплывала раз в несколько месяцев. После того, как мы отправляли товары, в очереди доставки застревали заказы одновременно и как отправленные, и как не отправленные. Я пробовал ряд обходных путей, чтобы исправить это.

Попутно я узнал, как мыслит Гилфойл. Приложение доставки извлекало всю БД, фильтровало по дате и сохраняло все заказы после даты запуска приложения. Оно полагалось на SOAP-сервис. Нет, сервис был чистой функцией. Все побочные эффекты были на клиенте. Там я обнаружил иерархию в 120 классов с различными методами и 10-уровневое наследование. Единственная проблема… ВСЕ МЕТОДЫ БЫЛИ ПУСТЫМИ.

В конце концов я узнал, что это было необходимо для создания структуры, к которой он мог бы затем применить рефлексию. Рефлексия позволила бы ему создать строку с разделителями (структура которой управлялась базой данных), которую он мог бы отправлять через сокет. Оказывается, всё это в итоге отправлялось в Kewill (сервис, который общался с перевозчиками). Почему происходила ошибка? Kewill повторно использовал 9-значные числа каждый месяц, а кто-то отключил задание cron, которое удаляло старые заказы.

Прекрасный беспорядок
Ещё много можно рассказать об этой кодовой базе. Например, о команде супер-старших разработчиков, которые переписывали всё это, не делая коммитов кода в течение 5 лет. Или о консультантах Red Hat, создавших одну БД, чтобы управлять всеми. Или об улучшении Джастином страницы поиска продавцов. Она была точкой входа во всё приложение. Каждый представитель службы поддержки клиентов звонил продавцу и вводил либо его ID, либо название, чтобы найти его информацию. Это приводило на огромную страницу с любой информацией, которая вам могла бы понадобиться, и всеми ссылками, которые вы могли бы захотеть посетить. Но она была чертовски медленной.

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

Два способа разделения
Почему Джастин смог это сделать? Потому что у проекта не было генерального плана, формата API, документации и т.п. Система была полным и абсолютным хаосом. Никто не мог его исправить, поэтому никто и не пытался. Что мы делали?

Это монолитное приложение из-за чистой необходимости превратилось в экосистему из хороших маленьких приложений по краям. Каждый кому поручали улучшить какую-то часть приложения, неизбежно отказывался от распутывания паутины и создавал своё уютное микро-приложение.

Работать так было на удивление приятно. Исчезли проблемы дублирования кода, согласованности и расширяемости. Код как можно меньше затрагивал окружающую его область и был легко заменяемым. Он был слабо связан, просто потому что связывать его было сложнее.

Итого
За всю мою карьеру с тех пор мне не доводилось работать в такой замечательно уродливой кодовой базе. Возможно, тот код был давно заброшен старшими разработчиками, а дописывали его стажеры и джуны. Или, возможно, это было потому, что не было никакой прослойки между разработчиками и пользователями, никакого сбора требований, никаких ТЗ. Просто вы стоите за столом представителя службы поддержки клиентов и спрашиваете его, как улучшить его жизнь.

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

Источник: https://jimmyhmiller.github.io/ugliest-beautiful-codebase
Автор оригинала: Jimmy Miller
👍17
День 2032. #ЧтоНовенького #CSharp13
Флаги функций с поддержкой тримминга
Два новых атрибута позволяют определять флаги функций, которые можно использовать для включения/отключения областей функциональности, а также для автоматического включения/отключения функций при тримминге или AOT-компиляции.

FeatureSwitchDefinitionAttribute
Атрибут FeatureSwitchDefinition может быть использован, чтобы флаг функции после сборки определялся как константа.
public class Feature
{
[FeatureSwitchDefinition("Feature.IsSupported")]
internal static bool IsSupported =>
AppContext.TryGetSwitch("Feature.IsSupported", out bool enabled)
? enabled
: true;

internal static void Implementation() => …;
}

Здесь мы извлекаем значение IsSupported из конфигурации сборки. В данном случае оно определено в файле проекта (.csproj):
<ItemGroup>
<RuntimeHostConfigurationOption Include="Feature.IsSupported"
Value="false" Trim="true" />
</ItemGroup>

Мы добавляем RuntimeHostConfigurationOption с именем нужной функции (соответствующим параметру атрибута) и булевым значением, включена она или нет.

При сборке с включённым триммингом недосягаемый код, находящийся под флагом, удаляется. Когда приложение собирается с такой настройкой в файле проекта, Feature.IsSupported расценивается как константа false и Feature.Implementation удаляется из сборки.

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

FeatureGuardAttribute
Атрибут FeatureGuard можно использовать для свойства флага функции в качестве защитной конструкции для кода, аннотированного атрибутами RequiresUnreferencedCode, RequiresAssemblyFiles или RequiresDynamicCode:
public class Feature
{
[FeatureGuard(typeof(RequiresDynamicCodeAttribute))]
internal static bool IsSupported =>
RuntimeFeature.IsDynamicCodeSupported;

[RequiresDynamicCode("Feature requires dynamic code support.")]
internal static void DynamicImplementation()
=> …; // Использует dynamic
}

Здесь RuntimeFeature определяет API, которые доступны в среде выполнения. В данном случае свойство IsDynamicCodeSupported показывает, поддерживается ли в среде выполнения динамический код.
Поскольку свойство IsSupported возвращает false, когда динамический код не поддерживается, его можно использовать в качестве защиты для методов, которым требуется динамический код во время выполнения.
if (Feature.IsSupported)
Feature.DynamicImplementation();

Атрибут FeatureGuard сообщает об этом анализатору тримминга и инструментам тримминга. Поэтому, если мы в файле проекта укажем
<PublishAot>true</PublishAot>

то Feature.DynamicImplementation будет удалён при AOT-публикации.

Источник: https://github.com/dotnet/core/blob/main/release-notes/9.0/preview/preview4/runtime.md
6👍7
День 2033. #BlazorBasics
Основы Blazor. (Ре-)Рендеринг Компонентов. Начало

Рендеринг компонентов — основная механика приложений Blazor, превращающая компоненты C# в HTML и CSS. Методы жизненного цикла позволяют нам выполнять пользовательский код синхронно или асинхронно.

Приложения Blazor состоят из дерева компонентов, которые создают интерактивный UI. Рендеринг начинается на вершине дерева и переходит к дочерним элементам каждого компонента. Так родительский компонент решает, какие дочерние компоненты создавать.

Методы жизненного цикла компонентов Blazor
Процесс рендеринга не может быть прерван; в противном случае UI бы «завис». Поэтому компоненты Blazor используют методы жизненного цикла, чтобы позволить выполнять пользовательский код в течение их жизненного цикла.

Компонент Blazor впервые отрисовывается после создания его экземпляра при включении в качестве дочернего компонента см. диаграмму ниже.

Метод SetParametersAsync
Это первый метод обратного вызова в жизненном цикле компонента. Он получает параметры, предоставленные родительским компонентом. Переопределяя SetParametersAsync, мы можем использовать пользовательский код для изменения значений, предоставляемых родительским компонентом. По умолчанию параметры будут установлены в свойства, декорированные атрибутом Parameter. Если мы хотим использовать параметры компонента по-другому, мы можем использовать пользовательский код и переопределить метод SetParametersAsync, чтобы определить желаемое поведение.
@code {
// параметры компонента

public override async Task
SetParametersAsync(ParameterView pv)
{
// пользовательский код

await base.SetParametersAsync(pv);
}
}


Методы OnInitialized и OnInitializedAsync
После того, как компонент Blazor завершает выполнение метода SetParametersAsync и, следовательно, все параметры из родительского компонента установлены, вызываются методы жизненного цикла OnInitialized и OnInitializedAsync. Это идеальное место для загрузки данных из БД и назначения их свойствам, используемым в шаблоне компонента.
@code {
private string? message;

[Parameter]
public string Name { get; set; }

protected override void OnInitialized()
{
message = $"Привет, {Name}!";
}
}

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

Окончание следует…

Источник:
https://www.telerik.com/blogs/blazor-basics-blazor-component-re-rendering
1👍8
Родительский компонент запускает создание дочернего компонента. Метод жизненного цикла SetParametersAsync и методы OnInitialized(Async) запускаются только для первой отрисовки. Метод OnParametersSet(Async) запускается, когда родительский компонент предоставляет другие значения параметров своему дочернему компоненту. Метод StateHasChanged может использоваться разработчиком для запуска повторной отрисовки компонента вручную.
👍8
День 2034. #BlazorBasics
Основы Blazor. (Ре-)Рендеринг Компонентов. Окончание

Начало

Методы OnParametersSet и OnParametersSetAsync
SetParametersAsync вызывается только при первом рендеринге компонента Blazor. А методы OnParametersSet и OnParametersSetAsync запускаются всякий раз, когда родительский компонент повторно отрисовывается, предоставляя другие значения для параметров дочернего компонента.

Переопределение этих методов предоставляет нам доступ к обновлению значений в компоненте на основе нового набора параметров.
@code {
private string? message;

[Parameter]
public string Name { get; set; }

protected override void OnParametersSet()
{
message = $"Привет, {Name}!";
}
}

После завершения методов OnParametersSet или OnParametersSetAsync запускается автоматическая повторная отрисовка компонента.

Метод StateHasChanged
Мы можем вызвать метод StateHasChanged класса ComponentBase, чтобы сообщить компоненту Blazor об изменении состояния компонента. Он запустит повторную отрисовку компонента, которая вызовет все применимые методы жизненного цикла в процессе отрисовки.

Не следует вызывать метод StateHasChanged в следующих сценариях:
- Обработка событий. Неважно, являются ли события синхронными или асинхронными. Класс ComponentBase запускает отрисовку для большинства обработчиков событий.
- Реализация методов жизненного цикла, таких как OnParametersSetAsync или OnInitialized (синхронных или асинхронных). Класс ComponentBase запускает отрисовку для большинства событий жизненного цикла.

Как правило, обычно вам не нужно вызывать метод StateHasChanged вручную. Однако, если вы ожидаете, что UI покажет обновленное значение, а этого не происходит, велики шансы, что вызов StateHasChanged в правильном месте исправит вашу проблему.

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

Асинхронные и синхронные методы жизненного цикла
Для синхронного кода родительские компоненты всегда визуализируются перед дочерними компонентами. Асинхронный код более сложныq. При использовании асинхронных методов жизненного цикла порядок завершения родительского и дочернего компонента не является детерминированным, поскольку он зависит от кода инициализации. Однако Blazor обеспечивает доступность и корректное обновление параметров компонента при визуализации дочернего компонента.

Используйте синхронные методы жизненного цикла, когда не нужно ожидать асинхронных операций. Когда же нужно вызвать БД или использовать другие асинхронные операции, используйте асинхронные методы.

Итого
Компоненты Blazor используют методы жизненного цикла для предотвращения блокировки пользовательского интерфейса и позволяют разработчикам выполнять пользовательский код:
- SetParametersAsync позволяет выполнять пользовательский код, обрабатывающий параметры, предоставленные родительским компонентом, только для первого рендеринга компонента.
- OnInitialized и OnInitializedAsync часто используются для загрузки данных из БД или для инициализации свойств, отображаемых как часть шаблона компонента.
- OnParametersSet и OnParametersSetAsync вызываются, когда родительский компонент повторно отрисовывается, предоставляя другой параметр дочернему компоненту.
- Если UI не отображает ожидаемое значение на экране, вы можете вызвать StateHasChanged в соответствующем месте кода.

Источник: https://www.telerik.com/blogs/blazor-basics-blazor-component-re-rendering
👍8
День 2035. #ЧтоНовенького
Прекращается Поддержка BinaryFormatter
Начиная с .NET 9, BinaryFormatter больше не будет включён в среду выполнения. Его API все ещё присутствует, но реализация всегда будет выдавать исключение PlatformNotSupportedException, независимо от типа проекта. Установка существующего сейчас флага обратной совместимости больше недостаточна для использования BinaryFormatter.

Есть два варианта решения:
1. Настоятельно рекомендуется отказаться от BinaryFormatter из-за связанных ним рисков безопасности и выбрать другой сериализатор.

2. Продолжить использовать BinaryFormatter. Для этого нужно добавить неподдерживаемый пакет совместимости System.Runtime.Serialization.Formatters, который заменяет реализацию, вызывающую исключение в среде выполнения, но подвержен описанным ниже уязвимостям.

Проблема с BinaryFormatter
Дело в том, что любой десериализатор, позволяющий иметь во входных данных информацию о создаваемых объектах, является проблемой безопасности. BinaryFormatter, включённый в первый выпуск .NET Framework в 2002 году, является таким десериализатором. Из-за известных рисков эта функциональность была исключена из .NET Core 1.0. Но в отсутствие чёткого пути миграции на что-то более безопасное, спрос среди клиентов привёл к тому, что BinaryFormatter был обратно включен в .NET Core 2.0. С тех пор команда .NET шла по пути к его удалению, постепенно отключая его по умолчанию в разных типах проектов, но позволяя потребителям включать его с помощью флагов, если было необходимо для обратной совместимости.

Миграция
Выбор другого сериализатора возможен, только если вы контролируете как производителя, так и потребителя сериализованных данных. Если это ваш случай, то в зависимости от ваших потребностей рекомендуются следующие сериализаторы:
- System.Text.Json (JSON)
- DataContractSerializer (XML)
- MessagePack (binary)
- protobuf-net (binary)

Если вы не контролируете производителя, либо десериализуете данные, которые были сериализованы ранее и сохранены (и нет возможности их преобразовать) вы можете использовать новый API для чтения данных BinaryFormatter без выполнения универсальной и уязвимой десериализации с помощью NuGet-пакета System.Formats.Nrbf.

Источник: https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/
👍9
День 2036. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 20. Невозможно оптимизировать все желаемые атрибуты качества. Начало
«Приложение, которое я хотел бы получить, не должно иметь ошибок, не должно использовать много памяти или замедлять компьютер, должно быть полностью безопасным, мгновенно реагировать на каждую мою команду и быть абсолютно надёжным, работать на любом устройстве, мгновенно загружаться и быть бесплатным.»

Похоже на сказочное приложение. Желания клиента неразумны. Невозможно объединить в одном приложении все лучшие достижения и передовые возможности. Различные качественные характеристики неизбежно вступают в конфликт друг с другом: улучшение одной часто приводит к ухудшению другой. Поэтому важной частью анализа требований является определение наиболее важных характеристик, чтобы разработчики могли учесть это.

Нефункциональные требования не реализуются напрямую. Они служат лишь источником производной функциональности, архитектурных решений или подходов к проектированию и реализации. Некоторые нефункциональные требования ограничивают выбор вариантов, доступных дизайнеру или разработчику. Например, из-за требования функциональной совместимости, возможно, придётся использовать только стандартные API.

Ниже перечислены атрибуты качества, которые каждая команда разработчиков ПО должна учитывать, изучая значение понятия качества для их продукта:
1. Доступность
Смогу ли я использовать систему, когда и где мне нужно?
2. Соответствие стандартам
Соответствует ли система всем применимым стандартам в отношении функциональности, безопасности, сертификации и т.п.?
3. Эффективность
Экономно ли система использует ресурсы компьютера?
4. Возможность установки
Смогу ли я легко установить, удалить и переустановить систему и её обновления?
5. Целостность
Имеет ли система защиту от неточных данных, от их повреждения и потери?
6. Совместимость
Достаточно ли хорошо система взаимодействует с окружением для обмена данными и услугами?
7. Сопровождаемость
Смогут ли разработчики легко менять, исправлять и улучшать систему?
8. Производительность
Достаточно ли быстро система реагирует на действия пользователя и внешние события?
9. Переносимость
Можно ли легко перенести систему на другие платформы?
10. Надёжность
Отсутствуют ли сбои в работе системы?
11. Повторное использование
Смогут ли разработчики повторно использовать части системы в других продуктах?
12. Устойчивость
Реагирует ли система на ошибочные входные данные и непредвиденные условия работы?
13. Безопасность
Защищает ли система пользователей от вреда и имущество от повреждения?
14. Масштабируемость
Может ли система легко расширяться для обслуживания большего количества пользователей, данных или транзакций?
15. Защищённость
Защищена ли система от атак вредоносных программ, злоумышленников, неавторизованных пользователей и кражи данных?
16. Удобство использования
Смогут ли пользователи быстро научиться работать с системой и эффективно выполнять свои задачи?
17. Проверяемость
Смогут ли тестировщики определить, насколько правильно реализовано ПО?

По аналогии с функциональностью разработчики должны сбалансировать ценность некоего качества и стоимость его достижения. Например, всем хотелось бы, чтобы ПО всегда было доступно для использования, но достижение этого может стоить очень дорого. Если есть требование нулевого времени простоя, вариант – добавить резервную систему. При обновлении сначала обновляется резервная система, тестируется и переключается в рабочий режим, а бывшая основная система обновляется после. Иметь две независимые системы — дорого, но это дешевле, чем останавливать производство, если система выходит из строя.

Окончание следует…

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2037. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 20. Невозможно оптимизировать все желаемые атрибуты качества. Окончание

Начало

Определение атрибутов качества
Разработчики должны знать, какие атрибуты качества наиболее важны. Недостаточно просто сказать: «Система должна быть надёжной» или «Система должна быть удобной для пользователя». На этапе выявления требований бизнес-аналитик должен выяснить, что именно заинтересованные стороны имеют в виду под надёжностью или удобством. По каким характеристикам можно судить об этом? Какие примеры ненадёжности или неудобства можно привести?

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

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

Улучшение одних атрибутов качества может ухудшить другие. Вот несколько примеров конфликтов:
- Многофакторная аутентификация более надёжна, но снижает удобство использования из-за дополнительных шагов и, возможно, задействованных устройств.
- Продукт, предназначенный для повторного использования, может оказаться менее эффективным, чем если бы код функциональности был оптимизирован для одного приложения.
- Оптимизация производительности может ухудшить переносимость системы.
- Внедрение решений, упрощающих обучение новых пользователей, может сделать систему неудобной для экспертов.

Но некоторые пары атрибутов качества создают эффект синергии. Проектирование системы с учетом высокой надёжности способствует улучшению:
- доступности (если система не даёт сбоев, то остаётся доступной для использования);
- целостности (снижается риск потери или повреждения данных из-за сбоя);
- устойчивости (меньше вероятность отказа продукта);
- безопасности (если механизмы безопасности продукта работают надёжно, то никто не пострадает).

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

Архитектура и атрибуты качества
Поскольку необходимость компромиссов — частое явление, архитекторы должны знать, какие атрибуты наиболее важны, иначе не смогут принять решения, ведущие к желаемым результатам.

Возвращение на поздних стадиях разработки или после выпуска и переделка архитектуры системы в рамках исправления недостатков качества — дорогое удовольствие. Поэтапное построение систем при отсутствии предварительного знания наиболее важных целей в области качества может привести к проблемам, которые трудно исправить. Как это часто бывает с программными проектами, потратив чуть больше времени на то, чтобы лучше понять цели в области качества, можно прийти к менее дорогим и более надёжным решениям.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍4
День 2038. #ЗаметкиНаПолях
Удалять или не Удалять
Мягкое удаление — это метод, используемый для того, чтобы пометить запись как удалённую, не удаляя её на самом деле. Во многих случаях вы будете использовать что-то вроде столбца IsDeleted, который устанавливается в true, когда запись удалена. Таким образом, запись на самом деле не будет удалена из БД, но будет помечена как удалённая.
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}

Чаще всего «удалённые» записи не нужны, поэтому их отфильтровывают:
public async Task<IEnumerable<User>> GetUsers()
{
return await _context.Users
.Where(u => !u.IsDeleted).ToArrayAsync();
}

Вместо того, чтоб добавлять Where(…) к каждому запросу, можно использовать глобальный фильтр в Entity Framework:
protected override void 
OnModelCreating(ModelBuilder mb)
{
mb.Entity<User>().HasQueryFilter(u => !u.IsDeleted);
}

И в каждом запросе из таблицы Users удалённые записи будут отфильтрованы. Конечно, вы можете изменить это, если хотите:
public async Task<IEnumerable<User>> GetAllUsers()
{
return await _context.Users
.IgnoreQueryFilters().ToArrayAsync();
}


Подводные камни
1. Нужны ли удалённые записи?
Во многих случаях нет. А если они не нужны, зачем хранить их в базе? Они просто занимают место и замедляют запросы. То, что не нужно, можно удалить. При составлении отчётов может потребоваться просмотр удалённых записей, но как часто создаются отчёты?

2. Глобальные фильтры запросов
Это и хорошо, и плохо. Когда вы смотрите на запрос, он не говорит вам всей правды! Вы должны знать, что к запросу применяется глобальный фильтр. Это может сбивать с толку и приводить к ошибкам.

3. Производительность
Если в таблице много записей, запрос должен будет отфильтровывать удалённые каждый раз. Можно добавить индекс, но это повлияет на производительность вставки и обновления.

Альтернативы
1. Архивная таблица или схема
Так вы сможете сохранить удалённые записи, но они не будут находиться в той же таблице, что и активные. Таким образом можно поддерживать чистоту и быстроту таблицы активных записей. Также можно создать представление, которое объединит активные и архивные записи, если нужно увидеть их вместе в определённых обстоятельствах.

2. Архивная база данных
Так вы сможете поддерживать чистоту и быстроту БД активных записей. Это сложнее в настройке, но может быть хорошим решением, если у вас много удалённых записей. А если данных много, это может сэкономить деньги, если вы используете управляемые экземпляры в Azure или AWS. Например, Azure предлагает уровень архива, где даже терабайты данных стоят намного меньше, чем в активной БД (если вы не читаете оттуда много данных и вам не нужен «живой» архив).

Источник: https://steven-giesel.com/blogPost/a807373c-dcc6-42f9-995f-e69dcea1cd47/to-soft-delete-or-not-to-soft-delete
👍17
День 2039. #Оффтоп
Баг в Течение Часа в Год
Было 8 ноября 2021, я работал сортировщиком ошибок в команде Google Docs. День начался как любой другой. Я заварил кофе и начал просматривать отчёты об ошибках за предыдущий день. Кое-что привлекло моё внимание.

Было необычно много сообщений об ошибках, и все они говорили об одном и том же. Пользователь создал ответ или новый комментарий в документе, но его метка времени на странице говорила, что он был создан «завтра». Быстро выявилась закономерность. Все ошибки появлялись:
- У пользователей в тихоокеанском часовом поясе (PT)
- Между 23:00 и 23:59 (PT) 7 ноября
Я бы посчитал это совпадением, но перевод на зимнее время случился в 2:00 (PT) 7 ноября!

Расследование
Баг меня заинтересовал, поэтому я решил заняться его устранением самостоятельно. Не потребовалось много времени, чтобы прийти к выводу, что ошибка, должно быть, в логике форматирования относительной даты Closure Library, которую использовал Docs. Код начинался с попытки вычислить количество дней между текущим временем и временем ввода через:
1. Получение текущего времени (new Date()).
2. Сброс часов, минут, секунд и миллисекунд объекта до нуля, чтобы получить время начала текущего дня.
3. Вычисление количества миллисекунд между временем начала текущего дня и временем ввода, деления его на количество миллисекунд в дне и округления в меньшую сторону.

Я некоторое время смотрел на код, и тут меня осенило! Введенное время между 23:00 и 23:59 (PT) 7 ноября было в поясе тихоокеанского стандартного времени (PST) после окончания летнего времени, но начало текущего дня (00:00) было в поясе тихоокеанского летнего времени (PDT) до окончания летнего времени. Поэтому в тот день между 00:00 и 23:00 не было 23 часов. Два времени были в двух разных часовых поясах с разницей в час, поэтому между двумя временами было 24 часа (целый день)! А что делал код, когда время ввода на день позже начала текущего дня? Он форматировал время как «завтра»…

Исправление
К счастью, класс Date в JS предоставляет удобный метод getTimezoneOffset(), который возвращает количество минут между часовым поясом объекта Date и часовым поясом UTC. Я использовал его для вычисления разницы. По сути, это удаляло любые различия, возникавшие только из-за изменения летнего/зимнего времени между текущим временем и временем начала текущего дня. Так что теперь количество часов между 00:00 и 23:00 в этот день вычислялось как 23!

Бонусный баг
Эта ошибка фактически распространялась и на время начала летнего времени. В 2021 году летнее время началось в 2:00 (по тихоокеанскому времени) 14 марта. Что же произойдёт, если оставить комментарий в 00:00 15 марта, когда текущее время было 23:00 14 марта?

Можно было бы ожидать, что между 00:00 и 23:00 вечера 14 марта будет 23 часа, но их всего 22. Из-за того, что летнее время начинается в 2:00, что на самом деле 3:00, между временем начала дня 14 и 15 марта всего 23 часа!

И что делает код для этого количества часов? Он вычисляет количество дней между двумя временами как ноль из-за округления в меньшую сторону и форматирует время как «сегодня»…
На самом деле этот баг не проявлялся в Google Docs, поскольку нельзя написать комментарий в будущем, и посмотреть его в прошлом.

Почему 1 час в год только по тихоокеанскому времени?
На самом деле эта ошибка теоретически могла произойти в любом часовом поясе. Возможно, все отчёты об ошибках в тот день были случайно получены от пользователей в тихоокеанском часовом поясе. Или, возможно, есть какой-то другой фактор, о котором я не подумал.

Источник: https://tomeraberba.ch/the-1-hour-per-year-bug
Автор оригинала: Tomer Aberbach
👍8👎2
День 2040. #УрокиРазработки
Уроки 50 Лет Разработки ПО


Урок 21. Проблемы легче предупредить, чем исправить
Время, потраченное на вдумчивое рассмотрение проекта, с лихвой окупается временем, которое не было потрачено на исправление проблем позже. Вам может понадобиться дополнительное время на то, чтобы усовершенствовать проект в условиях неопределённости, поэтому старайтесь распределять усилия в соответствии с характером проблемы. Однако даже приложив максимум усилий для создания безупречного проектного решения, позднее вы можете обнаружить недостатки и придётся их корректировать.

Технический долг и рефакторинг
Проекты, выполненные в спешке, могут привести к появлению технического долга — так называют недостатки, которые кто-то должен устранить в будущем, чтобы обеспечить надлежащее функционирование продукта и его расширяемость. Небольшой технический долг может быть приемлемым компромиссом, если экономия на проектировании и разработке кода ускоряет достижение текущей бизнес-цели. Однако недостатки остаются. Чем дольше команда откладывает их решение, тем более масштабной, дорогостоящей и разрушительной будет доработка. Как и любой кредит, технический долг следует рассматривать как временный и требующий постепенного погашения.

Доработка в целях уменьшения технического долга часто принимает форму рефакторинга — реорганизации существующего кода для улучшения его структуры без изменения функциональности. Вы можете упростить код, сделать его более удобным для сопровождения или расширения, повысить его эффективность, удалить повторяющиеся и ненужные части или внести другие улучшения.

Существенные изменения проекта могут потребовать значительных усилий по реорганизации, тогда как команды обычно предпочитают создавать новые полезные функции.

Внесение изменений в проект требует усилий, тогда как уровень ценности для клиента остаётся прежним, но этот труд помогает поддерживать стабильную основу, необходимую для дальнейшего развития продукта. Хорошее проектирование сводит к минимуму возникающий технический долг, а рефакторинг устраняет его. Разумный баланс этих двух факторов даёт наилучшие результаты. Недостаточное проектирование может привести к значительным доработкам; но на слишком детальную проработку проекта может потребоваться чересчур много времени, при этом хороший результат не гарантирован.

Постоянное совершенствование проектного решения упрощает работу с кодом. Однако на практике обычно выбирается другой путь: небольшой рефакторинг и большое внимание к целесообразности добавления новых функций.

Продумать или узнать все в начале проекта практически невозможно. Однако вы можете использовать свой опыт и опыт других, чтобы выбрать определённое направление. Сегодня вы можете принимать решения, которые завтра минимизируют потребность внесения изменений.

Цель проектирования в том, чтобы принять разумные решения сейчас и предотвратить ненужные изменения в будущем. Опираясь на свои рассуждения и мнение заинтересованных сторон, выбирайте такие решения, которые уменьшат вероятность появления необходимости изменять те или иные части продукта в будущем.

Архитектурные недостатки
Внесение небольших изменений в проект по мере продвижения — не слишком болезненный процесс, при этом продукт непрерывно и постепенно улучшается. Серьёзная реорганизация архитектуры ради увеличения устойчивости продукта или улучшения пользовательского опыта дастся гораздо сложнее.

Разработчики ПО всегда создают проект либо в процессе работы, либо в ходе тщательного обдумывания. Накопление технического долга из-за того, что у команды нет времени на надлежащее проектирование, просто отодвигает проблему в будущее, где её влияние продолжает расти. Инвестирование в проектное решение позволяет сэкономить силы и время на реструктуризацию и повторную реализацию в будущем, когда предпочтительнее работать над чем-то другим.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍10
День 2041. #юмор
👍20
День 2042.
DotNext 2024. 10-11 сентября. Москва + трансляция.
10 сентября выступаю на DotNext с докладом «Что нового в .NET 9», которая выйдет в ноябре этого года. Расскажу о новинках в C#, ASP.NET, Entity Framework, MAUI и даже WinForms.

Кроме того, в программе конференции доклады про кроссплатформенность, JIT, архитектуру, безопасность, перформанс, инструменты в разработке и многое другое. Кроме докладов на конференции мастер-классы, интервью и круглые столы от спикеров из разных компаний.

Буду рад всех видеть и пообщаться лично! Также можете задавать вопросы к докладу в онлайне. Если решите купить билет самостоятельно, пишите в личку — постараюсь помочь.

До встречи!
👍47👎1
День 2043. #Testing
Автоматизированные Тесты. Начало
Т
есты важны для обеспечения качества приложения. Существует множество видов тестов: модульные, интеграционные, функциональные, дымовые и т.п. Но некоторые тесты не вписываются ни в одну категорию, и создатели придают им другое значение, создавая их.

Тесты — это компромиссы:
- Уверенность: Гарантирует ли тест, что приложение работает так, как ожидалось?
- Продолжительность: Сколько времени требуется для выполнения теста?
- Надёжность: Выдаёт ли тест случайные сбои?
- Усилия по написанию: Легко ли реализовать тестовый сценарий?
- Стоимость обслуживания: Сколько времени нужно на обновление теста при изменении кода?

Мой способ написания тестов значительно изменился со временем. Теперь мои тесты стали намного эффективнее, а их количество существенно сократилось. Изначально я писал тесты почти для каждого публичного метода для всех классов сборки. Я имитировал все внешние зависимости, гарантируя, что каждый тест проверяет только поведение внутри тестируемого метода. Иногда я тестировал внутренние классы, чтобы обеспечить всестороннее тестирование. Это приводило к многочисленным тестам, в которых каждый блок функциональности подвергался тщательному тестированию.

Теперь я принял новую стратегию, которая оказалась очень эффективной для разработанного мной приложения. Мои текущие практики включают:
1. Тестирование в основном публичного API, поскольку внутренние компоненты неявно тестируются и включаются в метрики покрытия кода. Публичный API – не обязательно классы или методы, а может быть точками входа приложения или конечными точками HTTP.
2. Минимизация использования моков, резервирование их для особых случаев, таких как взаимодействие клиентов с внешними сервисами.
3. Использование строгого статического анализа, чтобы избежать написания тестов для того, что может быть обнаружено во время компиляции.
4. Больше утверждений в коде (например, Debug.Assert).

Это даёт несколько преимуществ:
1. Меньше тестов нужно поддерживать.
2. Пользовательские сценарии чётко определены и протестированы.
3. Не нужно переписывать тесты при рефакторинге кода или изменениях в реализации, пока API не изменился.
4. Возможно безопасно рефакторить код, обеспечивая неизменное поведение.
5. Помогает выявлять устаревшие сегменты кода.

Тестирование публичного API
Публичный API — это то, как ваше приложение отображается для пользователей. Публичный API может отличаться в зависимости от типа проекта:
- Библиотека — классы и методы, доступные пользователям библиотеки.
- Веб-API — конечные точки HTTP.
- Приложение CLI — аргументы командной строки.

Зачем тестировать публичный API?
- Вы гарантируете, что приложение работает, как ожидается, и в тестах, и когда его будут использовать пользователи.
- Вы не связаны с деталями реализации, поэтому вы можете рефакторить код, не обновляя тесты, пока поведение не изменится.
- Меньше тестов, чем при тестировании многих отдельных классов или методов.
- Увеличивается покрытие кода, т.к. тестируется больше частей приложения.
- Легче отлаживать приложение, т.к. можно воспроизводить полные сценарии.

Например, если вы пишете приложение ASP.NET Core, не тестируйте класс Controller напрямую. Используйте WebApplicationFactory для настройки и запуска сервера, затем отправляйте запрос и проверяйте ответ. Так вы тестируете приложение в целом, включая маршрутизацию, привязку модели, фильтры, промежуточное ПО, внедрение зависимостей и т. д. Также возможен рефакторинг на Minimal API или изменение промежуточного ПО без обновления тестов.

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

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

Продолжение следует…

Источник:
https://www.meziantou.net/automated-tests.htm
Автор оригинала: Gérald Barré
👍14👎1
День 2044. #Testing
Автоматизированные Тесты. Продолжение

Начало

Подразумевает ли термин «модульное тестирование» тестирование каждой единицы реализации? Не совсем. Кент Бек, автор книги «Экстремальное программирование: разработка через тестирование», признал, что название «модульный тест», возможно, не лучший выбор. В этом контексте «модуль» относится к определённому поведению, которое может включать взаимодействие нескольких модулей реализации. Некоторые даже определяют «модуль» как целый пакет или библиотеку, и эта точка зрения также верна. Некоторые используют термины «изолированные» и «коммуникабельные» тесты, чтобы различать тесты, использующие и не использующие моки зависимостей.

Пишите код помогающий писать тесты
Написание теста должно быть простым, а тестовый код — понятным. Важно создать тестовую инфраструктуру, которая упрощает процесс написания тестов. Часто используют класс TestContext, содержащий общий код для написания тестов. Например, он может содержать код запуска приложения, отправки запроса, подтверждения ответа, регистрации мока и т. д. Так вы сможете сосредоточиться на написании теста, а не на шаблонном коде:
// Arrange
using var ctx = new TestContext();
ctx.ReplaceService<IService, MockImplementation>();
ctx.SetConfigurationFile("""{ "path: "/tmp" }""");
ctx.SeedDatabase(db =>
db.Users.Add(new User { Username = "user" }));
var user = ctx.SetCurrentUser("user");

// Act
var response = await ctx.Get("/user");

// Assert
Assert.AreEqual(response, """
Id: 1
UserName: user
""");


Нужно ли имитировать зависимости?
Моки (Moq) — способ протестировать приложение без использования реальных зависимостей. Поэтому, используя моки, вы тестируете не реальное приложение, а его разновидность. Важно максимально приблизить эту разновидность к реальности. Может быть сложно поддерживать тесты с моками. Сервисы, которые вы имитируете, могут изменить поведение, и вы можете не заметить этого, поскольку не используете их напрямую. Также приходится писать много кода для настройки тестов.

Разработчики, как правило, используют слишком много моков. Нужно ли имитировать файловую систему? Это не так-то просто, и у многих неправильные предположения о ней. Например, в Windows она чувствительна к регистру, некоторые имена файлов или символы в них недопустимы. В случае кроссплатформенного приложения может потребоваться протестировать поведение, специфичное для ОС. Почему бы просто не писать во временный каталог? То же самое касается БД. Можно использовать Docker для запуска БД, чтобы протестировать SQL и ограничения базы?

В большинстве случаев нужно имитировать внешние сервисы, которые не находятся под вашим контролем. Рассмотрите возможность использования фактических сервисов, когда это возможно. Конечно, желательно, чтобы тесты были изолированы. Здесь есть много стратегий. Например, если нужно читать или записывать файлы, можно создать временный каталог для каждого теста. Если вы запускаете БД в контейнере, можно использовать разные имена баз для изоляции тестов. Можно положиться на эмуляторы, предоставляемые поставщиками облачных услуг, для локального запуска некоторых зависимостей. Так вы будете уверены, что код работает, как ожидалось, и не нужно писать сложные моки. Убедитесь, что приложение можно настроить, например, легко подставлять разные строки соединений или папки для хранения данных приложения.

Полезные инструменты для избежания моков
- TestContainers или .NET Aspire. Если запуск Docker-контейнера медленный, можно переиспользовать его между несколькими тестовыми запусками. .NET Aspire также может предоставлять ресурсы в облаке (Azure, AWS и т. д.).
- Эмуляторы облачных сервисов. Например, Azure предоставляет эмулятор для хранилища.

Продолжение следует…

Источник:
https://www.meziantou.net/automated-tests.htm
👍15👎1
День 2045. #Testing
Автоматизированные Тесты. Продолжение

Начало
Продолжение

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

Я стараюсь избегать использования фреймворков-имитаторов, а создавать тестовые двойники вручную. Тестовые двойники могут более точно имитировать реальные сценарии, чем фреймворки-имитаторы. Они могут сохранять состояние между несколькими вызовами методов, что может быть сложно с фреймворками-имитаторами. Может потребоваться убедиться, что мок вызывается в правильном порядке (например, Save перед Get – хотя это можно сделать), или что вызывается правильное количество раз.

Кроме того, при отладке тестов можно легко перейти в код тестового двойника, тогда как это не всегда осуществимо с фреймворками-имитаторами. Добавляя пользовательские методы к тестовым двойникам для настройки или проверки содержимого, вы можете повысить выразительность, сделав тесты более читаемыми и простыми в обслуживании. Тестовые двойники также можно повторно использовать в нескольких тестах.

Это может показаться более трудоёмким, но эти усилия не существенны. Обычно вы не имеете дело с чрезмерным количеством фиктивных классов, и можете повторно использовать обобщённую реализацию в различных тестах. Так вы избегаете дублирования блоков кода для настройки в каждом тесте.
// Используем фреймворк-имитатор FakeItEasy
// Что, если сервис вызовет GetAsync до SaveAsync?
var service = A.Fake<IService>();
A.CallTo(() => service.SaveAsync()).Returns(true);
A.CallTo(() => service.GetAsync()).Returns(["value"]);

var sut = new MyClass(service);
sut.DoSomething();

Сравните с:
// Используем собственный двойник 
var service = new StubService();
service.AddItem("value"); // Добавляем данные

var sut = new MyClass(service);
sut.DoSomething();


Нужно ли добавлять интерфейсы, чтобы иметь возможность имитировать?
Интерфейсы с единственной реализацией бесполезны. Они просто добавляют сложности коду. По возможности не усложняйте код только для того, чтобы иметь возможность использовать моки. Если в коде будет единственная реализация, то вам может не понадобиться интерфейс. Кроме того, можно создать мок самого класса, а не интерфейса.

- Используйте виртуальные методы, чтобы можно было переопределить поведение в производном классе в тесте.
- Используйте делегаты (действие для переопределения поведения), чтобы можно было предоставить пользовательскую реализацию в тесте.
- Предоставьте конфигурацию, чтобы пользователь мог предоставить пользовательскую реализацию.

Окончание следует…

Источник:
https://www.meziantou.net/automated-tests.htm
Автор оригинала: Gérald Barré
👍7👎1
День 2046. #Testing
Автоматизированные Тесты. Окончание

Начало
Продолжение 1
Продолжение 2

Пишите больше утверждений в коде
Хотя утверждения (Assert) в тестах являются наиболее распространённым способом проверки поведения кода, можно писать утверждения и в коде. Например, использовать Debug.Assert для проверки состояния приложения. Это может быть полезно для проверки некоторых предположений, которые у вас есть относительно кода. Одним из преимуществ является то, что ошибка будет возникать на ранней стадии и также при отладке приложения, а не только при запуске тестов.

Утверждения также могут улучшить читаемость кода. Вы можете увидеть ожидаемое поведение непосредственно в коде, что может быть полезно при написании сложного кода или когда вы хотите проверить определённое поведение:
public void DoSomething()
{
Debug.Assert(_service != null,
"Этого быть не должно!");
_service.DoSomething();
}


Тестовые фреймворки
Используйте тот, который вам больше нравится: xUnit, NUnit и MSTests очень похожи по функциям. Синтаксис может отличаться, но концепции одинаковы. Для утверждений можно использовать встроенные или библиотеку вроде FluentAssertions.

Покрытие
Не стоит стремиться к 100% покрытию тестами. Вот некоторые проблемы с этой метрикой:
- 100% покрытие означает, что ошибок нет? Нет, это просто означает, что весь код покрыт тестами. И чаще всего только ваш код, но не зависимости.
- Сколько времени вы потратите на написание тестов, чтобы покрыть последние 10% кода? Стоит ли оно того?
- Нужно ли покрывать все пути в коде? Например, если есть метод, который выдаёт ArgumentNullException, нужно ли его тестировать? Добавляет ли это больше уверенности в коде или это просто пустая трата времени?

Вместо этого сосредоточьтесь на покрытии большей части кода. 70-80% часто является хорошим компромиссом с точки зрения уверенности и усилий. Это означает, что есть тесты, которые покрывают большую часть кода, и вы не тратите слишком много времени на их написание. Не забывайте, что тесты предназначены для того, чтобы придать достаточно уверенности в коде. Не нужно тестировать всё.

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

Нестабильность тестов
Тесты должны быть надёжными. Если тест не проходит, это должно быть связано с ошибкой в коде, а не в тесте. Если тест не проходит случайно, вы потеряете доверие к тестовому набору и начнёте игнорировать не пройденные тесты.

Есть несколько способов справиться с нестабильными тестами:
- Исправить тест - лучшее решение.
- Поместить тест в карантин и создать задачу для его исправления позже.
- Удалить тест, если он бесполезен.

Некоторые инструменты CI могут обнаружить нестабильность теста. Например, Azure DevOps может это делать.

См. также:
-
Мутационное тестирование с помощью Stryker
-
Пишите Тесты Быстрее с Approval Tests

Источник: https://www.meziantou.net/automated-tests.htm
👍5
День 2047. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 22. Проблемы многих систем скрываются в интерфейсах. Начало

Простейшая программа состоит из единственного модуля кода и UI. Интерфейс описывает, как стыкуются два архитектурных элемента внутри многокомпонентной системы или система c внешним окружением. Некоторые интерфейсы должны соответствовать установленным стандартам и протоколам, другие характерны для конкретного приложения.

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

Внутренние и внешние интерфейсы — распространённые источники проблем. Например, многократно используемые библиотеки с плохо или неправильно описанными в документации API могут увеличить время разработки, т.к. разработчикам придётся тратить время и силы, чтобы правильно интегрировать компоненты в свою систему. Добросовестный дизайнер гарантирует правильное сочетание всех частей сложной системы через интерфейсы. Новые компоненты, которые разработчики интегрируют в существующую систему, должны соответствовать установленным соглашениям об интерфейсе.

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

Определение интерфейса включает в себя:
- синтаксис (тип и формат) и семантику (смысловое значение) запросов и ответов, проходящих через интерфейс, в том числе данные и управляющие воздействия;
- ограничения на типы данных или значения, которые могут передаваться через интерфейс;
- механизм или протоколы, на основе которых действует интерфейс, например обмен сообщениями или вызов удалённых процедур;
- предварительные условия, которые должны быть выполнены в начале взаимодействия;
- постусловия, которые должны быть выполнены обеими сторонами после взаимодействия как в случае успеха, так и при неудаче.

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

Каждая реализация интерфейса должна соответствовать определению контракта, а также не должна причинять вред, например потреблять избыточное количество памяти или без необходимости блокировать доступ к объектам данных. Дизайн должен предусматривать обработку ошибок интерфейса. Если реализация интерфейса по какой-либо причине не сможет выполнить свои обязанности, то она должна вернуть соответствующее уведомление, чтобы помочь вызывающей стороне исправить ошибку.

Сложные системы, состоящие из множества взаимосвязанных компонентов, сложно модифицировать. Изменение одного из интерфейсов может запустить каскад изменений во множестве компонентов, использующих его. Если система разрабатывается без чётко определённых интерфейсов между компонентами, то по мере добавления новых возможностей, требующих изменения интерфейса, технический долг может быстро накапливаться. Проблемы также могут возникнуть, если новая функциональность не соответствует существующим интерфейсам.

Окончание следует…

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍7
День 2048. #УрокиРазработки
Уроки 50 Лет Разработки ПО

Урок 22. Проблемы многих систем скрываются в интерфейсах. Окончание

Начало

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

Лучше задать вопрос: «Какие функции действительно понадобятся пользователям моего интерфейса?» Написание тестов перед реализацией поможет продумать порядок использования интерфейса и добавить в него только необходимые возможности, убрав ненужные элементы. Понимание задач, решаемых пользователями с помощью ПО, также способствует созданию оптимизированного интерфейса.
Постарайтесь предугадать вероятные изменения, которые разработчики будут вносить в систему с течением времени, и подумайте, как они могут повлиять на интерфейсы. Это особенно пригодится при использовании итеративных методологий разработки. Благодаря приоритету запланированных улучшений разработчики будут знать о тех частях системы, которые с большей вероятностью изменятся, и о тех, которые должны оставаться более стабильными.

Проверка входных данных
Каждый компонент, участвующий во взаимодействии, должен проверять получаемые входные данные перед их обработкой. Многие уязвимости безопасности обусловлены тем, что вредоносный код, внедряемый злоумышленником через интерфейс, не отклоняется как недопустимый ввод. Хорошо спроектированная система будет правильно обрабатывать исключения, возникающие во внутренних и внешних интерфейсах. Компьютер, который внезапно «теряет» сетевой принтер, и не может найти его без перезагрузки – это проблема интерфейса взаимодействия.

Проблемы с UI
Пользователей интересует не внутренняя архитектура системы, а её интерфейс. Продукты с недостатками UI пользователи считают недоработанными. Непоследовательное поведение интерфейса смущает и расстраивает. Продукты получаются сложными в использовании, вынуждают тратить время впустую, допускают ошибки и плохо работают в реалистичных сценариях использования.
Определение стандартов интерфейса помогает обеспечить согласованное взаимодействие с пользователем как в одном приложении, так и в комплексе из нескольких связанных приложений. Если можно получить рекомендации по организации UI (правила внутри компании или в отрасли), используйте их. Они помогут всем приложениям выглядеть и вести себя одинаково.

Хорошо спроектированные интерфейсы не должны требовать вспомогательной документации в виде справочных экранов и руководств. Они облегчают пользователям процесс освоения нового приложения и оберегают их от ошибок. Одна из книг, посвящённых UI, — классический труд Алана Купера «Интерфейс. Основы проектирования взаимодействия». Ориентируясь на сценарии использования, а не на функции продукта, вы сможете избежать многих проблем с пользовательским опытом.

Сложности с интерфейсами
Иногда проблемы с интерфейсом обнаруживаются, когда команды пытаются интегрировать свои модули в продукт. Сбои при интеграционном тестировании после объединения нескольких модулей могут вызвать желание найти и наказать виноватых. Это нездоровый конфликт. Если архитектура правильно структурирована, интерфейсы хорошо определены, разработчики неукоснительно следуют правилам, определяемым интерфейсами, а модули успешно проходят созданные для них тесты, то интеграция должна протекать гладко и без взаимных обвинений.

Источник: Карл Вигерс “Жемчужины Разработки”. СПб.: Питер, 2024. Глава 3.
👍8
День 2049. #юмор
👍17👎2
День 2050.
Извините за повтор, но есть хорошие новости.

DotNext 2024 стартует уже сегодня.
Я выступаю в 10:30 по Москве с докладом «Что нового в .NET 9». А посмотреть мой (и ещё несколько) докладов можно абсолютно бесплатно в рамках Community Day. Всё, что нужно – это зарегистрироваться.

В общем, я пошёл готовиться, буду рад видеть всех, кто приедет. А в онлайне смотрите, задавайте вопросы, ну и лайки там, отзывы по выступлению, вотэтовсё 😉

До встречи!
👍52