⚡ Tl;dr
- Модуляризация — это метод разделения сложных систем на более мелкие удобоваримые части для лучшего управления и восприятия.
- Она повышает эффективность, надежность и ремонтопригодность программных проектов за счет организации кода в модули.
- Она снижает когнитивную нагрузку на разработчиков за счет уменьшения объема информации, которую им приходится обрабатывать за один раз, что облегчает понимание сложных систем и предотвращает их «выгорание».
- Модули при разработке программного обеспечения можно рассматривать как строительные кубики, наподобие деталей «Лего».
- Каждый модуль имеет уникальный набор общедоступных интерфейсов, структур данных или сообщений, которые служат контрактами для других разработчиков.
- При работе с модулями важно относиться к ним как к «черным ящикам» и взаимодействовать с ними только через определенные общедоступные интерфейсы, чтобы избежать сильного связывания и повысить модульность системы.
- Сборки используются для группировки кода в .NET, поскольку они обеспечивают более высокий уровень инкапсуляции (использование внутреннего доступа). Это позволяет разработчикам контролировать уровень доступа другого кода к членам типа и помогает защитить детали реализации типа или элемента.
- Чтобы сделать реализацию прозрачной для тестирования, можно использовать атрибут в файле csproj и указать имя сборки тестового проекта.
Почему разработчики пренебрегают модуляризацией?
Одной из причин, по которой разработчики могут не уделять должного внимания модуляризации, является недостаток знаний по этому вопросу. Разработчики часто изучают языки и фреймворки по документации, в которой могут не подчёркиваться наилучшие практики организации кода и создания модулей.
Другим фактором может быть недостаточное понимание бизнес-процессов и системного домена, что может привести к тому, что несвязанные куски кода будут тесно связаны между собой.
Кроме того, разработчики могут уделять слишком много внимания технологиям и фреймворкам, которые помогают быстро соорудить проект, а не рассматривать бизнес-процессы и связи между ними.
Плохое проектирование и отсутствие модуляризации могут привести к тому, что проект придётся полностью переписывать проект, в то время как использование определенных фреймворков иногда позволяет этого избежать.
Почему модуляризация так высоко ценится при разработке ПО?
Модуляризация — это метод разделения сложных систем на более мелкие, удоборваримые части для лучшего управления и восприятия. Она основана на принципе «разделяй и властвуй» и позволяет повысить эффективность, надежность и удобство поддержки программных проектов за счет организации кода в модули.
Кроме того, она помогает разработчику разгрузить голову за счет уменьшения объема информации, которую необходимо обрабатывать за один приём, что облегчает понимание сложных систем и предотвращает «выгорание».
Принцип Lego: использование модулей в качестве строительных кубиков для программных систем
Модули при разработке программного обеспечения можно рассматривать как строительные блоки, наподобие деталей lego. Каждый модуль имеет уникальный набор общедоступных интерфейсов, структур данных или сообщений, которые выступают в качестве контрактов для других разработчиков. При работе с модулями важно рассматривать их как «черные ящики» и взаимодействовать с ними только через их определённые открытые интерфейсы. Это позволяет избежать сильного связывания и улучшить модуляризацию системы. Одна из распространенных ошибок, которую я встречал в проектах, — это когда все классы делаются публичными, что означает отсутствие модуляризации. Все можно потрогать и соединить с любым другим элементом, что приводит к быстрому превращению в «большой клубок грязи», чего мы хотим избежать.
Пример модуля с высокой связью. Избегайте такого!
Чтобы избежать этого, важно использовать модификатор внутреннего доступа? и раскрывать только чистый, хорошо определенный API, используя общедоступные интерфейсы и структуры данных. Разработка через тестирование (TDD) также может помочь в этом вопросе, поскольку первым потребителем вашего модуля будете вы сами. Следуя этим рекомендациям, можно создать модульную систему, которую легко понять, поддерживать и расширять.
Пример модуля в .NET
Модуль — это самостоятельный фрагмент кода, который служит достижению определённой цели или реализует функцию и может быть импортирован и использован в других фрагментах кода. Модули упрощают многократное использование кода и часто используются при разработке для разбиения больших проектов на более мелкие и управляемые части.
Модуль в .NET
Модуль — это отдельная сборка или набор сборок, сгруппированных в каталоге Solution Folder.
Divstack Estimation Tool — пример подмодуля модуля Emails
Divstack.Company.Estimation.Tool.Emails.Core — пример подмодуля модуля Emails. Данный модуль предоставляет технические возможности для отправки электронных писем. Назначение — быть многократно используемым.
Почему для группирования кода используются сборки? Почему не словарь?
Сборка Divstack.Company.Estimation.Tool.Emails.Core является примером подмодуля модуля Emails, который предоставляет техническую возможность для отправки электронных писем. Она предназначена для многократного использования.
Одна из причин использования сборок для группирования кода заключается в том, что они обеспечивают более высокий уровень инкапсуляции. Модификаторы доступа, такие как internal и public, позволяют разработчикам контролировать уровень доступа другого кода к членам типа, что помогает защитить детали реализации типа или члена.
Напротив, подход со словарём не позволяет достичь такого же уровня инкапсуляции, поскольку не поддерживает модификаторы доступа и всегда предоставляет доступ к классу.
Рассмотрим пример
В этом модуле имеется только два публичных класса.
public interface IEmailSender
{
void Send(string email, string subject, string text);
}
public static class EmailCoreModule
{
public static IServiceCollection AddCore(this IServiceCollection services)
{
services.AddScoped<IEmailSender, EmailSender>();
services.AddSingleton<IMailConfiguration, MailConfiguration>();
services.AddScoped<IMailTemplateReader, MailTemplateReader>();
return services;
}
}
Другие классы являются деталями реализации
Модуль — как «черный ящик» для сторонних разработчиков. Сторонние разработчики имеют доступ только к интерфейсу, предоставляемому модулем, и могут не вникать в то, как он реализован. Это может быть как сервис типа SendGrid, так и обычный SMTP-сервер. Преимущество такого подхода заключается в том, что не имеет значения, как реализован модуль, если он обеспечивает ожидаемую функциональность через интерфейс.
Сила подхода «черный ящик»
Публичный API должен быть стабильным, то есть не должен часто и неожиданно меняться. Разработчику важно тщательно продумать интерфейс API, чтобы его было легко понять и использовать. Вспомните, насколько важно разработать чистый REST API. Обеспечить удобство API для разработчика можно, например, при помощи модульного тестирования, поскольку тесты могут служить первым «клиентом» кода. Поэтому часто рекомендуется использовать методику «разработка через тестирование» (TDD), при которой тесты пишутся до того, как пишется сам код. Написав сначала тесты, разработчик может сосредоточиться на создании чистого и удобного интерфейса API.
Как протестировать реализацию инкапсулированного модуля?
Распространенной проблемой в понимании модульных тестов является восприятие слова «модуль». В наших модулях мы будем иметь публичные интерфейсы. В наших модульных тестах мы будем проверять реализацию интерфейса, имеющего модификатор доступа public.
public interface IEmailSender
{
void Send(string email, string subject, string text);
}
internal sealed class EmailSender : IEmailSender
{
private const string EmailType = "text/html";
private readonly IBackgroundProcessQueue _backgroundProcessQueue;
private readonly IMailConfiguration _mailConfiguration;
public EmailSender(IMailConfiguration mailConfiguration, IBackgroundProcessQueue backgroundProcessQueue)
{
_mailConfiguration = mailConfiguration;
_backgroundProcessQueue = backgroundProcessQueue;
}
public void Send(string email, string subject, string text)
{
if (!IsNullOrEmpty(_mailConfiguration.MailFrom))
{
_backgroundProcessQueue.Enqueue(() => SendMessageAsync(email, subject, text));
}
}
public async Task SendMessageAsync(string email, string subject, string text)
{
var client = new SendGridClient(_mailConfiguration.ApiKey);
var message = BuildMessage(email, subject, text);
await client.SendEmailAsync(message);
}
private SendGridMessage BuildMessage(string email, string subject, string text)
{
var message = new SendGridMessage();
message.AddTo(email);
message.AddContent(EmailType, text);
var fromEmailAddress = new EmailAddress(_mailConfiguration.MailFrom);
message.SetFrom(fromEmailAddress);
message.SetSubject(subject);
return message;
}
}
Чтобы сделать его видимым для тестового проекта, необходимо добавить этот код в файл Divstack.Company.Estimation.Tool.Emails.Core.csproj.
<ItemGroup>
<InternalsVisibleTo Include="Divstack.Company.Estimation.Tool.Emails.Core.UnitTests" />
</ItemGroup>
Заключение
Важно отметить, что модуляризация — это мощная техника, позволяющая в целом повысить качество кода, сделать его более удобным для сопровождения и тестирования. При правильном использовании модификатора внутреннего доступа и соблюдении рекомендуемых практик можно создать модульную систему, которую легко воспринимать, поддерживать и расширять.