Code Review C#

Code Review C# – 5 błędów które zdradzają Juniora

Rekruter otwiera Twoje repozytorium na GitHubie. Patrzy na jeden plik — kontroler albo serwis. I już wie.

Nie po tym ile kodu napisałeś. Po tym jak go napisałeś.

W tym poście omówię dokładnie 5 błędów które pokazałem w odcinku “Code Review na żywo” na kanale dev-hobby.pl. Do każdego dostajesz: zły kod, wzorcową poprawkę i jedną regułę do zapamiętania.

💡  Powiązane wideo: Code Review na żywo — Czy Twój kod w C# wygląda jak kod Juniora? → youtube.com/@mariuszjurczenko
📋  Spis treści
1.  Błąd #1 — Złe nazewnictwo
2.  Błąd #2 — God Method / SRP
3.  Błąd #3 — Magic strings i hardcoded values
4.  Błąd #4 — Brak obsługi błędów / null
5.  Błąd #5 — Publiczne pola zamiast właściwości
6.  Checklist pre-PR
7.  Podsumowanie i CTA

Co sprawdza rekruter — zanim w ogóle przejdzie do kodu

Rekruter patrzy na Twój kod nie po to, żeby sprawdzić czy działa. Zakłada, że działa. Sprawdza cztery rzeczy:

  • Nazewnictwo — czy rozumiesz co piszesz. Widać to po nazwach zmiennych i klas.
  • Czytelność — czy myślisz o tym, kto będzie czytał Twój kod po Tobie.
  • Zasady SOLID — Single Responsibility, separation of concerns.
  • Testowalność — czy Twój kod w ogóle da się przetestować.

Teraz pokażę Ci 5 błędów które łamią każdą z tych zasad — i jak je naprawić.

Błąd #1 — Złe nazewnictwo

Masz prosty kod. Wygląda krótko, wygląda czysto. Ale co on robi? Bez czytania każdej linii — nie wiesz.

❌ Zły kod — Manager, DoStuff, GetThing

// ❌ KOD JUNIORA
public class Manager
{
    public List<string> data = new List<string>();

    public void DoStuff(string x)       // DoStuff — co robi?
    {
        if (x != null)
            data.Add(x);
    }

    public string GetThing(int i)       // GetThing — co zwraca?
    {
        return data[i];
    }
}

Manager — manager czego? Emaili? Zamówień? Użytkowników?
Nie wiesz bez czytania środka.
DoStuff to chyba najgorsza nazwa metody jaką rekruter może zobaczyć.
GetThing podobnie — co to za rzecz?

Rekruter widząc takie nazwy myśli: ten developer nie myśli o tym kto będzie czytał jego kod. A to sygnał, że trudno będzie z nim pracować w zespole.

✅ Dobry kod — nazwy które mówią co robią

// ✅ PO POPRAWCE — nazwa mówi co klasa robi
public class UserEmailRepository
{
    private readonly List<string> _userEmails = new();

    public void AddEmail(string email)      // jasne: dodaje email
    {
        if (!string.IsNullOrWhiteSpace(email))
            _userEmails.Add(email);
    }

    public string GetEmailAt(int index)     // jasne: email pod indeksem
        => _userEmails[index];
}

📌  Reguła: nazwa powinna mówić CO coś robi, nie JAK to robi.
Bez czytania ciała metody — wiesz co robi AddEmail.

Błąd #2 — God Method / SRP

Drugi błąd jest może jeszcze bardziej niebezpieczny — bo kod wygląda jakby działał, i działa. Ale jest nienaprawialny.

❌ Zły kod — ProcessOrder robi 4 rzeczy naraz

// ❌ KOD JUNIORA — jedna metoda robi WSZYSTKO
public void ProcessOrder(Order order)
{
    // 1. Walidacja
    if (order == null) throw new Exception("Brak zamówienia");
    if (order.Items.Count == 0) throw new Exception("Puste zamówienie");

    // 2. Obliczenie kwoty
    decimal total = 0;
    foreach (var item in order.Items)
        total += item.Price * item.Quantity;
    if (order.HasDiscount) total *= 0.9m;

    // 3. Zapis do bazy ❌ hardcoded connection string
    var conn = new SqlConnection("Server=localhost;Database=shop");
    conn.Open();
    var cmd = new SqlCommand("INSERT INTO Orders...", conn);
    cmd.ExecuteNonQuery();

    // 4. Wysyłka emaila
    var smtp = new SmtpClient("smtp.gmail.com");
    smtp.Send("no-reply@shop.pl", order.Email, "Zamówienie",
        $"Dziękujemy! Kwota: {total:C}");
}

Ta metoda robi cztery rzeczy naraz: waliduje, liczy, zapisuje do bazy i wysyła email. Nie da się jej przetestować. Nie da się zmienić jednej odpowiedzialności bez ryzyka zepsucia pozostałych.

Jak chcę napisać test dla obliczenia kwoty — muszę mockować bazę danych i serwer SMTP. To jest niemożliwe do sensownego testowania.

✅ Dobry kod — SRP w praktyce

// ✅ PO POPRAWCE — każda klasa ma JEDEN obowiązek
public class OrderProcessor
{
    private readonly IOrderValidator _validator;
    private readonly IPriceCalculator _calculator;
    private readonly IOrderRepository _repository;
    private readonly IEmailService _emailService;

    public OrderProcessor(
        IOrderValidator validator,
        IPriceCalculator calculator,
        IOrderRepository repository,
        IEmailService emailService)
    {
        _validator = validator;
        _calculator = calculator;
        _repository = repository;
        _emailService = emailService;
    }

    public async Task ProcessAsync(Order order)
    {
        _validator.Validate(order);                              // SRP
        var total = _calculator.Calculate(order);                // SRP
        await _repository.SaveAsync(order, total);               // SRP
        await _emailService.SendConfirmationAsync(order, total); // SRP
    }
}

// Każda klasa ma JEDEN powód do zmiany:
// OrderValidator     → zmieniają się reguły walidacji
// PriceCalculator    → zmienia się logika cen/rabatów
// IOrderRepository   → zmienia się sposób zapisu do bazy
// IEmailService      → zmienia się format/provider emaila
// OrderProcessor     → zmienia się FLOW (kolejność kroków)

📌  Reguła: jedna klasa = jeden powód do zmiany.
Jeśli zmieniają się wymagania emaila — zmieniam tylko EmailService.
Reszta kodu nie wie że cokolwiek się zmieniło.

Błąd #3 — Magic strings i hardcoded values

Trzeci błąd jest subtelny — kod działa, wygląda zwięźle, ale jest bombą zegarową.

❌ Zły kod — “admin”, “vip”, hasło w kodzie

// ❌ KOD JUNIORA — magic strings
// literówka? kompilator nie sprawdzi
public decimal GetDiscount(User user) 
{
    if (user.Role == "admin")    return 0.15m;  
    if (user.Role == "vip")      return 0.10m;
    if (user.Role == "premium")  return 0.05m;
    return 0m;
}

// ❌ Hardcoded connection string z hasłem w kodzie źródłowym
var conn = new SqlConnection(
    "Server=localhost;Database=shop;User=sa;Password=Admin123");

Wyobraź sobie że string “admin” jest w 15 miejscach projektu. Ktoś zmienia rolę na “administrator”. Musi przeszukać cały projekt. O jednym miejscu zapomni — i masz buga.

A connection string z hasłem Admin123 hardcoded w kodzie? To nie tylko zły design — to katastrofa bezpieczeństwa. To trafi do Git repozytorium.

✅ Dobry kod — enum, stałe, konfiguracja

// ✅ Enum zamiast stringa — kompilator sprawdzi czy rola istnieje
public enum UserRole { Admin, Vip, Premium, Standard }

public static class DiscountRates
{
    public const decimal Admin   = 0.15m;  // zmiana w jednym miejscu
    public const decimal Vip     = 0.10m;
    public const decimal Premium = 0.05m;
}

public decimal GetDiscount(User user) => user.Role switch
{
    UserRole.Admin   => DiscountRates.Admin,
    UserRole.Vip     => DiscountRates.Vip,
    UserRole.Premium => DiscountRates.Premium,
    _                => 0m
};

// ✅ Connection string z konfiguracji — NIE z kodu
var connectionString = configuration.GetConnectionString("ShopDb");
// Hasło pochodzi z appsettings.json / secrets.json / env variables
// Nigdy nie trafia do repozytorium Git

📌 Reguła: jeśli wartość pojawia się w więcej niż jednym miejscu
— to enum lub stała. Hasła i connection strings
— zawsze w konfiguracji (appsettings.json + user-secrets), nigdy w kodzie.

Błąd #4 — Brak obsługi błędów / null

Czwarty błąd to coś co każdy Junior robi — i co kiedyś doprowadzi do NullReferenceException na produkcji o 3 w nocy.

❌ Zły kod — zwraca null, caller zapomni sprawdzić

// ❌ KOD JUNIORA — zwraca null bez żadnej informacji
public User GetUser(int id)
{
    return _db.Users.FirstOrDefault(u => u.Id == id);
    // Co jeśli user nie istnieje? Zwraca null.
    // Caller musi pamiętać żeby to sprawdzić.
    // Zazwyczaj zapomni.
}

// Użycie — bomba zegarowa:
var user = GetUser(999);
Console.WriteLine(user.Name);   // NullReferenceException!
SendWelcomeEmail(user.Email);   // NullReferenceException!

✅ Dobry kod — trzy wzorce obsługi braku wartości

✅  Wariant 1 — rzuć wyjątek z kontekstem (brak = błąd logiki)

// Użyj gdy: brak elementu = błąd logiki biznesowej
public User GetUserOrThrow(int id)
{
    return _db.Users.FirstOrDefault(u => u.Id == id)
        ?? throw new UserNotFoundException($"User id={id} nie istnieje.");
}

✅  Wariant 2 — TryGet pattern (brak = normalny przypadek)

// Użyj gdy: brak = normalny przypadek (optional lookup)
public bool TryGetById(int id, out User? user)
{
    user = _users.FirstOrDefault(u => u.Id == id);
    return user is not null;
}
// Użycie:
if (!TryGetById(999, out var user))
    return NotFound("User nie istnieje");

✅  Wariant 3 — Result<T> (złożone operacje z różnymi błędami)

// Użyj gdy: operacja może się nie powieść z różnych powodów
public Result<User> TryFind(int id)
{
    var user = _users.FirstOrDefault(u => u.Id == id);
    return user is not null
        ? Result<User>.Ok(user)
        : Result<User>.Fail($"User id={id} nie istnieje");
}

📌  Reguła: nigdy nie zwracaj null bez poinformowania callera
jakie są możliwe stany.
Caller powinien wiedzieć co robić w każdym przypadku.
Wybór wzorca zależy od kontekstu — ważne jest że wybierasz świadomie.

Błąd #5 — Publiczne pola zamiast właściwości

Ostatni błąd — pozornie mały detal, ale dla rekrutera to wyraźny sygnał że ktoś nie zna C#.

❌ Zły kod — publiczne pola bez kontroli

// ❌ KOD JUNIORA — publiczne pola
public class Product
{
    public string  Name;   // każdy może zmienić bez kontroli
    public decimal Price;  // ktoś może wpisać -999
    public int     Stock;  // ktoś może wpisać -1000
}

var product = new Product();
product.Price = -999;    // kompilator nie protestuje
product.Stock = -1000;   // niespójny stan — i nikt nie wie

✅ Dobry kod — właściwości z walidacją w konstruktorze

// ✅ PO POPRAWCE — właściwości z private set + walidacja
public class Product
{
    public string  Name  { get; private set; }
    public decimal Price { get; private set; }
    public int     Stock { get; private set; }

    public Product(string name, decimal price, int stock)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new ArgumentException("Nazwa nie może być pusta");
        if (price <= 0)
            throw new ArgumentOutOfRangeException(nameof(price), "Cena > 0");
        if (stock < 0)
            throw new ArgumentOutOfRangeException(nameof(stock), "Stock >= 0");
        Name = name; 
        Price = price; 
        Stock = stock;
    }

    public void ReduceStock(int quantity) // dedykowana metoda dla zmiany stanu
    {
        if (quantity > Stock)
            throw new InvalidOperationException("Niewystarczający stan");
        Stock -= quantity;
    }
}

📌  Reguła: właściwości + private set + walidacja w konstruktorze
= immutable by default.
Obiekt rodzi się poprawny albo w ogóle nie istnieje.
To jest enkapsulacja w praktyce — jeden z fundamentów OOP.

Checklist — sprawdź swój kod przed Pull Requestem

✅  Pre-PR Checklist — C# Code Review
☐  Nazwy klas, metod i zmiennych mówią CO robią — nie JAK
☐  Żadna metoda nie robi więcej niż jednej rzeczy (SRP)
☐  Brak magic strings — używam enumów i stałych (DiscountRates.Admin zamiast “admin”)
☐  Brak hardcoded credentials — connection strings i klucze w konfiguracji
☐  Każde null/brak obsługuję świadomie: throw, TryGet lub Result<T>
☐  Właściwości z private set zamiast publicznych pól
☐  Walidacja w konstruktorze — obiekt rodzi się poprawny

Podsumowanie — 5 błędów w jednym miejscu

#1Złe nazewnictwo Manager, DoStuff, GetThing
— rekruter nie wie co robi klasa. Użyj nazw które mówią CO, nie JAK.
#2God Method (SRP) Jedna metoda robi 4 rzeczy
— niemożliwa do przetestowania. Każda klasa = jeden powód do zmiany.
#3Magic strings “admin” zamiast enuma, hasło w kodzie.
Literówka niewidoczna dla kompilatora, ryzyko bezpieczeństwa.
#4Null bez kontekstu Zwrócone null bez poinformowania callera = NullReferenceException na produkcji. Używaj throw/TryGet/Result.
#5Publiczne pola Brak enkapsulacji — obiekt może wejść w niespójny stan. Właściwości + walidacja w konstruktorze.

Każdy z tych błędów sam w sobie nie dyskwalifikuje.
Ale razem mówią rekruterowi: ta osoba nie myśli jeszcze jak developer.

Każdy z tych błędów sam w sobie nie dyskwalifikuje. Ale razem mówią rekruterowi: ta osoba nie myśli jeszcze jak developer.

📥  Pobierz bezpłatną Roadmapę Junior .NET Developer 12 kroków od podstaw C# do pierwszej pracy jako .NET Developer. →  dev-hobby.pl
Masz pytanie do konkretnego błędu?
Zostaw komentarz pod filmem — odpowiadam na każdy.

Zobacz też

Zobacz także — powiązane artykuły

👉 MCP w .NET (C#) – jak zbudować serwer AI krok po kroku

👉 Tworzenie klas i obiektów w C# — kompletny przewodnik

👉 Pattern Matching w C# – switch expressions i type patterns

CTA

📌 Chcesz więcej praktycznego C# i .NET?

👉subskrybuj nasz kanał na YT!

👉 sprawdź moje kursy .NET i C#.

Dołącz do “Od Zera do .NET Developera”

Zacznij swoją przygodę z programowaniem w oparciu o sprawdzone praktyki rynkowe. Wybierz kompletną ścieżkę rozwoju i zbuduj solidne fundamenty.

Dołącz do ścieżki teraz →
Masz pytania? Napisz: mariuszjurczenko@dev-hobby.pl

Dodaj komentarz