CodeGym /Kursy /C# SELF /Porady dotyczące stylu i czytelności kodu OOP

Porady dotyczące stylu i czytelności kodu OOP

C# SELF
Poziom 25 , Lekcja 4
Dostępny

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ściPascalCase, bo to część zewnętrznego interfejsu klasy:

public int Balance { get; set; }

Zmienne

Zmienne lokalnecamelCase, 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:

  1. Konstruktory
  2. Właściwości
  3. Metody
  4. 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.
Dobrze:
  • 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}");
    }
}
1
Ankieta/quiz
Błędy przy dziedziczeniu, poziom 25, lekcja 4
Niedostępny
Błędy przy dziedziczeniu
Częste błędy przy deklarowaniu klas i obiektów
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION