Принципы чистого кода

Как принципы чистого кода меняют ваш опыт программирования

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

Концепция была формализована Робертом К. Мартином, «дядей Бобом», в книге «Чистый код: Справочник по гибкой разработке программного обеспечения» (2008), которая до сих пор остается каноническим справочником по этой теме. Книга Мартина Фаулера «Рефакторинг: Улучшение дизайна существующего кода» рассматривает дополнительную проблему: что делать, когда существующий код не является чистым и нуждается в улучшении. Вместе эти две работы определяют интеллектуальную основу практики чистого кода. Это руководство охватывает их ключевые принципы с примерами работающего кода на наиболее распространенных языках, включая RPG и PL/I, для разработчиков, поддерживающих корпоративные устаревшие системы.

Устранение невидимых нарушений в коде

SMART TS XL Обнаруживает сложные конструкции, дублирование кода и неиспользуемый код в COBOL, Java, Python, RPG и других языках.

Подробнее

Что такое чистый код?

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

Наиболее цитируемым является определение Мартина Фаулера: «Любой дурак может написать код, который поймет компьютер. Хорошие программисты пишут код, который поймет человек». Мерилом чистоты кода является понимание человеком, а не машинное исполнение.

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

Принципы чистого кода: краткий справочник

Принципы чистого кода Роберта К. Мартина можно кратко сформулировать как набор практических рекомендаций. В таблице ниже показано соответствие каждого принципа его основному правилу и проблеме, которую он предотвращает:

ПринципОсновное правилоПроблема, которую это предотвращает
Значимые именаНазвания должны отражать назначение, переменные, функции и классы.Когнитивные издержки на декодирование чего x, tmp или obj на самом деле представляет
Малые функцииВсе функции выполняют одну задачу и помещаются на одном экране.Непроверяемые, нечитаемые монолиты, смешивающие обязанности
Единая ответственностьКаждый класс/модуль имеет одну причину для изменений.Классы богов, которые ломаются при изменении любой из десятка несвязанных между собой вещей.
СУХОЙ (не повторяйтесь)Каждый фрагмент знаний имеет одно представление.Исправления ошибок были применены в одной копии, но не в других; логическое смещение.
KISS (Keep It Simple — Делай проще)Отдавайте предпочтение самому простому и эффективному решению.Излишне сложный код, решающий проблемы, которых ни у кого нет.
ЯГНИ (Тебе это не понадобится)Не разрабатывайте новые функции, пока они не понадобятся.Мертвый спекулятивный код, который усложняет код без какой-либо пользы.
Принцип открытости/закрытостиОткрыто для продления, закрыто для модификацииКод, нарушающий существующее поведение при добавлении нового поведения.
Разделение проблемРазные обязанности в разных местах.Запутанный код, в котором изменение одной вещи приводит к поломке несвязанных с ней вещей.
Избегайте комментариев типа «для чего?»; используйте их для объяснения «почему?».Код объясняет суть; комментарии объясняют, почему.Устаревшие комментарии, которые вводят в заблуждение больше, чем дают информации.
Правило бойскаутаОставьте код чище, чем вы его нашли.Постепенное ухудшение качества вследствие постепенного пренебрежения

Основные принципы «чистого кода»: объяснение

DRY, Don't Repeat Yourself

Принцип DRY (Don't Repeat Yourself — не повторяйте себя) гласит, что каждый фрагмент знаний должен иметь единственное, однозначное и авторитетное представление в системе. Когда одна и та же логика встречается в нескольких местах, эти представления неизбежно расходятся. Изменение бизнес-правила превращается в поиск и замену в нескольких файлах, а отсутствие одной копии приводит к ошибке.

Принцип DRY применим не только к скопированному коду. Он распространяется на конфигурацию, документацию и схемы данных. Если одна и та же информация должна храниться в двух местах, вы нарушаете принцип DRY, независимо от того, был ли какой-либо код скопирован буквально.

KISS, Keep It Simple

Принцип KISS (Keep It Simple, Stupid – «Делай проще, дурак») утверждает, что системы работают лучше всего, когда они просты, и что простота должна быть главной целью проектирования. Сложность — это не признак изысканности, это признак того, что что-то можно было бы выразить более ясно.

Практическое следствие: если два решения решают одну и ту же проблему, отдавайте предпочтение более простому. Более сложное решение может обрабатывать граничные случаи, с которыми вы еще не сталкивались. Это определенно сделает код более сложным для понимания всеми, кто с ним столкнется.

YAGNI, You Aren't On Need It (Ты не понадобишься)

Принцип YAGNI (You Ain't Gone Insight — «нельзя добавлять функциональность, если она не нужна»). Это ответ на распространенное стремление создавать гибкие, расширяемые системы для предполагаемых будущих требований, которые никогда не материализуются. Каждая абстракция, интерфейс и параметр конфигурации, существующие для гипотетического будущего варианта использования, представляют собой когнитивную нагрузку для каждого разработчика, читающего код сегодня.

Значимые имена

Имена являются основным механизмом коммуникации в исходном коде. Функция с именем process() Функция не сообщает ничего о том, что она обрабатывает, когда и что возвращает. Функция с именем calculateMonthlyInterest() точно передает информацию.

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

Малые функции

Эмпирическое правило Роберта К. Мартина: функции должны быть небольшими, меньше, чем кажется необходимым. Функцию, выполняющую одну задачу, можно четко назвать, протестировать изолированно и понять без чтения ее реализации. Функция, выполняющая несколько задач, требует одновременного понимания всех этих задач.

Принцип единственной ответственности, примененный к функциям: функция должна делать одно дело, делать его хорошо и делать только это. Если для описания того, что делает функция, вам нужно слово «и», вероятно, она делает слишком много.

Чистый код на Java

Многословность Java делает соблюдение принципов чистого кода особенно важным. Объём шаблонного кода, требуемый языком, может скрывать истинное предназначение, если им не управлять должным образом.

Ява

// Before: unclear names, mixed responsibilities, magic numbers
public double calc(int x, int y) {
    double r = 0;
    if (y > 1000) {
        r = x * y * 0.1;
    } else {
        r = x * y * 0.05;
    }
    return r;
}

// After: meaningful names, single responsibility, named constants
private static final double PREMIUM_DISCOUNT_RATE = 0.10;
private static final double STANDARD_DISCOUNT_RATE = 0.05;
private static final int PREMIUM_THRESHOLD = 1000;

public double calculateDiscount(int quantity, int unitPrice) {
    double subtotal = quantity * unitPrice;
    return isPremiumOrder(unitPrice) 
        ? subtotal * PREMIUM_DISCOUNT_RATE 
        : subtotal * STANDARD_DISCOUNT_RATE;
}

private boolean isPremiumOrder(int unitPrice) {
    return unitPrice > PREMIUM_THRESHOLD;
}

Ключевые принципы чистого кода для Java: предпочтение композиции наследованию, использование потоков для обработки данных вместо многословных циклов, выделение «магических чисел» в именованные константы и сосредоточение классов на выполнении одной задачи. Принципы чистого кода для Java, предложенные Робертом К. Мартином, включают правило, согласно которому классы должны быть небольшими, измеряемыми не строками кода, а обязанностями.

Ява

// Clean Java: streams over imperative loops
List<String> activeUserEmails = users.stream()
    .filter(User::isActive)
    .map(User::getEmail)
    .collect(Collectors.toList());

Чистый код на Python

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

питон

# Before: vague names, long function, no separation
def do_stuff(d):
    res = []
    for i in d:
        if i['a'] > 18:
            res.append(i['n'].upper())
    return res

# After: meaningful names, separated concerns, Pythonic style
def get_adult_names_uppercase(users: list[dict]) -> list[str]:
    return [
        user["name"].upper()
        for user in users
        if user["age"] > 18
    ]

питон

# Clean Python: context managers for resource handling
# Before: manual, error-prone
f = open("data.txt")
data = f.read()
f.close()

# After: guaranteed cleanup, self-documenting intent
with open("data.txt") as f:
    data = f.read()

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

Чистый код в JavaScript и TypeScript

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

Javascript

// Before: var, callback hell, no error handling
function getUser(id, cb) {
    db.query('SELECT * FROM users WHERE id = ' + id, function(err, rows) {
        if (err) cb(err);
        cb(null, rows[0]);
    });
}

// After: async/await, parameterized query, proper error handling
async function getUserById(userId: number): Promise<User | null> {
    const [rows] = await db.execute(
        'SELECT * FROM users WHERE id = ?',
        [userId]
    );
    return rows[0] ?? null;
}

машинопись

// Clean TypeScript: explicit types replace implicit any
// Before
function process(data) {
    return data.map(x => x.v * 2);
}

// After
interface DataPoint {
    value: number;
    label: string;
}

function doubleValues(dataPoints: DataPoint[]): number[] {
    return dataPoints.map(point => point.value * 2);
}

Чистый JavaScript использует const по умолчанию, let когда требуется переплет, и никогда. varЧистые функции, один и тот же вход всегда дает один и тот же результат, без побочных эффектов, являются наиболее чистым строительным блоком для логики JavaScript.

Чистый код на C#

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

острый

// Before: magic numbers, verbose loop, mutable state
public double CalculateTotal(List<OrderItem> items)
{
    double total = 0;
    foreach (var item in items)
    {
        if (item.Quantity > 10)
            total += item.UnitPrice * item.Quantity * 0.9;
        else
            total += item.UnitPrice * item.Quantity;
    }
    return total;
}

// After: named constant, LINQ, single expression
private const double BulkDiscountRate = 0.9;
private const int BulkDiscountThreshold = 10;

public double CalculateTotal(IEnumerable<OrderItem> items) =>
    items.Sum(item => item.Quantity > BulkDiscountThreshold
        ? item.UnitPrice * item.Quantity * BulkDiscountRate
        : item.UnitPrice * item.Quantity);

Принципы чистого кода в C#: используйте свойства вместо открытых полей для инкапсуляции, используйте LINQ для декларативных операций с данными, используйте записи для неизменяемых структур данных, отдавайте предпочтение интерфейсам перед конкретными типами в сигнатурах методов и используйте ссылочные типы, допускающие значение null.string? vs string) для явного обеспечения безопасности при работе с нулевыми значениями.

Чистый код на Kotlin

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

Котлин

// Before: verbose Java-style Kotlin
class User {
    var name: String = ""
    var email: String = ""
    var age: Int = 0
}

fun processUsers(users: List<User>): List<String> {
    val result = mutableListOf<String>()
    for (user in users) {
        if (user.age >= 18) {
            result.add(user.email)
        }
    }
    return result
}

// After: idiomatic clean Kotlin
data class User(val name: String, val email: String, val age: Int)

fun getAdultEmails(users: List<User>): List<String> =
    users.filter { it.age >= 18 }.map { it.email }

Kotlin data class Предоставляет функции equals, hashCode, copy и toString автоматически, устраняя избыточный код, делающий классы данных Java многословными и подверженными ошибкам. Функции расширения позволяют добавлять полезные методы к существующим классам без наследования.

Чистый код в RPG и PL/I: устаревшие языки, современные принципы

Принципы чистого кода применимы ко всем языкам программирования, включая корпоративные языки, используемые в финансовых системах, страховых платформах и государственных приложениях по всему миру. RPG (Report Program Generator) и PL/I до сих пор активно поддерживаются во многих организациях, и та же дисциплина, которая делает Java или Python чистыми, обеспечивает устойчивость RPG и PL/I.

Чистый код в RPG (ILE RPG) :

рпг

     // Before: cryptic two-character names, magic numbers
     C                   EVAL      D = Q * P * 1.05
     C                   IF        Q > 100
     C                   EVAL      D = Q * P * 0.95
     C                   ENDIF

     // After: meaningful names, named constants, clear intent
      /free
       dcl-c BULK_DISCOUNT_THRESHOLD 100;
       dcl-c BULK_DISCOUNT_RATE      0.95;
       dcl-c STANDARD_RATE          1.05;

       dcl-proc CalculateOrderTotal;
         dcl-pi *N packed(15:2);
           quantity  packed(7:0) value;
           unitPrice packed(9:2) value;
         end-pi;

         if quantity > BULK_DISCOUNT_THRESHOLD;
           return quantity * unitPrice * BULK_DISCOUNT_RATE;
         else;
           return quantity * unitPrice * STANDARD_RATE;
         endif;
       end-proc;
      /end-free

Основные принципы создания чистых ролевых игр: используйте свободный формат ILE RPG (/freeДля удобства чтения используйте синтаксис, а не фиксированный формат, называйте процедуры описательно, заменяйте жестко закодированные значения именованными константами. dcl-cи разложить длинные программы на целенаправленные процедуры, используя dcl-proc.

Чистый код на PL/I :

изгиб

/* Before: single-letter names, no structure */
CALC: PROC(X, Y) RETURNS(FLOAT);
  DCL (X, Y, R) FLOAT;
  IF Y > 1000 THEN R = X * Y * 0.1;
  ELSE R = X * Y * 0.05;
  RETURN(R);
END CALC;

/* After: meaningful names, named constants, clear intent */
DCL PREMIUM_THRESHOLD  FIXED DECIMAL(7) INIT(1000);
DCL PREMIUM_RATE       FLOAT           INIT(0.10);
DCL STANDARD_RATE      FLOAT           INIT(0.05);

CALCULATE_DISCOUNT: PROC(QUANTITY, UNIT_PRICE) RETURNS(FLOAT);
  DCL (QUANTITY, UNIT_PRICE) FLOAT;
  DCL SUBTOTAL              FLOAT;

  SUBTOTAL = QUANTITY * UNIT_PRICE;

  IF UNIT_PRICE > PREMIUM_THRESHOLD
    THEN RETURN(SUBTOTAL * PREMIUM_RATE);
    ELSE RETURN(SUBTOTAL * STANDARD_RATE);

END CALCULATE_DISCOUNT;

Принципы чистого кода в PL/I повторяют принципы современных языков: осмысленные идентификаторы (31-символьного ограничения PL/I достаточно для описательных имен), именованные константы вместо «магических чисел», процедуры, ориентированные на выполнение одной задачи, и явная обработка ошибок с использованием системы условий ON.

Инструменты для чистого кода и статический анализ

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

ИнструментЯзыкиЧто оно навязывает
SonarQube / SonarCloud30 + языкиСложность, дублирование, «запахи кода», безопасность
ЧекстайлJavaСоглашения об именовании, форматирование, структура
PMDJava, ApexДублирование кода, неиспользуемые переменные, сложность.
ESLint + typescript-eslintJavaScript, TypeScriptСтиль, сложность, неиспользуемый код, асинхронные шаблоны
Пилинт + РадонПитонPEP 8, сложность, индекс ремонтопригодности
РеШарперC#Стиль кода, избыточный код, предложения по рефакторингу
ClippyРжавчинаИдиоматические выражения, распространённые ошибки
SMART TS XLCOBOL, RPG, PL/I, Java, Python и другие языки программирования.Межъязыковая сложность, дублирование, мертвый код

Наиболее распространенные метрики чистоты кода, измеряемые инструментами: цикломатическая сложность (количество ветвей принятия решений, более 10 — предупреждение, более 20 — критическая ошибка), когнитивная сложность (насколько сложно понять код, метрика, специфичная для SonarQube, которая улучшает цикломатическую сложность для измерения читаемости) и уровень дублирования (процент дублированного кода, более 3% требует внимания).

Как SMART TS XL Обеспечивает чистоту кода во всех корпоративных кодовых базах.

Перечисленные выше инструменты для очистки кода работают в рамках одного языка. В корпоративных средах, где сосуществуют Java-сервисы, Python-конвейеры, пакетные программы COBOL, модули RPG и потоки заданий JCL, необходимо одновременно измерять нарушения чистоты кода в каждом языке, и взаимосвязи между компонентами разных языков имеют такое же значение, как и качество кода в отдельном файле.

SMART TS XLАвтора статический анализ кода Применяет метрики чистоты кода одновременно ко всем языкам в среде. Цикломатическая сложность, частота дублирования, выявление мертвого кода и метрики структурной связности рассчитываются для программ на COBOL с использованием той же методологии, что и для классов Java, что позволяет получать сопоставимые, единые показатели качества для всего портфеля приложений.

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

Анализ зависимостей приложений выявляет архитектурные нарушения принципа чистого кода на системном уровне: какие компоненты стали «классами бога» в масштабах предприятия, где циклические зависимости нарушают принцип разделения ответственности между языками программирования, и где одна и та же бизнес-логика была независимо реализована в нескольких системах, при этом ни одна из копий не знала о существовании другой. Это межъязыковое нарушение принципа DRY (Don't Repeat Yourself — не повторяйте себя), когда одно и то же бизнес-правило поддерживается отдельно в программе на COBOL, сервисе на Java и конвейере на Python, является наиболее дорогостоящей категорией нарушений принципа чистого кода в корпоративных системах и невидимо для любого инструмента, работающего с одним языком программирования.

Для команд, применяющих принципы чистого кода во время модернизация наследия программы, SMART TS XL Предоставляет предварительный анализ перед рефакторингом, что делает работу выполнимой: мертвый код исключается из объема работ до начала преобразования, определяются компоненты с наибольшей сложностью для приоритетного внимания, и полный граф зависимостей становится доступен до внесения каких-либо изменений.

«Чистый код» — это командная дисциплина, а не индивидуальная практика.

Принципы, изложенные в этом руководстве, проще сформулировать, чем поддерживать. Любой отдельный разработчик может написать чистую функцию изолированно. Задача состоит в поддержании чистоты кода в команде, с течением времени и в рамках кодовой базы, которая растет по мере изменения состава ее участников. Для этого необходимы три вещи: общие стандарты, которые все знают и с которыми согласны, инструменты, которые автоматически обеспечивают соблюдение этих стандартов, и культура постепенного улучшения, правило бойскаутов, применяемое последовательно, оставляя каждую часть кода немного чище, чем она была изначально.

Наиболее полезной остается формулировка Роберта К. Мартина: «Чистый код всегда выглядит так, будто его написал кто-то, кому небезразлично». Доказательством того, что кому-то небезразлично, является не отсутствие ошибок, а наличие читаемых имен, сфокусированных функций, четкой структуры и отсутствия неожиданностей. Именно эти доказательства делают кодовую базу достойной работы, достойной внесения вклада и достойной поддержки на протяжении многих лет и при смене команды, которые неизбежно сопровождают любую успешную систему.