Длинные имена слишком длинные

Привет, Хабр! Представляю вашему вниманию перевод статьи "Long Names Are Long" автора Bob Nystrom.


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


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


Использование языка Dart в Google постепенно набирает обороты, и я много занимался подобными code review. Для разработчика языка это очень увлекательный процесс. Я получаю из первых рук представление о том, как люди используют Dart, что действительно полезно для его развития. У меня есть более четкое представление о том, какие ошибки являются общими, и какие функции интенсивно используются. Я чувствую себя как этнограф, ведущий дневник о жизни туземцев.


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


Да, имена могут быть слишком короткими. В те времена, когда язык С требовал, чтобы только внешние идентификаторы были уникальными вплоть до первых шести символов; когда автозаполнение ещё не было изобретено; когда каждое нажатие клавиши было как подъем в гору сквозь снега — это было проблемой. Я рад, что теперь мы живем в футуристической утопии, где клавиатурные пердежи, как p, idxcrpm и x3, редки.


Но маятник качнулся слишком далеко в другом направлении. Мы не должны быть Хемингуэем, нам также не нужно быть Теннесси Уильямсом. Излишне длинные имена также вредят ясности кода, в котором они используются. Очень длинные имена переменных затмевают операции, которые вы выполняете над ними. Код становится трудно визуально сканировать. Чтобы уложиться в требования по ширине кода, появляются дополнительные разрывы строк, которые прерывают логический поток кода. Длинные имена методов скрывают их одинаково важные списки аргументов. Длинные переменные раздражают от повторного использования, что приводит к растягиванию цепочек методов или каскадов.


Я видел имена переменных длинной более 60 символов. Вы можете поместить туда хайку или коан (и, вероятно, просветите читателя больше, чем выбранное имя). Но не бойтесь, я здесь чтобы помочь.


Выбор хорошего имени


У любого имени в программировании имеется две цели:


  • Имя должно быть ясным: нужно знать к чему оно относится.
  • Имя должно быть точным: нужно знать к чему оно не относится.

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


1. Избегайте слов, которые явным образом заданы в типе переменной или параметра


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


Учитывая это, излишне указывать тип в имени переменной. Мы только отказались от Венгерской нотации. Отпусти и забудь:


// Плохо:
String nameString;
DockableModelessWindow dockableModelessWindow;

// Хорошо:
String name;
DockableModelessWindow window;

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


// Плохо:
List<DateTime> holidayDateList;
Map<Employee, Role> employeeRoleHashMap;

// Хорошо:
List<DateTime> holidays;
Map<Employee, Role> employeeRoles;

Это также относится к именам методов. Имя метода не нуждается в описании его параметров или их типов — список параметров сделает это за вас.


// Плохо:
mergeTableCells(List<TableCell> cells)
sortEventsUsingComparator(List<Event> events,
    Comparator<Event> comparator)

// Хорошо:
merge(List<TableCell> cells)
sort(List<Event> events, Comparator<Event> comparator)

Это приводит к тому, что вызовы читаются лучше, чем это:


mergeTableCells(tableCells);
sortEventsUsingComparator(events, comparator);

Это только я, или здесь есть эхо-эхо?


2. Избегайте слов, которые не убирают неоднозначность в имени


Некоторые люди склонны впихивать все, что они знают о чем-либо, в имя переменной. Помните, что имя является идентификатором: оно указывает на то место, где оно определено. Это не исчерпывающий каталог всего, что читатель может захотеть узнать об объекте. Определение сделает это лучше. Имя только направит его туда.


Когда я вижу имя как recentlyUpdatedAnnualSalesBid, я спрашиваю себя:


  • Есть ли обновленные годовые заявки по продаже, которые не являются последними?
  • Есть ли недавние годовые заявки по продаже, которые не были обновлены?
  • Есть ли недавно обновленные годовые заявки, не связанные с продажами?
  • Есть ли недавно обновленные данные по годовым продажам, которые не являются заявками?

Если ответ "нет" на хоть один из этих вопросов, то это, как правило, указывает на лишнее слово в имени.


// Плохо:
finalBattleMostDangerousBossMonster;
weaklingFirstEncounterMonster;

// Хорошо:
boss;
firstMonster;

Конечно, вы вполне можете зайти слишком далеко. Сокращение первого примера до bid может быть слишком расплывчатым. Но, если вы сомневаетесь, оставьте его таким. Вы всегда можете добавить дополнительную классификацию позже, если имя окажется причиной коллизии или будет неточным. Однако вряд ли вы вернетесь позже, чтобы обрезать весь этот лишний жир.


3. Избегайте слов, которые понятны из контекста


Я могу использовать слово "Я" в этом параграфе, потому что вы знаете, что этот текст от Bob Nystrom. Мне не нужно постоянно повторять здесь "Bob Nystrom" (несмотря на соблазн Bob Nystrom усилить таким образом Bob Nystromа). Код работает абсолютно так же. Метод или поле встречается в контексте класса. Переменная встречается в контексте метода. Примите этот контекст как должное и не повторяйте его.


// Плохо:
class AnnualHolidaySale {
  int _annualSaleRebate;
  void promoteHolidaySale() { ... }
}

// Хорошо:
class AnnualHolidaySale {
  int _rebate;
  void promote() { ... }
}

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


4. Избегайте слов, которые ничего не значат


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


В большинстве случаев данные слова не несут никакой значимой информации для разработчика. Обычно подозрения падают на такие слова, как: data, state, amount, value, manager, engine, object, entity и instance.


Хорошее имя рисует некоторую картину в сознании читателя. Называя что-либо "менеджером", мы не передаем читателю никакой информации о том, что этот объект должен делать. Делает ли он расчет оценки производительности? Назначает повышение своим работникам?


Задайте себе вопрос: "Будет ли это имя означать то же самое, если я уберу это слово?". Если да, то слово не имеет значения — выгоняйте с острова.


Применение руководства к… вафлям


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


class DeliciousBelgianWaffleObject {
  void garnishDeliciousBelgianWaffleWithStrawberryList(
      List<Strawberry> strawberryList) { ... }
}

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


class DeliciousBelgianWaffleObject {
    void garnishDeliciousBelgianWaffle(
        List<Strawberry> strawberries) { ... }
}

Если в программе нет невкусных бельгийских вафель или вафель каких либо других национальностей, то мы спокойно можем отбросить все прилагательные (#2):


class WaffleObject {
  void garnishWaffle(List<Strawberry> strawberries) { ... }
}

Этот метод находится внутри WaffleObject, поэтому из контекста мы знаем, что конкретно он собирается украсить (#3):


class WaffleObject {
  void garnish(List<Strawberry> strawberries) { ... }
}

Очевидно что это объект. Всё является объектами в объектно-ориентированном программировании (#4):


class Waffle {
  void garnish(List<Strawberry> strawberries) { ... }
}

Теперь намного лучше.


Я думаю что это довольно простые рекомендации. Вы вправе думать, что бесполезно беспокоиться о таких мелочах. Но я считаю, что присвоение имен — это одна из самых фундаментальных задач, которые мы выполняем при программировании. Имена — это структура, которую мы накладываем на бесформенное море битов, которым является компьютер.

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

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

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

    0
    Мы только отказались от Венгерской нотации. Отпусти и забудь</blockquote
    Разве отказались?
      +1
      Ну он может и отказался, его дело.
      // Плохо:
      mergeTableCells(List cells)
      sortEventsUsingComparator(List events,
      Comparator comparator)

      // Хорошо:
      merge(List cells)
      sort(List events, Comparator comparator)


      если функция merge находится в другом файле и там ещё пачка аналогичных,
      мне проще, когда в названии функции есть намек на ее содержание.
        0
        Сегодня я встречал её только в embedded разработке ввиду её специфики.
          +8
          Разве отказались?

          Мы и не начинали.
          0
          Ещё сильно «радует» явное использование префиксов namespace при использовании достаточно уникальных (и говорящих за себя) имен в C++ (и других языках с namespace) — типа std::string, std::printf и пр. на каждом шагу.

          Очень интересно, какая религия не позволяет разрабочику указать «using namespace std» в файле, или даже в пределах scope где std так и пестрит.
            0
            Это делается для того, чтобы явно обозначить использование std-шных контейнеров и алгоритмов в проекте. string, например, далеко не уникальное имя, и очень много где используется своя реализация.
              0
              Я говорю как раз о случаях когда оно уникально в пределах не то что отдельного файла, а всего проекта, и всегда ссылается на std.
              0
              и других языках с namespace

              Нет. Контрпример: Java.

                0

                Интересно, часто вы видели в джаве out.println вместо System.out.println со статическим импортом System.out? Ближайший аналог неймспейсов в джаве — это именно статические импорты.

                  0

                  разговор был про неймспейсы. Никто на джаве не пишет java.lang.System.out.println.

                    0
                    Почему вы отождествляете пакет и неймспейс? Я вот утверждаю, что класс ближе к понятию неймспейс, чем пакет, потому что как и в C++ в классе могут содержаться функции (статические методы), константы (статические поля) и классы (статические вложенные). А в пакете могут быть только классы. Например, в C++ есть std::sprintf, который можно не квалифицировать, если написать using. В Java есть похожий метод java.lang.String::format. Его тоже можно не квалифицировать, используя import static. Но так мало кто делает, все используют квалификатор (пусть и неполный) String.format.
                      0

                      Если рассматривать пространство имен как дерево, то классы — это листья.
                      Я к тому, что спор про то, пакет или класс "ближе" к неймспейсам — это такое… Довольно странное занятие. (а import — это всего лишь изоляция подграфа для удобства работы с ним)

                0
                Очень хорошо описанно писать или нет using namespace std в C++ Core Guidelines
                  0
                  Я понимаю пользу явного указания namespace (в отдельных случаях), но, как я уже сказал выше — речь о случаях когда конфликты практически невозможны.

                  Похоже, некоторые разработчики просто считают using вредным, но вот читабельность кода от этого нисколько не улучшается, а если учесть темплейты и прочее, то конструкции разворачиваются просто невероятные, и это при том что гайдлайны проекта требуют всё вмещать в 80 колонок.
                +3
                На мой взгляд в длинных названиях методов и переменных нет ничего страшного, если они используются нечасто. Однако, если они в наборе часто используемых методов например какого-нибудь API, то они должны быть в оперативной памяти программиста.

                Например, если это библиотека для работы с изображениями, то имена методов должны быть простыми и однозначными: crop, rotate, resize и т.д. Тогда они логичные и их легко запомнить. Если это будет что-то типа makeImageResize(), то такие имена сложно удержать в памяти, придётся постоянно дёргать документацию или полагаться на автозаполнение, что приведёт к тому, что программисты будут избегать этой библиотеки.

                Уже более 10 лет назад примерно на равных соревновались три библиотеки для JavaScript: jQuery, Prototypre.js и Mootools. На мой взгляд, не последнюю роль в выходе jQuery в победители сыграла его лаконичность. Сравните:

                $$('.foo').getElement('div.bar') и $('.foo').find('div.bar')
                el.addEvent('click', function(){}) и el.on('click', function(){})
                $$('.foo').setStyle('width', '300px') и $('.foo').css('width', '300px');

                Я в то время пытался использовать MooTools и никак не мог запомнить названия методов, а jQuery зашло моментально, можно было вспомнить что писать чисто по логике.

                С тех пор я стараюсь выбирать для своих проектов менее многословные библиотеки, если есть такая возможность. И в том числе поэтому не люблю Java и Java-подобные фреймворки. Правильное использование неймспейсов делает длинные имена абсолютно излишним.
                  +3

                  Есть и другая крайность — слишком общие названия.
                  Тот же find — что именно он найдет и вернет? Первый элемент, последний, случайный, конкретный, все подходящие? Что это за элемент будет? getElement — уже лучше, хотя бы понятно, что он будет один.

                    0

                    В этом смысле меня "радует" интерфейс к MongoDB в Node.js, где есть find, который возвращает курсор (по сути, итератор), и есть findOne, который возвращает сразу объект. А ещё там есть findOneAndReplace, а есть replaceOne, которые отличаются, судя по докам, тем, что первая операция атомарна, а вторая — нет. Весело, блин...

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

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

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