Статьи

Топ 10 полезных и в то же время параноидальных методов программирования Java

После некоторого программирования (например, почти 20 лет в моем случае время летит, когда вам весело), ​​вы начинаете понимать эти привычки. Потому что, ты знаешь …

Все, что может пойти не так, делает.

Вот почему люди используют «защитное программирование», то есть параноидальные привычки, которые иногда имеют полный смысл, а иногда являются довольно неясными и / или умными и, возможно, немного жуткими, когда вы думаете о человеке, который написал это. Вот мой личный список из 10 лучших, но параноидальных техник программирования Java. Поехали:

1. Поместите строковый литерал первым

Это просто плохая идея предотвратить случайное исключение NullPointerException , поместив литерал String в левую часть сравнения equals() как таковое:

1
2
3
4
5
// Bad
if (variable.equals("literal")) { ... }
 
// Good
if ("literal".equals(variable)) { ... }

Это ежу понятно. Ничто не потеряно от перефразирования выражения от менее хорошей версии к лучшей. Если бы у нас были настоящие Варианты, правда? Разное обсуждение …

2. Не доверяйте ранним API JDK

В первые дни Java программирование, должно быть, было большой болью. API были все еще очень незрелыми, и вы, возможно, натолкнулись на фрагмент кода, подобный этому:

1
2
3
4
5
6
7
8
String[] files = file.list();
 
// Watch out
if (files != null) {
    for (int i = 0; i < files.length; i++) {
        ...
    }
}

Выглядит параноиком? Возможно, но читайте Javadoc :

Если это абстрактное имя пути не обозначает каталог, тогда этот метод возвращает ноль. В противном случае возвращается массив строк, по одной для каждого файла или каталога в каталоге.

Да правильно. Лучше добавить еще один чек, чтобы быть уверенным:

01
02
03
04
05
06
07
08
09
10
if (file.isDirectory()) {
    String[] files = file.list();
 
    // Watch out
    if (files != null) {
        for (int i = 0; i < files.length; i++) {
            ...
        }
    }
}

Облом! Нарушение правил № 5 и № 6 наших 10 тонких рекомендаций при написании кода Java . Так что будьте готовы и добавьте эту null проверку!

3. Не верьте этому «-1»

Это параноик, я знаю. В Javadoc String.indexOf() четко сказано, что…

возвращается индекс первого вхождения символа в последовательности символов, представленной этим объектом, или -1, если символ не встречается.

Значит, -1 можно считать само собой разумеющимся, верно? Я говорю нет. Учти это:

1
2
3
4
5
// Bad
if (string.indexOf(character) != -1) { ... }
 
// Good
if (string.indexOf(character) >= 0) { ... }

Кто знает. Возможно, в какой-то момент времени им потребуется ДРУГОЕ кодирование, чтобы сказать, что otherString содержалась бы, если бы проверялась без otherString регистра… Возможно, хороший случай для возврата -2 ? Кто знает.

В конце концов, у нас были миллиарды дискуссий об ошибке в миллиард долларов, которая равна NULL . Почему бы нам не начать обсуждение -1 , что в некотором смысле является альтернативным null для примитивного типа int ?

4. Избегайте случайного назначения

Ага. Это случается с лучшими (хотя, не со мной. См. # 7).

(Предположим, что это JavaScript, но давайте будем параноиком по поводу языка)

1
2
3
4
5
6
7
8
// Ooops
if (variable = 5) { ... }
 
// Better (because causes an error)
if (5 = variable) { ... }
 
// Intent (remember. Paranoid JavaScript: ===)
if (5 === variable) { ... }

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

5. Проверьте на ноль И длину

Всякий раз, когда у вас есть коллекция, массив и т. Д., Убедитесь, что она присутствует и не пуста.

1
2
3
4
5
// Bad
if (array.length > 0) { ... }
 
// Good
if (array != null && array.length > 0) { ... }

Вы никогда не знаете, откуда взялись эти массивы. Возможно из раннего JDK API?

6. Все методы являются окончательными

Вы можете сказать мне все, что вы хотите о ваших принципах открытого / закрытого, это все бред. Я не доверяю вам (чтобы правильно продлить мои занятия), и я не доверяю себе (чтобы не случайно продлить мои занятия). Вот почему все, что явно не предназначено для подтипирования (то есть только интерфейсы), строго final . См. Также пункт № 9 из нашего списка 10 тонких рекомендаций при кодировании Java .

1
2
3
4
5
// Bad
public void boom() { ... }
 
// Good. Don't touch.
public final void dontTouch() { ... }

Да. Это окончательно. Если это не работает для вас, исправьте его, или установите его, или перепишите байт-код. Или отправьте запрос функции. Я уверен, что ваше намерение отвергнуть вышесказанное в любом случае не очень хорошая идея.

7. Все переменные и параметры являются окончательными

Как я сказал. Я не доверяю себе (чтобы случайно не перезаписать мои ценности). Сказав это, я не доверяю себе вообще. Потому как…

вчера-регулярное выражение

… Поэтому все переменные и параметры также становятся final .

01
02
03
04
05
06
07
08
09
10
11
// Bad
void input(String importantMessage) {
    String answer = "...";
 
    answer = importantMessage = "LOL accident";
}
 
// Good
final void input(final String importantMessage) {
    final String answer = "...";
}

Хорошо, я признаю На этот раз я не часто обращаюсь, правда, хотя должен. Хотелось бы, чтобы в Java все было правильно, как в Scala , где люди просто набирают val везде, даже не задумываясь об изменчивости — за исключением случаев, когда они нуждаются в этом явно (редко!) Через var .

8. Не доверяйте дженерикам при перегрузке

Да. Это может случится. Вы верите, что написали этот супер приятный API, который просто потрясающий и полностью интуитивный, и вместе с ним появляется пользователь, который просто вводит все до Object до тех пор, пока проклятый компилятор не перестанет ругаться, и вдруг они сошлются на неправильный метод, думая, что это ваш вина (это всегда так).

Учти это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// Bad
<T> void bad(T value) {
    bad(Collections.singletonList(value));
}
 
<T> void bad(List<T> values) {
    ...
}
 
// Good
final <T> void good(final T value) {
    if (value instanceof List)
        good((List<?>) value);
    else
        good(Collections.singletonList(value));
}
 
final <T> void good(final List<T> values) {
    ...
}

Потому что, вы знаете … Ваши пользователи, они как

1
2
3
4
// This library sucks
@SuppressWarnings("all")
Object t = (Object) (List) Arrays.asList("abc");
bad(t);

Доверьтесь мне. Я видел все. Включая такие вещи, как

7h6fah71

Хорошо быть параноиком.

9. Всегда бросайте на переключатель по умолчанию

Переключатель … Одно из тех забавных утверждений, где я не знаю, окаменеть ли от страха или просто плакать. Во всяком случае, мы застряли с switch , поэтому мы можем сделать это правильно, когда нам нужно. Т.е.

01
02
03
04
05
06
07
08
09
10
11
12
13
// Bad
switch (value) {
    case 1: foo(); break;
    case 2: bar(); break;
}
 
// Good
switch (value) {
    case 1: foo(); break;
    case 2: bar(); break;
    default:
        throw new ThreadDeath("That'll teach them");
}

Потому что в тот момент, когда value == 3 введено в программное обеспечение, оно обязательно придет! И не говорите enum , потому что это произойдет и с enums !

10. Переключить с помощью фигурных скобок

На самом деле, switch — самое злое утверждение, которое когда-либо было разрешено любому человеку, когда он был пьян или проиграл пари. Рассмотрим следующий пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// Bad, doesn't compile
switch (value) {
    case 1: int j = 1; break;
    case 2: int j = 2; break;
}
 
// Good
switch (value) {
    case 1: {
        final int j = 1;
        break;
    }
    case 2: {
        final int j = 2;
        break;
    }
 
    // Remember:
    default:
        throw new ThreadDeath("That'll teach them");
}

Внутри оператора switch существует только одна область видимости, определенная среди всех операторов case . На самом деле, эти операторы case даже не являются операторами, они похожи на метки, а switch — это вызов goto. Фактически, вы даже можете сравнить операторы case с удивительным оператором FORTRAN 77 ENTRY , устройством, тайна которого превосходит только его мощь.

Это означает, что переменная final int j определена для всех различных случаев, независимо от того, выполним ли мы break или нет. Не очень интуитивно понятно. Вот почему всегда полезно создать новый вложенный контекст для каждого case помощью простого блока . (но не забывайте break в блоке!)

Вывод

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

Теперь твоя очередь!

Какой твой самый параноидальный изюминка в программировании?