Blog Dla Programistów C#/.NET

Czy Clean Code ma sens? Małe funkcje, DRY, interfejsy i komentarze w .NET bez dogmatów

czwartek, 22 października 2026 Tagi: KarieraProgramowanie

Code review. Twoja metoda ma 12 linii. Komentarz od tech leada: "Za długa. Podziel." Nie "niezrozumiała". Nie "ma buga". Po prostu za długa. Nikt nie zapytał, czy po podziale kod będzie lepszy. Bo i po co pytać, skoro "święta księga" mówi, że funkcja ma mieć 5 linii.

Powiem wprost coś, za co część programistów mnie nie polubi: Clean Code - tak, jak go dziś stosujemy - robi w projektach więcej szkody niż pożytku. Nie dlatego, że to zła książka, tylko dlatego, że z rozsądnych wskazówek zrobiliśmy przykazania, których nikt nie śmie kwestionować na code review. Od razu zaznaczę: nie zamierzam palić książki. Chcę pokazać, jak ślepe stosowanie tych zasad psuje prawdziwy kod.

W tym artykule rozbierzemy na części 4 dogmaty, które słyszysz na co dzień: małe funkcje, DRY za wszelką cenę, interfejs i warstwę na wszystko oraz zakaz komentarzy. Każdy pokażę na konkretnym kodzie w C# - a trzeci przykład to coś, co widzę w 9 na 10 projektów .NET, więc możliwe, że i w Twoim. Na koniec zobaczysz, co z Clean Code warto zostawić, i dostaniesz 3 pytania, które zastąpią Ci 100 reguł na code review.

Czy Clean Code ma sens? Małe funkcje, DRY, interfejsy i komentarze w .NET bez dogmatów

Skąd się wzięła religia Clean Code?


Rok 2008. Robert C. Martin, znany jako Uncle Bob, wydaje książkę "Clean Code". Powiedzmy sobie jasno: ta książka zrobiła dużo dobrego. Nauczyła całe pokolenie programistów, że kod czyta się wielokrotnie częściej, niż się go pisze (sam Martin szacuje ten stosunek na ponad 10 do 1), więc warto pisać go z myślą o tym, kto będzie go czytał.

Warto jednak pamiętać o kontekście. Książka ma już 18 lat, przykłady są w Javie, a intencje autora były jak najbardziej dobre. Gdyby programiści potraktowali ją jak zbiór podpowiedzi, nie byłoby o czym pisać. Ale potraktowali ją jak pismo święte - i z podpowiedzi zrobiły się dogmaty. Problem zaczyna się wtedy, gdy heurystyki stają się przykazaniami.

4 dogmaty, które słyszysz na co dzień. Oto 4 zdania, które słyszałem na code review częściej niż "dzień dobry":

• "Funkcja: max 5 linii" (sama książka idzie jeszcze dalej i sugeruje 2-4 linie),
• "Duplikacja to grzech. DRY. Zawsze.",
• "Interfejs i warstwa na wszystko",
• "Komentarz w kodzie to porażka".

Każde z nich, stosowane bez myślenia, zamienia prosty kod w łamigłówkę. Sprawdźmy to w praktyce - na prawdziwym kodzie.

Dogmat #1: małe funkcje, czyli gra w skakanie


Dogmat pierwszy: funkcja ma mieć maksymalnie 5 linii. Zobaczmy, jak wygląda kod napisany dokładnie według tej reguły - publiczna metoda serwisu, który składa zamówienie:

public async Task<OrderResult> PlaceOrderAsync(PlaceOrderCommand command)
{
var customer = await GetCustomerAsync(command.CustomerId);
ValidateCommand(command);
var order = await BuildOrderAsync(command, customer);
ApplyCustomerDiscounts(order, customer);
await FinalizeOrderAsync(order, customer);
return OrderResult.Success(order.Id);
}

Kod napisany idealnie według książki. 6 linii. Czyta się jak proza: pobierz klienta, zwaliduj, zbuduj zamówienie, nalicz rabaty, sfinalizuj. Każdy fan Clean Code'a daje aprowkę bez scrollowania.

Gra "ile skoków". To teraz zagrajmy w grę. Pytanie biznesowe, najprostsze na świecie: co się stanie, gdy klient zamówi produkt, którego nie ma na stanie? Znajdź odpowiedź w tym kodzie. Start.

Skok 1. PlaceOrderAsync woła BuildOrderAsync. Buduje zamówienie... ale o magazynie ani słowa. Lecimy głębiej.

private async Task<Order> BuildOrderAsync(PlaceOrderCommand command, Customer customer)
{
var order = CreateEmptyOrder(customer);
await AddOrderLinesAsync(order, command.Items);
return order;
}

Skok 2. Pętla po pozycjach, każda leci do kolejnej metody. Odpowiedzi dalej nie ma.

private async Task AddOrderLinesAsync(Order order, IReadOnlyList<OrderItemDto> items)
{
foreach (var item in items)
await AddSingleLineAsync(order, item);
}

Skok 3. Pobierz produkt... i jest jakieś EnsureProductInStock. Ciepło, ciepło. Ale żeby wiedzieć, co się właściwie stanie, trzeba skoczyć jeszcze raz.

private async Task AddSingleLineAsync(Order order, OrderItemDto item)
{
var product = await GetProductAsync(item.ProductId);
EnsureProductInStock(product, item.Quantity);
order.AddLine(product, item.Quantity);
}

Skok 4. Jest. Rzucamy wyjątek.

private void EnsureProductInStock(Product product, int quantity)
{
if (product.Stock < quantity)
throw new OutOfStockException(product.Name);
}

Ścieżka zrozumienia wyglądała tak: PlaceOrderAsync → BuildOrderAsync → AddOrderLinesAsync → AddSingleLineAsync → EnsureProductInStock. 4 skoki, żeby odpowiedzieć na najprostsze pytanie biznesowe w tym systemie.

Zwróć uwagę na jeszcze jedną rzecz: każda z tych prywatnych metod jest wywoływana dokładnie raz. Zero reużycia. Do tego dochodzą GetCustomerAsync, ValidateCommand, CreateEmptyOrder, GetProductAsync, ApplyCustomerDiscounts, FinalizeOrderAsync i NotifyCustomerAsync - razem 11 metod prywatnych, a logika jest jedna. To nie jest podział problemu na kawałki wielokrotnego użytku. To rozczłonkowanie go na 11 szuflad, z których musisz otworzyć 4, żeby znaleźć jedną odpowiedź.

Wersja pragmatyczna. Teraz ta sama logika w jednej metodzie:

public async Task<OrderResult> PlaceOrderAsync(PlaceOrderCommand command)
{
var customer = await _customers.GetByIdAsync(command.CustomerId);
if (customer is null)
return OrderResult.Failure("Klient nie istnieje");

if (command.Items.Count == 0)
return OrderResult.Failure("Zamówienie musi mieć co najmniej jedną pozycję");

var order = new Order(customer.Id);

foreach (var item in command.Items)
{
var product = await _products.GetByIdAsync(item.ProductId);
if (product is null)
return OrderResult.Failure($"Produkt {item.ProductId} nie istnieje");

if (product.Stock < item.Quantity)
return OrderResult.Failure($"Brak towaru: {product.Name} (na stanie: {product.Stock})");

order.AddLine(product, item.Quantity);
}

/* Rabat 5% dla klientów VIP - wymóg z umów B2B (ticket SALES-482) */
if (customer.IsVip)
order.ApplyDiscount(0.05m);

await _orders.SaveAsync(order);
await _emails.SendOrderConfirmationAsync(customer.Email, order);

return OrderResult.Success(order.Id);
}

Jedna metoda, nieco ponad 20 linii - mieści się w całości na jednym ekranie. Czytasz ją z góry na dół jak listę kroków: klient, pozycje, stan magazynu, rabat VIP, zapis, mail. Gdzie jest odpowiedź na pytanie o magazyn? Tutaj, w pętli. Zero skoków. Zapamiętaj też komentarz przy rabacie - wróci przy dogmacie #4.

Kiedy dzielić funkcję (naprawdę)? Żeby było jasne: nie mówię "nigdy nie dziel". Mówię: dziel, kiedy podział coś daje. Konkretnie, gdy:

• fragment jest używany w więcej niż jednym miejscu,
• fragment ma inny poziom abstrakcji (np. parsowanie pliku vs decyzja biznesowa),
• chcesz przetestować fragment w izolacji.

Natomiast "bo licznik linii przekroczył 5" nie jest żadnym powodem. Mała funkcja to narzędzie, nie cel.

Całość można zamknąć w jednej myśli: czytelność to nie liczba linii w funkcji. Czytelność to liczba skoków, które musisz wykonać, żeby zrozumieć, co się dzieje. Książka każe Ci optymalizować długość funkcji. Ty optymalizuj długość ścieżki zrozumienia.

Dogmat #2: DRY zawsze, czyli jak rodzi się potwór


Dogmat numer 2: duplikacja to grzech śmiertelny. Zobacz, jak DRY (Don't Repeat Yourself) stosowany religijnie zabija projekt. To historia w 3 aktach - widziałem ją na żywo więcej niż raz.

Akt 1: niewinna duplikacja. Mamy 2 walidacje: tworzenie i edycję pracownika.

public void ValidateCreate(CreateEmployeeRequest request)
{
if (string.IsNullOrWhiteSpace(request.Email))
throw new ValidationException("Email jest wymagany");
if (string.IsNullOrWhiteSpace(request.Password))
throw new ValidationException("Hasło jest wymagane");
if (request.Age < 18)
throw new ValidationException("Pracownik musi być pełnoletni");
}

public void ValidateUpdate(UpdateEmployeeRequest request)
{
if (string.IsNullOrWhiteSpace(request.Email))
throw new ValidationException("Email jest wymagany");
if (request.Age < 18)
throw new ValidationException("Pracownik musi być pełnoletni");
}

Prawie identyczne - różnią się jednym warunkiem. Na code review pada święte słowo: "DRY! Scal to". No to scalamy.

Akt 2: scalamy.

/* Po code review: "Nie powtarzaj się!" */
private void ValidateEmployee(string email, string? password, int age, bool requirePassword)
{
if (string.IsNullOrWhiteSpace(email))
throw new ValidationException("Email jest wymagany");
if (requirePassword && string.IsNullOrWhiteSpace(password))
throw new ValidationException("Hasło jest wymagane");
if (age < 18)
throw new ValidationException("Pracownik musi być pełnoletni");
}

Jedna metoda, jedna flaga. Wygląda porządnie. Co może pójść nie tak?

Akt 3: pół roku później. Doszedł import pracowników z CSV - tam nie ma hasła, a email bywa pusty, bo uzupełni go HR. Doszły konta administratorów - hasło minimum 16 znaków. Doszli kontraktorzy B2B - zamiast wieku walidujemy NIP. Metoda rośnie razem z wymaganiami:

private void ValidateEmployee(
string? email,
string? password,
int? age,
string? nip,
bool requirePassword,
bool allowMissingEmail,
bool isAdmin,
bool isImport,
bool isB2b)
{
if (!allowMissingEmail && string.IsNullOrWhiteSpace(email))
throw new ValidationException("Email jest wymagany");

if (requirePassword && !isImport)
{
if (string.IsNullOrWhiteSpace(password))
throw new ValidationException("Hasło jest wymagane");
if (isAdmin && password!.Length < 16)
throw new ValidationException("Hasło administratora: min. 16 znaków");
}

if (isB2b)
{
if (string.IsNullOrWhiteSpace(nip))
throw new ValidationException("NIP jest wymagany dla B2B");
}
else if (!isImport && (age is null || age < 18))
{
throw new ValidationException("Pracownik musi być pełnoletni");
}
}

9 parametrów, w tym 5 flag. Metoda dalej jest w 100% DRY. Kod się nie powtarza. Jest tylko jeden problem: nikt już nie wie, jak ona działa. A tak wygląda jej wywołanie:

ValidateEmployee(email, null, null, null, false, true, false, true, false);

Co znaczy ten false w środku? Nie wiesz. Ja też nie. Nikt nie wie bez otwierania metody. Powodzenia przy debugowaniu w piątek o 17:55.

DRY dotyczy wiedzy, nie linijek. Zasada DRY pochodzi z książki "The Pragmatic Programmer" (Andrew Hunt, David Thomas, 1999) i w oryginale mówi o wiedzy: jedna reguła biznesowa ma mieć jedno źródło prawdy. Nie mówi: "żadne dwie linijki nie mogą wyglądać podobnie". Duplikacja kodu to nie to samo, co duplikacja wiedzy.

Walidacja tworzenia i walidacja edycji to były różne decyzje biznesowe, które przypadkiem wyglądały tak samo - i rozjechały się przy pierwszej okazji. Sandi Metz ujęła to tak: "Duplikacja jest o wiele tańsza niż zła abstrakcja." Pomaga tu też prosta zasada trzech: pierwsza kopia - OK, druga kopia - OK, dopiero przy trzeciej widzisz, czy to naprawdę ten sam wzorzec.

Lek: osobne walidatory. Jak to naprawić? Prosto: osobny walidator na każdy przypadek użycia - na przykład po jednym walidatorze FluentValidation na request:

public class CreateEmployeeValidator : AbstractValidator<CreateEmployeeRequest>
{
public CreateEmployeeValidator()
{
RuleFor(x => x.Email).NotEmpty().WithMessage("Email jest wymagany");
RuleFor(x => x.Password).NotEmpty().WithMessage("Hasło jest wymagane");
RuleFor(x => x.Age).GreaterThanOrEqualTo(18).WithMessage("Pracownik musi być pełnoletni");
}
}

public class UpdateEmployeeValidator : AbstractValidator<UpdateEmployeeRequest>
{
public UpdateEmployeeValidator()
{
RuleFor(x => x.Email).NotEmpty().WithMessage("Email jest wymagany");
RuleFor(x => x.Age).GreaterThanOrEqualTo(18).WithMessage("Pracownik musi być pełnoletni");
}
}

Tak, reguły dla emaila i wieku się powtarzają. I dobrze - bo każda z nich może się teraz zmienić osobno, bez flag i bez zgadywania, co oznacza kolejny bool. Wspólne mogą zostać atomowe klocki, jak sprawdzanie formatu NIP-u. Bo to faktycznie jest jedna wiedza:

/* Wspólny, atomowy klocek: format NIP-u to naprawdę jedna wiedza */
public static class NipRules
{
public static IRuleBuilderOptions<T, string> ValidNip<T>(this IRuleBuilder<T, string> rule) =>
rule.NotEmpty().WithMessage("NIP jest wymagany")
.Matches(@"^\d{10}$").WithMessage("NIP musi mieć 10 cyfr");
}

/* Użycie w walidatorze kontraktora B2B */
RuleFor(x => x.Nip).ValidNip();

Duplikację usuniesz w 5 minut. Złą abstrakcję, na której wisi pół systemu, będziesz odkręcać latami. Jak zły tatuaż - łatwo zrobić, boli przy usuwaniu. Jeśli masz takiego potwora w swoim projekcie, wiesz już, skąd się wziął.

Dogmat #3: interfejs i warstwa na wszystko


Dogmat trzeci widzę w 9 na 10 projektów .NET. Interfejs na wszystko, warstwa na wszystko - "bo tak się robi". Typowa struktura projektu wygląda mniej więcej tak:

Controllers/
└─ EmployeesController.cs
Services/
├─ IEmployeeService.cs
└─ EmployeeService.cs
Repositories/
├─ IEmployeeRepository.cs
└─ EmployeeRepository.cs
Mappers/
├─ IEmployeeMapper.cs
└─ EmployeeMapper.cs
Dtos/
├─ EmployeeDto.cs
└─ CreateEmployeeDto.cs
Entities/
└─ Employee.cs

7 plików na 1 pole. Weźmy zadanie z Jiry na poziomie stażysty: dodaj pracownikowi numer telefonu. Jedno pole. Policz pliki, przez które musisz przejść: kontroler, interfejs serwisu, serwis, interfejs repozytorium, repozytorium, mapper, encja. 7 plików, zanim w ogóle dotkniesz tego nieszczęsnego pola (a po drodze dochodzą jeszcze DTO i migracja). Zobaczmy, co te warstwy w ogóle robią.

Warstwa przelotowa. Oto serwis:

public class EmployeeService : IEmployeeService
{
private readonly IEmployeeRepository _repository;

public EmployeeService(IEmployeeRepository repository) => _repository = repository;

public Task<Employee?> GetByIdAsync(int id) => _repository.GetByIdAsync(id);
public Task<List<Employee>> GetAllAsync() => _repository.GetAllAsync();
public Task AddAsync(Employee employee) => _repository.AddAsync(employee);

/* ...i 6 kolejnych metod, które robią DOKŁADNIE to samo: podają dalej */
}

Ten serwis nie waliduje. Nie liczy. Nie decyduje. Bierze wywołanie i podaje dalej - jak lotnisko przesiadkowe. To nie jest warstwa logiki. To warstwa przelotowa.

Repozytorium nad repozytorium. A tutaj klasyka gatunku:

public class EmployeeRepository : IEmployeeRepository
{
private readonly AppDbContext _db;

public EmployeeRepository(AppDbContext db) => _db = db;

public Task<Employee?> GetByIdAsync(int id) =>
_db.Employees.FirstOrDefaultAsync(e => e.Id == id);

public Task<List<Employee>> GetAllAsync() =>
_db.Employees.ToListAsync();

public async Task AddAsync(Employee employee)
{
_db.Employees.Add(employee);
await _db.SaveChangesAsync();
}
}

Opakowaliśmy jedną linijkę Entity Frameworka w klasę plus interfejs. Mała tajemnica: EF Core to już jest repozytorium. DbSet realizuje wzorzec repository, a DbContext - unit of work. Microsoft pisze o tym czarno na białym w dokumentacji. Budujemy więc repozytorium nad repozytorium. Abstrakcję nad abstrakcją.

Znam 2 argumenty obrońców takiego podejścia:

• "A jak kiedyś zmienimy bazę?" Przez 15 lat pisania w .NET widziałem to raz. I wiesz co? Repozytorium nie uratowało projektu, bo zapytania i tak były pisane pod konkretny silnik.

• "A testy?" Do testów masz dziś SQLite in-memory i Testcontainers z prawdziwym Postgresem czy SQL Serverem. Mockując własne repozytorium, nie testujesz SQL-a. Testujesz mocki.

Wersja slim. Dla kontrastu - tak może wyglądać całe "pobierz pracownika po ID":

app.MapGet("/employees/{id:int}", async (int id, AppDbContext db) =>
await db.Employees.FindAsync(id) is { } employee
? Results.Ok(employee)
: Results.NotFound());

Bez 6 dodatkowych plików. Endpoint, DbContext, wynik. 1 plik.

Kiedy interfejs ma sens? I znowu, dla jasności: w dużej domenie warstwa logiki ma sens. IPaymentProvider z implementacjami dla Stripe i PayU? Jak najbardziej. Interfejs broni się, gdy:

• jest prawdziwą granicą systemu: płatności, maile, storage - rzeczy spoza Twojego kodu,
• istnieją (albo na pewno będą) co najmniej 2 implementacje,
• to świadomie zaprojektowany punkt rozszerzeń (np. wtyczki).

Natomiast "bo tak się robi" - przy jednej implementacji dziś, jutro i za 5 lat - nie jest powodem. Interfejs to obietnica wymienności. Nie składaj obietnic, których nikt nie potrzebuje.

Chodzi o jedno: warstwa ma coś robić. Walidować, liczyć, decydować. Jeśli tylko podaje dalej, to nie jest architektura, tylko ceremonia. Każda warstwa i każdy interfejs to podatek, który płacisz przy każdej zmianie. Płać go tam, gdzie coś za niego dostajesz.

Dogmat #4: "komentarz to porażka"


Został dogmat czwarty: komentarz w kodzie to porażka, bo kod ma się tłumaczyć sam. To pół prawdy - zaraz zobaczysz którą połowę.

Kod świetnie mówi, co robi. Ale kod nigdy nie powie, dlaczego robi to akurat tak. Stąd prosty podział:

• komentarz opisujący, CO robi kod - zwykle zbędny (napraw to nazwami),
• komentarz opisujący, DLACZEGO - bezcenny i niezastąpiony.

Kodem nie wyrazisz kontekstu biznesowego, workaroundu, ustaleń z klientem ani numeru ticketu. Porównaj 2 komentarze. Pierwszy tylko powtarza kod:

/* pobierz pracownika po id */
var employee = await db.Employees.FindAsync(id);

Drugi wyjaśnia decyzję, której nie widać w kodzie:

/* Przelewy wysyłamy o 4:00, bo bank odrzuca paczki poza oknem 3:00-5:00.
Ustalone z bankiem 03.2025 - szczegóły w tickecie PAY-217. */
var sendTime = new TimeOnly(4, 0);

Pierwszy możesz spokojnie usunąć. Drugiego nie zastąpisz niczym. Spróbuj wyrazić to samo nazwą metody: SendPaymentsAtFourAmBecauseBankRejectsBatchesOutsideWindow... No właśnie.

Pamiętasz komentarz przy rabacie VIP z pierwszego przykładu? Powód biznesowy plus numer ticketu. Za pół roku ktoś Ci za niego podziękuje. Pewnie Ty sam.

Zasada jest krótka: kod mówi CO. Komentarz mówi DLACZEGO.

A co z wydajnością?


Krótko, bo to temat na osobny artykuł. W 2023 roku Casey Muratori pokazał głośny eksperyment "Clean Code, Horrible Performance". Wziął ten sam problem (sumowanie pól figur geometrycznych w C++) i napisał go na 2 sposoby: "po bożemu", czyli z polimorfizmem i małymi metodami, oraz prosto - ze switchem i tablicą. W jego benchmarku prosta wersja była kilkanaście razy szybsza.

Czy to znaczy, że masz pisać wszystko pod wydajność? Nie. Uczciwie: w typowym CRUD-zie wąskim gardłem jest baza danych i sieć, a nie wywołania wirtualne - i tak czekasz na bazę. Ale w hot pathach (parsery, przetwarzanie danych, gry) abstrakcje mają realną cenę.

To kolejny dowód na główną tezę tego artykułu: "zawsze rób X" nie istnieje w inżynierii. Istnieją trade-offy. Za każdą regułę ktoś płaci - pytanie tylko, czy wiesz czym.

Co z Clean Code warto zostawić


Na początku obiecałem, że nie palę książki - czas się z tego wywiązać. Bo w "Clean Code" jest złoto. Oto, co zostaje u mnie:

• Nazwy, które mówią, co się dzieje. To 80% czytelności i dostajesz je za darmo.

• Funkcja robi jedną rzecz. Świetny kompas, dopóki nie robisz z niego licznika linii.

• Spójny styl w całym projekcie.

• Zasada skauta. Zostaw kod odrobinę lepszym, niż go zastałeś.

• Testy tam, gdzie błąd naprawdę zaboli.

Problem nie leży w książce. Problem leży w czytaniu jej jak świętej księgi.

I teraz uwaga. Jeśli czytasz ten tekst z nadzieją, że powiem "wyrzuć to wszystko do kosza" - to właśnie Ty masz dziś zmienić zdanie. Bo dogmat w drugą stronę to dalej dogmat.

3 pytania zamiast 100 reguł


Zamiast 100 reguł z książki mam 3 pytania, które zadaję każdemu fragmentowi kodu - na review cudzego i swojego:

1. Czy przeczytam ten kod z góry na dół, bez skakania? To załatwia dogmat małych funkcji, bo mierzysz zrozumienie, a nie linie.

2. Czy typowa zmiana wymagań dotknie 1-2 miejsc, a nie 7 plików w 5 warstwach? To załatwia przerost abstrakcji i religijne DRY.

3. Czy za pół roku będzie jasne, DLACZEGO jest tak, a nie inaczej? Tu pomagają 3 rzeczy: dobre nazwy, komentarze "dlaczego" i test, który dokumentuje regułę biznesową.

3 razy "tak"? Twój kod jest czysty. Naprawdę czysty, a nie "czysty według checklisty". 3 × TAK = czysty kod - niezależnie od tego, czy metoda ma 5 linii, czy 30. Zapisz sobie te pytania i miej je pod ręką na następnym code review.

Ściąga: dogmat → co zamiast


Wszystkie 4 dogmaty w jednym miejscu:

DogmatKiedy szkodziCo zamiast
"Funkcja: max 5 linii"Metody wywoływane raz, 4 skoki po jedną odpowiedźDziel, gdy podział coś daje: reużycie, test w izolacji, inny poziom abstrakcji
"DRY. Zawsze."Scalanie różnych reguł biznesowych kończy się metodą z 5 flagamiDRY dla wiedzy, nie linijek. Osobny walidator na przypadek użycia, zasada trzech
"Interfejs i warstwa na wszystko"Warstwy przelotowe, repozytorium nad EF Core, 7 plików na 1 poleInterfejs na granicy systemu i przy 2+ implementacjach. Warstwa ma coś robić
"Komentarz to porażka"Nikt nie wie, dlaczego kod działa tak, a nie inaczejKod mówi CO, komentarz mówi DLACZEGO


Podsumowanie


"Clean Code" to nie jest zła książka. To zbiór wskazówek sprzed prawie 2 dekad, który potraktowaliśmy jak religię. Ale inżynieria nie działa na przykazaniach, tylko na kompromisach:

• Mała funkcja bywa świetna - i bywa labiryntem na 4 skoki.

• DRY bywa zbawieniem - i bywa potworem z 5 flagami.

• Interfejs bywa granicą systemu - i bywa pustą ceremonią.

• Komentarz bywa zbędny, gdy mówi CO - i bezcenny, gdy mówi DLACZEGO.

Różnica nigdy nie leży w regule. Leży w myśleniu. A jeśli potrzebujesz prostego narzędzia, wróć do 3 pytań: czy czytam bez skakania, czy zmiana dotknie 1-2 miejsc i czy za pół roku będzie wiadomo, dlaczego.

Jeśli lubisz takie konkrety, mam dla Ciebie coś więcej. Prowadzę społeczność ponad 10 000 programistów .NET. Raz w tygodniu dostajesz ode mnie jednego maila: najważniejsze newsy ze świata .NET bez przekopywania internetu, techniczne mięso jak w tym artykule i rabaty na moje kursy, których nie znajdziesz nigdzie indziej. Zero spamu - wypisujesz się jednym kliknięciem.

Jeśli chcesz dołączyć, zostaw swój adres tutaj: modestprogrammer.pl/vip. Powodzenia na następnym code review :)

Autor artykułu:
Kazimierz Szpin
Kazimierz Szpin
CTO & Founder - FindSolution.pl
Programista C#/.NET. Specjalizuje się w Blazor, ASP.NET Core, ASP.NET MVC, ASP.NET Web API, WPF oraz Windows Forms.
Autor bloga ModestProgrammer.pl
Dodaj komentarz
© Copyright 2026 modestprogrammer.pl | Sztuczna Inteligencja | Regulamin | Polityka prywatności. Design by Kazimierz Szpin. Wszelkie prawa zastrzeżone.