Projekt zaczyna się niewinnie, a po kilku sprintach pojawiają się interfejsy bez drugiej implementacji, konfiguracja pod trzy nieistniejące wdrożenia i moduły, których nikt nie potrafi już bezpiecznie zmienić. Zasada YAGNI pomaga odróżnić rozsądne przygotowanie architektury od kodowania hipotetycznej przyszłości. Pokażę, jak stosować ją w aplikacjach .NET, gdzie daje największe korzyści, a także kiedy jej bezrefleksyjne użycie może stworzyć dług techniczny.
Najważniejsze decyzje architektoniczne wynikają z realnych potrzeb
- YAGNI oznacza, że nie implementujemy funkcji, abstrakcji ani rozszerzeń, dopóki nie są rzeczywiście potrzebne.
- Zasada ogranicza złożoność, koszt utrzymania i liczbę miejsc podatnych na błędy.
- W .NET często dotyczy nadmiarowych interfejsów, generycznych repozytoriów, mikroserwisów i konfiguracji tworzonych na zapas.
- YAGNI nie oznacza rezygnacji z jakości, testów ani bezpieczeństwa. Oznacza świadome odraczanie niepotrzebnych decyzji.
- Nie warto stosować tej zasady do wymagań prawnych, bezpieczeństwa, obserwowalności ani kosztownych zmian infrastruktury.

Czym jest YAGNI i jaki problem rozwiązuje
YAGNI to skrót od You Ain’t Gonna Need It, czyli „prawdopodobnie nie będzie ci to potrzebne”. W anglojęzycznych materiałach spotkasz określenie yagni principle, ale w praktyce najważniejsze jest nie brzmienie nazwy, tylko decyzja stojąca za tą zasadą. Nie tworzę kodu na podstawie przypuszczeń, jeśli obecne wymagania go nie uzasadniają.
Najczęstszy problem nie polega na tym, że programista nie potrafi zaprojektować rozbudowanej architektury. Problem w tym, że projektuje ją za wcześnie. Dodanie obsługi wielu baz danych, kilku dostawców płatności czy własnego systemu pluginów może wyglądać profesjonalnie, ale każda taka decyzja zwiększa liczbę elementów, które trzeba testować, dokumentować i utrzymywać.
Ważny jest też koszt odwrócenia decyzji. Nie każda zmiana wymaga przygotowania z wyprzedzeniem. W dobrze testowanym kodzie zamiana jednej implementacji na inną może być prostsza niż utrzymywanie przez rok abstrakcji, która od początku nie miała realnego użytkownika.
Patrzę na YAGNI jako na zasadę zarządzania niepewnością. Nie przewiduję dokładnie, jak produkt będzie wyglądał za dwa lata, tylko buduję taki punkt startowy, który pozwala bezpiecznie zareagować, gdy pojawią się nowe wymagania.
Jak YAGNI wpływa na architekturę aplikacji .NET
W ekosystemie .NET łatwo wygenerować dużo kodu zanim powstanie pierwsza działająca funkcja. Szablony, biblioteki i popularne wzorce są użyteczne, ale mogą też zachęcać do dokładania warstw tylko dlatego, że są modne. Architektura powinna wynikać z problemu biznesowego, a nie z liczby projektów w solution.
Interfejs dopiero wtedy, gdy daje konkretną wartość
Interfejs ma sens, gdy istnieje potrzeba wymiany implementacji, izolowania zależności, testowania granicy albo komunikacji między modułami. Jeśli klasa ma jednego konsumenta i jedną implementację, dodatkowe IFoo może być tylko pośrednikiem utrudniającym nawigację po kodzie.
Nie traktuję tego jako zakazu stosowania interfejsów. Dla adaptera do zewnętrznego API, zegara systemowego czy repozytorium będącego granicą infrastruktury interfejs może być bardzo praktyczny. YAGNI pyta raczej, czy dana abstrakcja rozwiązuje dzisiejszy problem, a nie czy wygląda zgodnie z podręcznikowym wzorcem.
Repozytorium generyczne nie jest obowiązkowym elementem
Wiele projektów zaczyna się od generycznego repozytorium z metodami Add, Update, Delete i GetAll. Przy Entity Framework Core taka warstwa często powiela możliwości DbContext, ukrywa zapytania i utrudnia korzystanie z mechanizmów ORM.
Jeśli aplikacja ma złożone zapytania, lepiej zacząć od konkretnych operacji związanych z przypadkami użycia. Abstrakcja powinna ukrywać zmienność, a nie tylko przenosić wywołania z jednej klasy do drugiej.
Nie każda aplikacja potrzebuje mikroserwisów
Rozdzielenie systemu na mikroserwisy oznacza osobne wdrożenia, monitoring, komunikację, obsługę błędów i często większe wymagania dotyczące DevOps. Dla niewielkiego produktu modularny monolit może dać lepszą szybkość pracy i prostsze debugowanie.
W praktyce wolę najpierw wydzielić granice modułów w kodzie, a dopiero później rozważać procesy i wdrożenia niezależne od siebie. Możliwość późniejszego podziału nie wymaga budowania mikroserwisów od pierwszego dnia, ale wymaga sensownego uporządkowania zależności.
Praktyczne przykłady stosowania zasady w C#
Najłatwiej zrozumieć YAGNI na małych decyzjach. Każda z nich wydaje się nieistotna, ale wiele podobnych wyjątków potrafi zbudować system trudny do rozwijania.
Jeden sposób wysyłki wiadomości
Załóżmy, że aplikacja wysyła powiadomienia e-mail. Nie ma jeszcze SMS-ów, pushy ani integracji z komunikatorem. W takiej sytuacji prosty serwis może być wystarczający.
public sealed class EmailNotificationService
{
public Task SendAsync(string recipient, string message)
{
// Wysyłka wiadomości e-mail
return Task.CompletedTask;
}
}Tworzenie od razu modelu NotificationChannel, fabryki kanałów, konfiguracji priorytetów i dziesięciu interfejsów nie przybliża produktu do działania. Gdy pojawi się drugi kanał, można wydzielić wspólną abstrakcję na podstawie rzeczywistych różnic. Najpierw poznaję kształt problemu, potem uogólniam rozwiązanie.
Jedna reguła biznesowa nie potrzebuje silnika reguł
Jeżeli rabat wynika dziś z jednej prostej zasady, warunek w serwisie aplikacyjnym może być czytelniejszy niż własny silnik reguł. Rozbudowany mechanizm będzie uzasadniony dopiero wtedy, gdy reguły są liczne, zmienne, konfigurowalne przez użytkowników albo wymagają wersjonowania.
To nie znaczy, że należy pisać wszystko w kontrolerze. Regułę można umieścić w nazwanej metodzie domenowej lub serwisie, ale bez projektowania języka deklaratywnego, którego nikt jeszcze nie potrzebuje.
Jedna baza i jedna chmura na początek
W aplikacji wdrażanej w Azure ktoś może zaplanować jednocześnie obsługę SQL Database, PostgreSQL, Cosmos DB i lokalnego cache. Jeśli wymagania wskazują jedną relacyjną bazę danych, taki plan najczęściej tylko zwiększy koszty oraz liczbę decyzji operacyjnych.
Rozsądniej jest zadbać o backup, migracje, monitoring i limity kosztów dla używanej usługi. Przenośność do innego dostawcy może być ważna, ale powinna wynikać z umowy, regulacji, ryzyka biznesowego lub rzeczywistego planu migracji.
Funkcja AI bez całej platformy agentowej
Jeśli produkt potrzebuje jednego scenariusza generowania opisu, nie trzeba od razu budować orkiestratora agentów, systemu pluginów i abstrakcji dla pięciu modeli. Wystarczy ograniczona integracja z jasno określonym wejściem, wyjściem, logowaniem i obsługą błędów.
W tym obszarze szczególnie łatwo pomylić eksperyment z produktem. Najpierw mierzę jakość i koszt konkretnego przypadku użycia, a dopiero później inwestuję w routing modeli, pamięć konwersacji czy wieloetapowe workflow.
Jak stosować YAGNI bez tworzenia długu technicznego
Odraczanie funkcji nie może oznaczać ignorowania jakości kodu. Dobra implementacja może być mała, ale nadal powinna mieć czytelne nazwy, testy dla ważnych reguł i granice, które chronią system przed przypadkowym bałaganem.
Przed dodaniem abstrakcji zadaję sobie kilka prostych pytań:
- Czy istnieje dziś więcej niż jedna implementacja albo realny plan jej użycia?
- Czy ta decyzja wynika z wymagania biznesowego, technicznego lub prawnego?
- Czy późniejsza zmiana będzie droga, ryzykowna albo niemożliwa bez migracji danych?
- Czy dodatkowa warstwa upraszcza kod, czy tylko zmienia miejsce, w którym znajduje się ta sama logika?
- Czy potrafię wskazać konkretny przypadek użycia, który uzasadnia koszt rozwiązania?
Pomaga mi także rozróżnienie między decyzjami łatwymi i trudnymi do odwrócenia. Zmiana nazwy klasy albo wydzielenie interfejsu zwykle jest tania. Migracja danych, wybór modelu tożsamości, sposób przechowywania sekretów czy kontrakt publicznego API mogą wymagać wcześniejszego namysłu. YAGNI najlepiej działa przy decyzjach odwracalnych.
Warto zostawić ślad po świadomym odroczeniu. Krótka notatka w zadaniu, decyzji architektonicznej albo dokumentacji może wyjaśniać, dlaczego system obsługuje jeden wariant. Dzięki temu zespół nie będzie przypadkiem traktował ograniczenia jako przeoczenia.
Nie odkładam też fundamentów, które są tanie teraz, a drogie później. Dotyczy to między innymi automatycznych testów, logowania błędów, kontroli dostępu, migracji bazy i podstawowej obserwowalności. Brak funkcji to nie to samo co brak zabezpieczeń.
Kiedy YAGNI nie powinno decydować
Największy błąd polega na zamianie YAGNI w wymówkę dla krótkowzroczności. Zasada nie mówi, żeby ignorować wymagania, tylko żeby nie implementować rzeczy hipotetycznych. Są obszary, w których przewidywanie konsekwencji jest częścią profesjonalnej pracy.
Bezpieczeństwo i zgodność
Jeśli aplikacja przetwarza dane osobowe, płatności albo informacje poufne, mechanizmy ochronne trzeba zaplanować przed wdrożeniem. Nie odkładam szyfrowania, zarządzania sekretami, kontroli uprawnień czy audytowania tylko dlatego, że pierwszy użytkownik jeszcze nie zgłosił problemu.
Wymagania niefunkcjonalne
Wydajność, dostępność, limity i odzyskiwanie po awarii nie zawsze wymagają rozbudowanej architektury, ale powinny mieć określony poziom docelowy. Jeśli system musi obsłużyć 10 tysięcy żądań na minutę albo działać z dostępnością 99,9 procent, nie można udawać, że te wymagania pojawią się dopiero później.
Publiczne API i migracje danych
Każdy publiczny kontrakt może stać się zobowiązaniem na lata. Tutaj ostrożność jest rozsądna, szczególnie gdy z API korzystają zewnętrzni klienci. Podobnie z modelem danych. Zmiana schematu po uruchomieniu produktu bywa znacznie droższa niż przemyślenie kluczowych relacji i identyfikatorów wcześniej.
Przeczytaj również: Pętla do-while w C# - składnia, przykłady i pułapki
Decyzje narzucone przez infrastrukturę
Czasem konkretny wybór chmury, systemu kolejkowego albo dostawcy tożsamości wynika z umowy lub standardu organizacji. W takim przypadku nie buduję abstrakcji dla samej abstrakcji, ale też nie ignoruję ograniczeń, które mogą utrudnić zmianę. Trzeba zapisać, co jest świadomym kompromisem, a co faktyczną blokadą.
YAGNI, KISS, DRY i SOLID nie mówią tego samego
Te zasady często pojawiają się razem, lecz rozwiązują różne problemy. Ich mechaniczne stosowanie może prowadzić do sprzecznych decyzji, dlatego zawsze patrzę na konkretny koszt i korzyść.
| Zasada | Na czym się skupia | Praktyczne pytanie |
|---|---|---|
| YAGNI | Odraczaniu niepotrzebnych funkcji i abstrakcji | Czy naprawdę potrzebujemy tego teraz? |
| KISS | Prostocie rozwiązania | Czy da się to wyjaśnić i utrzymać prościej? |
| DRY | Ograniczaniu powielonej wiedzy | Czy jedna reguła nie występuje w kilku miejscach? |
| SOLID | Projektowaniu zależności i odpowiedzialności | Czy kod da się zmieniać bez naruszania całego systemu? |
Przykładowo, DRY może skłaniać do połączenia dwóch podobnych fragmentów, ale YAGNI przypomina, żeby nie tworzyć przedwcześnie uniwersalnego frameworka. Z kolei SOLID nie wymaga, by każda klasa miała interfejs. Dobra architektura jest kompromisem, a nie kolekcją zastosowanych akronimów.
Najbezpieczniejsza kolejność wygląda dla mnie tak. Najpierw rozumiem wymaganie, potem tworzę prostą implementację, a następnie obserwuję, gdzie naprawdę pojawia się powtarzalność lub zmienność. Dopiero wtedy refaktoryzuję. Testy i automatyzacja pozwalają zrobić to bez obawy, że każda poprawa naruszy istniejące zachowanie.
Najlepszy moment na rozszerzenie rozwiązania pojawia się wraz z dowodem
YAGNI nie zachęca do pisania byle jak. Zachęca do ograniczenia zakresu do tego, co ma dziś uzasadnienie, przy zachowaniu jakości, bezpieczeństwa i możliwości dalszej zmiany. W aplikacji .NET zwykle oznacza to mniej pustych interfejsów, mniej warstw tworzonych z przyzwyczajenia i więcej kodu, który odpowiada na realny przypadek użycia.
Gdy nowe wymaganie rzeczywiście się pojawi, decyzja architektoniczna będzie oparta na danych, a nie na wyobrażeniu przyszłości. To właśnie wtedy warto inwestować w abstrakcję, skalowanie lub dodatkowy moduł, bo system pokazał już, czego naprawdę będzie potrzebował.
