JavaRush /Курсы /C# SELF /Советы по стилю и читаемости ООП-кода

Советы по стилю и читаемости ООП-кода

C# SELF
25 уровень , 4 лекция
Открыта

1. Введение

"Чистый код" — не какая-то священная корова, а реальный инструмент выживания программиста. В любом, даже самом прекрасном, ООП-проекте очень быстро накапливается множество классов, полей, методов, хитрых связей… Если нарушить эстетику и структуру, через неделю свой же код становится неразрешимым квестом. Подробнее про "борьбу за выживание" можно почитать у Роберта Мартина, "Чистый код" — как раз о таких битвах.

Стиль в коде — это не про "кому как нравится", а про то, чтобы всем было легче жить:

  • Коллега (или вы сами) может быстро понять, что происходит.
  • Ошибки (особенно архитектурные) видны сразу.
  • Код проще дорабатывать, делая меньше ошибок.

Давайте посмотрим, как сделать ООП-код таким, чтобы его любили проверяющие, коллеги и даже линтеры.

2. Имена: ваша первая линия обороны

Именование классов

Классы в C# принято называть в PascalCase (каждая новая часть слова начинается с большой буквы, например: MyNewClass) и так, чтобы имя чётко отвечало на вопрос "Что это?". Названия должны быть существительными!

public class StudentAccount { /* ... */ }
public class InvoiceGenerator { /* ... */ }

Плохо:

class doMagic { ... } // Плохо: Что за магия? Нарушен PascalCase.

Именование методов

Методы — тоже в PascalCase, но здесь лучше использовать глаголы + объект действия:

public void PrintReport() { ... }
public string GetFormattedName() { ... }

Методы должны отражать действие (Print, Get, Save, Calculate и т.д.), чтобы при чтении было понятно, что произойдет.

Именование полей и свойств

Поля обычно private, называются с маленькой буквы через camelCase, часто с подчеркиванием:

private int _count;
private Student _owner;

СвойстваPascalCase, так как это часть внешнего интерфейса класса:

public int Balance { get; set; }

Переменные

Локальные переменныеcamelCase, максимально коротко и понятно к контексту:

string inputName;
int studentCount;

И, пожалуйста, переменные типа a1, result2, something — только если вы хотите создать себе квест на следующий месяц.

3. Организация структуры класса

Правильное расположение членов класса делает навигацию проще и помогает быстро понять, что за чем следует.

Обычно размещают так:

  1. Конструкторы
  2. Свойства
  3. Методы
  4. Вложенные типы (enum, class и т.д.)

Пример:


public class Student
{
    // --- Поля ---
    private string _name;

    // --- Конструктор ---
    public Student(string name)
    {
        _name = name;
    }

    // --- Свойства ---
    public string Name
    {
        get => _name;
        set => _name = value;
    }

    // --- Методы ---
    public void PrintInfo()
    {
        Console.WriteLine($"Имя: {_name}");
    }
}

Эти "блоки" удобно разделять комментариями (// --- Методы ---), особенно в больших классах. JetBrains Rider, Visual Studio и другие IDE позволяют быстро свернуть/развернуть секции.

4. Комментарии и документация

Комментарии — это хорошо. Но плохо, если ими злоупотреблять или писать "объяснения к непонятному коду", когда можно просто поменять сам код!

Хороший комментарий — тот, который объясняет "почему", а не "что".

// Используем Guid как уникальный идентификатор, т.к. система распределённая
public Guid Id { get; set; }

Документация методов, классов и свойств

Используйте xml-документацию для классов и публичных методов. IDE будут показывать эти описания при наведении курсора.


/// <summary>
/// Представляет студента университета.
/// </summary>
public class Student
{
    /// <summary>
    /// Имя студента.
    /// </summary>
    public string Name { get; set; }
}

Что комментировать НЕ надо

  • Простые вещи (i++ // увеличиваем i на 1).
  • Плохо названные переменные ("// здесь что-то происходит" — ага, но что?).

5. Разделяй и властвуй

Маленькие классы и методы

Золотое правило: один класс — одна ответственность (см. Single Responsibility Principle). Если класс Student делает и учёт оценок, и обработку e-mail, и управление расписанием — что-то тут не так.

  • Классы до 300-400 строк — нормально. Больше — повод задуматься.
  • Методы до 15-20 строк — читабельно. Исключения бывают, если это метод-обработчик большого кейса.

Пример "раздувшегося" метода:


public void Process()
{
    // Уведомление клиента
    // Сохраняем изменения
    // Отправляем e-mail
    // Записываем логи
    // ... (15 шагов)
}

Лучше:


public void Process()
{
    NotifyClient();
    SaveChanges();
    SendEmail();
    LogActivity();
}

Каждый из методов выделен в отдельный частный метод, код становится компактней и легче для тестирования.

6. Полезные советы

Визуальная структура: форматирование, отступы, пустые строки

IDE умеют красиво форматировать код автоматически (Ctrl+K, D в Visual Studio, Ctrl+Alt+L в Rider), но принципы всё равно надо знать.

  • ОТСТУПЫ — 4 пробела. Не табы, не 2 пробела.
  • ПУСТЫЕ СТРОКИ — отделяйте методы друг от друга, поля от свойств, свойства от методов.
  • СКОБКИ всегда на новой строке для классов и методов (стиль Allman):

public class Test
{
    public void Print()
    {
        Console.WriteLine("Hello");
    }
}

"Сильные" и "слабые" члены класса: модификаторы доступа

Всегда старайтесь делать всё максимально закрытым: открывайте только то, что действительно должно быть доступно снаружи. Если поле или метод нужен только внутри класса — делайте его private. Только если должны работать наследники — protected. public — только для контрактов.

Плохо:

public string ConnectionString; // Кто угодно может поменять!

Лучше:


private string _connectionString;
public string ConnectionString
{
    get => _connectionString;
    private set => _connectionString = value;
}

Использование автоматических свойств

С появлением автоматических свойств и init-only-сеттеров писать "ручные" пропертя стало просто неприлично.

Пример:


public string Name { get; set; } // Отлично!
public int Age { get; init; }    // Только для инициализации, безопаснее.

А если нужны вычисляемые свойства:


public string FullName => $"{FirstName} {LastName}";

Инкапсуляция и геттеры/сеттеры

Если у свойства есть некий бизнес-смысл для изменения или контроля, используйте приватное поле + открытый геттер/сеттер с логикой.


private int _grade;
public int Grade
{
    get => _grade;
    set
    {
        if (value < 0) _grade = 0;
        else if (value > 100) _grade = 100;
        else _grade = value;
    }
}

Так вы не дадите себе или другим случайно "сломать" объект.

7. Еще полезные советы

Не бойтесь интерфейсов и абстракций

Интерфейсы нужны для удобных контрактов, тестирования и расширения приложения.

Плохо:

  • интерфейс с одним методом, который нигде не востребован;
  • интерфейс, который реализует только один класс.
Хорошо:
  • интерфейс, который используют 2+ класса;
  • интерфейс для абстракции внешних систем (например, для логгирования, хранения данных).

Пишите код так, чтобы его можно было легко протестировать

Один из признаков хорошего ООП-кода — тестируемость.

  • Не делайте методы, в которых всё завязано на глобальные переменные или статические поля.
  • Не бойтесь внедрения зависимостей — через параметры конструктора (Dependency Injection).
  • Разделяйте вычисления (логику) и взаимодействие с пользователем (ввод/вывод). Это облегчит не только тестирование, но и доработку приложения.

Лайфхаки и анти-паттерны для начинающих

  • Не пишите "класс Бога" (God Object), которые делают всё.
  • Не делайте "магических чисел" без пояснений (if (status == 42) — почему 42?).
  • Не злоупотребляйте наследованием ради наследования — иногда лучше использовать композицию (класс с полями других классов, а не наследник).
  • Не пишите сложные методы длиной в 100 строк — их невозможно протестировать и понять.
  • Всегда оставляйте место для расширения (open/closed principle).

8. Как выглядят плохой и хороший код

Плохой пример:


class s // Плохо: имя класса с маленькой буквы.
{
    public int a; // нет смысла, плохое имя
    public void m() // Плохо: метод с именем из одной буквы.
    {
        Console.WriteLine(a);
        // Плохо: непонятно, что делает метод.
    }
}

Хороший пример:


// Представляет студента.
public class Student
{
    // Возраст студента.    
    private int _age;
    public int Age
    {
        get => _age;
        set => _age = value < 0 ? 0 : value;
    }

    // Выводит информацию о студенте.
    public void PrintInfo()
    {
        Console.WriteLine($"Возраст студента: {Age}");
    }
}
2
Задача
C# SELF, 25 уровень, 4 лекция
Недоступна
Правильное именование классов и методов
Правильное именование классов и методов
2
Задача
C# SELF, 25 уровень, 4 лекция
Недоступна
Организация структуры класса
Организация структуры класса
1
Опрос
Ошибки при наследовании, 25 уровень, 4 лекция
Недоступен
Ошибки при наследовании
Частые ошибки при объявлении классов и объектов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ