Pull to refresh

Comments 34

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

Почему бы вместо этого

Arrays.asList(«localhost», «github.com»)
.stream()
.map(URL::new)
.collect(Collectors.toList())

не использовать обычный цикл для прохода по коллекции? Придумали проблему на пустом месте, обязательно вам захотелось использовать стримы, вместо того, чтобы написать код, понятный, не машине, а человеку.
Это пример кода, который показывает потенциальные проблемы использования лямбд. Это не фрагмент кода CUBA :-)

А проблема читабельности кода Java после введения лямбда-выражений в 8-ке — это отдельная тема, достойная большой статьи. Если этими вещами злоупотреблять — то получится страшное.

Просто заметка — у так назывемых «внутренних» циклов, есть одно преимущество — можно поставить
.parallelStream

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

У вас проблема с лямбдами, но в примерах вы делаете еще больше проблем с пониманием написанного, что бы-таки применить лямбды… В скором времени после релиза восьмерки я видел замечательно правило по этому поводу: «Если в вашем коде лямбды больше мешают, нежели помогают — просто не используйте лямбды. Любая задача решаемая через лямбды может быть решена без них.»
Эта статья не совсем про лямбды в целом, а про отдельные вещи, которые нужны в ограниченном наборе случаев. Посколькую мы пишем фреймворк, то есть места, где использование способов, описанных в статье, помогает значительно улучшить производительность. Эта задача решатеся при помощи Reflection API без лямбд, но это значительно медленнее.
>У вас проблема с лямбдами
> что бы-таки применить лямбды…
Проблема не с лямбдами. Если вы вглядитесь, вы поймете, что изначально проблема в checked exceptions. Которые плохо совмещаются с функциями — это чистая правда.

Просто посмотрите на описанные тут приемы с иной точки зрения. Представьте, что вы это вот все не руками пишете, а генерируете. Или создали на базе этого знания библиотеку, что вполне возможно. Если взять тот же vavr.io, то приведенный пример будет выглядеть как:

Arrays.asList(«localhost», «github.com»)
.stream()
.map(s->Try.of(()->new URL(s)))
.collect(Collectors.toList())

и все. На выходе вы получите List<Try>>. А захотите — можете сразу тут отфильтровать невалидные URL, и это будет так же просто и кратко.
Ну это же только минимальный пример. Или вы считаете что стримы сложнее в принципе и использовать их не стоит?
Я писал не об этом, а о том, что использование разных трюков и хитростей, как написано в статье, — это плохо и ведет к появлению трудноподдерживаемого кода.
Сегодня этим кодом занимается сеньор и никто ему не мешает самовыражаться (за чужой счет). Завтра его переведут на другой проект и на поддержку кода придет джуниор, который будет неделями разбираться, что это за хаки и как они работают. Проще надо быть, проще. А самовыражаться в домашних проектах. Все равно их никто не увидит.
Самовыражаться в коде боевого проекта — вообще плохая идея :-) А хаки, трюки и хитрости должны быть локализованы и подробно задокументированы, иногда без них нельзя. А ещё, некоторые задачи нельзя передавать недостаточно опытным разработчикам, если они недостаточно четко понимают, как оно внутри работает, как бы оскорбительно это не звучало. Пока разработчик не понимает, зачем нужен synchronized, отдавать ему код по многопоточной обработке данных не надо.
Лично меня не сильно пугает синтаксис стримов, если аккуратно писать — то все читаемо получается. И стримы не сложнее «понятного человеку» кода. До Java 8, лямбд и стримов я видел примеры нечитаемого кода. Как обычно — все сводится к тому, как человек пишет.

На coursera есть курс Kotlin for Java developers там задачка про таксопарк. Так вот, там, если решать с использованием стримов — то все получается очень красиво. Если решать в более «императивном» стиле — то кода будет в три раза больше, мне кажется :-)
Строго говоря, в Kotlin не совсем стримы, вернее, совсем не стримы) Там используются функциональные вызовы, выполняющие императивные операции. Но в целом, да, согласен, понимаю о чем идет речь.

Со своей стороны всем, кто не понимает зачем нужны стримы, могу порекомендовать прекрасный, на мой взгляд, Kotlin Koans Collections.
Почему бы вместо этого… не использовать обычный цикл для прохода по коллекции?

Потому что обычный цикл хуже читается и содержит больше бойлерплейта.


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

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

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

void printElements(List<String> strings){
    strings.forEach(item -> System.out.println("Item = %s", item));
}

Ну как бы тут нет вложенных циклов.
>Я писал не об этом,
Но получилось в итоге именно об этом. Все свелось к:

>Почему бы вместо этого… не использовать обычный цикл для прохода по коллекции?

>понятный, не машине, а человеку.
Ну, это ваше личное мнение. Мне вот по большей части все равно, какой стиль читать, но при этом стримы и сопутствующий стиль (map/reduce) имеют и очевидные преимущества. Одно из них — как раз большая очевидность выражения «намерений».

Если использовать обычный цикл, то exception на new URL никуда не денется, и его так или иначе придется обработать. Это можно сделать разными способами, многие из которых неправильные. А уж от «намерений» как правило вообще мало что остается.

Вы попробуйте, напишите, только try не забудьте — вот тогда и будет видно, насколько у вас получится понятно человеку. Я практически уверен, что настолько же лаконично вряд ли выйдет.
UFO just landed and posted this here
Это не совсем магия. При перекладывании данных из формочки в базу и обратно, вы, скорее всего, ЯВНО с этим не встретитесь. Но если вы пишете фреймворк, то рано или поздно столкнетесь с необходимостью использовать reflection, например. Reflection — магия? А если захотите, чтобы фреймворк работал ещё быстрее, то будете искать варианты, как ускорить reflection. И придете к MethodHandle, лямбдам и вот этому вот всему. В рамках формата перевода много от себя не напишешь, можно почитать пример тут, если не страшно :-) А ещё можно глянуть в исходники одного из классов библиотеки spring-data-commons — ClassGeneratingPropertyAccessorFactory, там тоже есть чему порадоваться. А этим кодом многие люди пользуются и он как-то поддерживается.
UFO just landed and posted this here
Ну, вот, как-то мне нужно было сделать такую простую вещь, как разбор INI-файла. Но хотелось автоматизации, т.к. сложная структура с разделами и подразделами, проверка валидности значений, автоподастановка значений по-умолчанию, контроль изменений, кэширование и т.п.
Сначала это был просто код. Но когда данный стало много, то код стал нечитаем.
Было переписано на набор классов, которые делали основную автоматизацию. Но по мере разрастания количество и сложности данных это опять стало нечитаемым — куча дублирующегося когда, в частности.
В результате было переписано с рефлекшином, когда класс данных динамически разбирается и под него формируются метода работы с данным. Кода стало в четрые раза меньше, основной код стал неизмеримо понятнее. А если бы это были не статические, а динамические данные, то могли возникнуть вопросы быстродейтсвия и пришлось лезть в LambdaFactory.
Рефлекшн уже не магия. На нём много чего основано и есть такое ощущение, что в случае работы с со сложными динамическими данными без него на определённом этапе уже не обойдёшься — слишком много дурного кода придётся писать. Его уже нужно знать и уметь применять.

Любой ваш код разбирающий ini файл плохой. И исправить его можно только выкинув вообще и заменив на сериализацию.

Отличная идея. Придется правда пользователей научить править бинарный файл с сериализованными объектами. Но ничего, они вытерпят.
Вы правда не знаете про сериализацию в текст?
Печально это…
Я знаю много сериализаций, поэтому когда не оговаривается специально, я подразумеваю что речь идет о стандартной сериализации.

Если же вы имели ввиду сериализацию в текст то ini это текст и смысл вашего комментария становится совсем туманным.
А с каких пор ini-файл перестал быть текстом? Или под текстом имеется ввиду сугубо xml/yaml/json/properties/conf/etc.?
Вау! А мужики-то и не знают, уж, простите!
А исходные данные в сериализацию вы как забивать будете? А править их потом ручками в случае сложной структуры? INI-файлы, они, как бы, очень простой человеко-ориентированный кросс-платформенный формат.
Только не говорите про XML, сами его редактируйте в чистом поле средствами какого-нибудь notepad.
Вы на полном серьезе говорите что отредактировать xml это проблема?

Что лучше писать велосипед по парсингу кастомных конфигов, чем воспользоваться стандартным решением которое вообще не надо писать?

Что не знаете откуда взять классы для хранения ваших конфигурацинных данных?

А потом еще удивляемся неработающим игрушкам из-за опечатки в конфиге. Они тоже небось считают что кастомный ini это круто.
Вы в документации как будете формат XML описывать? Удачи. Как неподготовленный пользователь должен достаточно сложный XML править?
Всё равно нужно решать задачу валидации данных, структуры данных, значений по-умолчанию ии т.п. «стандартного решения» не будет.
И самое главное, вы какую задачу применением XML решаете? Формата данных, который может править неподготовленный пользователь? Нет. Тогда какую?
Проблема описания XML в документации решена уже столько раз… Даже не сосчитать. Вы точно не первые кто xml использует.

Не нужно. Десериализуем файл. Ситуация бинарная: десериализовалось или нет. Во втором случае конфиг некорректен вообще. Выводим ошибку не стартуем.
В первом валидируем поля наших конфиг классов. Задача стандартнее некуда. Тесты простейшие и пишутся без проблем. Вероятность ошибки минимальная.

Отсутствие кода лучше чем любой код. Если задачу можно решить без написания кода это великолепно и ее только так и надо решать. Я решаю задачу упрощения приложения и уменьшения вероятности ошибок.
Так как вы будете в документации XML описывать для обычного пользователя, далёкого от IT? Описание формата INI-файла занимает одну страницу.
И я же говорю, вам всё равно придётся прикручивать проверки правильности значений и структуры, значения по умолчанию и т.п. Количество кода изменится не сильно, но с точки зрения пользователя результат будет намного хуже.
Создается впечатление что вы не в курсе о существовании библиотек парсинга ini файлов.
Как метко заметил товарищ Кларк, любая достаточно развитая технология неотличима от магии. Из чего прямо следует, что встреча с магией это повод заняться самообразованием, а вовсе не причина остановить прогресс.
Спасибо за статью.
Хотелось, чтобы этот автор написал статью про MethodHandle, если есть желание и интерес
Спасибо за статью.
Есть небольшой вопрос по поводу проверяемых исключений в лямбдах. В чем, собственно, заключается зло при оборачивании проверяемых исключений в непроверяемые?
Для себя нахожу более удобным для этих целей синтаксис JOOL.
Например, Ваш код:
.map(url -> callUnchecked(() -> new URL(url)))

Аналогично, с использованием JOOL:
import static org.jooq.lambda.Unchecked.function;
...
.map(function(URL::new))


Sign up to leave a comment.