🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

​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.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 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.

czytanie to początek

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ę →