Каждый раз, когда мы добавляем новый if
или for
в код, мы должны увеличивать отступ тела условия или цикла. Чем больше вложенных циклов и условий, тем шире становится ваш код и тем труднее его читать. В этой короткой статье я покажу несколько приемов, с помощью которых можно избежать увеличения отступов. Ваш код будет более понятным, и вы никогда не увидите горизонтальной прокрутки.
Правило 0. Не слушай меня, слушай свое руководство по стилю.
Прежде всего, напомню о самом главном - стиле кода. Если вы работаете в команде, важно писать код так, чтобы вы не переключались от стиля к стилю при его чтении (помните, что многие разработчики могут работать с одним и тем же файлом). Поэтому следует согласовать стиль кодирования: как называть переменные, каков размер отступа в пробелах или табуляциях, где ставить фигурные скобки и так далее. Если вы пишете код в одиночку, вы все равно можете использовать стиль кодирования, определенный создателями языка программирования или сообществом. Придерживаться его.
Правило 1. Поместите else
как можно ближе к if
блоку
Иногда вы пролистываете код, переходите от одного метода к другому и видите следующее:
// 20 more lines before this String[] params = text.split(" "); members.clear() members.add(params[1]); members.add(params[2]); tournamentEnabled = true; markup = new Markup(); rows = List.of(members); markup.setRows(rows); markup.setAction("tournament"); status = Message.create() .withText("Let's go!") .withMarkup(markup) .toChat(chatId) .send(); validateStatus(status); } else { Log.error("Empty command"); }
И вроде бы все нормально, но как только взгляд опускается до линии } else {
, ты впадаешь в ступор и спрашиваешь себя: если есть else
, то что это было за if
? И вам нужно прокрутить вверх, чтобы понять весь блок:
void handleMessage(Message message) { String text = message.getText(); if (text != null) { // 35 lines of code } else { Log.error("Empty command"); } }
Вы можете инвертировать условие и поменять местами тела блоков if
и else
, чтобы тело if
стало короче тела else
. Это убережет нас от зависания else
заявлений.
void handleMessage(Message message) { String text = message.getText(); if (text == null) { Log.error("Empty command"); } else { // 20 more lines before this String[] params = text.split(" "); members.clear() members.add(params[1]); members.add(params[2]); tournamentEnabled = true; markup = new Markup(); rows = List.of(members); markup.setRows(rows); markup.setAction("tournament"); status = Message.create() .withText("Let's go!") .withMarkup(markup) .toChat(chatId) .send(); validateStatus(status); } }
Правило 2. if / else можно заменить на if-return
В предыдущем коде вы можете уменьшить отступ блока else
, если вы поместите return
в конец блока if
:
void handleMessage(Message message) { String text = message.getText(); if (text == null) { Log.error("Empty command"); return; } // 20 more lines before this String[] params = text.split(" "); members.clear() members.add(params[1]); members.add(params[2]); tournamentEnabled = true; markup = new Markup(); rows = List.of(members); markup.setRows(rows); markup.setAction("tournament"); status = Message.create() .withText("Let's go!") .withMarkup(markup) .toChat(chatId) .send(); validateStatus(status); }
В этом случае return
ничего не ломает, а, наоборот, исключает ненужное вложение. Конечно, может быть неочевидно, что это оператор _17 _ / _ 18_ или кто-то может быть против такого кода, учитывая, что метод должен иметь один вход и один выход. И они будут правы, поэтому решение должно быть продиктовано вашим руководством по стилю.
Тот же трюк работает с _19 _ / _ 20 _ / _ 21_:
if (command.startWith("/help")) { Message.create() .withText("Help message") .toChat(chatId) .send(); } else if (command.startWith("/clear")) { data.clear(); Message.create() .withText("Cleared!") .toChat(chatId) .send(); } else if (command.startWith("/add") && isAdmin) { data.add(params[1]); sendStatusNessage(); } else if // ...
С _22 _-_ 23_ это будет:
if (command.startWith("/help")) { Message.create() .withText("Help message") .toChat(chatId) .send(); return; } if (command.startWith("/clear")) { data.clear(); Message.create() .withText("Cleared!") .toChat(chatId) .send(); return; } if (command.startWith("/add") && isAdmin) { data.add(params[1]); sendStatusNessage(); return; } // ...
Хотя в этом случае отступ остается прежним, однотипные блоки визуально лучше выделяются.
Правило 3. if / else можно заменить на if-continue внутри цикла
Чтобы удалить else
внутренние петли, вы должны использовать continue
вместо return
. До:
for (Members member : members) { if (member.isBlocked()) { Log.warning("Member {} is blocked", member); tournamentMembers.remove(member); if (tournamentMembers.size() > MIN_MEMBERS_COUNT) { tournamentEnabled = false; } } else { Member opponent = tournamentMembers.pop(); tournamentEnabled = true; Message.create() .withText("Tournament started. Your opponent is {}", opponent) .toChat(member.getId()) .send(); Message.create() .withText("Tournament started. Your opponent is {}", member) .toChat(opponent.getId()) .send(); Message.create() .withText("Tournament started. {} vs {}", member, opponent) .toChat(chatId) .send(); } }
Добавив continue
в конец тела if
, мы получим рабочий код с меньшим отступом:
for (Members member : members) { if (member.isBlocked()) { Log.warning("Member {} is blocked", member); tournamentMembers.remove(member); if (tournamentMembers.size() > MIN_MEMBERS_COUNT) { tournamentEnabled = false; } continue; } Member opponent = tournamentMembers.pop(); tournamentEnabled = true; Message.create() .withText("Tournament started. Your opponent is {}", opponent) .toChat(member.getId()) .send(); Message.create() .withText("Tournament started. Your opponent is {}", member) .toChat(opponent.getId()) .send(); Message.create() .withText("Tournament started. {} vs {}", member, opponent) .toChat(chatId) .send(); }
Правило 4. Избегайте лишних изменений
Если код достаточно понятен, лучше оставить как есть. Например:
if (chatId == config.tournamentChat()) { processTournamentChat(message); } else { processRegularCommand(message); }
не следует заменять на:
if (chatId == config.tournamentChat()) { processTournamentChat(message); return; } processRegularCommand(message);
Так что читабельность только ухудшается.
Правило 5. Если вложенность превышает допустимый уровень, введите новый метод.
Если внутри одного метода так много вложенных циклов и условий, что код переместился в правую сторону из-за отступов и вот-вот появится горизонтальная прокрутка, то пора провести рефакторинг и заменить некоторые части метода другим методом. Например:
Даже не пытайтесь понять этот код. Я просто придумал это для примера. Рефакторинг - тема отдельной статьи, поэтому я не буду рассматривать этот процесс поэтапно. Дело в изменениях:
Применяя эти правила, мы сделали код менее широким, но более длинным. Это нормально, потому что лучше прокручивать вверх / вниз, чем вверх / вниз и влево / вправо, а высокий уровень вложенности вместе с фигурными скобками также затрудняет понимание кода. Кроме того, код стал более понятным за счет методов с осмысленными именами, но в этом заслуга рефакторинга.