Powszechne Zasady Projektowe w C# — DRY, KISS, YAGNI i 4 inne
Zanim poznasz nazwane wzorce projektowe (Strategy, Observer, Decorator), warto znać kilka prostszych, bardziej ogólnych zasad, które stoją za nimi wszystkimi. To nie formalne wzorce z diagramami UML — to praktyczne reguły, które prawdopodobnie już stosujesz intuicyjnie, tylko nie wiedziałeś, że mają nazwy.
DRY — Don’t Repeat Yourself
Ta sama logika nie powinna istnieć w dwóch miejscach. Nie chodzi o unikanie powtórzenia dwóch identycznych linijek — chodzi o unikanie powtórzenia tej samej decyzji biznesowej w wielu miejscach.
// ❌ Ta sama walidacja skopiowana w dwóch metodach
public void UtworzUzytkownika(string email)
{
if (string.IsNullOrWhiteSpace(email) || !email.Contains('@'))
throw new ArgumentException("Nieprawidłowy email");
// ...
}
public void ZaktualizujEmail(string email)
{
if (string.IsNullOrWhiteSpace(email) || !email.Contains('@'))
throw new ArgumentException("Nieprawidłowy email");
// ...
}
// ✅ Reguła w jednym miejscu — zmiana wymagań to jedna edycja, nie polowanie na kopie
private static void WalidujEmail(string email)
{
if (string.IsNullOrWhiteSpace(email) || !email.Contains('@'))
throw new ArgumentException("Nieprawidłowy email");
}
public void UtworzUzytkownika(string email) { WalidujEmail(email); /* ... */ }
public void ZaktualizujEmail(string email) { WalidujEmail(email); /* ... */ }
Gdy reguła walidacji się zmieni (np. dojdzie sprawdzenie domeny), poprawiasz jedno miejsce zamiast pamiętać o wszystkich kopiach.
KISS — Keep It Simple, Stupid
Nie komplikuj kodu wzorcami i abstrakcjami, których problem nie potrzebuje.
// ❌ Fabryka i interfejs dla jednej, prostej operacji, która się nie zmienia
public interface IDodawacz { int Dodaj(int a, int b); }
public class DodawaczFactory { public static IDodawacz Utworz() => new Dodawacz(); }
public class Dodawacz : IDodawacz { public int Dodaj(int a, int b) => a + b; }
var wynik = DodawaczFactory.Utworz().Dodaj(2, 3);
// ✅ To wszystko, czego potrzeba
int Dodaj(int a, int b) => a + b;
var wynik = Dodaj(2, 3);
Interfejs i fabryka mają sens, gdy istnieje realna potrzeba wymienności implementacji. Dodawanie ich “na wszelki wypadek” to złożoność bez korzyści — dokładnie to, przed czym ostrzega KISS.
YAGNI — You Aren’t Gonna Need It
Nie buduj funkcjonalności, której nikt jeszcze nie potrzebuje, “bo może się przyda w przyszłości”.
// ❌ Generyczny, konfigurowalny system eksportu — dla jednego formatu, którego potrzebujesz DZIŚ
public interface IEksporter<T> { string Eksportuj(T dane, EksportFormat format, EksportOpcje opcje); }
// ...trzy klasy pomocnicze, żeby wyeksportować listę do CSV, którego jedyny użytkownik nigdy nie poprosił o nic innego
// ✅ Dokładnie to, co jest potrzebne teraz
string EksportujDoCsv(List<Zamowienie> zamowienia)
=> string.Join("\n", zamowienia.Select(z => $"{z.Id},{z.Kwota}"));
Jeśli za pół roku faktycznie pojawi się potrzeba eksportu do XML, dodasz ją wtedy — z pełną wiedzą o tym, jak naprawdę ma wyglądać, zamiast zgadywać dziś.
Tell, Don’t Ask
Mów obiektowi, co ma zrobić, zamiast wypytywać go o stan i decydować za niego z zewnątrz.
// ❌ "Ask" — kod zewnętrzny odpytuje stan i sam podejmuje decyzję
if (koszyk.Suma > 0 && koszyk.Produkty.Count > 0 && !koszyk.JestZamkniety)
{
koszyk.JestZamkniety = true;
magazyn.ZarezerwujProdukty(koszyk.Produkty);
}
// ✅ "Tell" — obiekt sam wie, kiedy operacja jest dozwolona
public class Koszyk
{
public bool JestZamkniety { get; private set; }
private List<Produkt> _produkty = new();
public void Zlozz(Magazyn magazyn)
{
if (JestZamkniety || !_produkty.Any())
return; // logika decyzyjna ŻYJE tam, gdzie żyją dane
JestZamkniety = true;
magazyn.ZarezerwujProdukty(_produkty);
}
}
koszyk.Zlozz(magazyn); // wywołujący nie musi znać żadnej z tych reguł
Gdy logika decyzyjna mieszka razem z danymi, których dotyczy, nie da się jej przypadkowo obejść z zewnątrz — a zmiana reguły wymaga edycji jednego miejsca.
SoC — Separation of Concerns
Różne odpowiedzialności (walidacja, logika biznesowa, zapis do bazy, wysyłka e-maila) powinny żyć w różnych, wyspecjalizowanych miejscach, nie w jednej metodzie.
// ❌ Wszystko naraz — walidacja, logika, zapis i powiadomienie w jednej metodzie
public void ZlozZamowienie(Zamowienie z)
{
if (z.Pozycje.Count == 0) throw new Exception("Puste zamówienie");
z.Suma = z.Pozycje.Sum(p => p.Cena);
_baza.Zapisz(z);
_email.Wyslij(z.KlientEmail, "Potwierdzenie zamówienia");
}
// ✅ Każda odpowiedzialność w swoim miejscu
public void ZlozZamowienie(Zamowienie z)
{
_walidator.Waliduj(z);
_kalkulator.PoliczSume(z);
_repozytorium.Zapisz(z);
_powiadomienia.WyslijPotwierdzenie(z);
}
Druga wersja jest dłuższa liniowo, ale każdy fragment logiki można zmienić, przetestować i zrozumieć niezależnie od pozostałych.
SoC a Single Responsibility Principle — czym się różnią
To pokrewne, ale nie identyczne idee. SRP dotyczy pojedynczej klasy: ma mieć jeden powód do zmiany. SoC to szersza, architektoniczna zasada: różne rodzaje odpowiedzialności (UI, logika biznesowa, dostęp do danych) powinny żyć w osobnych warstwach czy modułach, niezależnie od tego, ile klas to obejmuje. Możesz przestrzegać SRP w każdej pojedynczej klasie, a mimo to złamać SoC, jeśli logika biznesowa i kod dostępu do bazy są przemieszane w tej samej warstwie architektonicznej.
Rule of Three — kiedy naprawdę wyciągać wspólny kod
DRY nie znaczy “abstrahuj przy pierwszym powtórzeniu”. Rule of Three mówi: poczekaj do trzeciego wystąpienia, zanim wydzielisz wspólną abstrakcję.
// Pierwsze powtórzenie — jeszcze za wcześnie na abstrakcję, mogą się okazać przypadkowo podobne
// Drugie powtórzenie — możliwe, że to nadal zbieg okoliczności
// Trzecie powtórzenie — teraz masz dowód na realny wzorzec, nie domysł
Abstrakcja wyciągnięta po jednym czy dwóch przypadkach często okazuje się zła — bo nie widziałeś jeszcze wystarczająco wariantów, żeby wiedzieć, co naprawdę jest wspólne, a co przypadkowo podobne. Zła, przedwczesna abstrakcja bywa droższa w naprawie niż chwilowe powtórzenie kodu.
Podsumowanie
Te siedem zasad nie wymaga znajomości formalnych wzorców projektowych — to fundament, na którym te wzorce stoją. DRY i Rule of Three razem mówią: nie powtarzaj się, ale nie abstrahuj też na wyrost. KISS i YAGNI pilnują, żeby kod robił dokładnie tyle, ile trzeba, nie więcej. Tell, Don’t Ask i SoC dbają o to, żeby logika i dane, których dotyczy, żyły razem, a różne odpowiedzialności nie mieszały się w jednym miejscu.
🚀 Co dalej?
Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.
- 🗺️ Pobierz darmową roadmapę Junior .NET Developer — 12 kroków od podstaw C# do pierwszej pracy: dev-hobby.pl
- 🎬 Subskrybuj kanał YouTube — nowe filmy co tydzień.
Zamień wiedzę w umiejętności
Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.
Pobieram roadmapę →
3 comments