Семь советов по написанию чистого кода

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

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

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

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

 

1. Всегда знайте почему вы перехватываете исключение

 

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

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

  private static List readLines(String fileName) {
    String line;
    ArrayList file = new ArrayList();
    try {    
      BufferedReader in = new BufferedReader(new FileReader(fileName));
      while ((line = in.readLine()) != null)
        file.add(line);
      in.close();
    } catch (Exception e){
      System.out.println(e);
      return null;
    }
    return file;
  }

Блок catch не выполняет ничего полезного. Фактически, он скрывает полезную информацию в случае если приложение «упало». Если вы не перехватывает исключения явно (а добавите в сигнатуру метода), то стек вызовов не потеряется. Если же перехватывает в конкретном блоке метода (как в примере) — то информация теряется. К тому же, метод содержит больше строк кода.

Более выгодный вариант такой:

  private static ArrayList readLines(String fileName) throws IOException {
    String line;
    ArrayList file = new ArrayList();

    BufferedReader in = new BufferedReader(new FileReader(fileName));
    while ((line = in.readLine()) != null)
      file.add(line);
    in.close();

    return file;
  }

 

2. Никогда не указывайте типы реализаций

 

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

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

Еще один раунд упрощения приводит нас к:

private static List readLines(String fileName) throws IOException {
    String line;
    List file = new ArrayList();

    BufferedReader in = new BufferedReader(new FileReader(fileName));
    while ((line = in.readLine()) != null)
      file.add(line);
    in.close();

    return file;
  }

 

 

3. Используйте «говорящие» имена переменных

 

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

Рассмотрим все тот же предыдущий пример. Что скрывается за переменной file? Верно, она содержит файл, но на самом деле — это список все строк файла. Почему бы тогда не назвать ее lines?. Итак, получим:

private static List readLines(String fileName) throws IOException {
    String line;
    List lines = new ArrayList();

    BufferedReader in = new BufferedReader(new FileReader(fileName));
    while ((line = in.readLine()) != null)
      lines.add(line);
    in.close();

    return lines;
  }

 

4. Вырезать-и-вставить

 

Один простой подход, который предлагается довольно часто как путь к улучшению кода — это выключение функции вставки (из буфера) на компьютере каждого разработчика. Хотя идея выглядит абсурдной, главное что мысль озвучена. Если у вас есть, скажем, кусок кода из 7 строк, который что-то делает и вы хотите выполнить то же самое для другого набора переменных, не занимайтесь копированием/вставкой кода. Вместо этого напишите метод.

На то существует несколько причин. Одна из них — простая краткость. Другая — это сделает ваш код легко читаемым, поскольку фактически вы замещаете 14 строк кода двумя. Это очевидно, ваши затраты — это один дополнительный метод, но преимущество в том, что метод изолирован от остального кода и врядли влияет на его читабельность. И главная причина в том что, поступая так, вы облегчаете себе жизнь. Даже если это единичный случай использования вашего метода, все равно вы продолжаете развивать и изменять код. С кодом проще управляться, если он разбит на части, т.е. методы или функции.

 

5. Переменные и взаимодействие

 

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

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

String tmp = null;
// 50 lines that don't touch tmp
tmp = whatever;
// ...

что означает, что тот кто просматривает этот код, будет вынужден отсчитать 50 строк прежде чем увидеть используется ли переменная tmp. Совместив определение переменной и присвоение ей значения, вы избавитесь от этой проблемы, не потеряв ничего.

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

int tmp;
for (...) {
  // code that uses tmp
}
// no more mentions of tmp below this point

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

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

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

 

6. Не сохраняйте возвращаемое значение, если не собираетесь его использовать

 

Еще один момент, который позволит упростить ваш код. Предположим вы пишете следующее:

boolean present = myCollection.remove(object);

Однако, вы могли бы записать его так:

myCollection.remove(object);

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

К тому же выражение становится чище, в общем, нет причин, чтобы не удалить эту переменную :)

 

7. Исключайте ненужный код

 

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

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

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

Автор: Lars Marius Garshol
Оригинал.


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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>