1. Wprowadzenie
"Czysty kod" — to nie jakaś święta krowa, tylko realne narzędzie przetrwania programisty. W każdym, nawet najpiękniejszym, projekcie OOP bardzo szybko zbiera się masa klas, pól, metod, sprytnych powiązań… Jeśli zaburzysz estetykę i strukturę, po tygodniu twój własny kod stanie się nierozwiązywalnym questem. Więcej o "walce o przetrwanie" możesz poczytać u Roberta Martina, "Czysty kod" — właśnie o takich bitwach.
Styl w kodzie — to nie "jak kto lubi", tylko żeby wszystkim żyło się łatwiej:
- Kolega (albo ty sam) może szybko ogarnąć, co się dzieje.
- Błędy (szczególnie architektoniczne) widać od razu.
- Kod łatwiej rozwijać, robiąc mniej błędów.
Zobaczmy, jak napisać kod OOP tak, żeby polubili go sprawdzający, koledzy i nawet lintersy.
2. Nazwy: twoja pierwsza linia obrony
Nazewnictwo klas
Klasy w C# nazywamy w PascalCase (każda nowa część słowa zaczyna się wielką literą, np.: MyNewClass) i tak, żeby nazwa jasno odpowiadała na pytanie "Co to jest?". Nazwy powinny być rzeczownikami!
public class StudentAccount { /* ... */ }
public class InvoiceGenerator { /* ... */ }
Źle:
class doMagic { ... } // Źle: Co za magia? Naruszony PascalCase.
Nazewnictwo metod
Metody — też w PascalCase, ale tutaj najlepiej używać czasowników + obiekt działania:
public void PrintReport() { ... }
public string GetFormattedName() { ... }
Metody powinny odzwierciedlać akcję (Print, Get, Save, Calculate itd.), żeby przy czytaniu było jasne, co się wydarzy.
Nazewnictwo pól i właściwości
Pola zazwyczaj są private, nazywane małą literą w camelCase, często z podkreśleniem:
private int _count;
private Student _owner;
Właściwości — PascalCase, bo to część zewnętrznego interfejsu klasy:
public int Balance { get; set; }
Zmienne
Zmienne lokalne — camelCase, maksymalnie krótko i jasno do kontekstu:
string inputName;
int studentCount;
I proszę, zmienne typu a1, result2, something — tylko jeśli chcesz sobie zrobić quest na następny miesiąc.
3. Organizacja struktury klasy
Poprawne rozmieszczenie członków klasy ułatwia nawigację i pomaga szybko ogarnąć, co po czym następuje.
Zazwyczaj układamy tak:
- Konstruktory
- Właściwości
- Metody
- Zagnieżdżone typy (enum, class itd.)
Przykład:
public class Student
{
// --- Pola ---
private string _name;
// --- Konstruktor ---
public Student(string name)
{
_name = name;
}
// --- Właściwości ---
public string Name
{
get => _name;
set => _name = value;
}
// --- Metody ---
public void PrintInfo()
{
Console.WriteLine($"Imię: {_name}");
}
}
Te "bloki" wygodnie oddzielać komentarzami (// --- Metody ---), szczególnie w dużych klasach. JetBrains Rider, Visual Studio i inne IDE pozwalają szybko zwijać/rozwijać sekcje.
4. Komentarze i dokumentacja
Komentarze są spoko. Ale źle, jeśli się z nimi przesadza albo pisze "wyjaśnienia do niezrozumiałego kodu", kiedy można po prostu zmienić sam kod!
Dobry komentarz — to taki, który tłumaczy "dlaczego", a nie "co".
// Używamy Guid jako unikalnego identyfikatora, bo system jest rozproszony
public Guid Id { get; set; }
Dokumentacja metod, klas i właściwości
Używaj xml-dokumentacji dla klas i publicznych metod. IDE pokażą te opisy po najechaniu kursorem.
/// <summary>
/// Reprezentuje studenta uniwersytetu.
/// </summary>
public class Student
{
/// <summary>
/// Imię studenta.
/// </summary>
public string Name { get; set; }
}
Czego NIE komentować
- Proste rzeczy (i++ // zwiększamy i o 1).
- Źle nazwane zmienne ("// tutaj coś się dzieje" — aha, ale co?).
5. Dziel i rządź
Małe klasy i metody
Złota zasada: jedna klasa — jedna odpowiedzialność (patrz Single Responsibility Principle). Jeśli klasa Student robi i obsługę ocen, i obsługę e-maili, i zarządzanie planem — coś tu nie gra.
- Klasy do 300-400 linii — spoko. Więcej — czas się zastanowić.
- Metody do 15-20 linii — czytelnie. Wyjątki są, jeśli to metoda obsługująca duży przypadek.
Przykład "spuchniętej" metody:
public void Process()
{
// Powiadomienie klienta
// Zapisujemy zmiany
// Wysyłamy e-mail
// Zapisujemy logi
// ... (15 kroków)
}
Lepiej:
public void Process()
{
NotifyClient();
SaveChanges();
SendEmail();
LogActivity();
}
Każda z akcji wydzielona do osobnej prywatnej metody, kod jest bardziej kompaktowy i łatwiejszy do testowania.
6. Przydatne porady
Wizualna struktura: formatowanie, wcięcia, puste linie
IDE potrafią automatycznie ładnie sformatować kod (Ctrl+K, D w Visual Studio, Ctrl+Alt+L w Rider), ale zasady i tak warto znać.
- WCIECIA — 4 spacje. Nie taby, nie 2 spacje.
- PUSTE LINIE — oddzielaj metody od siebie, pola od właściwości, właściwości od metod.
- NAWIASY zawsze w nowej linii dla klas i metod (styl Allman):
public class Test
{
public void Print()
{
Console.WriteLine("Hello");
}
}
"Silne" i "słabe" człony klasy: modyfikatory dostępu
Zawsze staraj się robić wszystko maksymalnie zamknięte: otwieraj tylko to, co naprawdę musi być dostępne z zewnątrz. Jeśli pole lub metoda jest potrzebna tylko w klasie — daj private. Tylko jeśli muszą korzystać dziedziczące — protected. public — tylko dla kontraktów.
Źle:
public string ConnectionString; // Każdy może zmienić!
Lepiej:
private string _connectionString;
public string ConnectionString
{
get => _connectionString;
private set => _connectionString = value;
}
Używanie automatycznych właściwości
Odkąd są automatyczne właściwości i init-only settery, pisanie "ręcznych" property to już obciach.
Przykład:
public string Name { get; set; } // Super!
public int Age { get; init; } // Tylko do inicjalizacji, bezpieczniej.
A jeśli potrzebujesz właściwości wyliczanej:
public string FullName => $"{FirstName} {LastName}";
Enkapsulacja i gettery/settery
Jeśli właściwość ma jakiś biznesowy sens zmiany lub kontroli, użyj prywatnego pola + publicznego gettera/settera z logiką.
private int _grade;
public int Grade
{
get => _grade;
set
{
if (value < 0) _grade = 0;
else if (value > 100) _grade = 100;
else _grade = value;
}
}
Dzięki temu nie pozwolisz sobie ani innym przypadkowo "zepsuć" obiektu.
7. Jeszcze więcej przydatnych porad
Nie bój się interfejsów i abstrakcji
Interfejsy są potrzebne do wygodnych kontraktów, testowania i rozbudowy aplikacji.
Źle:
- interfejs z jedną metodą, której nikt nie używa;
- interfejs, który implementuje tylko jedna klasa.
- interfejs, z którego korzystają 2+ klasy;
- interfejs do abstrakcji systemów zewnętrznych (np. do logowania, przechowywania danych).
Pisz kod tak, żeby łatwo było go testować
Jedna z cech dobrego kodu OOP — testowalność.
- Nie rób metod, które są zależne od globalnych zmiennych lub pól statycznych.
- Nie bój się wstrzykiwania zależności — przez parametry konstruktora (Dependency Injection).
- Oddzielaj obliczenia (logikę) od interakcji z użytkownikiem (wejście/wyjście). To ułatwi nie tylko testowanie, ale i rozwój aplikacji.
Lifehacki i antywzorce dla początkujących
- Nie pisz "klasy Boga" (God Object), która robi wszystko.
- Nie rób "magicznych liczb" bez wyjaśnienia (if (status == 42) — dlaczego 42?).
- Nie przesadzaj z dziedziczeniem dla samego dziedziczenia — czasem lepiej użyć kompozycji (klasa z polami innych klas, a nie dziedziczenie).
- Nie pisz skomplikowanych metod na 100 linii — nie da się ich przetestować ani zrozumieć.
- Zawsze zostawiaj miejsce na rozszerzenie (open/closed principle).
8. Jak wygląda zły i dobry kod
Zły przykład:
class s // Źle: nazwa klasy małą literą.
{
public int a; // bez sensu, zła nazwa
public void m() // Źle: metoda z nazwą jedną literą.
{
Console.WriteLine(a);
// Źle: nie wiadomo, co robi metoda.
}
}
Dobry przykład:
// Reprezentuje studenta.
public class Student
{
// Wiek studenta.
private int _age;
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value;
}
// Wyświetla informacje o studencie.
public void PrintInfo()
{
Console.WriteLine($"Wiek studenta: {Age}");
}
}
GO TO FULL VERSION