Очисти свой проклятый код! Почему меня это должно волновать?

Я долго колебался, писать ли эту статью, потому что блогов, постов, книг на эту тему уже полно, но…
Несколько лет назад в своей комнате на доске я запостил картинку с надписью « Шесть правил успеха» Арнольда Шварценеггера. Впервые я увидел его на столе моего бывшего руководителя группы во время работы в Дрездене, и он мне очень понравился. », но одно из правил, которое я постоянно пропускал, гласит: «Отдай что-нибудь взамен». Объединив ее с «Доверяй себе» и «Не бойся потерпеть неудачу», я решил сделать эту статью, надеюсь, она тебе понравится 🙂 . «Жизнь слишком коротка, поэтому я не могу ждать», — пел Тилль Линдеманн, так что начнем!

Чистый код — одна из самых популярных книг по информационным технологиям… Но подождите, правда? Я не вижу, чтобы многие из нас (программистов) следовали хотя бы 30% советов, которые дал нам дядя Боб. За всю свою карьеру программиста я видел, что на самом деле очень небольшое число программистов действительно заботятся о том, чтобы код оставался чистым, и выполняют непрерывный рефакторинг кода, с которым они работают каждый день.

Поскольку я в основном занимаюсь C++, но в последние месяцы я также ежедневно использую Python3, я постараюсь представить примеры на Python. Кроме того, я большой поклонник примеров, следующих принципу KISS, поэтому я постараюсь показать проблему как можно проще, чтобы вы сосредоточились на разгадке проблемы, а не на несущественных деталях.

Код должен быть читабельным, как книга!

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

Код читают чаще, чем пишут

как написал Гвидо Ван Россум (автор языка Python).

Нет ничего хуже, чем смотреть на свой собственный код через несколько месяцев и пытаться понять, что он делает…

Эта концепция представлена ​​в популярном изображении из книги Чистый код Роберта К. Мартина:

Мы должны уметь читать код и понимать алгоритм очень быстро. Моя честная мечта — писать код максимально простым и осмысленным, чтобы его мог анализировать другой программист, не задумываясь о том, «что за хрень».
Хорошо, но как мы можем этого добиться? ? Есть несколько правил, которым мы можем следовать, чтобы улучшить читабельность кода. Тогда начнем с…

Первое правило от SOLID: принцип единой ответственности

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

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

def get_result(user, password) -> dict:
    authorized_users = ("user1", "user2")
    if user == "user1" and password == "pass123": # authentication
        if user in authorized_users:              # authorization
            return {"result", "OK"}
        else:
            return {"result": "Not authorized"}
    else:
        return {"result": "Not authenticated"}

В приведенном выше коде много проблем, но давайте подумаем, с чего мы можем начать его улучшать.
Сосредоточьтесь на принципе единой ответственности.
Во-первых, давайте проверим требования. Хорошо сформулированные требования можно использовать как список шагов (алгоритмов), которые необходимо реализовать. Давайте извлечем их:

  1. Аутентификация пользователя
  2. Авторизация пользователя
  3. Возврат результата

Может быть, мы можем инкапсулировать, извлечь эти шаги из функции get_result() в более мелкие?

def authenticate(user, password) -> bool:
    return user == "user1" and password == "pass123"

def authorize(user) -> bool:
    AUTHORIZED_USERS = ("user1", "user2")
    return user in authorized_users

def get_result(user, password) -> dict:
    if authenticate(user, password):
        if authorize(user):
            return {"result", "OK"}
        else:
            return {"result": "Not authorized"}
    else:
        return {"result": "Not authenticated"}

Прежде всего — каждая функция делает только одну вещь. У нас есть отдельная функция для каждой задачи/шага.
Функции короткие, поэтому очень легко понять, что они делают, просто взглянув на их тела и названия (краткие глаголы, выражающие назначение функции).
Обратите внимание, что мы не больше не нужны комментарии! Наши комментарии стали частью имен функций. Это большое улучшение!
Наконец, мы изменили authorized_users на верхний регистр, потому что это в основном константа (согласно PEP8 это соглашение должно использоваться, но оно популярно и в других языках).

Меньше кода = проще код (конечно, даже короткий код может быть сложным, в этом нет сомнений, особенно на Python 😉).
Меньше чтения = быстрее понимание того, что он делает, меньше «WTF» в минуту 🙂

Но… идеально ли это? Возможно, authenticate() и authorize() плотные, короткие и не требующие пояснений, но можем ли мы сделать что-то большее? Конечно можем!

Предпочитайте шаблон Early Return вместо создания деревьев if-else

Давайте посмотрим на функцию get_result(). Подумайте, не будет ли лучше понять это, если мы избавимся от вложенных операторов if и воспользуемся так называемым «шаблоном раннего возврата»:

def get_result(user, password) -> dict:
    if not authenticate(user, password):
        return {"result": "Not authenticated"}
    if not authorize(user):
        return {"result": "Not authorized"}
    return {"result", "OK"}

Разве это не яснее и короче? Текущая форма функции get_result() выглядит вполне нормально, но с ней все еще есть одна «проблема». Это довольно коротко, но все же мы выполняем три шага, чтобы получить результат, и это не слишком заметно (по крайней мере, для меня). Мы можем сделать одну очень простую вещь, чтобы улучшить читабельность…

Разделяйте блоки кода пустой строкой для лучшей читаемости

Просто взгляните на это:

def get_result(user, password) -> dict:
    if not authenticate(user, password):
        return {"result": "Not authenticated"}

    if not authorize(user):
        return {"result": "Not authorized"}

    return {"result", "OK"}

Не лучше ли прочитать такую ​​функцию вместо предыдущей? Ключевые части кода теперь открыты, мы можем ясно видеть все три шага, которые выполняются для получения результатов от функции.
Конечно, вы увидите реальную силу создания пробелов между частями кода, выполняющими отдельную работу. когда функции будут немного длиннее и сложнее, чем эта. Но тогда… дважды подумайте, есть ли возможность разделить его на более мелкие функции! 😉

До и после

Наконец, давайте посмотрим, как наш код изменился по сравнению с этим:

def get_result(user, password) -> dict:
    authorized_users = ("user1", "user2")
    if user == "user1" and password == "pass123": # authentication
        if user in authorized_users:              # authorization
            return {"result", "OK"}
        else:
            return {"result": "Not authorized"}
    else:
        return {"result": "Not authenticated"}

в это:

def authenticate(user, password) -> bool:
    return user == "user1" and password == "pass123"

def authorize(user) -> bool:
    AUTHORIZED_USERS = ("user1", "user2")
    return user in authorized_users

def get_result(user, password) -> dict:
    if not authenticate(user, password):
        return {"result": "Not authenticated"}

    if not authorize(user):
        return {"result": "Not authorized"}

    return {"result", "OK"}

Если понравилось подпишитесь и оставьте комментарий 🙂
Если нашли и проблема, опечатка, баг (особенно) пишите комментарий! 😉