Clean Code Architecture C#: Повний Посібник та Питання Співбесіди 2026
Опануйте Clean Code та Clean Architecture в C# з принципами SOLID, впровадженням залежностей та питаннями співбесіди для .NET 10.

Clean Code Architecture в C# поєднує принципи Чистого Коду Роберта Мартіна з патерном Clean Architecture для створення підтримуваних, тестованих та масштабованих .NET застосунків. Технічні співбесіди дедалі більше фокусуються на цих концепціях, оскільки вони розкривають спосіб мислення кандидата про проектування програмного забезпечення, що виходить за межі простого функціонування коду.
Коли запитують про Clean Architecture, інтерв'юери очікують пояснення правила залежностей: залежності вихідного коду вказують всередину, до політик вищого рівня. Рівень Domain нічого не знає про Infrastructure, а не навпаки.
Принципи SOLID як Основа Чистого Коду C#
Принципи SOLID формують хребет Clean Code в C#. Офіційна документація Microsoft з основ .NET рекомендує ці патерни для корпоративних застосунків. Кожен принцип вирішує конкретну проблему підтримки коду.
Принцип Єдиної Відповідальності (SRP): Клас має одну причину для зміни. Нижче наведений OrderService займається виключно обробкою замовлень, делегуючи збереження та сповіщення окремим компонентам.
public class OrderService
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
public OrderService(
IOrderRepository orderRepository,
INotificationService notificationService)
{
_orderRepository = orderRepository;
_notificationService = notificationService;
}
public async Task<Order> CreateOrderAsync(CreateOrderRequest request)
{
// Validates and creates the domain entity
var order = Order.Create(request.CustomerId, request.Items);
// Persists through the repository abstraction
await _orderRepository.AddAsync(order);
// Notifies through a separate service
await _notificationService.SendOrderConfirmationAsync(order);
return order;
}
}Принцип Відкритості/Закритості (OCP): Класи залишаються відкритими для розширення, але закритими для модифікації. Нові методи оплати вимагають нових класів, а не змін в існуючих.
public interface IPaymentProcessor
{
string PaymentMethod { get; }
Task<PaymentResult> ProcessAsync(Payment payment);
}
// StripePaymentProcessor.cs
public class StripePaymentProcessor : IPaymentProcessor
{
public string PaymentMethod => "Stripe";
public async Task<PaymentResult> ProcessAsync(Payment payment)
{
// Stripe-specific implementation
var charge = await _stripeClient.CreateChargeAsync(payment.Amount);
return new PaymentResult(charge.Id, charge.Status == "succeeded");
}
}
// PaymentService.cs
public class PaymentService
{
private readonly IEnumerable<IPaymentProcessor> _processors;
public PaymentService(IEnumerable<IPaymentProcessor> processors)
{
_processors = processors;
}
public async Task<PaymentResult> ProcessPaymentAsync(
Payment payment, string method)
{
var processor = _processors
.FirstOrDefault(p => p.PaymentMethod == method)
?? throw new NotSupportedException($"Payment method {method} not supported");
return await processor.ProcessAsync(payment);
}
}Додавання підтримки PayPal означає додавання класу PayPalPaymentProcessor. PaymentService залишається незмінним.
Чотири Рівні Clean Architecture в .NET
Clean Architecture організовує код у концентричні рівні. Репозиторій Clean Architecture Джейсона Тейлора надає широко використовуваний шаблон .NET. Кожен рівень має чіткі відповідальності, а залежності течуть всередину.
| Рівень | Відповідальність | Залежності |
|---|---|---|
| Domain | Сутності, об'єкти-значення, доменні події | Немає |
| Application | Варіанти використання, DTO, інтерфейси | Domain |
| Infrastructure | База даних, зовнішні API, файлова система | Application, Domain |
| Presentation | Контролери, представлення, API endpoint'и | Application |
public class Customer
{
public Guid Id { get; private set; }
public string Email { get; private set; }
public CustomerStatus Status { get; private set; }
private Customer() { } // EF Core constructor
public static Customer Create(string email)
{
if (string.IsNullOrWhiteSpace(email))
throw new DomainException("Email cannot be empty");
return new Customer
{
Id = Guid.NewGuid(),
Email = email.ToLowerInvariant(),
Status = CustomerStatus.Active
};
}
public void Deactivate()
{
if (Status == CustomerStatus.Inactive)
throw new DomainException("Customer already inactive");
Status = CustomerStatus.Inactive;
}
}Доменна сутність інкапсулює бізнес-правила. Вона валідує власні інваріанти та надає поведінку через методи, а не сетери.
Приватні сетери запобігають зовнішньому коду переводити сутності в недійсний стан. Метод Deactivate() забезпечує правило, що неактивні клієнти не можуть бути деактивовані повторно. Цей патерн часто зустрічається в реалізаціях Domain-Driven Design та Clean Architecture.
Патерни Впровадження Залежностей для Clean Architecture
Впровадження Залежностей (DI) забезпечує інверсію залежностей, яку вимагає Clean Architecture. .NET 10 включає вбудований DI контейнер, що підтримує впровадження через конструктор, scoped lifetimes та keyed services, представлені в .NET 8.
var builder = WebApplication.CreateBuilder(args);
// Domain services - typically transient
builder.Services.AddTransient<IOrderValidator, OrderValidator>();
// Application services - scoped per request
builder.Services.AddScoped<IOrderService, OrderService>();
// Infrastructure - scoped to share DbContext
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
// External clients - singleton with HttpClient pooling
builder.Services.AddHttpClient<IPaymentGateway, StripeGateway>(client =>
{
client.BaseAddress = new Uri("https://api.stripe.com/v1/");
});
// Keyed services for multiple implementations (.NET 8+)
builder.Services.AddKeyedScoped<INotificationService, EmailNotificationService>("email");
builder.Services.AddKeyedScoped<INotificationService, SmsNotificationService>("sms");
var app = builder.Build();Рівень Application визначає інтерфейси. Рівень Infrastructure їх реалізує. Рівень Presentation (або composition root) з'єднує все разом.
Патерн Repository з Entity Framework Core 9
Патерн Repository абстрагує доступ до даних за доменно-орієнтованими інтерфейсами. EF Core 9, що постачається з .NET 10, забезпечує базовий ORM. Розширені патерни EF Core описані в посібнику з продуктивності EF Core SharpSkill.
public interface IOrderRepository
{
Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default);
Task<IReadOnlyList<Order>> GetByCustomerAsync(Guid customerId, CancellationToken ct = default);
Task AddAsync(Order order, CancellationToken ct = default);
void Update(Order order);
}
// Infrastructure/Repositories/OrderRepository.cs
public class OrderRepository : IOrderRepository
{
private readonly ApplicationDbContext _context;
public OrderRepository(ApplicationDbContext context)
{
_context = context;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
return await _context.Orders
.Include(o => o.Items)
.FirstOrDefaultAsync(o => o.Id == id, ct);
}
public async Task<IReadOnlyList<Order>> GetByCustomerAsync(
Guid customerId, CancellationToken ct = default)
{
return await _context.Orders
.Where(o => o.CustomerId == customerId)
.OrderByDescending(o => o.CreatedAt)
.ToListAsync(ct);
}
public async Task AddAsync(Order order, CancellationToken ct = default)
{
await _context.Orders.AddAsync(order, ct);
}
public void Update(Order order)
{
_context.Orders.Update(order);
}
}Repository повертає доменні сутності, а не DTO. Маппінг на DTO відбувається в рівні Application, зберігаючи чистоту Domain.
Готовий до співбесід з .NET?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Модульне Тестування Компонентів Clean Architecture
Clean Architecture спрощує тестування, оскільки залежності впроваджуються через інтерфейси. xUnit та NSubstitute надають тестовий фреймворк та бібліотеку мокінгу для .NET.
public class OrderServiceTests
{
private readonly IOrderRepository _orderRepository;
private readonly INotificationService _notificationService;
private readonly OrderService _sut;
public OrderServiceTests()
{
_orderRepository = Substitute.For<IOrderRepository>();
_notificationService = Substitute.For<INotificationService>();
_sut = new OrderService(_orderRepository, _notificationService);
}
[Fact]
public async Task CreateOrderAsync_ValidRequest_PersistsAndNotifies()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: new[] { new OrderItemDto("SKU-001", 2, 29.99m) });
// Act
var order = await _sut.CreateOrderAsync(request);
// Assert
await _orderRepository.Received(1).AddAsync(Arg.Any<Order>());
await _notificationService.Received(1)
.SendOrderConfirmationAsync(Arg.Is<Order>(o => o.Id == order.Id));
}
[Fact]
public async Task CreateOrderAsync_EmptyItems_ThrowsDomainException()
{
// Arrange
var request = new CreateOrderRequest(
CustomerId: Guid.NewGuid(),
Items: Array.Empty<OrderItemDto>());
// Act & Assert
await Assert.ThrowsAsync<DomainException>(
() => _sut.CreateOrderAsync(request));
}
}Тести перевіряють поведінку, а не реалізацію. _sut (система, що тестується) взаємодіє з моками. Твердження перевіряють, чи правильні методи були викликані з правильними аргументами.
Питання Співбесіди про Clean Architecture C#
Технічні співбесіди перевіряють розуміння Clean Architecture на кількох рівнях. Ці питання зустрічаються на співбесідах для сеньйорів .NET. Більше матеріалів для підготовки в модулі питань співбесіди Clean Architecture SharpSkill.
Кандидати часто плутають Clean Architecture з N-tier архітектурою. Ключова різниця: в Clean Architecture залежності вказують всередину, до Domain. В N-tier кожен рівень залежить від рівня нижче, роблячи Domain залежним від Infrastructure.
П: Чим Clean Architecture відрізняється від традиційної багатошарової архітектури?
Традиційна багатошарова архітектура робить кожен рівень залежним від рівня нижче: Presentation залежить від Business Logic, який залежить від Data Access. Clean Architecture інвертує це: Domain не має залежностей, Application залежить від Domain, а Infrastructure залежить від обох. Ця інверсія означає, що технологія бази даних може змінитися без торкання бізнес-правил.
П: Коли б ви не використовували Clean Architecture?
Clean Architecture додає непряму адресацію. Для CRUD-орієнтованих застосунків без складних бізнес-правил накладні витрати перевищують користь. Простому API, яке безпосередньо проксіює таблиці бази даних, не потрібні чотири рівні. Цінність з'являється, коли складність бізнес-логіки виправдовує розділення.
П: Як ви обробляєте наскрізні проблеми, такі як логування та кешування?
Два патерни працюють добре: патерн декоратора та middleware. Кешуючий декоратор обгортає інтерфейс репозиторію, реалізуючи той самий інтерфейс, додаючи логіку кешу. Логування зазвичай використовує middleware або DI interceptor, який обгортає виклики сервісів без забруднення бізнес-логіки.
public class CachingOrderRepository : IOrderRepository
{
private readonly IOrderRepository _inner;
private readonly IDistributedCache _cache;
public CachingOrderRepository(
IOrderRepository inner,
IDistributedCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<Order?> GetByIdAsync(Guid id, CancellationToken ct = default)
{
var cacheKey = $"order:{id}";
var cached = await _cache.GetStringAsync(cacheKey, ct);
if (cached is not null)
return JsonSerializer.Deserialize<Order>(cached);
var order = await _inner.GetByIdAsync(id, ct);
if (order is not null)
{
await _cache.SetStringAsync(
cacheKey,
JsonSerializer.Serialize(order),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
},
ct);
}
return order;
}
// Other methods delegate to _inner
}П: Як ви структуруєте валідацію в Clean Architecture?
Валідація відбувається на двох рівнях. Доменна валідація (інваріанти) належить сутностям: Замовлення не може мати нуль позицій. Прикладна валідація (валідація вхідних даних) належить обробникам команд або валідаторам: запит повинен містити дійсний ID клієнта. FluentValidation добре інтегрується для валідації вхідних даних, тоді як доменна валідація залишається в конструкторах та методах сутностей.
Організація Коду та Конвенції Найменування
Структура проекту комунікує архітектуру. Стандартний шаблон Clean Architecture організовує проекти за рівнями.
src/
├── MyApp.Domain/
│ ├── Entities/
│ ├── ValueObjects/
│ ├── Events/
│ └── Exceptions/
├── MyApp.Application/
│ ├── Common/
│ │ ├── Interfaces/
│ │ └── Behaviors/
│ ├── Orders/
│ │ ├── Commands/
│ │ ├── Queries/
│ │ └── EventHandlers/
│ └── Customers/
├── MyApp.Infrastructure/
│ ├── Persistence/
│ ├── Services/
│ └── Configuration/
└── MyApp.WebApi/
├── Controllers/
├── Filters/
└── Middleware/Вертикальні зрізи (Orders, Customers) групують пов'язані варіанти використання. Ця організація масштабується краще, ніж горизонтальні зрізи (Commands, Queries) зі зростанням застосунку.
Питання Продуктивності в Clean Architecture
Абстракція має свою вартість. Кожен виклик інтерфейсу додає непряму адресацію. Ці практики мінімізують накладні витрати, зберігаючи тестованість.
Використовуйте records для DTO: Records генерують ефективні реалізації Equals та GetHashCode. Вони незмінні за замовчуванням, запобігаючи випадковій мутації.
public record OrderDto(
Guid Id,
Guid CustomerId,
IReadOnlyList<OrderItemDto> Items,
decimal Total,
DateTime CreatedAt);
public record OrderItemDto(
string Sku,
int Quantity,
decimal UnitPrice);Уникайте надмірної абстракції: Не кожен клас потребує інтерфейсу. Абстрагуйте зовнішні залежності (база даних, HTTP клієнти, файлова система). Внутрішні доменні сервіси з однією реалізацією рідко потребують інтерфейсів.
Профілюйте перед оптимізацією: Накладні витрати рівнів Clean Architecture зазвичай незначні порівняно з I/O операціями. Запит до бази даних, що займає 50мс, затьмарює мікросекунди, витрачені на диспетчеризацію методів.
Застосування Принципів Clean Code до Методів C#
Clean Code фокусується на читабельності на рівні методів та класів. Ці практики застосовуються незалежно від архітектурного патерну.
Методи роблять одну річ: Метод з назвою ProcessOrderAndSendEmail порушує SRP. Розділіть його на ProcessOrder та SendOrderConfirmation.
Значущі імена: CalculateOrderTotal передає намір. DoCalculation ні. Імена змінних підкоряються тому ж правилу: customerOrders замість list.
Малі методи: Якщо метод перевищує 20 рядків, він ймовірно робить занадто багато. Виділіть допоміжні методи з описовими іменами.
// Before: Long method doing multiple things
public decimal CalculateInvoiceTotal(Invoice invoice)
{
decimal subtotal = 0;
foreach (var item in invoice.Items)
{
subtotal += item.Quantity * item.UnitPrice;
}
decimal discount = 0;
if (invoice.Customer.IsPreferred)
{
discount = subtotal * 0.1m;
}
decimal tax = (subtotal - discount) * 0.2m;
return subtotal - discount + tax;
}
// After: Small methods with single responsibilities
public decimal CalculateInvoiceTotal(Invoice invoice)
{
var subtotal = CalculateSubtotal(invoice.Items);
var discount = CalculateDiscount(subtotal, invoice.Customer);
var tax = CalculateTax(subtotal - discount);
return subtotal - discount + tax;
}
private decimal CalculateSubtotal(IEnumerable<InvoiceItem> items)
=> items.Sum(item => item.Quantity * item.UnitPrice);
private decimal CalculateDiscount(decimal subtotal, Customer customer)
=> customer.IsPreferred ? subtotal * 0.1m : 0;
private decimal CalculateTax(decimal taxableAmount)
=> taxableAmount * 0.2m;Рефакторизована версія читається як зведення бізнес-логіки. Кожен допоміжний метод може тестуватися незалежно.
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Ключові Висновки для Clean Code Architecture в C#
- Правило залежностей: залежності коду вказують всередину, від Infrastructure до Domain, ніколи назовні
- Принципи SOLID керують проектуванням класів: єдина відповідальність, відкритість для розширення, заміщення Лісков, розділення інтерфейсів, інверсія залежностей
- Чотири рівні розділяють відповідальності: Domain (сутності), Application (варіанти використання), Infrastructure (зовнішні системи), Presentation (API/UI)
- Впровадження залежностей з'єднує рівні в composition root, зазвичай в
Program.cs - Патерн Repository абстрагує доступ до даних за доменно-орієнтованими інтерфейсами, які повертають сутності, а не DTO
- Модульні тести перевіряють поведінку через мокінг інтерфейсів, валідуючи що правильні методи отримують правильні аргументи
- Clean Code на рівні методів: малі методи, значущі імена, єдині відповідальності
- Вартість продуктивності абстракції зазвичай незначна порівняно з I/O операціями; профілюйте перед оптимізацією
- Питання співбесіди перевіряють розуміння правила залежностей, компромісів та практичних патернів, таких як декоратори для наскрізних проблем
Чи знайдеш ти помилку в .NET?
Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Автор:
Anthony Fillion-MailletЗасновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 24 серпня 2026 р.
Поділитися
Пов'язані статті

Поглиблений C# LINQ у 2026: Оператори, Продуктивність та Питання на Співбесідах
Опануйте поглиблені оператори LINQ, відкладене виконання та оптимізацію продуктивності. Охоплює GroupBy, SelectMany, оптимізацію запитів та поширені питання на співбесідах для .NET розробників.

SignalR в ASP.NET Core 2026: Комунікація в Реальному Часі, Хаби та Питання на Співбесідах
Повний посібник з SignalR в ASP.NET Core. Дізнайтеся як реалізувати комунікацію в реальному часі, налаштувати хаби, керувати групами та підготуватися до технічних співбесід.

ASP.NET Core Minimal APIs у 2026: Архітектура, Продуктивність та Питання на Співбесідах
Повний посібник з Minimal APIs в ASP.NET Core - архітектура, групи маршрутів, фільтри ендпоінтів, Native AOT та ключові питання на співбесідах для .NET розробників.