Перейти к основному содержимому

Культура написания и поддержки кода

Разработчику Архитектору
Теория данных (раздел 3)

Структуры в коде — Структуры данных; persistence — ORM.

Код пишут люди, а читают его — те же люди через неделю, месяц или после отпуска коллеги. Культура кода помогает снизить цену этих чтений — единые имена, автоматическое форматирование, комментарии там, где без контекста не обойтись, и ревью, в котором обсуждают смысл, а не "мне не нравится стиль".

Синтаксис языка вы уже учите в разделах про языки программирования. Здесь — как оформлять и сопровождать код в команде, чтобы он оставался понятным без устных легенд.

Подробнее про метрику "запутанности" функций — в статье Цикломатическая сложность и читаемость кода. Правило сопоставления кода с предметной областью (MAPPER) — в отдельной статье; по симптому в коде — справочник тем.

Стандарты и "разбитые окна"

Единый стиль (форматтер, линтер, код на английском в идентификаторах) снимает споры в PR. Правило бойскаутов: оставить файл чище, чем нашли. См. также справочник тем.


Соглашения об именовании​

★ Соглашения об именовании — правила, которые определяют, как называть переменные, методы, классы, поля и другие элементы кода. Они делают код предсказуемым — открыв файл на C# или Python, вы уже знаете, чего ждать от UserAccount и user_account.

Основные стили именования:

Play ITЗагрузка интерактивного демо…

Play ITЗагрузка интерактивного демо…

СтильВидОбычно применяют
PascalCaseFirstNameклассы, методы (C#), типы
camelCasefirstNameпеременные, параметры, методы (в JS, Java, C# для полей)
snake_casefirst_nameпеременные и функции в Python, константы в Rust
Pascal_Snake_CaseFirst_Nameредко в коде; legacy API, автогенерация
camel_Snake_Casefirst_Nameпромежуточный стиль; чаще в генераторах, чем вручную
UPPER_SNAKE_CASE (screaming snake case)MAX_AGEконстанты, макросы, env-переменные
kebab-casemain-headerURL, CSS-классы, имена файлов в вебе
Train-CaseMain-HeaderHTTP-заголовки (Content-Type), MIME-типы
COBOL-CASEMAIN-HEADERразделы COBOL, mainframe-соглашения
flatcasefirstnameслитно, нижний регистр; редко в идентификаторах
UPPERFLATCASEFIRSTNAMEслитно, верхний регистр; legacy, сжатые имена

Примеры по стилям​

camelCase — первое слово с маленькой буквы, каждое следующее с заглавной:

int age;
string firstName;
double totalAmount;

PascalCase — каждое слово с заглавной (классы, в C# — методы и свойства):

class UserAccount { }

void CalculateTotal() { }

string FullName { get; set; }

snake_case — слова в нижнем регистре через _ (типично для Python):

user_age = 25
full_name = "John Doe"

def calculate_total():
pass

UPPER_SNAKE_CASE (ещё screaming snake case) — константы и макросы:

const MAX_AGE = 100;
const DEFAULT_NAME = "Unknown";

Pascal_Snake_Case — каждое слово с заглавной, разделитель _:

User_Account
Content_Type

camel_Snake_Case — первое слово с маленькой, остальные Pascal через _:

user_Account
main_Header

kebab-case — в CSS и разметке:

.main-header {
color: red;
}

Train-Case — PascalCase через дефис (типично для HTTP):

Content-Type: application/json
Accept-Language: ru-RU

COBOL-CASE — верхний регистр через дефис (имена в mainframe и legacy):

IDENTIFICATION-DIVISION
PROCEDURE-DIVISION

В исходниках COBOL сами division пишут через пробел (IDENTIFICATION DIVISION.), но в конфигах, ключах и интеграциях часто встречается именно дефисный вариант.

flatcase — слова слитно, всё в нижнем регистре:

useraccount
firstname

UPPERFLATCASE — слова слитно, всё в верхнем регистре:

USERACCOUNT
MAXAGE

Соглашения в разных языках​

Один проект — один стиль. Ниже — типичные соглашения; у вашей команды может быть свой style guide — он важнее таблицы.


C#​

ЭлементСтиль
КлассыPascalCase
Методы, свойстваPascalCase
Параметры, локальные переменныеcamelCase
Приватные поля_ + camelCase (_lastName)
КонстантыPascalCase (MaxAge) или UPPER_SNAKE для внешних/legacy API
class UserAccount { }

void CalculateTotal() { }

int age;
string firstName;

private string _lastName;

const int MaxAge = 100;

Java​

ЭлементСтиль
КлассыPascalCase
Методы, поляcamelCase
Константы (static final)UPPER_SNAKE_CASE
class UserAccount { }

void calculateTotal() { }

int age;
String firstName;

private String lastName;

final int MAX_AGE = 100;

Python​

ЭлементСтиль
КлассыPascalCase
Функции, переменныеsnake_case
"Приватные" поля_ + snake_case (соглашение, не строгая изоляция)
Константы модуляUPPER_SNAKE_CASE
class UserAccount:
pass

def calculate_total():
pass

user_age = 25

class User:
def __init__(self):
self._private_field = "secret"

MAX_AGE = 100

JavaScript / TypeScript​

ЭлементСтиль
КлассыPascalCase
Функции, переменныеcamelCase
Приватные поля (ES2022+)#fieldName
КонстантыUPPER_SNAKE_CASE или camelCase по гайду проекта
class UserAccount {}

let userAge = 25;
function calculateTotal() {}

class User {
#secret = "value"; // настоящее private-поле
_legacyPrivate = "convention"; // только соглашение, не защита
}

const MAX_AGE = 100;

Осмысленные имена​

Имя — мини-документация. Хорошее имя отвечает хотя бы на два вопроса: что хранится и в каком контексте (единицы, роль в домене).

СлабоЛучшеПочему
data, infoactiveUsers, invoiceLineItemsВидна предметная область
addrLnaddressLineНе нужно расшифровывать аббревиатуру
Versi(...)isFeatureEnabled(...)Понятно при поиске и в стеке вызовов
uEmailAddressForUserLogin123loginEmailДлина без смысла мешает читать

Короткие имена (i, j, n) нормальны, когда область видимости маленькая — классический цикл:

for (var i = 0; i < orders.Count; i++)
{
Process(orders[i]);
}

В глобальной области, в публичном API и в методах на 200 строк i и tmp — уже нет.

Аббревиатуры оставляйте, если они приняты в проекте (id, url, http) или есть в глоссарии заказчика. Всё остальное лучше писать словами.

Роберт Мартин и Стив Макконнелл в книгах о коде сходятся — имя должно объяснять намерение, а не деталь реализации, которая завтра сменится.

Проверку стиля имен удобно отдать линтеру (eslint, pylint, StyleCop) — тогда в ревью не спорят о регистре букв.


Дополнительные соглашения​

Булевы переменные — префиксы is, has, can, should:

bool isActive;
bool hasPermission;
bool canEdit;

Интерфейсы в C# и Java часто с префиксом I (в Java тренд смещается к имени роли без I — смотрите гайд проекта):

interface IUserRepository { }

События — прошедшее время или On + имя:

event EventHandler Clicked;
void OnSaveClicked() { }

Файлы обычно совпадают с главным типом: UserAccount.cs, user_account.py.


Форматирование и стиль​

Форматирование — про пробелы, отступы, переносы строк. Оно не меняет логику, но сильно влияет на скорость чтения и на то, как выглядит diff в ревью.

В Python опираются на PEP 8 (4 пробела, разумная длина строки). В JavaScript/TypeScript часто берут Google JS Style Guide или правила команды.


Три слоя единообразия​

  1. Style guide — текстовые правила ("как мы пишем").
  2. Formatter — автоматически правит отступы и переносы (Prettier, Black, dotnet format).
dotnet format
  1. Linter — ищет потенциальные ошибки и спорные места (ESLint, Pylint, Checkstyle).
  2. Review — то, что машина не поймала — архитектура, имена, бизнес-логика.
ИнструментРольЗаметка
PrettierФорматтер JS/TS/CSS/JSONМало настроек — зато нет вечных споров
BlackФорматтер Python"Один стиль для всех"
ESLintЛинтер JS/TSМожет включать правила стиля, но ядро — качество кода
CheckstyleЛинтер JavaИмена, сложность, импорты

EditorConfig (.editorconfig) в корне репозитория задаёт базу — кодировка, концы строк, размер отступа — до запуска любого форматтера.


#region в C#​

Директивы #region / #endregion только сворачивают блоки в IDE; на выполнение не влияют.

#region Поля и свойства
private string name;
public string Name { get; set; }
#endregion

#region Методы
public void PrintName() => Console.WriteLine(name);
#endregion

Во многих командах регионы не приветствуют: лучше разбить класс на несколько файлов или типов, чем прятать "простыню" за сворачиваемыми блоками. Если используете — короткие понятные названия, без глубокой вложенности.


Комментарии​

Самодокументируемый код — когда структура и имена объясняют что происходит. Комментарий нужен, когда в коде не видно почему так сделано — ограничение API, требование регулятора, временный workaround с номером задачи.

ПлохоЛучше
// увеличиваем счётчик над count++Убрать комментарий, переименовать в incrementActiveUserCount()
// цикл по пользователямУбрать; имя коллекции users уже достаточно
Без комментария при status == 3// В legacy-БД 3 = архив, не "удалён" (см. TASK-1204)
# Плохо: дублирует код
for user in users: # перебираем пользователей
send_reminder(user)

# Лучше — контекст, которого нет в типах
# Напоминания только по будням — требование SLA договора 2023-14
for user in users:
send_reminder(user)

Устаревший комментарий вводит в заблуждение сильнее, чем его отсутствие. Правило простое: меняете поведение — в том же PR обновляете или удаляете комментарий. На ревью это так же важно, как тесты.

Аннотации (@Override, @Deprecated) и структурированная документация (см. ниже) — отдельный слой: они для IDE и генерации справки, не вместо ясных имён.


XML-документация и аналоги в других языках​

Обычный комментарий — для людей в файле. XML-doc / JSDoc / docstring — для подсказок IDE, генерации HTML-справки и контракта публичного API.


C# (XML)​

Комментарии с /// и тегами <summary>, <param>, <returns>:

Код ITЗагрузка примера кода…

Частые теги:

ТегНазначение
<summary>Краткое описание (IntelliSense)
<param name="...">Параметр метода
<returns>Возвращаемое значение
<exception cref="...">Какие исключения возможны
<example>Пример вызова

В .csproj для генерации XML при сборке:

<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

Документацию собирают DocFX, Sandcastle или встроенные средства Visual Studio.


JavaScript (JSDoc)​

Код ITЗагрузка примера кода…


Java (Javadoc)​

Код ITЗагрузка примера кода…


Python (docstring)​

Код ITЗагрузка примера кода…

Стили docstring (Google, NumPy, Sphinx) выбирает проект; главное — единообразие.


Go (godoc)​

Комментарий непосредственно перед объявлением, без спецтегов:

// User — пользователь системы.
type User struct {
Name string
Age int
}

// IsAdult сообщает, достиг ли пользователь 18 лет.
func (u User) IsAdult() bool {
return u.Age >= 18
}

IntelliSense и навигация в IDE​

IntelliSense (в Visual Studio) — общее имя для подсказок при вводе — автодополнение, сигнатура метода, тип под курсором, переход к определению. В JetBrains это Smart Completion, в VS Code — Language Server (Pylance, TypeScript и т.д.).

Зачем писать /// и JSDoc: при вызове user.IsAdult() IDE покажет ваш <summary> / описание — не нужно открывать файл реализации для каждой мелочи.

Go to Definition (Ctrl+клик / F12) — переход к объявлению. Peek Definition — просмотр кода во всплывающем окне без смены файла.

image.png

Под капотом — парсер строит AST, language service добавляет типы и ошибки (Roslyn, TypeScript Language Service, Pyright). Документация в комментариях подхватывается тем же анализом.

Современные IDE и плагины (Copilot и аналоги) дополняют, но не отменяют договорённости команды: сгенерированный код тоже нужно именовать и форматировать по правилам проекта.


Принципы, которые поддерживают читаемость​

Кратко — с примерами. Подробнее про ООП и паттерны — в соответствующих разделах энциклопедии.


KISS — делай проще​

Выбирайте решение, которое закрывает текущую задачу без лишних слоёв. Расшифровка "Keep It Simple" иногда дополняют словом Straightforward — суть та же: не усложнять "на вырост".


DRY — не повторяй знание​

// Плохо: одна и та же проверка email в пяти формах
if (!value.includes("@")) throw new Error("...");

// Лучше
function isValidEmail(value: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
}

DRY про логику и правила, не про побайтовое совпадение двух строк, которые случайно похожи.


YAGNI — не пиши "на будущее" без запроса​

Фабрики, абстракции и параметры "на всякий случай" увеличивают объём кода, который придётся читать и тестировать, даже если фича не понадобится.


SRP — одна ответственность у модуля​

// Плохо: валидация, расчёт и отправка письма в одном методе
void ProcessOrder(Order o) { /* 200 строк */ }

// Лучше: отдельные шаги с говорящими именами
void ProcessOrder(Order o)
{
Validate(o);
var total = CalculateTotal(o);
NotifyCustomer(o, total);
}

Закон Деметры — меньше цепочек​

// Хрупко: знание внутренней структуры заказа
var city = order.Customer.Address.City;

// Лучше: спросить у доменного объекта
var city = order.GetDeliveryCity();

CQS — команда или запрос​

Метод либо меняет состояние (команда), либо возвращает данные (запрос), но не оба сразу без явной причины. Так проще рассуждать о побочных эффектах.


SOLID​

Набор из пяти принципов ООП-проектирования (единственная ответственность, открытость/закрытость, подстановка Лисков, разделение интерфейсов, инверсия зависимостей). Они помогают держать модули узкими и заменяемыми — см. чек-лист, вопросы 9–13.


TDD — красный → зелёный → рефакторинг​

  1. Написать падающий тест на поведение.
  2. Минимальный код, чтобы тест прошёл.
  3. Улучшить структуру, тесты остаются зелёными.

TDD не обязателен везде, но полезен, когда логика легко ломается при правках.


Культура в команде — ревью и тон​

Просмотр кода (code review, рецензирование, инспекция кода) — систематическая проверка изменений до слияния в общую ветку. Цели двойные: найти дефекты, уязвимости и несоответствие требованиям и поднять общий уровень команды (обмен знаниями, единый стиль).

На ревью смотрят не только "работает ли", но и:

В экстремальном программировании обычное "один проверяет код другого" доведено до парного программирования — непрерывный совместный просмотр в момент написания. Для распределённых команд тот же смысл даёт обязательный review в Git (GitHub, GitLab) плюс статический анализ.

КатегорияПрактикаПример
ИменованиеОписательные имена в публичном APItotalUsers вместо tu
СтильСоглашения языка + форматтерPrettier / Black в pre-commit
Комментарии"Зачем", не "что"Ссылка на задачу для workaround
СложностьОбсуждать рост ветвленийСм. статью 2
РевьюНебольшие PR, вопросы без ярлыков"Рассмотрите вынос валидации"
Безопасность обсужденийОшибка — повод улучшить процессНормы ревью в README или wiki

Размер pull request 200–400 строк (ориентир) — способ сохранить внимание ревьюера. Огромный diff лучше дробить по смыслу; крупный рефакторинг отделяют от фичи, чтобы ревьюеры видели либо поведение, либо структуру.

Психологическая безопасность — когда можно спросить "не понял этот кусок" без страха насмешки. Её поддерживают явные правила ревью: комментарий к коду, не к человеку; критерии "approve"; эскалация споров к тимлиду, а не к чату "кто виноват".

Автоматизация (форматтеры, линтеры, статический анализ) снимает рутину; AI-помощники ускоряют черновик, но финальную ответственность за имена, границы модулей и тесты несёт автор PR.


Краткая шпаргалка​

  1. Имена и формат — по гайду проекта; спорные места ловит tooling.
  2. Комментарий — про контекст, который не выразить именем.
  3. Публичный API — XML-doc / JSDoc / docstring.
  4. Функция разрастается — смотрите цикломатическую сложность и дробите по смыслу.
  5. Перед merge — чек-лист и зелёные тесты/линтер локально.

Дальше: Цикломатическая сложность → MAPPER → Итоги. По симптому в коде — справочник тем.