Статьи

Стиль кода Java: окончательное решение

Разве это не смешно, как предполагаемые самые ничем не примечательные вещи могут привести к спорным дискуссиям или иногда даже жарким спорам с трудными фронтами? Например, я несколько раз был свидетелем того, как использование ключевого слова final вызывает довольно страстные аргументы. И для стороннего наблюдателя это могло бы выглядеть так, как будто на карту поставлено окончательное решение о том, быть злым или божественным.

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

В поисках советов по литературе единственной точкой соприкосновения на полпути кажется окончательное определение констант …

1
2
3
class Foo {
  public static final String CONSTANT = "constantValue";
}

… и пункт 15 Джошуа Блоха: свести к минимуму изменчивость 1 , где он рекомендует сделать все поля неизменяемого класса final и гарантировать, что класс не может быть расширен (тогда как последний не обязательно должен быть достигнут final ):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public final class Foo {
 
  private final int value;
 
  public Foo( int value) {
    this.value = value;
  }
 
  public int getValue() {
    return value;
  }
 
  [...]
}

Оттуда мнения расходятся. Роберт Симмонс-младший В своей книге Hardcore Java 2 он посвятил целую главу final ключевому слову, в заключение которой он дал твердый совет «распространить final по всему коду». Эта хорошо написанная глава содержит много идей о преимуществах преобразования логических ошибок в ошибки времени компиляции путем объявления переменных, параметров, методов или классов final .

С другой стороны, Роберт К. Мартин явно не согласен со следующим утверждением: «есть несколько хороших вариантов использования final , таких как случайная final константа, но в противном случае ключевое слово добавляет мало значения и создает много беспорядка» 3 . Продолжая, он объясняет, что тип ошибок, которые могут быть обнаружены final , обычно покрывается его модульными тестами.

Хотя я склонен согласиться с Мартином, я бы не сказал, что Симмонс вообще не прав. В прошлом я фактически использовал final ключевое слово часто сам, чтобы избежать ошибок в программировании или неправильного использования. Одна из причин, по которой я передумал, — это, вероятно, мой переход на подход TDD пару лет назад.

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

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

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

1
2
3
4
5
6
7
8
9
public void doit( final String message ) {
    final int value = calculate();
    final Item item = create( value, message );
    executorService.submit( new Runnable() {
      public void run() {
        handle( item );
      }
    } );
  }

Хотя код совершенно бесполезен и может быть организован по-другому, он отражает некоторый реальный стиль кодирования относительно final я столкнулся в последнее время. Хотя этот стиль предотвращает переназначение локальных переменных при аварии, он также скрывает тот факт, что одно final объявление фактически является обязательным. Это потому, что item переменной используется в анонимной реализации Runnable . Следующий фрагмент избавляется от ненужных объявлений, чтобы подчеркнуть разницу:

1
2
3
4
5
6
7
8
9
public void doit( String message ) {
    int value = calculate();
    final Item item = create( value, message );
    executorService.submit( new Runnable() {
      public void run() {
        handle( item );
      }
    } );
  }

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

Что приводит меня к окончательному выводу, что спор будет бушевать!

  1. Эффективная Java (второе издание), глава 4 — Классы и интерфейсы, Джошуа Блох, 2008
  2. Hardcore Java, Глава 2 — Последняя история, Роберт Симмонс-младший, 2004
  3. Чистый код, Глава 16, Рефакторинг SerialDate, Роберт С. Мартин, 2009

Ссылка: Стиль Java Code: окончательное решение от нашего партнера JCG Фрэнка Аппеля в блоге Code Affine .