All streams
Search
Write a publication
Pull to refresh
0
0
Send message
Т.е. ошибка так или иначе возвращается клиенту. Получается, что первый и второй случаи с точки зрения клиента отличаются лишь подробностью описания ошибки, а с точки зрения сервиса — уровнем логирования ошибки: в первом случае WARN — потому что система сама предотвратила некорректное поведение программы, отвалидировав входящие параметры; во втором случае — ERROR, потому что несмотря на наши усилия некорректное поведение все таки произошло.
Но в обоих случаях способ обработки ошибки один и тот же — возврат этой самой ошибки клиенту, для того чтобы он самостоятельно обработал ее на своей стороне (сделал ретрай\поправил код\написал вашему суппорту). И это противоречит вашему утверждению, что запись в лог является обработкой ошибки.

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

По поводу второго типа: в каких случаях мы не должны уведомлять клиента о «необработанных ошибках», хотя бы теми же кодами http-статусов?
Как уведомлять разработчика программы, о том что произошла ошибка? Разработчик другой системы, получив респанс с ошибкой, должен писать в суппорт вашей программы? Или все таки само приложение должно залогировать ошибку(в файл, базу, сентри или что-нибудь похожее), а разработчик, получив уведомление от какой-либо системы мониторинга, должен сесть и разбираться с ним?

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

Поясните, пожалуйста, что вы имеете в виду. Я не вижу в своем примере явного приведения типов — ни в самой реализации метода, ни в примерах его использования. Но признаю, что есть опечатка в коде — это, само собой, не filter, а map, и еще можно было бы заиспользовать super/extends для еще большой универсальности.

Но нет, в попытке обобщить (DRY! цикл писать два раза – зло!) мы создаём (есть же дженерики, значит надо всё дженерилизовать!) реализацию map(), придумав для этого новую концепцию Streams

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

что если вам нужно обработать конкретный json и передать дальше – вам не нужно писать «универсальную структуру»

К сожалению выше у меня не получилось выразиться более ясно — в моих комментариях речь не идет о том, что нужно постоянно писать новые структуры, нет, речь идет только о переиспользовании существующего кода и уменьшению количества бойлерплейта. С тем, что реализация новых структур данных в проекте — это очень редкий юзкейс я в полной мере согласен, но речь идет, еще раз, об использовании. Прочитать абстрактный набор данных, выбрать из него только лишь необходимое, обработать и передать дальше — вы ведь согласны что это чуть ли не самый частый кейс в программировании? И из-за частоты этого кейса вы будете очень часто повторять одни и те же конструкции, которые будут зашумливать самое важное — бизнес логику работы. Весь этот шум можно и нужно прятать за каким-либо апи, но го не позволяет это сделать полноценным образом. И речь идет вовсе не о принципе DRY, а об упрощении понимания написанного кода.

Представьте, что у вас отобрали проверку совместимости типов в методе append и вам приходится каждый раз самостоятельно делать тайп чек или оборачивать вызов этого метода во враппер. Но в append проверка есть — вот только лично мне недостаточно проверки только лишь в этом методе, мне ее не хватает в filter/map/reduce и во всех остальных местах. И мне не нравится то, что лишь разработчики языка выбирают список мест, в котором тайп чек будет, потому что я тоже хочу так делать. И снова повторюсь — я знаю как применять систему типов и интерфейсов в го, но как как бы ты не использовал интерфейсы — полноценно заменить дженерики ими не получится.

переиспользования дженериков по поводу и без повода – как раз из-за отстутствия чего Go и выигрывает в читабельности и понятности

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

А переиспользовать и доводить до абсурда можно практически любые конструкции практически в любом языке. Но эти самые конструкции так же дают возможность писать поддерживаемый, элегантный и читаемый код. Я понимаю чего опасаетесь вы и авторы языка, но сдвигание баланса в сторону минимализма так же и уменьшает сферу применения языка(речь не столько про сферы практического применения в целом, сколько про требования предъявляемые к языку и к коду конкретной командой в конкретном проекте) и количество человек, желающим с ним работать.
Но вы ведь понимаете, что это не равнозначные примеры? Вы сравниваете свой «хелпер-метод» с методом из stream-api, к которому предъявлялось несоизмеримо больше требований. Мне кажется, что если покопаться в исходниках го, то можно найти множество примеров на столько же на первый взгляд, как и приведенный кусок кода.

А аналогом вашему коду — т.е. без проверок на null и различных стратегий обработки упорядоченных\неупорядоченных списков — будет выглядеть так:

public <T,R> List<R> filter(List<T> in, Function<T,R> fn) {
  List<R> out = new ArrayList();
  for (T val: in) {
    out.add(fn.apply(val));
  }
  return out;
}


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

Для такого дженерики вообще не нужны ни разу. Вы сейчас хорошо показали, в чём проблема – начинается с «я хочу писать реюзабельные структуры данных», а заканчивается «я не могу массив из json обработать без дженериков». И это проблема. :D

Где-то было сказано про невозможность обработать этот кейс с помощью го? Или что конкретно я не могу это сделать? Мой аргумент начинался с того, что я не могу написать универсальную структуру — не важно список, дерево или что-то еще — которую смогу переиспользовать типобезопасным образом и без бойлерплейта. Проблема именно в этом, а не в чем-то другом, так что давайте обойдемся без пассивно-агрессивных нападок на мой уровень программирования, хорошо?
Напомню почему считаю, что это проблема — потому что ручное приведение типов не удобно и не безопасно, и оба этих критерия очень важны для меня.
Понимаешь, что там три вложенных цикла (разворачиваешь бойлерплейт у себя в голове, что есть дополнительной когнитивной нагрузкой). Или не понимаешь, конечно – и лепишь монстроидальные однострочные конструкции .map.filter.map.collect..., а потом удивляешься, почему всё так медленно работает.

В случае с filter вы осознаете работу цикла прочитав 6 символов, а не несколько строчек (объявление промежуточного слайса, итератор, проверка условия, добавление объекта в промежуточный слайс), т.е. когнитивная нагрузка будет ниже, еще и глазами можно меньше двигать. С последующими map/reduce/collect — то же самое. В результате, вы можете добиться того, что весь пайп обработки у вас помещается на один экран, и осознать что там происходит — дело нескольких секунд. И не забываем про те 15 секунд, которые требуются на написание каждого подобного цикла. А уж что выгоднее экономить — такты процессора, или рабочее время коллег каждый выбирает самостоятельно.

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

По поводу быстро или медленно — но это же просто итерации по коллекциям, при чем обычно вам заранее будет известен примерный объем данных, которые необходимо обработать — по крайней мере их порядок (из тз, спецификации сервиса или из вашего собственного практического опыта). Обычно вы заранее в состоянии прикинуть узкие места вашего алгоритма — при чем в 99% случаях узким горлом будут не итерации по коллекции, а какое-либо IO. Так зачем зачем заставлять заранее оптимизировать этот 1% случаев, если это можно сделать потом, и то при условии, что умный компилятор или рантайм не сделают этого за вас? Зачем заставлять экономить пару тактов процессора, если можно сэкономить несколько минут работы вашим коллегам — рабочее время которых ценится выше, чем эти самые такты?

Это же техническое решение было, а не политическое.
Я не осуждаю авторов го. Моя позиция — не оспаривание решения разработчиков го, а лишь «фичареквест» реализации которой я очень жду. Я не сомневаюсь в опыте этих людей и я также прекрасно осознаю сложности связанные с добавлением поддержки дженериков, и уж само собой я знаю как в go выживать без дженериков. Но почему-то в обсуждении вопроса «какую конкретно проблему могут решить дженерики» на мой вполне себе конкретный пример мне начали говорить, что проблема по большой части надуманная, случай редкий, и вообще я либо не умею программировать, либо просто еще не привык к го. А дженерики — это сложно, люди сразу же начнут говнокодить и писать непонятные штуки. В общем касти вручную, пиши боллерплейт и радуйся жизни (сразу же прошу прощения за ёрничанье).
Первый раз вижу подобную реакцию от комьюнити в обсуждении фича-реквеста. Если зайти к тому же джава-комьюнити и сказать что ты ждешь добавления элвис-оператора или интерполяции строк — тебе никто не скажет, что if'ов и String.format в 99% случаев хватает, скорее скажут что Oracle #$%!& и пора уходить на котлин\c#.
Я не против дженериков, я говорю, что все, кто утверждает, что го без дженериков не нужен и что они там необходимы

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

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

Раз уж мы так любим примеры, можете привести пример какой-нибудь реальной проблемы и реальных случаев при которых она доставляет неудобства?
И тут, мне кажется, мы снова приходим к вопросу о частоте использования обсуждаемых кейсов.

Как часто вам приходится использовать iota (ну не считаю я этот огрызок заменой enum'ов) или кодогенерацию? Лично на мой взгляд это на столько специфичные вещи, необходимость в которых возникает очень редко, однако они добавлены в язык. А дженерики — нет.

как часто вам приходится выбирать структуру данных, которой нет из коробки в Go — скажем, red-black tree? (интересует конкретное число – там, 3 раза в день, или 10 раз в месяц)

В случае с деревьями — да, очень редко. Буквально пару-тройку раз за всю практику. Но вот очереди, кеши, листы — существующие в проекте я использую каждый день, добавляю новые экземпляры в проект — ну тут прийдется взять цифру с потолка, потому что никогда не обращаю на это внимание. Скажем, условные пару раз в месяц. По поводу упомянутых хелпер-методов — использую во множестве раз каждый день, будь то библиотечные методы, или написанные самостоятельно. А так же есть Optional'ы и функциональные интерфейсы, которые так же очень способствуют написанию лаконичного и понятного кода. И без дженериков они не реализуемы.

Второй вопрос по поводу оформления map во враппер – это, конечно же делается – блин, это буквально циклы, их писать 15 секунд и вероятность ошибиться 0.0001%. Это пишется быстрее, чем комментарий о том, как сложно жить без дженериков:

Однако это бойлерплейт, и от его присутствия проекта хочется избавиться. По поводу вероятности ошибиться — на хабре есть блог компании pvs studio, в котором они выкладывают результаты анализов различных проектов, и практически в каждом репорте присутствует категория ошибок с вязанных с опечатками и copy-paste ошибками. В случае с go кто-нибудь может скопировать существующий код, и заменить привести к другому типу объектов. И я не говорю уже про многословность подобных решений с обертками, которая замыливает бизнеслогику.

Давайте, чтобы вам было проще понять фундаментальную разницу (точнее отсутствие оной), я переименую interface{} в Object:

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

И я вас прекрасно понимаю – если вам приходится map использовать сотни раз в день на все типы, то подход Go будет казаться многословным. Но я из головы могу придумать только один вариант, когда это будет реальностью – «лабораторные по информатике», на которых люди учат map/reduce/filter.

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

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

Я не могу считать бойлерплейт из навороченных и вложенных циклов читабельным. Описанный выше кейс в Java решается в 5-10 строк с помощью stream-api или той же гуавы\rxjava. И видя в коде цепочку filter().map().collect() ты сразу понимаешь какие именно действия совершаются в указанном куске кода.
Извините, но я не могу по-другому воспринимать его комментарий из другой ветки:
В случае с Go дженерики могли бы в некоторых случаях упростить жизнь, но это не серебряная пуля, и в большинстве практических задач они не нужны вообще. В рамках реального проекта интерфейсов (нормальных, с описанием методов) достаточно, чтобы спокойно жить и не беспокоиться о том, что в каком-то другом языке дженерики есть а тут нет.

Просто чтобы уточнить — я не хейтер го. Я писал на нем экспортеры для прометеуса и небольшие сервисы для проксирования\балансировки запросов и я не испытывал особого отвращения от языка в этих кейсах.
Но перекладывая весь свой опыт в Java/Groovy на Golang, я понимаю, что он неприменим(точнее слишком уж неудобен) в большой части случаев из моей практики. И по большей части причина этого как раз таки в отсутствии дженериков, т.к. мне постоянно приходиться работать с коллекциями в том или ином их виде (как и ооочень большой пласт программистов, я думаю) и я не хочу постоянно писать циклы для фильтрации\модификации списков. А golang не позволяет мне вынести весь этот бойлерплейт в хелпер-методы, сохранив при этом безопасность работы с типами. Так же как не позволяет создать удобные унифицированные структуры данных.
Отсутствие дженериков — т.е. отсутствие возможности реюзать чужой код(в общем случае) — это проблема, как для меня так и для множества других разработчиков. И система типов\интерфейсов го никак не позволяет решить эту проблему. Это все к вопросу про то, какие бы проблемы решили дженерики если бы их добавили в язык.
Но в реальности это происходит не настолько часто.

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

В первом случае даже в языке, который предоставляет дженерики, придётся писать для каждого объекта, который попадает в шину, свои методы/свойства и в Go это можно решить требованием объекта предоставлять некоторый интерфейс.

В случае с Go вы будете пушить\пулить в\из шины какой-то условный абстрактный Loggable, а вам нужна шина, которая работает с конкретной имплементацией этого Loggable — условным Request. Т.е. опять приходится либо оборачивать шину в обертку, либо снова каждый раз приводить типы вручную.

Да, опять же, без этого можно жить и с этим можно смириться. Но считать, что без дженериков удобней чем с ними — извините, но я не понимаю этого. Тем более почему-то в go можно объявить с каким типом объектов работают каналы\массивы\слайсы — так почему бы не дать разработчикам возможность делать то же самое, но и с другими структурами?
Вы выкидываете sync.Map, оборачиваете обычный map[UUID]UserStruct в структуру с RWMutex, добавляете в эту структуру поле со счётчиком и вместо методов Delete/Load с интерфейсами в качестве параметра и возвращаемого значения делаете свои обёртки, которые принимают UUID как ключ и возвращают указатели на структуру пользователя.

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

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

Pipe-фреймворки и пулы объектов — при наличии метрик и логгирования уже простыми каналами\слайсами не обойтись. И либо в каждом проекте пишутся заново, либо используется готовая реализация с interface{}, использовать который — дурной тон.

И совсем уже реальный пример из моей практики: пара деревьев, одно хранит содержимое корзины (речь идет про ретейл), другое дерево — сложные правила расчета маржей. Правила обхода деревьев и их балансировки отличаются, как и типы хранимых данных. При чем второе дерево расшарено между потоками, т.е. должно быть синхронизированным. Если реализовывать без interface{}, то получатся две +- одинаковых структуры и две группы методов работы с ними.
И вот, вы сидите со многостраничной спецификации по модулю, но вместо реализаций требований заказчика реализуете структуры данных\методы (обязательно эффективные реализации, а в случае с обходом и балансировкой деревьев это может быть достаточно сложный алгоритм), хотя могли бы воспользоваться проверенной библиотекой от какого-либо вендора.

Да, можно использовать библиотечные реализации структур данных с interface{}, и спрятать из за оберткой с конкретными интерфейсами, но это workaround, а не решение.
Почему вас не устраивает пример с sync.Map? Из-за отсутствия дженериков приходится постоянно приводить значение к нужному типу, а это и менее удобно, и менее безопасно. Тоже самое и с любыми другими универсальными структурами данных и методов работы с ними.

Без дженериков, конечно, можно жить. Но с ними гораздо удобней, т.к. компилятор берет на себя проверку совместимости типов.
Не нашел возможности разослать приглашения друзьям, реферальную ссылку так же не обнаружил. Можете подсказать где их найти?
Можете сказать рассматривали ли rsync в качестве альтернативы? И если рассматривали, то почему отказались в пользу btsync
Говоря, что образование в университетах в упадке почему вы не вспомнили ICPC и то, что наши вузы за последние 16 лет 10 раз были победителями соревнования? Получается все таки есть хорошие вузы в стране? Тогда почему бы вам не не смягчить ваши формулировки?

«университеты преподают то, что уже не используется в мейнстриме» — нужно открывать кафедру Erlang'а в вузах? Изучать языки\технологии и расширять кругозор — это по-большей части задача студента.

Information

Rating
Does not participate
Registered
Activity