четверг, 25 мая 2017 г.

Типичные ошибки в коде Java

Эта страница содержит наиболее типичные ошибки, которые я вижу в Java-коде людей, работающих со мной. Статический анализ (мы используем qulice) не можем поймать все ошибки по очевидным причинам, и поэтому я решил перечислить их все здесь.

Все перечисленные ошибки относятся к объектно-ориентированному программированию в целом и к Java в частности.


Имена классов

Ваш класс должен быть абстракцией реального объекта - это не  «валидаторы», «контроллеры», «менеджеры» и т.д. Если имя вашего класса заканчивается «-er» - это плохой дизайн: не создавайте объекты, которые заканчиваются на -ER.

Utility классы - это анти-шаблоны
StringUtils, FileUtils и IOUtils от Apache

Конечно, никогда не добавляйте суффиксы или префиксы, чтобы различать интерфейсы и классы. Например, все эти имена ужасно ошибочны: IRecord, IfaceEmployee или RecordInterface. Обычно имя интерфейса - это имя реального объекта, в то время как имя класса должно объяснять детали его реализации. Если ничего не говорится о реализации, назовите ее Default, Simple или нечто подобное. Например:
class SimpleUser implements User {};
class DefaultRecord implements Record {};
class Suffixed implements Name {};
class Validated implements Content {};
Имена методов

Методы могут либо вернуть что-то, либо вернуть void. Если метод возвращает что-то, тогда его имя должно объяснять, что он возвращает, например (не используйте префикс получения никогда):
boolean isValid(String name);
String content();
int ageOf(File file);
Если метод возвращает void, его имя должно объяснить, что оно делает. Например:
void save(File file);
void process(Work work);
void append(File file, String line);
Существует только одно исключение из только что упомянутого правила - методы тестирования для JUnit. Они описаны ниже.

Имена методов тестирования

Имена методов в тестах JUnit должны быть созданы как английские предложения без пробелов. Это проще объяснить на примере
/**
 * HttpRequest can return its content in Unicode.
 * @throws Exception If test fails
 */
@Test
public void returnsItsContentInUnicode() throws Exception {
}
Важно, чтобы первое предложение вашего JavaDoc начиналось с имени тестируемого класса, за которым следует баннер. Итак, ваше первое предложение всегда должно быть похоже на «кто-то может что-то сделать».

Имя метода будет указано точно так же, но без субъекта. Если я добавлю тему в начале имени метода, я должен получить полное английское предложение, как в примере выше: «HttpRequest возвращает его содержимое в Unicode».

Обратите внимание, что метод тестирования не начинается с может. Только комментарии JavaDoc начинаются с «может». Кроме того, имена методов не должны начинаться с глагола.

Хорошая практика - всегда объявлять тестовые методы как бросание исключения.

Переменные имена

Избегайте составных имен переменных, таких как timeOfDay, firstItem или httpRequest. Я имею в виду переменные как класса, так и в методе. Имя переменной должно быть достаточно длинным, чтобы избежать двусмысленности в его видимости, но не слишком, если это возможно. Имя должно быть существительным в единственном числе или во множественном числе, или соответствующей аббревиатурой.
List<String> names;
void sendThroughProxy(File file, Protocol proto);
private File content;
public HttpRequest request;
Иногда у вас могут быть конфликты между параметрами конструктора и свойствами в классе, если конструктор сохраняет входящие данные в экземпляре объекта. В этом случае я рекомендую создавать аббревиатуры, удаляя гласные.
public class Message {
  private String recipient;
  public Message(String rcpt) {
    this.recipient = rcpt;
  }
}
Во многих случаях лучший намек на имя переменной можно узнать, прочитав его имя класса. Просто напишите это маленькой буквой, и вы должны быть хорошими:
File file;
User user;
Branch branch;
Однако никогда не делайте то же самое для примитивных типов, таких как Integer number или String string.

Вы можете также использовать прилагательное, когда есть несколько переменных с разными характеристиками. Например:
String contact(String left, String right);

Конструкторы

Без исключений должен быть только один конструктор, который сохраняет данные в объектных переменных. Все остальные конструкторы должны вызывать эту функцию с разными аргументами.
public class Server {
  private String address;
  public Server(String uri) {
    this.address = uri;
  }
  public Server(URI uri) {
    this(uri.toString());
  }
}
Одноразовые переменные

Избегайте одноразовых переменных любой ценой. Под «разовым» я подразумеваю переменные, которые используются только один раз. Как в этом примере:
String name = "data.txt";
Return new File (name);
Эта вышепеременная используется только один раз, и код должен быть реорганизован в:
Return new File ("data.txt");
Иногда, в очень редких случаях, в основном из-за лучшего форматирования, могут использоваться одноразовые переменные. Тем не менее, старайтесь избегать таких ситуаций любой ценой.

Исключения

Излишне говорить, что вам никогда не следует глотать исключения, а позволять им вздыматься как можно выше. Частные методы всегда должны использовать checked исключения.

Никогда не используйте исключения для управления потоком. Например, этот код неверен:
int size;
try {
  Size = this.fileSize ();
} Catch (IOException ex) {
  Size = 0;
}
Серьезно, что если в этом IOException говорится, что «диск заполнен?» Будете ли вы по-прежнему считать, что размер файла равен нулю и двигаться дальше?

вдавливание

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

Final Файл файла = новый Файл (каталог,
  "File.txt");

Правильный отступ должен выглядеть так:
StringUtils.join (
  Arrays.asList (
    "первая линия",
    "вторая линия",
    StringUtils.join (
      Arrays.asList ("a", "b")
    )
  ),
  "separator"
);
Второе важное правило с отступом говорит, что вы должны поставить как можно больше на одной строке - в пределах 80 символов. Пример выше недействителен, так как он может быть сложен:
StringUtils.join (
  Arrays.asList (
    «Первая строка», «вторая строка»,
    StringUtils.join (Arrays.asList ("a", "b"))
  ),
  "separator"
);

Избыточные константы


Константы класса должны использоваться, когда вы хотите обмениваться информацией между методами класса, и эта информация является признаком (!) Вашего класса. Не используйте константы в качестве замены строковых или числовых литералов - очень плохая практика, которая приводит к загрязнению кода. Константы (как и любой объект в ООП) должны иметь значение в реальном мире. Какое значение имеют эти константы в реальном мире:
Class Document {
  Private static final String D_LETTER = "D"; // плохая практика
  Private static final String EXTENSION = ".doc"; // хорошая практика
}
Другой типичной ошибкой является использование констант в модульных тестах, чтобы избежать дублирования строковых / числовых литералов в методах тестирования. Не делай этого! Каждый тестовый метод должен работать со своим собственным набором входных значений.

Используйте новые тексты и цифры в каждом новом методе тестирования. Они независимы. Итак, почему они должны использовать одни и те же входные константы?

Связывание тестовых данных


Это пример связывания данных в тестовом методе:
Пользователь user = новый Пользователь («Jeff»);
// какой-то другой код здесь
MatcherAssert.assertThat (user.name (), Matchers.equalTo ("Jeff"));
В последней строке мы соединяем «Jeff» с тем же строковым литералом из первой строки. Если через несколько месяцев кто-то захочет изменить значение на третьей строке, ему придется потратить дополнительное время на то, чтобы найти, где еще «Jeff» используется тем же методом.

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


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

Отправить комментарий