Пара слов об именовании переменных и методов


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


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


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


    Не будем затягивать и, пожалуй, начнем.


    Переменные


    Один из самых раздражающих видов переменных — это такие переменные, что дают ложное представление о природе данных, которые они хранят. Эдакие переменные-мошенники.


    В среде Python-разработчиков крайне популярна библиотека requests, и если вы когда-либо искали что-то связанное с requests, то наверняка натыкались на подобный код:


    import requests
    
    req = requests.get('https://api.example.org/endpoint')
    req.json()

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


    Когда вы делаете запрос (requests.Request), то получаете ответ (requests.Response), так отразите это у себя в коде:


    response = requests.get('https://api.example.org/endpoint')
    response.json()

    Не r, не res, не resp и уж точно не req, а именно response. res, r, resp (про req и вовсе молчу) — это все переменные, содержание которых можно понять, только взглянув на их объявление, а зачем прыгать к объявлению, когда можно изначально дать подходящее название?


    Давайте рассмотрим еще один пример, но теперь из Django:


    users_list = User.objects.filter(age__gte=22)

    Когда вы видите где-то в коде users_list, то вы совершенно справедливо ожидаете, что сможете сделать так:


    users_list.append(User.objects.get(pk=3))

    Но нет, вы этого сделать не можете, так как .filter() возвращает QuerySet, а QuerySet далеко не list:


    Traceback (most recent call last):
    # ...
    # ...
    AttributeError: 'QuerySet' object has no attribute 'append'

    Если вам очень важно указывать суффикс, то укажите хотя бы такой, который соответствует действительности:


    users_queryset = User.objects.all()
    users_queryset.order_by('-age')

    Если же вам очень хочется написать именно _list, то будьте добры позаботиться о том, чтобы в переменную и правда попадал список:


    users_list = list(User.objects.all())

    Привязка названия переменной к типу хранящихся в ней данных — это чаще всего плохая идея, особенно когда вы работаете с динамическими языками. В случаях, когда очень нужно отметить, что объект представляет собой контейнерный тип данных, достаточно просто указать название переменной во множественном числе:


    users = User.objects.all()

    или, если вам уж совсем неймется и вы в любом случае намерены использовать суффиксы, то лучше добавить суффикс _seq (меньшее из зол), чтобы отметить, что это последовательность:


    users_seq = [1, 2, 3]
    # или 
    users_seq = (1, 2, 3)
    # или
    users_seq = {1, 2, 3}

    Таким образом, вы будете знать, что в переменной хранится последовательность и что по ней можно итерировать, но не будете делать предположений о прочих свойствах и методах объекта.


    Еще одним видом раздражающих переменных являются переменные с сокращенными именами.


    Вернемся к requests и рассмотрим этот код:


    s = requests.Session()
    # ... 
    # ... 
    s.close()

    Это яркий пример совершенно неоправданного сокращения названия переменной. Это ужасная практика, и ее ужасы становятся еще более очевидны, когда такой код занимает больше 10-15 строк кода.


    Конкретно в случае requests скрипя зубами можно простить подобное сокращение, когда код занимает не более 5-10 строк и записывается вот так:


    with requests.Session() as s:
        # ...
        # ...
        s.get('https://api.example.org/endpoint')

    Тут контекстный менеджер позволяет дополнительно выделить объемлющий блок для переменной s.


    Но гораздо лучше написать как есть, а именно:


    session = requests.Session()
    # ...
    # ...
    session.get('https://api.example.org/endpoint')
    # ...
    # ...
    session.close()

    или


    with requests.Session() as session:
        session.get('https://api.example.org/endpoint')

    Вы можете возразить, что это ведь более многословный вариант, но я вам отвечу, что это окупается, когда вы читаете код и сразу понимаете, что session — это Session. Поймете ли вы это по переменной s, не взглянув на ее определение?


    Рассмотрим еще один пример:


    info_dict = {'name': 'Isaak', 'age': 25}
    # ...
    # ... 
    info_dict = list(info_dict)
    # ...
    # ...

    Вы видите dict и можете захотеть сделать так:


    for key, value in info_dict.items():
        print(key, value)

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


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


    В приведенном выше примере выбор переменной достаточно неудачный, и можно было бы дать имя, точнее выражающее контекст (не нужно бояться использовать имена, относящиеся к предметной области), однако даже в этом случае можно было бы сделать этот код лучше:


    info_dict = {'name': 'Isaak', 'age': 25}
    # ...
    # ... 
    info_keys = list(info_dict)
    # ...
    # ...

    или даже так, что более идиоматично:


    info_dict = {'name': 'Isaak', 'age': 25}
    # ...
    # ... 
    info_keys = info_dict.keys()
    # ...
    # ...

    Комментарии-кэпы


    Комментарии – это то, что может как испортить ваш код, так и сделать его лучше. Хороший комментарий требует времени на обдумывание и написание, и потому чаще всего мы все сталкиваемся с отвратительными комментариями, которые не представляют собой ничего, кроме визуального мусора.


    Возьмем небольшой пример из JavaScript:


    // Remove first five letters
    const errorCode = errorText.substr(5)

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


    Давайте попробуем сделать этот комментарий полезным:


    // Remove "net::" from error text
    const errorCode = errorText.substr(5)

    А еще лучше прибегнуть к более декларативному подходу и избавиться от комментария вообще:


    const errorCode = errorText.replace('net::', '')

    Говоря о комментариях, нельзя не упомянуть мертвый код. Мертвый код, пожалуй, раздражает куда больше, чем бесполезные комментарии, так как вы еще и должны разбираться, был ли код закомментирован временно (для отладки каких-то частей системы) или все же разработчик просто забыл его удалить?


    Как бы там ни было, мертвому коду не место в модулях и он должен быть удален! Если вдруг окажется, что это было что-то важное, то вы сможете просто откатиться к нужной версии (если, конечно, вы не программист-амиш, что не пользуется системой контроля версий).


    Методы


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


    Рассмотрим пример с методом:


    >>> person = Person()
    >>> person.has_publications()
    ['Post 1', 'Post 2', 'Post 3']

    В коде мы выразили весьма однозначный вопрос: "Имеются ли у этого человека публикации?", но какой ответ мы получили?


    Мы не спрашивали, какие у человека есть публикаций. Название этого метода подразумевает, что возвращаемое значение должно иметь булевый тип, а именно True или False:


    >>> person = Person()
    >>> person.has_publications()
    True

    А для получения постов вы можете использовать более подходящее название:


    >>> person.get_publications()
    ['Post 1', 'Post 2', 'Post 3']

    или


    >>> person.publications()
    ['Post 1', 'Post 2', 'Post 3']

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


    Список литературы для изучения вопроса


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


    1. Robert Martin — Clean Code
    2. Robert Martin — Clean Architecture
    3. Robert Martin — The Clean Coder: A Code of Conduct for Professional Programmers
    4. Martin Fowler — Refactoring: Improving the Design of Existing Code
    5. Colin J. Neill — Antipatterns: Managing Software Organizations and People
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      –10
      person.has_publications()
      True

      Мне почему-то не нравится этот вариант. Хочется видеть person.isHasPublication(). Я не нэйтив инглиш. Но почему-то ожидаю получить true именно в варианте is has… А в варианте has словно утверждение. Да, публикации есть.

      Может кто встречал какую-либо базовую грамматику английского для программистов как раз на подобные связки, для написания грамотного кода? А то я же как-то пишу… Не хочется, чтобы меня потом материли только потому что я не native, а кривое подобие google транcлейт, которое всё понимает, но разговаривает на уровне собаки :)
        +12

        is has — это кровавые слезы из глаз, два глагола подряд. has_publications — нормально, утверждение "имеет публикации" может быть как истиной, так и ложью, если вернет True — значит имеет, если False — значит нет. Глагол is применим к определениям, например, post.is_published().
        Базовая грамматика — она базовая не только для прораммистов, а для всех, могу посоветовать просто пройти базовый курс на любом из ресурсов по обучению английскому :)

          +1
          present perfect continuous в пассивном залоге — вот это кровавые слезы. четыре глагола подряд! :)
            +1

            Ну так-то да, мое утверждение насчет глаголов подряд требует уточнения :) Какие и когда можно в цепочку строить, а какие нельзя.

              0

              I haven't been noticing four verbs in a row here

                0
                so they haven't been being noticed?
                  0

                  no way that noticed could be something else than an adjective here

                    0
                    cmon! past participle is still a form of a verb, not an adjective.
                      0

                      but yeah but no but yeah but no

                        0
                        I know, right? *wink*
              0

              Использовать is с глаголом в прошедшей форме тоже не самый лучший выбор, хоть и интуитивно понятнее. Но все же. is_active(), is_empty(), но published().

                +2

                Это не глагол в прошедшей форме, это страдательное причастие Participle II ^_^ Можно расценивать слово published как свойство, прилагательное, определение, так же как active или empty.

                  0

                  И правда, бес попутал, спасибо за поправку. :)

              +1
              так это и есть базовая грамматика, conditional sentences.
              if the cup is empty, refill it.
              if the person has publications, show them on the screen.

              проверки на действия, происходящие прямо сейчас — present continuous.
              if the car is moving, keep eyes open.

              ну и так далее, просто нагуглите подходящую статью по conditional sentences.

              +1

              Спасибо за статью! Немного дополню вашу, в большинстве случаев справедливую, критику линтерами Python, которые помогут избежать сего:


              Привязка названия переменной к типу, хранящихся в ней данных — это чаще всего плохая идея, особенно, когда вы работаете с динамическими языками

              mypy


              Мертвый код, пожалуй, раздражает куда больше, чем бесполезные комментарии

              vulture


              Для оформления докстрингов используйте pydocstyle

                +2
                Не r, не res, не resp и уж точно не req, а именно response. res, r, resp (про req и вовсе молчу) — это все переменные, содержание которых можно понять, только взглянув на их объявление, а зачем прыгать к объявлению, когда можно изначально дать подходящее название?

                1. Если для понимания что скрывается под res, r, resp (или даже req) нужно прыгать к объявлению, то скорее всего там совершенно другие проблемы (ну например слишком длинная функция или метод, или код "обфусцирован" по самое не хочу).
                2. В py-2.x dict.keys() возвращает list, а в 3.x — dict_keys, что теперь делать — срочно переименовать все переменные? А 4.x придет?
                3. Ну то вообще такое в duck typing языке, например тот метод класса сможет работать с входящим MySmartDict, возвращающим для keys() шибко мудреный мутирующий итератор ShibkoMudreniyMutableIterator… Назовем её — shmmi?
                4. Last but not least — самая главная ошибка в таком наименовании — если вообще нужна смысловая нагрузка, то берите ее из бизнес-логики а не из языковой модели, т.к. иначе:
                  • оно либо избыточно (в смысле знания о том чем является переменная в логике языка как программистской сущности)
                  • либо неверно, ибо завтра туда передадут что-нибудь другое (всё меняется);
                  • ну и недостаточно одновременно (ибо не несет в себе знания бизнес логики приложения).

                Как правило программист читает код (в смысле языковых конструкций) на ура, но если при этом ни черта не понимает кто тут с кем и кого ест, то оно отсюда как раз, т. е. по причине что переменные "не говорят" про бизнес логику, а повторяют типизацию или всем известные сущности.
                Т.е. объяснять программисту что Reqest возвращает response (или request находу мутирующий в response), вместо например чего мы вообще туда лезем, или что оттуда берем, совершенно излишне. Кроме того, совсем не поможет разобраться в (бизнес) логике программы.


                const errorCode = errorText.substr(5)
                А еще лучше прибегнуть к более декларативному подходу и избавиться от комментария вообще:
                const errorCode = errorText.replace('net::', '')

                Ну да, исправляли коммент, и переписали код… А ничего что оно не совсем то:


                 errorText='how::it-works-if net::is not at begin?';
                -errorText.substr(5)               ==> "it-works-if net::is not at begin?"
                +errorText.replace('net::', '')    ==> "how::it-works-if is not at begin?"
                  0
                  Об этом я предупредил сразу:

                  В приведенном выше примере выбор переменной достаточно неудачный, и можно было бы дать имя, точнее выражающее контекст (не нужно бояться использовать имена, относящиеся к предметной области), однако даже в этом случае можно было бы сделать этот код лучше:


                  Ну и последний пример описывает конкретный кейс, где ошибка приходит в конкретном формате. Для каждого отдельного кейса нужно писать наиболее подходящее решение.
                  0

                  Для python я еще добавил бы PEP-8, где тоже сказано достаточно на этот счет

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

                  Самое читаемое