W dzisiejszym artykule przyjrzymy się najczęstszym błędom popełnianym przez osoby, które dopiero zaczynają przygodę z programowaniem w C#.NET. Pokażę Ci konkretne przykłady w kodzie oraz sposoby na uniknięcie tych wpadek.
Dlaczego warto znać te błędy?
Rozumiejąc typowe pułapki, oszczędzisz sobie wielu frustracji. W końcu najskuteczniejsza nauka to nauka na błędach – najlepiej cudzych!
1. Zbyt duże klasy
Nadmierna rozbudowa jednej klasy i brak podziału na mniejsze moduły.
Jednym z klasycznych błędów jest tworzenie zbyt dużych klas, takich potworów, trudnych do ogarnięcia i przetestowania. Początkujący programiści chcą mieć "wszystko w jednym miejscu", więc do jednego pliku trafia logika biznesowa, dostęp do bazy danych, metody walidujące, obróbka danych i jeszcze parę innych rzeczy.
Przykład (Błędny kod)
public class OrderService
{
// Wszystkie zależności i pola w jednej klasie
private readonly DbContext _context;
private readonly ILogger _logger;
public OrderService(DbContext context, ILogger logger)
{
_context = context;
_logger = logger;
}
public void CreateOrder(Order order)
{
// Walidacja
if (string.IsNullOrEmpty(order.CustomerName))
{
throw new Exception("Customer name is required");
}
// Logika biznesowa i zapis do bazy
_context.Orders.Add(order);
_context.SaveChanges();
// Wysyłanie maila do klienta
SendEmail(order.CustomerEmail, "Order created!");
}
private void SendEmail(string email, string message)
{
// Kod do wysyłki maila - też wewnątrz tej samej klasy
Console.WriteLine($"Email sent to {email} with message: {message}");
}
}
Dlaczego to problem?
• Klasa jest trudna w utrzymaniu.
• Zmiana jednej funkcjonalności może wymagać poprawiania wielu metod.
• Testowanie jednostkowe bywa skomplikowane, bo klasa ma mnóstwo zależności.
Poprawa
• Wydziel metody walidujące do osobnej klasy lub wykorzystaj atrybuty walidacji (DataAnnotations).
• Operacje związane z wysyłaniem maili przenieś do odrębnego serwisu (np. EmailService).
• Stosuj wzorce projektowe (np. Repository, Service, Factory) i zasadę Single Responsibility Principle.
public class OrderValidator
{
public void Validate(Order order)
{
if (string.IsNullOrEmpty(order.CustomerName))
{
throw new Exception("Customer name is required");
}
}
}
public class OrderService
{
private readonly DbContext _context;
private readonly IEmailService _emailService;
private readonly OrderValidator _validator;
public OrderService(DbContext context, IEmailService emailService, OrderValidator validator)
{
_context = context;
_emailService = emailService;
_validator = validator;
}
public void CreateOrder(Order order)
{
_validator.Validate(order);
_context.Orders.Add(order);
_context.SaveChanges();
_emailService.SendEmail(order.CustomerEmail, "Order created!");
}
}
2. Niepoprawna obsługa wyjątków
Brak lub niewłaściwa obsługa wyjątków.
Przykład (Błędny kod)
public void ProcessData(string path)
{
var fileContent = File.ReadAllText(path); // Nie łapiemy żadnych wyjątków
Console.WriteLine(fileContent);
}
• Jeśli plik nie istnieje, program wysypie się bez przyjaznej informacji.
• Początkujący często nie przewidują takich sytuacji.
Poprawa
1. Używaj try-catch w miejscach wrażliwych.
2. Gdy to możliwe, przewiduj sytuacje wyjątkowe wcześniej (np. sprawdź, czy plik istnieje).
public void ProcessData(string path)
{
try
{
if (!File.Exists(path))
{
Console.WriteLine("Plik nie istnieje.");
return;
}
var fileContent = File.ReadAllText(path);
Console.WriteLine(fileContent);
}
catch (IOException ex)
{
Console.WriteLine($"Błąd odczytu pliku: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Nieoczekiwany błąd: {ex.Message}");
}
}
3. Zapominanie o async/await
Blokowanie wątków w metodach asynchronicznych.
Przykład (Błędny kod)
public void DownloadData()
{
var data = GetDataFromApi().Result; // Blokowanie wątku głównego
Console.WriteLine(data);
}
private async Task<string> GetDataFromApi()
{
using var client = new HttpClient();
var response = await client.GetStringAsync("https://example.com/api/data");
return response;
}
• GetDataFromApi() jest asynchroniczna, ale wywołanie .Result blokuje wątek.
• Może to prowadzić do zablokowania aplikacji (szczególnie w GUI lub w ASP.NET).
Poprawa
• Zmień metodę wywołującą też na async, używaj await.
public async Task DownloadData()
{
var data = await GetDataFromApi();
Console.WriteLine(data);
}
4. Niezrozumienie zasięgu zmiennych (Scope)
Używanie zmiennych poza ich zakresem.
Przykład (Błędny kod)
public class ScopeExample
{
public void ShowScope()
{
for (int i = 0; i < 5; i++)
{
int value = i * 10;
}
Console.WriteLine(value); // błąd: value nie jest widoczne poza pętlą
}
}
• Poza pętlą value nie istnieje.
Poprawa
• Deklaruj zmienne w odpowiednim zakresie.
• Jeśli potrzebujesz dostępu poza pętlą, zdefiniuj zmienną w bloku wyższego poziomu.
public void ShowScope()
{
int sum = 0;
for (int i = 0; i < 5; i++)
{
sum += i * 10;
}
Console.WriteLine(sum);
}
5. Bezrefleksyjne kopiowanie/wklejanie kodu
Kopiuj-wklej prowadzi do duplikacji i chaosu.
Początkujący często kopiują podobne fragmenty kodu w wielu miejscach projektu, zamiast wydzielić wspólną logikę do metody lub klasy pomocniczej.
Przykład (Błędny kod – duplikacje)
public class ClientService
{
public void CreateClient(Client client)
{
// Sprawdzanie czy jest email
if (string.IsNullOrEmpty(client.Email))
{
throw new Exception("Email is required");
}
// Zapis do bazy
// ...
}
public void UpdateClient(Client client)
{
// Znowu sprawdzamy czy jest email (kopiuj-wklej)
if (string.IsNullOrEmpty(client.Email))
{
throw new Exception("Email is required");
}
// Aktualizacja w bazie
// ...
}
}
Poprawa
• Wydziel powtarzający się kod do osobnej metody/klasy walidującej.
public class ClientService
{
private readonly ClientValidator _validator;
public ClientService(ClientValidator validator)
{
_validator = validator;
}
public void CreateClient(Client client)
{
_validator.ValidateEmail(client);
// Zapis do bazy
}
public void UpdateClient(Client client)
{
_validator.ValidateEmail(client);
// Aktualizacja w bazie
}
}
6. Nieprawidłowe używanie static
Nadużywanie słowa kluczowego "static".
Przykład (Błędny kod)
public static class Helper
{
public static DbContext Context = new DbContext(); // Globalny stan? Zły pomysł
public static void DoSomething()
{
// Operacje na bazie
Context.SaveChanges();
}
}
• static powoduje, że obiekt Context jest współdzielony przez całą aplikację.
• Może to rodzić konflikty i problemy wielowątkowości.
Poprawa
• Stosuj static tylko tam, gdzie ma to faktyczny sens (np. metody pomocnicze bez stanu).
• DbContext powinien być zarządzany poprzez wstrzykiwanie zależności (Dependency Injection).
public class DbService
{
private readonly DbContext _context;
public DbService(DbContext context)
{
_context = context;
}
public void DoSomething()
{
_context.SaveChanges();
}
}
7. Brak testów jednostkowych
Pisanie kodu bez żadnych testów jednostkowych.
Przykład (Błędny nawyk)
• Pisanie funkcji bez pokrycia testami:
public int Add(int a, int b)
{
return a + b; // Gdzie test?
}
• Wydaje się proste, ale co z logiką biznesową, wyjątkami, brzegowymi wartościami?
Poprawa
• Nawet prosty test NUnit to już krok w dobrą stronę:
using Nunit.Framework;
using Shouldly;
public class CalculatorTests
{
[TestCase(2, 3, 5)]
[TestCase(-1, 5, 4)]
public void Add_TwoNumbers_ShouldReturnCorrectSum(int a, int b, int expected)
{
var calculator = new Calculator();
var result = calculator.Add(a, b)
result.ShouldBe(expected);
}
}
8. Zbyt wczesna optymalizacja
Pisanie skomplikowanych rozwiązań w obawie przed hipotetycznymi problemami z wydajnością.
Początkujący programiści czasem starają się "rozwiązywać" problemy, które jeszcze nie wystąpiły. Prowadzi to do nadmiernie skomplikowanego kodu.
Przykład (Przesadna optymalizacja)
public class StringProcessor
{
// Nadmierna obróbka i cache'owanie na siłę, bo "może się przyda"
private static Dictionary<string, string> _cache = new Dictionary<string, string>();
public string Process(string input)
{
if (_cache.ContainsKey(input))
{
return _cache[input];
}
// Skomplikowana logika, niby "optymalizacja"
var processed = new string(input.Reverse().ToArray()).ToUpper();
_cache[input] = processed;
return processed;
}
}
Poprawa
• Najpierw napisz czytelny i działający kod.
• Optymalizuj dopiero wtedy, gdy faktycznie masz problem z wydajnością.
9. Brak używania using w przypadku obiektów do zwalniania zasobów
Niezwalnianie zasobów, brak klauzuli using.
Przykład (Błędny kod)
public void ReadFileContent(string filePath)
{
var stream = File.OpenRead(filePath);
// Kod do czytania pliku
// ...
// Zapomnieliśmy stream.Dispose();
}
• Strumień nie jest zamykany, co może prowadzić do wycieków zasobów.
Poprawa
• Zastosuj klauzulę using (w nowszych wersjach C#, wystarczy using var):
public void ReadFileContent(string filePath)
{
using var stream = File.OpenRead(filePath);
// Kod do czytania pliku
}
10. Nieczytelne nazwy metod i zmiennych
Stosowanie skrótów i nic niemówiących nazw.
Przykład (Błędny kod)
public void CnclOrd(int id)
{
// Co to za metoda?
}
• Po nazwie CnclOrd trudno się domyślić, że chodzi o "CancelOrder".
Poprawa
• Stosuj pełne, opisowe nazwy:
public void CancelOrder(int orderId)
{
// Wiadomo, co tu robimy
}
Szkolenie Online: Zostań Programistą .NET
Jeśli chcesz uniknąć tych błędów i jednocześnie nauczyć się profesjonalnych praktyk w C#/.NET od podstaw aż po zaawansowane projekty, zapraszam Cię do mojego szkolenia online: Zostań Programistą .NET. Znajdziesz tam zorganizowaną ścieżkę nauki i dowiesz się, jak w praktyce projektować aplikacje, testować je i wdrażać z zachowaniem najlepszych standardów. To świetna okazja, żeby przyspieszyć swój rozwój i pracować nad rzeczywistymi projektami w bezpiecznym środowisku.
Podsumowanie
Popełnianie błędów to część procesu nauki, ale świadomość najpopularniejszych pułapek pozwoli Ci je ominąć lub szybko poprawić. Mam nadzieję, że ten artykuł pomoże Ci wejść na wyższy poziom w programowaniu w C#/.NET.
To wszystkie na dzisiaj. Jeżeli taki artykuł Ci się spodobał, to koniecznie dołącz do mojej społeczności – darmowe zapisy, gdzie będziesz również miał dostęp do dodatkowych materiałów i przede wszystkim bonusów. Do zobaczenia w kolejnym artykule.