Pull to refresh

Comments 85

<troll>
Ошибка 0: Использование Java вместо Scala
</troll>
Тут же про начинающих речь. С тем, количеством фичей, которые есть в Скале и список будет не из 10, а из 100 пунктов, а первой ошибкой будет — не разобрался в sbt и покончил с собой.
Чтобы с sbt разобраться нужно посмотреть выступление Даниэля Вестхайда с ScalaDays 2013? 2014? — он очень классно за час все объясняет.
www.parleys.com/talk/learn-you-sbt-fun-profit

После этого мои волосы стали мягкими и шелковистыми, а билды — простыми и надежными
И вас устраивает количество мусора в проекте? Разные build.sbt, pligins.sbt и отдельная папка project? Уж лучше лаконичный Gradle с одним скриптом build.gradle, чем этот вынос мозга.
У меня, например, gradle не подружился с идеей. На каждый чих она его запускает, что даёт жуткие тормоза. Но, может быть, это проблема интеграции с IDE.
Поставьте галочку offline mode в настройках грэдла в идее — может дело в репозитории.
С maven и sbt (отнюдь не в offline-mode) таких проблем не было. Сейчас проектов на gradle на поэкспериментировать нет под рукой.
В смысле? У меня есть 4 файла:
— build.sbt — описание всех проектов и подпроектов. Чаще всего делается с нуля для каждого.
— project/Dependencies.scala — я так привык задавать разные зависимости и делать package sets
— project/plugins.sbt — подключение плагинов. Мигрирует из проекта в проект
— settings.sbt — настройки scalariform, revolver, packaging, web plugin etc
Иногда еще таски дополнительные делаю, но там зависит.

Где там мусор? Когда в проекте 300 файлов исходников, 4 файла которые управляют всем билдом, все аккуратно разнесено по нужным файликам, каждый из которых не более 20 строк.

Если писать не hello-world, то билд-система должна быть достаточно мощной, но и простой для начинающих. В sbt, соглашусь, классовый переход между простотой и мощностью чуток затянут, не спорю, но для понимания достаточно один раз посмотреть видео выше.
Это много раскиданных файлов вместо одного скрипта на проект в корне.
Опять же, зачем мне учить еще один язык, вдобавок, не добавляющий никаких новых идиом, а скорее конфузящий. Как правильно перевести confusing?
Groovy намного ближе к Java, чем Scala. Использовать для Java проектов SBT я бы не стал. При этом в Gradle отличная поддержка всех трёх языков: Java/Groovy/Scala.
Я думаю, что речь тут о том, что когда Scala уже выучена — зачем учить ещё и Groovy, который кроме как для конфигурирование Gradle всё равно больше нигде смысла использовать нет в случае проекта на Scala
Сбивающий с толку, скорее.
По поводу пятой ошибки: сдается мне, в приведенном примере замедляет работу программы не столько обилие мусора, сколько квадратичная сложность алгоритма
Еще пара распространенных ошибок:
— использование не thread safe классов в многопоточных приложениях. Сколько людей пыталось сэкономить на
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd"); 

и обнаруживали потом странные даты.
— Классика жанра: январь месяц в Calendar имеет нулевой номер, лечится или JodaTime или LocalDate в Java 8, где месяцы как положено пронумерованы от 1 до 12.
Еще можно лечить ThreadLocal, помнится мы так делали.
Насчёт SimpleDateFormat ошибка была в том, что FindBugs не использовали :D Он же, кстати, неплохо находит третью, пятую и девятую ошибки и позволяет решить много головняков с NPE. Восьмую ошибку в ряде случаев находит fb-contrib (плагин к FindBugs, хотя он много мусора выдаёт).
Могу ещё посоветовать полезный maven plugin и ant task forbiddenapis: code.google.com/p/forbidden-apis/

Из коробки поддерживает следующее:
The JAR file contains the following signatures and can be used in Maven or Ant using . All signatures are versioned against the specified JDK version:

jdk-unsafe-*: Signatures of «unsafe» methods that use default charset, default locale, or default timezone. For server applications it is very stupid to call those methods, as the results will definitely not what the user wants (for Java * = 1.5, 1.6, 1.7, 1.8; Maven automatically adds the compile Java version)
jdk-deprecated-*: This disallows all deprecated methods from the JDK (for Java * = 1.5, 1.6, 1.7, 1.8; Maven automatically adds the compile Java version)
jdk-system-out: On server-side applications or libraries used by other programs, printing to System.out or System.err is discouraged and should be avoided (any java version, no specific version)
commons-io-unsafe-*: If your application uses the famous Apache Common-IO library, this adds signatures of all methods that depend on default charset (for versions * = 1.0, 1.1, 1.2, 1.3, 1.4, 2.0, 2.1, 2.2, 2.3, 2.4)
Другой частой причиной возникновения утечек является наличие циклических ссылок. В этом случае сборщик просто не может решить, нужны ли ещё объекты, перекрёстно ссылающиеся друг на друга.

GC спокойно справляется с этим, т.к. ищет из «корней» все достижимые объекты. Если некий набор перекрестно ссылающихся объектов недостижим, то он будет спокойно очищен сборщиком мусора.
Это если не замкнуть через какой-нибудь threadlocal и/или classloader =)
Это одни из «корней», т.е. GC и не должен очищать объекты доступные по ссылкам из thread local/classloader.
Тут скорее возможность получить OOM, если бесконтрольно класть что-то в thread local или перезагружать классы/класслоадеры.
Когда вы загружаете приложение, используя свой кусок иерархии класслоадеров, это уже далеко не корень. Простой пример — servlet container или osgi.
Можно поподробнее?
Про threadlocal я примерно вкурсе, а вот что можно сделать с classloader нет. Поделитесь

P.S. не static threadlocal зло)
Смоделирую кейс, который приходилось лечить и попробую написать на днях статью. Для комментария это слишком монструозно получится.
Кажется, я упомянул об этом в примечаниях переводчика :)
Прошу прощения, читал «по диагонали», не заметил примечание.
По-моему сюда нужно вписать использование == для String, Integer и т.д без понимания как это работает.
Думал, это будет первый пункт в списке.
Самое ужасное при сравнении для новичков в java это то, что сравнение Integer корректно работает для чисел меньше 127 по модулю. Так что при написании каких- нибудь лабораторных работ они могут даже не столкнуться с такой проблемой.
Если быть точнее до -128… +127
Давайте и я позанудствую: от -128 до значения опции -XX:AutoBoxCacheMax (по умолчанию 127).
И я позанудствую: от -128 до -Djava.lang.Integer.IntegerCache.high.
Ещё можно вспомнить про то, что числа, начинающиеся с нуля превращаются в восьмеричные.
В Scala, кстати, сравнение == это то же самое, что equals, а вот сравнение по ссылке делается через eq.
Я пришёл в java из c# и js, и просто охренел когда обнаружил такое поведение. Оно меня до сих пор просто убивает.
Да в жопу такую логику. Это неудобно.
Ну явное использование Integer вообще подозрительно и в подавляющем большинстве случаев ненужно. Поэтому проблема остаётся только со String.
А мне вот нужно.
К примеру сервер должен возвращать матрицу чисел, в некоторых ячейках могут быть null, тогда и появляется Double, Integer.
null в матрице? Опишите какую задачу вы решаете?

Что касается поведения, то по-моему equals является вариантом более лучшим, чем == хотя бы потому, что можно переопределить его логику. Ну и равенство ссылок тоже как-то надо проверять. Java придумывали крайне умные люди и они не зря так сделали. В ней нету не одной необъяснимой конструкции.
Получение среза данных из куба по mdx запросу.

В c# есть переопределение операторов, и это более удобно — не надо делать проверки на null, как с equals.

А так получается, что оператор == в java ну нафик не нужен.
Если нужно специальное значение в double, можно NaN'ами добить. Сэкономите большое количество памяти на самом деле. Кстати, если нужно много специальных значений, можно завести неканонические NaN'ы и читать их через Double.doubleToRawLongBits.
А вот за это спасибо :) Попробую.
Возникает логичный вопрос, если с# так хорош, то зачем вы на java перешли?
Хороший вопрос: ответ простой — на java есть одна хорошая open-source библиотека для работы с кубами и mdx — mondrian.
На C# ничего подобного не было, да и вообще где-либо.
А что делать, если генерики не могут быть параметризованы примитивными типами?
Использовать специальные коллекции, конечно. Их сейчас куча наплодилось. Почитайте, например, этот обзор. Или вот. Выигрыш и по памяти, и по скорости иногда в 10 раз.
По моему, первая ошибка новичка в Java — не читать книги по Java :) Т.к. в них большая часть ошибок разобрана.
Да и судя по опыту, это не только ошибка новичка, но и более опытных Java программистов. :(
К сожалению, и не только Java.
Кстати, какие лучшие книги по Java рекомендуете?
Классика, т.с., библии?
Для старта, мне кажется, нет ничего лучше:
Core Java Volume I--Fundamentals (9th Edition) (Core Series) by Cay S. Horstmann
Core Java, Volume II--Advanced Features (9th Edition) (Core Series) by Cay S. Horstmann
См. toster.ru, таких вопросов штук 20, не преувеличение.
Ошибка новичка №0:
Читать переводную литературу и статьи.

P.S. Местами предложения и термины лучше было не переводить вовсе.
StringBuilder oneMillionHelloSB = new StringBuilder();
    for (int i = 0; i < 1000000; i++) {
        oneMillionHelloSB.append("Hello!");
    }
System.out.println(oneMillionHelloSB.toString().substring(0, 6));

Кстати, если вы формируете огромную строку в StringBuilder'е и способны предугадать её длину (или хотя бы оценить сверху), полезно передать её параметром в конструктор. В данном случае передача в конструктор 1000000*"Hello!".length() заметно ускоряет работу:

Benchmark        Mode  Cnt   Score   Error  Units
SbBench.control  avgt   30  14.139 ± 0.231  ms/op // это как в статье
SbBench.fixedSB  avgt   30   9.991 ± 0.042  ms/op // это с параметром

Вы выигрываете на том, что не надо перевыделять массив и копировать из старого.
что мертвому припарки.
Избавились от кучи выделений памяти, а ускорились всего в полтора раза. Меня не впечатляет.
А можно с другой стороны посмотреть: изменили всего одну строчку и ускорились ажно в полтора раза :-)

Если вам очень часто нужен такой алгоритм для большого количества строчек (непонятно зачем, конечно) и хочется его ещё ускорить, можно применить стратегию разделяй и властвуй:

private static void timesClever0(StringBuilder sb, int n, String s) {
    if(n == 0) return;
    if(n % 2 == 0) {
        timesClever0(sb, n/2, s);
        sb.append(sb);
    } else {
        timesClever0(sb, n-1, s);
        sb.append(s);
    }
}

private static String timesClever(int n, String s) {
    if(n == 0) return "";
    StringBuilder sb = new StringBuilder(n*s.length());
    timesClever0(sb, n, s);
    return sb.toString();
}

Результаты JMH (сейчас я за другой машиной, с предыдущими не надо сравнивать):

Benchmark                Mode  Cnt   Score   Error  Units
SbBench.builderClever    avgt   30   9.442 ± 0,449  ms/op
SbBench.builderLength    avgt   30  15.443 ± 1,251  ms/op
SbBench.builderNoLength  avgt   30  22.329 ± 0,761  ms/op

Полный код бенчмарка здесь.
По-моему это очевидно и без бенчмарков. Аналогично и для коллекций: указание примерного размера при создании ArrayList / HashSet / HashMap etc. дает ощутимый выигрыш в производительности.
«Примерного» — неподходящее слово. Если размер близок, но чуть меньше, то память всё равно будет выделяться дважды. И насчёт «очевидно и без бенчмарков» — с этим осторожнее. В JIT-компиляторе столько магии, что всё надо реально проверять — и ассемблер смотреть, и профилировать. В некоторых тестах, например, ""+num (что эквивалентно new StringBuilder("").append(num).toString()) работает быстрее, чем Integer.toString(num). Как это получается, я до сих пор не понял.
А еще многие не умеют играть в «try/catch»:
try{
 //...
}catch(){
}finally{
  try{
   a.close();//закрыли поток
  }catch(){
   //..
  }
}

Просто и эффективно.
Конструкция в общем полезная, но сейчас есть try_with_resources и надобность в ней отпала.
по поводу 6. сразу дисклеймер — разговор про яву только.

во-первых, не надо использовать Optional в яве. не оправдано это.
во-вторых, возврат пустых коллекций приведет к тому, что придется научиться копировать их перед использованием. Поскольку Collections.emptyList не позволит их модифицировать, и это может сказаться в неожиданных местах.
ну, у меня только из опыта есть аргументы.

0. Это инструмент. Соответственно пользоваться им надо от необходимости. Я не вижу экономии букв, если просто ввести Optional.
1. Получается миксинг из подходов null и Optional. Потому что без третьих библиотек не обойтись, а там Optional не бывает. Даже если привык и нравится использовать Optional — все равно в середине перфекционизма может возникнуть работа с null.

2. И крайний аргумент. Самый идиотский, из-за которого работа с библиотеками становится пыткой — это type erasure. В случае c Optional придется потратить уйму времени на работу с orm или биндингами параметров в методах spring webmvc.
В результате получиться ситуация, что в контроллерах — null, в DAO — null. Ну а посередине, где получится, гордясь от знаний функциональности, будем впихивать Optional.

Я не против нового, сам первый встаю под новые знамена. Но случай Optional в яве — мне кажется сомнительным и не оправдывающим себя.
Ну тогда может и от lambda, stream api, новых concurrent collections и CompletableFuture и т.д откажимся?
Вы просто иронизируете, ничего страшного в миксе нету. Просто нужно качественно описание ваших интерфейсов)
не откажемся. потому что это интересно и полезно. в случае Optional — я не вижу полезности, а вот грабли — вижу.

Я же не о преимуществах сферического коня в вакууме спорю. Поэтому можно провести эксперимент — взять пару проектов покрупнее и посмотреть насколько распространены Optional.
Тут я, кстати, соглашусь: Optional мне кажется ересью в мире Java. Просто кидайте исключение вместо того, чтобы возвращать null. В большинстве случаев бизнес-логики это оптимальный вариант. Остальные перечисленные вами новшества Java 8 классные, их можно и нужно использовать.
Ну вы же наверное знаете, что исключение дорогостоящая операция)
Ещё я знаю, что преждевременная оптимизация — корень зла ;-)
Понял вас. В принципе, я согласен со всеми доводами, они все входят в мой внутренний список причин, по которым использование Java в принципе не оправдано)
А кто вам сказал, что коллекцию, которую вернул метод, можно модифицировать? Если это требуется, должно быть в контракте метода прописано. Но зачастую это дополнительные ненужные расходы ресурсов. Так всегда можно вернуть ImmutableList (Guava), который устроен экономнее. Для одного значения можно вернуть singletonList. Наконец, часто эффективнее вернуть ссылку на внутреннюю структуру, обернув её в unmodifiableList для безопасности. Если хотите модифицировать, просто оберните результат метода в new ArrayList<>(...).
Вы предлагаете всегда (при необходимости изменений) оборачивать в новый ArrayList. Это интересный метод, не всегда эффективный. И не всегда возможный. Например, если изменяемая коллекция передается по ссылке и при этом нет возможности установить возвращаемое значение.

Я просто не вижу особо смысла пытаться применить одинаковый подход «с сегодняшнего дня везде возвращаем пустой read-only список», потому что так в переводной статье написано. Каждое решение и его использование должно быть обосновано. Выносить это в must have — я не поддерживаю.

«А кто вам сказал, что коллекцию, которую вернул метод, можно модифицировать?»

Это есть в контракте Collection. И у ImmutableList он тоже есть кстати. И там ещё пара отдельных исключений возникает — например, поддерживает ли он null в коллекции и может ли он содержать «вот эти» конкретные типы. А мы спорим только об одном — это несправедливо по отношению к бедному Collection.
Необходимость модификации коллекции, которую вам вернули, исключительно редка. Если контракт метода подразумевает, что коллекция возвращается по ссылке и может быть изменена пользователями снаружи, это отдельный случай. Но и в таком случае смысла нет возвращать null, ведь тогда вы тем более не сможете модифицировать коллекцию! Надо завести в вашей переменной пустой ArrayList и его и возвращать. Отмечу, что это в большинстве случаев суровое нарушение инкапсуляции (если речь идёт о публичном методе), по сути ничем не лучше публичного поля: клиенты могут бесконтрольно менять внутреннее состояние объекта.
возращать new ArrayList — это нормально.

Я же изначально имел в виду другие грабли: в статье написано «пустой список». И этот подход в ловких руках с использованием Collections.emptyList() может дать неожиданный результат, о котором новичкам тоже надо знать.
Это есть в контракте Collection.
А можно ссылочку на этот контракт? А то вообще странно — в самой java.util.Collections есть метод — emptyList(). И наверное не зря же его придумали. Да и используется (сейчас посмотрел в IDEA в нашем проекте) в 14 случаях в JDK и в 170 случаях во внешних либах (Guava, Mockito, Apache Common) и singletoneList используется в 10 случаях в JDK и в 70 в библиотеках. Так что вполне рабочий метод — использовать unmodifiable коллекции. Хотя да, если часто изменять возвращаемые из методов коллекции, то может быть сюрприз, когда это сделать не удастся. Но это уже пользователи методов виноваты :)
Ну так контрактом является сам интерфейс Collection. У него есть методы, которые изменяют коллекцию.
То есть, если вы принимаете на вход Collection, то непонятно, что с ним можно сделать, если не вызвать add().
Наличие исключений дает ситуацию, что иногда можно изменять, иногда нет. Например список лимитированного размера — он вроде add поддерживает, но до определенного момента.

Тонкость emptyList() в том что он возвращает не только empty, но и read-only list. И это может ввести в заблуждение. И приводит к появлению виноватых пользователей.
Интересный вопрос — вообще наличие метода говорит о том, что можно добавлять. Но если прочитать JavaDoc метода (java.util.Collection#add(E e)) и там ясно написано, что поведение метода зависит от реализации:
Throws:
UnsupportedOperationException — if the add operation is not supported by this collection

Т.к. с одной стороны метод конечно есть, но в JavaDoc чётко написано — что он может не работать. Так что нужно читать JavaDoc.
Например, возвращать пустые массивы или коллекции лучше методами, чем null

вы точно это хотели сказать?

Это исключение возникает, когда коллекция модифицируется во время итерирования любыми методами, за исключением средств самого итератора.

не только во время итерирования
String oneMillionHello = "";
for (int i = 0; i < 1000000; i++) {
    oneMillionHello = oneMillionHello + "Hello!";
}
System.out.println(oneMillionHello.substring(0, 6));


Был глубоко убеждён, что компилятор умеет такое оптимизировать и уже достаточно давно
Ошибка №0: выбор Java за иконку кофе в логотипе =)
Sign up to leave a comment.