Czysty kod w C#: od .NET, CLR i BCL do Clean Architecture
Dlaczego mała zmiana w aplikacji potrafi zająć cały dzień? Zaczynamy od tego, jak działa .NET, a potem przechodzimy do czytelnego C#, granic odpowiedzialności i Clean Architecture. Z przykładami z codziennej pracy oraz sposobem na porządkowanie istniejącego kodu bez przepisywania wszystkiego od nowa.
Poniżej znajduje się pełna treść artykułu. Quizy i symulacje wymagają włączenia JavaScript.
Materiał specjalny · warsztat programisty
Kod, do którego można wrócić z czystą głową.
Poniedziałek, 9:12. Kawa jeszcze parzy. Na czacie pojawia się wiadomość: „Zmieniamy zasady anulowania rezerwacji. To chyba mała poprawka?”. Otwierasz projekt. Ta sama reguła mieszka w kontrolerze, zadaniu uruchamianym w tle i w panelu administratora. W każdym miejscu wygląda trochę inaczej.
Właśnie w takich chwilach sprawdza się jakość kodu. Gdy trzeba odnaleźć regułę, zrozumieć jej powód i zmienić zachowanie bez przypadkowego uszkodzenia reszty systemu. Przejdziemy tę drogę razem: od tego, co naprawdę robi .NET, przez czytelny C#, aż po granice, które pomagają rozwijać aplikację.
Autor: ArcCloud Studio · stan informacji o wersjach: 8 października 2026. Serwis rowerowy i scenki zespołowe są przykładem edukacyjnym, nie opisem projektu klienta.
Wybierz swój punkt wejścia
01 / Fundament
Czym jest .NET — i gdzie w tym wszystkim jest C#?
Przy pierwszym projekcie łatwo wrzucić do jednego worka C#, .NET, Visual Studio i ASP.NET Core. W pracy zespołowej to rozróżnienie szybko staje się przydatne. Kiedy aplikacja nie uruchamia się na serwerze, musimy wiedzieć, czy brakuje środowiska wykonawczego, czy wybraliśmy niewłaściwy system, czy też opublikowaliśmy niewłaściwy plik.
C# jest językiem, a .NET platformą do tworzenia i wykonywania aplikacji. W języku zapisujemy reguły. Platforma dostarcza środowisko wykonawcze, biblioteki oraz narzędzia. ASP.NET Core pomaga budować między innymi aplikacje HTTP i API. Visual Studio, Rider czy VS Code są narzędziami pracy; wybór edytora nie zmienia reguł biznesowych programu.
C#
Opisujesz zachowanie: rezerwację, anulowanie wizyty, obliczenie ceny. Kompilator sprawdza składnię i typy.
Runtime / CLR
Środowisko wykonuje kod zarządzany i zapewnia usługi potrzebne działającemu programowi.
SDK
Budujesz, uruchamiasz i publikujesz aplikację. SDK zawiera między innymi CLI, kompilator i środowisko wykonawcze.
Na komputerze programisty zwykle instalujemy SDK. Sposób uruchomienia na serwerze zależy od publikacji: aplikacja może korzystać z zainstalowanego runtime albo być dostarczona razem z nim. Dlatego zdanie „mam .NET” bywa zbyt mało precyzyjne. Lepiej ustalić wersję SDK, docelowy framework projektu oraz model wdrożenia.
Terminal · trzy pytania do środowiska
dotnet --list-sdks
dotnet --list-runtimes
dotnet --infoPierwsze polecenie mówi, czym możesz budować. Drugie — jakie środowiska wykonawcze są zainstalowane. Trzecie zbiera informacje pomocne przy diagnozie. To rozsądny początek, gdy projekt działa u jednej osoby, a u drugiej kończy się błędem, zanim dotrze do pierwszej linijki naszego kodu.
Dokumentacja: składniki platformy .NET oraz SDK i jego rola.
CLR i BCL: silnik oraz narzędzia, z których korzystasz codziennie
CLR, czyli Common Language Runtime, to środowisko wykonawcze dla kodu zarządzanego. We współczesnym .NET na typowym serwerze spotkasz implementację CoreCLR. Runtime zajmuje się między innymi wykonywaniem kodu, obsługą wyjątków, systemem typów i automatycznym zarządzaniem pamięcią. Szczegóły wykonania mogą się różnić zależnie od platformy i sposobu publikacji.
BCL, czyli Base Class Library, daje podstawowe typy i operacje. Gdy używasz string, List<T>, DateTimeOffset albo pracy na plikach przez System.IO, korzystasz z tego fundamentu. Własna klasa Booking opisuje Twój biznes. BCL dostarcza klocki, dzięki którym nie musisz samodzielnie pisać obsługi tekstu, kolekcji i dat.
Praktyczna różnica: „nie można znaleźć odpowiedniego runtime” to inna klasa problemu niż błędne porównanie dat w rezerwacji. Pierwszy dotyczy uruchomienia programu. Drugi — reguły zapisanej przez nas. Dobra diagnoza zaczyna się od ustalenia, na którym poziomie występuje błąd.
Co dzieje się po kliknięciu „Uruchom”?
W typowym modelu z JIT kompilator C# przygotowuje kod pośredni IL i metadane. Podczas działania kompilator JIT przekłada potrzebny kod na instrukcje właściwe dla procesora. Tak wygląda uproszczona ścieżka; szczegóły optymalizacji runtime są bogatsze niż ten jeden rysunek.
A co z Native AOT?
Native AOT przygotowuje kod natywny podczas publikowania, więc przy uruchomieniu nie korzysta z JIT. To może pomóc w czasie startu i zużyciu pamięci. Trzeba jednak sprawdzić zgodność bibliotek, użycie refleksji oraz ograniczenia dynamicznego ładowania kodu. Wynik publikacji jest związany z wybranym systemem i architekturą. AOT nadal korzysta z mechanizmów runtime, w tym GC. Decyzję warto oprzeć na pomiarze konkretnej aplikacji.
Garbage Collector sprząta pamięć. My nadal odpowiadamy za zasoby.
GC może odzyskać pamięć obiektów, do których aplikacja nie ma już osiągalnej drogi od swoich korzeni. Jeśli duży słownik lub obsługa zdarzeń nadal przechowuje referencję, obiekt może pozostać w pamięci znacznie dłużej, niż planowaliśmy. Automatyczne zarządzanie pamięcią nie zwalnia z myślenia o czasie życia danych.
Osobną sprawą są uchwyty i zasoby zewnętrzne. Plik powinien zostać zamknięty po zakończeniu pracy, a obiekt wymagający zwolnienia — użyty zgodnie ze swoim kontraktem. Konstrukcje using i await using pomagają wyrazić ten zamiar w kodzie. To również forma czytelności: widzimy, gdzie zasób jest potrzebny i gdzie kończy się jego życie.
Więcej: słownik .NET, wykonywanie kodu zarządzanego, Native AOT i podstawy GC.
02 / Wersje bez zgadywania
.NET Framework, .NET Core i .NET: skąd tyle nazw?
Przejmujesz aplikację, w repozytorium widzisz net48, w innym projekcie net8.0, a wspólna biblioteka celuje w netstandard2.0. Te zapisy opisują różne cele kompilacji. Samo podobieństwo nazw nie oznacza, że wszystkie projekty można bez przygotowania przenieść na ten sam serwer.
Tabela przewija się w bok →
| Rodzina | Co oznacza | Co sprawdzić w projekcie |
|---|---|---|
| .NET Framework | Rodzina związana z Windows. Najnowsza wersja to 4.8.1; wsparcie zależy od wspieranego systemu operacyjnego. | Zależności od Windows, starszego ASP.NET, bibliotek firm trzecich i sposobu wdrażania. |
| .NET Core | Historyczna linia 1.0–3.1, rozwijana jako platforma wielosystemowa. | Plan aktualizacji. Te wydania zakończyły okres wsparcia. |
| .NET 5 i nowszy | Kontynuacja rozwoju .NET Core pod krótszą nazwą. Współczesna platforma dla różnych systemów i rodzajów aplikacji. | Wersję docelową, wsparcie, pakiety i wymagania środowiska. |
| .NET Standard | Specyfikacja wspólnego zestawu API dla bibliotek. Nie jest środowiskiem do uruchamiania aplikacji. | Kto będzie używał biblioteki. Framework nie obsługuje .NET Standard 2.1. |
Nie było wydania „.NET Core 4”. Po .NET Core 3.1 przyszło .NET 5. Nazwy ASP.NET Core i Entity Framework Core zachowały człon „Core”, więc nadal zobaczysz go w nowych projektach. Wersja języka C# jest jeszcze innym pojęciem: projekt może używać konkretnej wersji języka, a jednocześnie celować w określony framework.
Co wybrać dziś?
Na dzień 8 października 2026 .NET 10 jest wydaniem LTS ze wsparciem do 14 listopada 2028. .NET 8 LTS oraz .NET 9 STS mają wsparcie do 10 listopada 2026. Dla nowej aplikacji warto ocenić .NET 10 LTS wraz ze zgodnością bibliotek i środowiska. W istniejącym projekcie na 8 lub 9 najważniejszy staje się konkretny plan aktualizacji i sprawdzenia zachowania po zmianie.
.NET 11 jest na ten dzień wydaniem zapowiadanym na listopad 2026. Materiał nie traktuje wersji przedpremierowej jako domyślnej podstawy produkcji. Przy późniejszej lekturze sprawdź bieżącą tabelę Microsoft; daty w artykule opisują moment jego przygotowania.
Dobry plan migracji zaczyna się od inwentaryzacji. Spisz pakiety, integracje, miejsca zależne od Windows, sposób budowania i scenariusze krytyczne dla firmy. Potem uruchom testy i zaplanuj możliwość powrotu do poprzedniej wersji. Samo zastąpienie net48 przez net10.0 w pliku projektu nie odpowiada na te pytania.
Jeśli utrzymujesz bibliotekę współdzieloną z aplikacją na .NET Framework, netstandard2.0 może nadal mieć uzasadnienie. Jeśli biblioteka służy wyłącznie współczesnemu .NET, wybór nowoczesnego celu pozwala korzystać z jego API. Decyzję podejmujemy na podstawie odbiorców biblioteki, a nie samego wieku nazwy.
Źródła wersji: oficjalna polityka wsparcia .NET, wsparcie .NET Framework i .NET Standard.
Z biurka programisty / poniedziałek, 10:05
„Znalazłem trzy miejsca. Teraz muszę ustalić, które z nich mówi prawdę”.
Zespół wraca do zmiany zasad anulowania. Jedna metoda porównuje godziny, druga daty kalendarzowe, trzecia patrzy tylko na status rezerwacji. Więcej czasu zajmuje zrozumienie obecnego zachowania niż napisanie nowego warunku. Tu zaczyna się rozmowa o czystym kodzie.
03 / Czytelność, która pomaga zmieniać
Co właściwie znaczy „czysty kod”?
Warto spojrzeć na kod oczami osoby, która za pół roku będzie naprawiać błąd. Być może będzie to nowy członek zespołu. Być może Ty, po kilku innych projektach i tygodniu urlopu. Czy szybko odnajdzie regułę? Czy zrozumie, skąd wzięła się liczba 24? Czy będzie mogła sprawdzić poprawkę bez ręcznego klikania przez całą aplikację?
Czytelność buduje się małymi decyzjami. Nazwa CanCancel mówi więcej niż Check. Parametr minimumNotice wyjaśnia sens wartości, która wcześniej była gołą liczbą. Krótka metoda jest pomocna wtedy, gdy skupia się na spójnym zadaniu. Rozdrobnienie kilku prostych linii na kilkanaście metod potrafi utrudnić lekturę.
W naszym przykładzie właściciel serwisu rowerowego ustalił, że klient może anulować wizytę najpóźniej 24 godziny przed rozpoczęciem. Musimy dopowiedzieć granice: dokładnie 24 godziny jest dozwolone; 24 godziny pomniejszone o jeden takt czasu już nie. Ponowne anulowanie wcześniej anulowanej rezerwacji powinno dać jednoznaczny wynik bez ponownego wykonywania skutków ubocznych.
Przed · odgadujesz znaczenie
if (b.S == 0 &&
(b.T - DateTime.Now).TotalHours > 24)
{
b.S = 1;
}Zapisz regułę zdaniem
„Potwierdzoną rezerwację można anulować co najmniej 24 godziny przed wizytą”.
Ustal statusy, granicę czasu i zachowanie przy ponowieniu. Dopiero potem dobierz nazwy oraz typy.
W tym krótkim fragmencie kryje się kilka decyzji, których nie widać od razu. Co oznacza status zero? Dlaczego operator jest ostrzejszy niż ustalona reguła? Czy czas jest lokalny? Skąd przyjdzie wartość „teraz” w teście? Poprawienie wcięć nie rozwiąże żadnej z tych wątpliwości.
Komentarz powinien zachować powód decyzji
Warto zapisać, dlaczego serwis potrzebuje 24 godzin: na przykład musi zwolnić stanowisko i poinformować mechanika. Taki komentarz pomaga przy późniejszej rozmowie o zmianie zasad. Komentarz „zmienia status na anulowany” nad metodą Cancel zwykle tylko powtarza kod.
Podobnie działa unikanie duplikacji. Ta sama reguła biznesowa powinna mieć jedno rozpoznawalne źródło. Jednak dwa podobne fragmenty obsługujące odmienne procesy nie zawsze powinny zostać połączone. Rozliczenie wizyty prywatnej i obsługa kontraktu flotowego mogą dziś wyglądać podobnie, lecz rozwijać się według innych zasad. Wspólny abstrakcyjny „proces wszystkiego” często ukrywa tę różnicę.
Czy SOLID nadal pomaga?
Tak, jeśli używasz tych zasad do stawiania pytań. SRP pomaga zauważyć, że zmiana regulaminu anulowania i zmiana dostawcy poczty mają różnych właścicieli. ISP zachęca do małych kontraktów potrzebnych konkretnemu odbiorcy. DIP pomaga oddzielić przypadek użycia od szczegółów zapisu. LSP przypomina, że implementacja musi dotrzymywać obietnic kontraktu. OCP skłania do szukania rozszerzeń w miejscach, które faktycznie często się zmieniają. Każda nowa warstwa powinna jednak rozwiązywać istniejący problem, a jej koszt musi być zrozumiały dla zespołu.
Najpierw reguła. Potem wygodna droga do jej użycia.
Zanim dodamy bazę, kontroler i wysyłkę e-maila, możemy sprawdzić samą decyzję. Poniższy fragment jest niezależnym przykładem reguły czasu. Pełny projekt do pobrania dalej w artykule rozwija ją o stan rezerwacji i przypadek użycia.
C# · jawna reguła i jawny czas
public static bool HasEnoughNotice(
DateTimeOffset startsAt,
DateTimeOffset now,
TimeSpan minimumNotice)
{
if (minimumNotice < TimeSpan.Zero)
throw new ArgumentOutOfRangeException(
nameof(minimumNotice));
return startsAt - now >= minimumNotice;
}
var allowed = HasEnoughNotice(
startsAt,
now,
TimeSpan.FromHours(24));Przekazujemy czas jako wartość, więc test nie musi czekać do jutra ani zmieniać zegara komputera. DateTimeOffset opisuje chwilę wraz z przesunięciem względem UTC; porównanie uwzględnia to przesunięcie. Nie jest jednak pełnym modelem strefy czasowej. Jeśli użytkownik rezerwuje cykliczne wizyty „o 9:00 w Warszawie”, potrzebujemy również reguł tej strefy, w tym zmiany czasu.
Regułę biznesową doprecyzowaliśmy jako 24 godziny faktycznie pozostałego czasu. Gdyby regulamin mówił „do końca poprzedniego dnia roboczego”, model musiałby wyglądać inaczej. Kalendarz świąt, strefa czasowa i godziny pracy stałyby się częścią problemu. Dobra implementacja zaczyna się od właściwego pytania do biznesu.
„Teraz” nie powinno pochodzić z formularza klienta. Zaufane źródło czasu wybiera aplikacja. W .NET można skorzystać z TimeProvider, a w teście kontrolować jego wartość. Użytkownik podaje identyfikator rezerwacji; serwer ustala czas i sprawdza uprawnienia.
Czy taka metoda od razu potrzebuje osobnego interfejsu? Niekoniecznie. Czysta funkcja na wartościach jest łatwa do uruchomienia i zrozumienia. Interfejs przydaje się przede wszystkim na granicy z zależnością, którą chcemy zastępować lub ukryć: magazynem danych, zegarem, usługą zewnętrzną. Nadmiar kontraktów ma własny koszt czytania.
O kontrolowaniu czasu: TimeProvider w dokumentacji Microsoft.
04 / Granice odpowiedzialności
C# Clean Architecture w praktyce
Aplikacja rośnie. Dochodzi panel obsługi, kalendarz, powiadomienia i integracja z płatnościami. Chcemy zachować możliwość zrozumienia reguły anulowania bez przeglądania konfiguracji poczty. Clean Architecture pomaga ułożyć zależności tak, żeby szczegóły techniczne nie przejmowały kontroli nad logiką biznesową.
Najważniejsza zasada dotyczy kierunku zależności w kodzie: prowadzą do wewnątrz, w stronę reguł i przypadków użycia. Domena nie musi znać kontrolera HTTP ani Entity Framework. Przypadek użycia może zdefiniować potrzebny kontrakt zapisu, a infrastruktura dostarczyć jego implementację.
API / interfejs oraz Infrastructure
HTTP, dostęp do bazy, e-mail, usługi zewnętrzne. Adaptery dopasowują świat zewnętrzny do potrzeb aplikacji.
Application
„Anuluj rezerwację”: pobierz, zapytaj domenę, spróbuj zapisać, zwróć wynik.
Domain
Status rezerwacji, termin wizyty i wymagane wyprzedzenie. Bez zależności od HTTP oraz konkretnej bazy.
Schemat pokazuje zależności kodu. Przepływ wywołań podczas działania może prowadzić z Application przez interfejs do adaptera Infrastructure.
Ten ostatni szczegół często wywołuje zamieszanie. „Przecież aplikacja musi wywołać bazę!” — tak, ale nie musi znać konkretnej klasy obsługującej bazę. Interfejs jest kontraktem. Adapter realizuje kontrakt. Kod uruchomieniowy, nazywany czasem composition root, łączy elementy przez dependency injection.
W małym projekcie granice możemy zacząć wyrażać modułami i folderami. Oddzielne projekty pomagają później wymusić reguły referencji na poziomie kompilacji. Istotne jest, żeby katalog Domain nie importował po cichu kontekstu bazy, a nazwa Application nie stała się nową szufladą na wszystko.
Przykładowy podział · zależności projektów
Domain → biblioteki bazowe .NET
Application → Domain
Infrastructure → Application, Domain
Demo / API → Application, Infrastructure
+ rejestracja zależnościTaka aplikacja może pozostać jednym wdrażanym procesem. Modularny monolit często daje wystarczającą kontrolę nad granicami, bez dodatkowej komunikacji sieciowej między usługami. Wydzielenie mikroserwisu ma sens dopiero wtedy, gdy mamy ku temu konkretne powody: odpowiedzialność zespołu, niezależne wdrażanie lub odmienne wymagania operacyjne.
Kierunek zależności i przykłady podziału: architektury aplikacji webowych w dokumentacji Microsoft.
05 / Jeden przypadek użycia
Prześledźmy anulowanie rezerwacji od początku do końca
Klient wybiera rezerwację i naciska „Anuluj”. W kompletnej aplikacji warstwa wejściowa najpierw potwierdza jego tożsamość i prawo do tej konkretnej rezerwacji. Sam poprawny identyfikator nie jest uprawnieniem. Dalej przekazuje żądanie do przypadku użycia, który prowadzi operację.
- Wejście.API odczytuje identyfikator, waliduje żądanie i sprawdza autoryzację do zasobu.
- Odczyt.Application pobiera rezerwację przez kontrakt repozytorium. Brak rekordu jest jawnym wynikiem.
- Decyzja.Domena sprawdza stan oraz czas. To tutaj mieszka reguła 24 godzin.
- Zapis.Adapter próbuje utrwalić nowy stan z kontrolą wersji. Konflikt nie jest cichym sukcesem.
- Odpowiedź.API zamienia wynik na uzgodniony kontrakt HTTP. Interfejs pokazuje użytkownikowi, co się wydarzyło.
To dobra chwila, by nazwać wyniki operacji. Cancelled, AlreadyCancelled, TooLate, NotFound i Conflict mówią, z czym mamy do czynienia. Oczekiwana odmowa biznesowa nie musi być wyjątkiem. Nieoczekiwana awaria połączenia z bazą wymaga osobnej obsługi i nie powinna udawać, że rezerwacji po prostu nie znaleziono.
Zobacz fragment domeny z projektu do pobrania
Domain/Booking.cs · metoda klasy Booking
public BookingCancellation Cancel(
DateTimeOffset now,
CancellationPolicy policy)
{
if (Status == BookingStatus.Cancelled)
return new(this,
CancellationDecision.AlreadyCancelled);
if (StartsAt <= now)
return new(this,
CancellationDecision.VisitStarted);
if (StartsAt - now < policy.MinimumNotice)
return new(this,
CancellationDecision.TooLate);
return new(
this with { Status = BookingStatus.Cancelled },
CancellationDecision.Cancelled);
}W przykładzie Booking jest rekordem. Udana decyzja tworzy nową wartość z anulowanym statusem. Pozostałe decyzje zachowują dotychczasowy obiekt. Pełne typy, konstruktory i kontrakty znajdują się w archiwum.
Co daje kontrakt repozytorium?
W naszym warsztacie przypadek użycia potrzebuje dwóch możliwości: znalezienia rezerwacji i próby zapisania nowego stanu z oczekiwaną wersją. Taki mały kontrakt jest czytelny. Nie budujemy uniwersalnego repozytorium dla każdego typu danych, z dziesiątkami metod, których nikt tutaj nie używa.
W rzeczywistej aplikacji można wybrać inny kompromis, na przykład bezpośrednie użycie EF Core w określonym module. Trzeba wtedy świadomie przyjąć tę zależność i sposób testowania. Clean Architecture nie wymaga kupienia konkretnej biblioteki, dodania mediatora ani stworzenia interfejsu do każdej klasy. Ma pomóc zespołowi utrzymać zrozumiałe granice.
Co, jeśli dwie osoby nacisną przycisk jednocześnie?
Odczyt może być aktualny tylko przez chwilę. Pomiędzy pobraniem rezerwacji a zapisem ktoś inny zmieni jej stan. Dlatego w przykładzie przenosimy wersję odczytanego rekordu do próby zapisu. Adapter powinien rozpoznać, że oczekiwana wersja już nie obowiązuje. Kod aplikacji musi wiedzieć, czy ponowić odczyt, czy zwrócić konflikt.
W adapterze produkcyjnym warunek wersji musi zostać sprawdzony atomowo przez mechanizm zapisu. Samo osobne zapytanie „czy wersja się zgadza?” nie zabezpiecza późniejszej aktualizacji. Przykład w pamięci pokazuje kontrakt, lecz nie zastępuje testu transakcji i współbieżności na rzeczywistym silniku bazy.
Z biurka programisty / piątek, 16:40
Rezerwacja anulowana. E-mail nie dotarł. Co ma zobaczyć klient?
Dostawca poczty przestał odpowiadać już po zapisie w bazie. Przycisk nadal czeka. Klient odświeża stronę i próbuje jeszcze raz. To moment, w którym granice transakcji i sposób obsługi ponowień zaczynają mieć większe znaczenie niż elegancki wygląd diagramu.
Ładny kod musi spotkać się z rzeczywistym światem
Zapis rezerwacji i wysłanie wiadomości przez zewnętrznego dostawcę zwykle nie tworzą jednej wspólnej transakcji. Jeśli potraktujemy je jak nierozłączną operację bez planu obsługi awarii, możemy pokazać błąd mimo skutecznie zapisanej zmiany. Użytkownik nie wie wtedy, czy powinien powtórzyć żądanie.
Jednym z rozwiązań jest outbox: razem ze zmianą stanu zapisujemy w tej samej transakcji informację, że trzeba wysłać powiadomienie. Osobny proces próbuje je dostarczyć i zapisuje postęp. Pozostają jednak pytania o ponowienia, duplikaty, identyfikator operacji, limit prób i wykrywanie zakleszczonych zadań. Samo nazwanie tabeli „Outbox” nie zapewnia jednokrotnego dostarczenia wiadomości.
W tym warsztacie celowo zatrzymujemy przykład przed integracją z pocztą. Chcemy dobrze rozumieć jedną granicę, a potem jawnie rozszerzać zakres. Kod do pobrania nie zawiera produkcyjnego logowania, kontroli właściciela rezerwacji, bazy SQL ani płatności. Te elementy trzeba zaprojektować i sprawdzić w kontekście konkretnej usługi.
Async, wyjątki i logi też wpływają na czytelność
Przy operacjach wejścia i wyjścia warto konsekwentnie przekazywać CancellationToken oraz stosować asynchroniczne API. Słowo async nie przyspiesza samo w sobie obliczeń ani nie oznacza automatycznego uruchomienia nowego wątku. Opisuje sposób współpracy kodu podczas oczekiwania na zakończenie operacji.
Podobnej dyscypliny wymaga obsługa błędów. Pusty catch może uciszyć alarm, ale pozostawia użytkownika w niepewnym stanie. Log powinien pomóc odtworzyć przebieg: identyfikator operacji, etap i wynik. Nie potrzebuje pełnego hasła, tokena ani zawartości całego formularza. Diagnozowalność warto zaprojektować razem z przypadkiem użycia.
Tło techniczne: częściowe awarie i odporność aplikacji.
06 / Sprawdź, zanim uznasz zmianę za gotową
Testy, które odpowiadają na pytania zespołu
Najłatwiej napisać test, który potwierdza szczęśliwy przypadek. W naszym serwisie rezerwacja na przyszły tydzień daje się anulować. To dobry początek, lecz najwięcej nieporozumień zwykle kryje się na granicy reguły i przy powtórzeniu operacji.
Tabela przewija się w bok →
| Sytuacja | Oczekiwane zachowanie | Co sprawdzamy |
|---|---|---|
| Dokładnie 24 godziny do wizyty | Anulowanie dozwolone | Granicę wynikającą z regulaminu |
| 24 godziny minus jeden takt | Odmowa: za późno | Dokładność porównania bez zaokrąglania |
| Ta sama chwila, inne przesunięcie UTC | Taki sam wynik | Porównanie chwil, a nie wyglądu zegara |
| Rezerwacja już anulowana | Jawny wynik ponowienia | Brak ponownej zmiany stanu |
| Rekord zmieniono po odczycie | Rozpoznany konflikt | Kontrakt kontroli wersji |
| Udane anulowanie i ponowny odczyt | Widoczny anulowany stan | Rzeczywisty zapis w użytym adapterze |
Test domeny można uruchomić bez serwera. Test przypadku użycia sprawdza współpracę reguły, zegara i kontraktu zapisu. Test integracyjny adaptera powinien odpowiedzieć, czy nasze zapytania, transakcje i ograniczenia działają na wybranym silniku bazy. Wreszcie test ścieżki użytkownika sprawdza, czy ekran potrafi pokazać sukces, odmowę oraz błąd.
Nie warto mieszać tych dowodów. Zielony wynik na kolekcji w pamięci nie potwierdza poprawności zapytania SQL. Baza testowa o innym silniku może inaczej traktować porównania, typy lub transakcje. Również stuprocentowe pokrycie linii nie odpowiada samo w sobie na pytanie, czy sprawdziliśmy najważniejsze ryzyka.
Warsztat do pobrania · C#
Serwis rowerowy: mały projekt, pełna droga jednej decyzji
Archiwum zawiera Domain, Application, adapter w pamięci, program demonstracyjny i scenariusze kontrolne. Bez dodatkowych pakietów NuGet. Możesz prześledzić zależności, zmienić zasady i sprawdzić, które oczekiwania przestały obowiązywać.
Pobierz projekt C# i instrukcję ZIP ↓Docelowy framework projektu: .NET 10. Instrukcja README opisuje wymagania, uruchomienie, wykonaną weryfikację i ograniczenia demonstracji.
Ćwiczenie na spokojne pół godziny
Właściciel chce zmienić wyprzedzenie z 24 na 12 godzin. Zanim poprawisz kod, zapisz przykłady dla dokładnie 12 godzin i chwili tuż poniżej tej granicy. Następnie zmień politykę oraz uruchom scenariusze. Zwróć uwagę, czy musisz edytować kontroler, adapter i model jednocześnie. Jeśli tak, poszukaj miejsca, w którym reguła wydostała się poza swoją odpowiedzialność.
Druga część ćwiczenia jest trudniejsza: nowe zasady mają dotyczyć tylko rezerwacji utworzonych po określonej dacie. Globalna zmiana jednej wartości może teraz naruszyć wcześniej zaakceptowane warunki. Być może rezerwacja powinna pamiętać wersję polityki. Ten przykład pokazuje, dlaczego warto pytać o znaczenie zmiany, zanim zachwycimy się jej prostotą w kodzie.
O doborze dowodów: strategia testowania aplikacji używających EF Core.
Przerwa na praktykę
Trzy decyzje przy prawdziwym biurku
Wybierz odpowiedź i przeczytaj uzasadnienie. Możesz próbować ponownie. Ćwiczenie działa lokalnie w przeglądarce; nie jest oceną umiejętności ani certyfikatem.
1. Gdzie umieścisz regułę 24 godzin?
Wyłącznie w przycisku i formularzu Angulara.
W domenie, wywoływanej przez przypadek użycia.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
2. Chcesz wykryć błąd transakcji w adapterze SQL.
Dodam test integracyjny na właściwym silniku bazy.
Zamienię bazę na List<Booking> i uznam temat za zamknięty.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
3. Mała aplikacja ma dwa proste formularze i jeden zespół.
Od razu rozdzielę ją na kilka mikroserwisów.
Zacznę od prostych modułów i sprawdzę, gdzie potrzebne są granice.
Wybierz odpowiedź, aby zobaczyć uzasadnienie.
Zacznij ponownie
Odpowiedz na trzy pytania. Przy każdej decyzji zobaczysz wyjaśnienie.
Odpowiedzi i wyjaśnienia w wersji tekstowej
1. Reguła należy do domeny wywoływanej przez przypadek użycia. Walidacja formularza wspiera obsługę, ale nie zabezpiecza serwera. 2. Zachowanie transakcji wymaga testu adaptera na właściwym silniku w osobnej bazie testowej. 3. Proste moduły mogą wystarczyć; granice rozbudowujemy wraz z potrzebą. Te odpowiedzi są dostępne również bez interaktywnych przycisków.
Z biurka programisty / pierwsza środa w nowym zespole
„Znalazłam CancelBooking. Widzę regułę i test jej granicy”.
Nowa osoba nie zna jeszcze historii projektu. Mimo to potrafi wskazać miejsce zmiany i wyjaśnić jej konsekwencje. Taki moment jest dobrym sygnałem jakości: wiedza o działaniu systemu jest dostępna w kodzie, testach i krótkiej dokumentacji, zamiast pozostawać wyłącznie w pamięci jednej osoby.
07 / Projekt, który już zarabia
Jak zacząć refaktoryzację bez przepisywania wszystkiego?
W istniejącej aplikacji największą wartość często ma spokojne uporządkowanie jednego często zmienianego procesu. Wybierz miejsce, w którym regularnie pojawiają się błędy, trudno dopisać test albo prosta prośba wymaga edycji wielu modułów. Zamiast ruszać cały system, narysuj drogę jednej operacji: od wejścia przez regułę do zapisu i odpowiedzi.
Na początku zapisz to, co aplikacja robi dzisiaj. Testy charakterystyki pomagają uchwycić obecne zachowanie, również wtedy, gdy dokumentacja jest niepełna. Nie każdy zastany szczegół jest zamierzony, więc odstępstwa od oczekiwań trzeba omówić. Podczas refaktoryzacji porządkujemy strukturę, zachowując ustalone zachowanie. Zmianę reguł biznesowych dobrze wyodrębnić, żeby odbiorca wiedział, co właściwie zatwierdza.
- Rozpoznaj proces.Wejścia, dane, reguły, integracje i skutki uboczne. Dopisz właściciela decyzji biznesowej.
- Zabezpiecz obecne zachowanie.Dodaj testy ważnych scenariuszy i sposób odtworzenia zgłaszanego problemu.
- Wydziel jedną odpowiedzialność.Przenieś regułę lub adapter małym krokiem. Utrzymuj działającą aplikację po każdej zmianie.
- Sprawdź granice.Odmowa, brak danych, konflikt, ponowienie i awaria zależności. Dopasuj testy do ryzyka.
- Wdróż i obserwuj.Zachowaj możliwość powrotu oraz informacje pozwalające ocenić zachowanie po wdrożeniu.
Code review jako rozmowa o przyszłej zmianie
W przeglądzie kodu warto zapytać: „Co stanie się, gdy zapis się nie powiedzie?”, „Czy ta metoda może zostać wywołana drugi raz?” i „Który test potwierdza tę granicę?”. Takie pytania pomagają autorowi doprecyzować rozwiązanie. Dyskusja wyłącznie o kolejności nawiasów jest mniej użyteczna; mechaniczne zasady formatowania można powierzyć narzędziom.
Dobry opis zmiany mieści w kilku zdaniach problem, nowe zachowanie i sposób sprawdzenia. Kiedy pojawia się trudna decyzja, krótka notatka architektoniczna może zachować kontekst: co wybraliśmy, dlaczego i kiedy warto wrócić do tematu. Dzięki temu kolejna osoba nie musi odtwarzać wielotygodniowej rozmowy z historii komunikatora.
Mała lista do następnego pull requestu
- Czy nazwy opisują pojęcia używane przez biznes?
- Czy reguła ma jedno rozpoznawalne źródło?
- Czy czas i zależności można kontrolować w teście?
- Czy odmowa, błąd techniczny i konflikt mają czytelne wyniki?
- Czy sprawdziliśmy prawo do zasobu, a nie tylko samo zalogowanie?
- Czy wiadomo, jak bezpiecznie wdrożyć zmianę i ją wycofać?
- Czy opisaliśmy ograniczenia testów oraz pozostałe ryzyka?
Kiedy wystarczy prostsze rozwiązanie?
Nie każda aplikacja potrzebuje rozbudowanego modelu domenowego. Panel z kilkoma prostymi operacjami CRUD może być czytelny z niewielką liczbą warstw. Dobrze nazwane przypadki użycia, konsekwentna walidacja i testy krytycznych ścieżek bywają wystarczającym punktem startu.
Mocniejsze granice stają się wartościowe, gdy rosną reguły, integracje, liczba kanałów wejściowych lub liczba zespołów. Wtedy koszt ustalenia, co wolno zmienić, może przewyższać koszt dodatkowych kontraktów. Architektura powinna odpowiadać tej konkretnej sytuacji.
Warto również zostawić przestrzeń na stopniowy rozwój. Nie musisz przewidzieć wszystkich przyszłych dostawców płatności. Możesz jednak oddzielić własny proces rozliczenia od szczegółów dzisiejszego SDK. Dzięki temu przy zmianie dostawcy wiesz, od którego miejsca zacząć analizę. Nadal trzeba sprawdzić różnice kontraktów, danych i zachowania — sam interfejs ich nie usuwa.
Gdy jutro zmieni się jedna reguła, czy zespół będzie wiedział, gdzie ją poprawić i jak potwierdzić, że reszta nadal działa?
Od lektury do własnego projektu
Masz system, w którym każda zmiana ciągnie za sobą kolejne poprawki?
Pokaż nam jeden konkretny proces: logowanie, rezerwację, zamówienie, płatność albo integrację. W ArcCloud Studio pracujemy z C#, .NET, Angularem oraz środowiskami AWS i Azure. Możemy sprawdzić, gdzie mieszają się odpowiedzialności, jakie problemy wpływają na użytkowników i od czego warto zacząć naprawy.
Ustalamy, co sprawdzić
Cel biznesowy, objawy, dostępne materiały i uzgodniony zakres. Dostępy oraz poufność ustalamy przed przekazaniem wrażliwych informacji.
Opisujemy konkretne działania
Problemy, ich wpływ, priorytety, proponowane poprawki i kryteria odbioru. Zakres wdrożenia, cenę i termin uzgadniamy osobno.
Sprawdzamy efekt zmiany
Po akceptacji możemy wdrożyć poprawki, wykonać uzgodnione testy i przekazać dokumentację potrzebną zespołowi.
Przegląd architektury i kodu wymaga dostępu do odpowiednich materiałów oraz wspólnie ustalonego zakresu. Automatyczny pomiar publicznego adresu URL nie zastępuje takiej pracy.
Do pierwszej wiadomości wystarczy opis problemu, używane technologie i oczekiwany efekt. Nie przesyłaj haseł ani kluczy dostępu przez publiczny formularz.
Jeśli chcesz pójść o krok dalej
Wróć do diagramu i prześledź jeden proces we własnym projekcie. Potem sprawdź nasz tekst o decyzjach architektonicznych i dokumentowaniu ich powodów albo warsztat o kolejkach, ponowieniach i odporności integracji.
Źródła Microsoft są podlinkowane przy odpowiednich rozdziałach. Historie, układ warsztatu oraz kod demonstracyjny przygotowaliśmy na potrzeby tego materiału. Grafika okładkowa jest ilustracją koncepcyjną przygotowaną z pomocą AI; nie przedstawia systemu klienta.
Jeśli po lekturze potrafisz wyjaśnić różnicę między językiem, runtime i bibliotekami, wskazać właściciela reguły oraz dobrać test do ryzyka, masz dobry punkt wyjścia do następnej zmiany. Zacznij od jednego procesu. Zobacz, co stanie się prostsze do zrozumienia.