В коде мы даём названия множеству вещей: переменным, функциям и их параметрам, классам. То, что делается настолько часто, должно делаться хорошо. Когда речь идёт о хороших именах, все говорят “вот будет кто-то читать ваш код…”. Но ведь вы сами постоянно его читаете. И не спустя месяц, полгода, год. Нет, сейчас – прямо во время разработки, вы постоянно смотрите и перечитываете свой код. Вы всё время вспоминаете, зачем создаётся эта переменная, что делает этот метод и для чего нужен этот класс.
Так что упрощение восприятия написанного окупается в сокращении затрат времени и сил на разработку программы. Имейте это в виду! Начнём.
Намерения программиста
Названия должны в явном виде передавать намерения программиста. И точка. Если имя не передаёт намерений, если, прочитав его, у вас остаются вопросы – имя подобрано неправильно. Если имя нужно пояснять комментарием – имя выбрано наобум.
Название переменной, функции или класса должно отвечать на все важные вопросы – зачем эта переменная (и т.д.) существует, что она делает и как используется.
1 |
int n1; //количество |
Название переменной n1 не передаёт вообще ничего. Более того, оно помечено комментарием. И даже комментарий не спасает ситуацию. Не лучше ли заменить его другим названием, которое будет указывать, что хранится в переменной?
1 |
int numberOfCells; |
Имя numberOfCells чётко и однозначно гласит, что в переменной хранится число клеток. Например, это игра в морской бой. Там могут пригодиться такие переменные:
1 2 |
int numberOfDamagedCells; int numberOfDeadCells; |
Также, если переменная хранит значение, которое измеряется в особой единице измерения и она не единственная возможная для этого значения, важно указывать единицу измерения в названии переменной.
1 2 3 4 5 6 7 |
//плохо double detailDiameter; int timeSinceFileCreation; //хорошо double detailDiameterInMeters; int daysSinceFileCreation; |
Содержательные имена неоценимо облегчают чтение и изменение кода. Что делает этот кусок кода, можете ли вы с ходу сказать?
1 2 3 4 5 6 7 8 9 |
public List<int[]> getThem() { List<int[]> list1 = new ArrayList<>(); for (int[] x : theList) { if (x[0] == 4) { list1.add(x); } } return list1; } |
По какой причине вы не можете сразу понять, что делает код и для чего? Метод короткий. Сложных конструкций в нём нет. Отступы и скобки расставлены верно. В коде задействовано только 3 переменные и 2 константы. В нём нет хитроумных методов, только список массивов.
Суть в том, что проблема кроется не в сложности кода, а в его неочевидности. Код не отвечает нам на те самые важные вопросы. Он подразумевает, что мы уже магическим образом знаем, какие данные хранятся в theList, чем так важен элемент theList с 0-ым индексом, какой особый смысл несёт значение 4 и как будет использоваться возвращаемый список.
Представим, что мы разрабатываем игру “Сапёр”. Игровое поле представляется как список ячеек с названием theList. Почему бы не переименовать его в gameBoard?
Отлично. Каждая ячейка поля представляется простым массивом элементов int. Оказывается, что в элементе с 0-ым индексом записан код состояния ячейки. А код 4 значит “флажок установлен”. И если мы сейчас просто укажем названия этих концепций, код значительно улучшится:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
private List<int[]> gameBoard; private final int STATUS_VALUE = 0; private final int FLAGGED = 4; ... public List<int[]> getFlaggedCells() { List<int[]> flaggedCells = new ArrayList<>(); for (int[] cell : gameBoard) { if (cell[STATUS_VALUE] == FLAGGED) { flaggedCells.add(cell); } } return flaggedCells; } |
Итак, простота кода нисколечко не изменилась. Да, в коде остались те же 3 переменные и 2 константы, да и уровни вложенности сохранились. И даже этот стрёмный список массивов int. Но! Код стал гораздо более читаемым.
Давайте пойдём до конца. Что за список массивов? Избегайте таких махинаций. Что он по факту означает? Список ячеек. Создадим простой класс для представления ячеек вместо использования массива int. Включим в него константы STATUS_VALUE и FLAGGED, чтобы можно было проверять, установлен ли флажок на ячейке.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Cell { private final int FLAGGED = 4; private int status; private int x; private int y; ... public boolean isFlagged() { return status == FLAGGED; } } |
Тогда код исходного метода getFlaggedCells() приобретёт следующий вид:
1 2 3 4 5 6 7 8 9 |
public List<Cell> getFlaggedCells() { List<Cell> flaggedCells = new ArrayList<>(); for (Cell cell : gameBoard) { if (cell.isFlagged()) { flaggedCells.add(cell); } } return flaggedCells; } |
Это красиво. Это читаемо. Этот код говорит сам за себя. Вот в чём сила выбора хороших и содержательных названий. Стал гораздо чище сам метод.
Неинформативные слова
Лучше не писать типы данных в названиях (список с названием pageList, например). Cегодняшние среды разработки позволяют быстро вспомнить типы переменных в случае, если понадобится. Типы данных в названии – это ненужная информация, которая не несёт ценности. Это лишние символы, которые вы всё время будете перечитывать. Более того, в один момент эта информация может стать сбивающей с толку дезинформацией. Может, вы решите вместо списка использовать массив и измените тип, но переменная так и останется – pageList.
В случае со списками, массивами и другими наборами данных я бы порекомендовала просто ставить элемент во множественное число – pages, ingredients, cells.
1 2 3 4 5 6 7 8 9 |
//плохо List<Page> pageList; String nameString; int fieldCountInt; //хорошо List<Page> pages; String name; int fieldCount; |
Однако этим возможные неинформативные слова не исчерпываются. Не нужно добавлять постфикс Object к объектам, Class к классам или Enum к перечислениям. Слово variable не должно встречаться в именах переменных, а слово table – в именах таблиц. И над каждым словом, которое вы пишете в название, вы должны хорошенько подумать. А передаёт ли оно ценную для читателя информацию? Может, можно его выкинуть без потери смысла? Или заменить чем-то более осмысленным?
Да, это занимает время. Но оно окупается с лихвой – вам будет гораздо проще и приятнее (и быстрее) заниматься разработкой кода, над которым вы хорошенько постарались.
Не используйте префиксы a и the. Как правило, переменную называют thePage только потому, что в области видимости уже есть page. И это – плохая причина для выбора такого имени.
1 2 3 4 5 6 7 |
//плохо Page pageObject; Page thePage; PageClass page; //хорошо Page page; |
Такие слова, как Data, Info, Manager, Processor обычно не очень информативны. Вместе с ними или без них – количество информации едва изменится. Поэтому старайтесь избегать их использования по возможности.
1 2 3 4 5 6 7 8 9 |
//плохо public class UserInfo { ... } //хорошо public class User { ... } |
Сходства и различия в именах
Сходства и различия в названиях переменных, функций, классов – это тоже информация. Если переменные похожи по имени, вы можете подумать, что они связаны. И наоборот – если переменные сходств в именах не имеют, будет казаться, что они представляют совершенно разные концепции.
Поэтому правило: выбирайте одно слово для каждой концепции. Рассмотрим пример: в классе UserService есть несколько методов получения некой информации о пользователях. Для одной концепции – получения информации – выбраны три разных слова: get, fetch и retrieve. Они все переводятся с английского примерно как “получить“. И вот как это выглядит:
1 2 3 4 5 6 7 8 9 10 11 |
public int getUserCount() { ... } public List<User> fetchUsers() { ... } public User retrieveUser(int userId) { ... } |
По ощущениям, это как будто три разных концепции. Но нет – это обыкновенное получение данных. Чтобы не разводить путаницу, лучше сделать так:
1 2 3 4 5 6 7 8 9 10 11 |
public int getUserCount() { ... } public List<User> getUsers() { ... } public User getUser(int userId) { ... } |
Используйте отличия, только если это действительно разные концепции. Будете последовательны – и код станет помогать вам, а не мешать.
Схемы кодирования имён
Избегайте схем кодирования имён. Как правило, кодированные имена плохо произносятся и в них легко запутаться.
В доисторические времена, когда в языках программирования действовали ограничения на длину имён, это правило нарушалось из-за необходимости. Например, в Fortran первая буква имени переменной обозначала код типа. В свою очередь, в ранних версиях BASIC имена могли состоять только из одной буквы и одной цифры. Когда компиляторы не поддерживали проверку типов переменных, для их запоминания действительно было полезно кодировать информацию о типе переменной в её названии. Однако в наши дни в этом нет нужды, и это только мешает.
1 2 3 4 5 6 7 |
//плохо int iCount; float fPrice; //хорошо int count; float price; |
Когда-то давно программисты использовали префиксы m_ для полей классов. Это делалось для того, чтобы сразу отличать переменные класса от переменных, созданных или переданных непосредственно в методах. Но теперь в этом нет необходимости: среда разработки заботливо будет выделять эти переменные разными цветами. Так что их можно будет легко отличить и без префиксов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//плохо public class Page { private String m_ordinalNumber; private String m_text; public void setText(String text) { m_text = text; } ... } //хорошо public class Page { private String ordinalNumber; private String text; public void setText(String text) { this.text = text; } ... } |
В общем-то, повсеместно использовать сокращения тоже не нужно. Если в вашей переменной нужно уместить 7 слов – да, это многовато. Но попробуйте не втискивать их в имя одной переменной, сокращая, а поразмыслить, что можно сделать, чтобы не было нужды помещать все 7 слов в одно название.
Ну а там, где слов меньше – и подавно: зачем сокращать? Ведь экономия букв приведёт к тому, что придётся каждый раз больше обдумывать название, вспоминая, что это означает. Если же не сокращать, тогда название можно будет прочесть и понять без лишних усилий.
1 2 3 4 5 6 7 8 9 10 11 |
//плохо int pgCnt; double tmpRes; int numCycles; String msgTxt; //хорошо int pageCount; double temporaryResult; int numberOfCycles; String messageText; |
Контекст имени
Контекст – это место, где хранится имя. Имена помещаются внутрь функций, классов, перечислений, пакетов, пространств имён. Судить о назначении переменной можно не только исходя только из её названия, но также по тому, в каком методе она используется, какому классу принадлежит и в каком пространстве имён располагается.
Допустим, мы хотим представить данные студента: имя, фамилию, номер курса, номер группы, факультет и специальность. Они будут выглядеть в программе примерно следующим образом:
1 2 3 4 5 6 |
String firstName; String lastName; int courseNumber; int groupNumber; String facultyName; String specialtyName; |
Когда вы видите все эти переменные вместе, становится понятно, что они относятся к студенту. Но если вы просто встретите в коде переменную facultyName, как быстро вы поймёте, что относится она к студенту? Вы можете попробовать снабдить все эти переменные префиксом student, чтобы явно обозначить их контекст – студента:
1 2 3 4 5 6 |
String studentFirstName; String studentLastName; int studentCourseNumber; int studentGroupNumber; String studentFacultyName; String studentSpecialtyName; |
Однако когда встаёт необходимость добавить контекст с помощью префикса сразу группе переменных, стоит задуматься о создании класса. Да, контекст можно обозначить при помощи класса Student, куда будут включены все эти переменные. Таким образом, не придётся дописывать всем префикс student. Но всё равно будет понятно, что переменные описывают студента.
1 2 3 4 5 6 7 8 9 |
public class Student { private String firstName; private String lastName; private int courseNumber; private int groupNumber; private String facultyName; private String specialtyName; ... } |
Добавлять префикс student к переменным класса Student было бы неправильно – это давало бы излишний контекст. Таким образом, получилась сущность “Студент”. Это уже пахнет как ООП.
Попробуйте следовать этим правилам и проверьте, не станет ли ваш код более удобным для понимания и разработки. Вы можете начать применять эти правила в новых программах или же заняться небольшим рефакторингом уже существующего проекта. Это даст немедленный результат и продолжит приносить плоды в долгосрочной перспективе. Более того, это приносит дикое удовольствие, делать код чистым. Дерзайте!