Статьи

Каковы плохие функции Java

обзор

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

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

Это сбивает с толку, когда меняется мода или ваша команда, и эта функция становится прекрасной или даже предпочтительной методологией.

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

Проверенные исключения

Я часто удивляюсь, насколько разработчики не любят думать об обработке ошибок. Новые разработчики даже не любят читать сообщения об ошибках. Это тяжелая работа, и они жалуются, что приложение упало, «оно не работает». Они понятия не имеют, почему возникло исключение, когда часто сообщение об ошибке и дамп стека говорят им, что именно пошло не так, если они могли видеть только подсказки. Когда я записываю трассировки стека для целей трассировки, многие просто видят журнал в форме сбоя, когда не было ошибки. Чтение сообщений об ошибках — это навык, и поначалу это может быть ошеломляющим.

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

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

Проверенные исключения могут быть чрезмерно использованы, конечно. Вопрос должен быть при создании проверенного исключения; я хочу раздражать разработчика, вызывающего код, заставляя его немного подумать об обработке ошибок? Если ответ «да», бросьте проверенное исключение.

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

Вы можете обойти ограничение лямбд, перебросив проверенное исключение, как если бы оно было непроверенным. Это работает, потому что JVM не имеет понятия о проверенных исключениях, это проверка времени компиляции, как обобщенные. Мой предпочтительный метод — использовать Unsafe.rethrowException, но есть 3 других способа сделать это. Thread.currentThread (). Stop (e) больше не работает в Java 8, несмотря на то, что это всегда было безопасно.

Был ли Thread.currentThread (). Stop (e) небезопасным?

Метод Thread.stop (Throwable) был небезопасным, когда он мог заставить другой поток вызывать исключение в случайной части кода. Это может быть проверенное исключение в части кода, которая не ожидала этого, или генерирование исключения, которое перехватывается в некоторых частях потока, но не в других, оставляя вас без понятия, что он будет делать.

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

Но как насчет Thread.currentThread (). Stop (e)? Это запускает текущий поток, чтобы выдать исключение в текущей строке. Это не хуже, чем просто с помощью исключения throw, вы выполняете операцию, которую не может проверить компилятор. Проблема в том, что компилятор не всегда знает, что вы делаете, и действительно ли он безопасен или нет. Для дженериков это классифицируется как «непроверенное приведение», которое является предупреждением, которое можно отключить с помощью аннотации. Java не поддерживает операции такого же типа с проверенным исключением, и вы в конечном итоге используете хаки или, что еще хуже, скрываете истинное проверенное исключение как исключение времени выполнения, а это означает, что есть небольшая надежда, что вызывающая сторона обработает его правильно.

Использование static плохо?

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

  1. статические изменяемые поля
  2. статическое неизменяемое поле (конечные примитивные или конечные поля, указывающие на объекты, которые не были изменены)
  3. статические методы.
  4. статические классы (которые не имеют неявной ссылки на внешний экземпляр)
  5. статический инициализатор блоков.

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

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

Короче говоря, статические это хорошо, а изменяемые статические поля являются исключением, а не правилом.

Синглтоны плохие?

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

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

Если вы исключите глобальное состояние и компоненты самостоятельного подключения, у вас останутся Singletons, которые являются неизменяемыми и передаются через внедрение зависимостей, и в этом случае они могут работать действительно элегантно. Обычный шаблон, который я использую для реализации стратегий, — это использование перечисления с одним экземпляром, который реализует интерфейс.

1
2
3
4
5
6
enum MyComparator implements Comparator {
   INSTANCE;
   public int compare(MyObject o1, MyObject o2) {
       // something a bit too complicated to put in a lambda
   }
}

Этот экземпляр может быть передан как реализация Comparator через внедрение зависимостей и без изменяемого состояния может безопасно использоваться в потоках и модульных тестах.

Могу ли я получить библиотеку или фреймворк, чтобы сделать эту очень простую вещь для меня?

Библиотеки и фреймворки могут сэкономить вам много времени и усилий, заставляя ваш собственный код делать то, что уже работает где-то еще.

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

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

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

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

Использование двойного за деньги плохо?

Использование дробных чисел без учета округления даст вам неожиданные результаты. С положительной стороны, для двойной, как правило, явно неправильно, как 10.99999999999998 вместо 11.

Некоторые считают, что BigDecimal является решением. Однако проблема в том, что BigDecimal имеет свои собственные ошибки, гораздо сложнее проверять / читать / записывать, но хуже всего может выглядеть правильно, когда это не так. Возьмите этот пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
double d = 1.0 / 3 * 3 + 0.01;
BigDecimal bd1 = BigDecimal.valueOf(1.0)
        .divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP)
        .multiply(BigDecimal.valueOf(3))
        .add(BigDecimal.valueOf(0.01))
        .setScale(2, BigDecimal.ROUND_HALF_UP);
BigDecimal bd2 = BigDecimal.valueOf(1.0)
        .divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP)
        .multiply(BigDecimal.valueOf(3)
        .add(BigDecimal.valueOf(0.01)))
        .setScale(2, BigDecimal.ROUND_HALF_UP);
System.out.println("d: " + d);
System.out.println("bd1: " + bd1);
System.out.println("bd2: " + bd2);

Это дает три разных результата. На вид, какой из них дает правильный результат? Можете ли вы сказать разницу между bd1 и bd2?

Это печатает:

1
2
3
d: 1.01
bd1: 1.00
bd2: 0.99

Вы видите по выходным данным, что не так? На самом деле ответ должен быть 1.01.

Другой недостаток BigDecimal — это то, что равно и сравнивают, чтобы не вести себя одинаково. Равное () может быть ложным, когда CompareTo () возвращает 0. т. е. в BigDecimal 1.0 равняется 1,00 — ложно, поскольку шкалы различаются.

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

Если BigDecimal не является отличной альтернативой, есть ли другие? Часто int и long используются с фиксированной точностью, например, целое число центов вместо доли в долларах. Это имеет некоторые проблемы, вы должны помнить, где находится десятичное место. Если Java поддерживает типы значений, возможно, имеет смысл использовать их как обертки для денег и обеспечить вам большую безопасность, но контроль, прояснение и производительность при работе с примитивами целых чисел.

Использование null значений

Для разработчиков, NullPointerException с Java, получение повторного NullPointerException является NullPointerException процессом. Действительно ли мне нужно создавать новый экземпляр каждого объекта, каждого элемента массива в Java? Другие языки не требуют этого, поскольку это часто делается с помощью встроенных структур данных. (Что-то, что рассматривается для Java)

Даже опытным Java-разработчикам трудно иметь дело с null значениями, и они считают большой ошибкой иметь нулевое значение в языке. ИМХО Проблема в том, что замены часто намного хуже. такие как объекты NULL, которые не являются NPE, но, возможно, должны были быть инициализированы чем-то другим. В Java 8, Optional является хорошим дополнением, которое делает обработку не-результата более понятной. Я думаю, что это полезно для тех, кто борется с NullPointerException, так как это заставляет вас думать, что результата может не быть вообще. Это не решает проблему неинициализированных полей.

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

Общий вопрос: как я должен был знать, что переменная была нулевой? Это неправильный путь в моей голове. Должно быть, почему предполагается, что оно не может быть нулевым? Если вы не можете ответить на этот вопрос, вы должны предположить, что он может быть нулевым, и NPE не должно вызывать удивления, если вы не проверите это.

Можно утверждать, что Java могла бы использовать более синтаксический сахар для создания кода, который обрабатывает ноль чище, такого как оператор Элвиса, но я думаю, что проблема заключается в том, что разработчики недостаточно думают о нулевых значениях. Например, вы проверяете, что переменная enum равна нулю, прежде чем включить ее? (Я думаю, что должен быть case null : в переключателе, но нет или провалиться по default : но это не так)

Насколько важно быстро писать код?

Java не является кратким языком, и без IDE для написания половины кода для вас было бы очень больно писать esp, если бы вы потратили целый день на написание кода.

Но это то, что разработчики делают весь день, не так ли? На самом деле, они этого не делают. Разработчики не тратят большую часть своего времени на написание кода, они тратят от 90% (для нового кода) до 99% (для унаследованного кода) на понимание проблемы .

Вы могли бы сказать; Я пишу 1000 строк кода весь день и позже и переписываю код (часто делая его короче), и через некоторое время я исправил код. Однако, хотя код все еще свеж в вашем уме, если бы вы писали только Код, который вам нужен в конце (или вы делаете это из распечатки), и вы делите его на общее время, потраченное на проект, от начала до конца, вы, вероятно, обнаружите, что на самом деле это было менее 100 строк кода в день Возможно менее 10 строк в день.

Так что же вы действительно делали за это время, если не писали готовый продукт? Было понятно, что требуется конечным пользователям и что необходимо для реализации решения.

Кто-то однажды сказал мне; не имеет значения, насколько быстро, насколько велико, как глубоко или сколько вырыли ям, если вы копаете их не в том месте.

Вывод

Я слышал мнения начинающих разработчиков о том, что вы не должны / я не могу представить, почему вы / должны быть уволены, если вы используете X, вы должны использовать только Y. Я считаю, что такие утверждения редко бывают точными на 100%. Часто есть или крайние случаи, и иногда очень распространенные случаи, когда такое утверждение вводит в заблуждение или просто неверно.

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

Ссылка: В чем плохие возможности Java от нашего партнера JCG Питера Лоури из блога Vanilla Java .