Pull to refresh

Comments 34

Увы, у меня нет подходящей картинки, чтобы подобрать иллюстрацию, что я чувствую, когда смотрю на код
func upgradeUser(endpoint, username string) error {
    getEndpoint := fmt.Sprintf("%s/oldusers/%s", endpoint, username)
    postEndpoint := fmt.Sprintf("%s/newusers/%s", endpoint, username)

    _, err := compose(
        http.Get,
        func(req *http.Response) ([]byte, error) {
            return ioutil.ReadAll(req.Body)
        },
        newUserFromJson,
        newUserFromUser,
        json.Marshal,
        func(buf []byte) (*http.Response, error) {
            return http.Post(
                postEndpoint,
                "application/json",
                bytes.NewBuffer(buf),
            )
        },
    )(getEndpoint)
    return err
}


Если описать словами, то это не «а, ну тут все понятно», а что-то ближе к «wtf здесь происходит вообще?»

Когда же я вижу первоначальный код (до конвертации в monad-style), то там все как-то сразу понятно. И не надо думать, как бы извернуться (в случае monad-style), если надо сделать что-то чуток отходящее от шаблона, типа дополнительную проверку вставить — просто вставляешь её куда надо и готово.

На всякий случай напоминаю, что при дизайне Go немаловажную роль играла простота и читаемость кода )
Я считаю, что лучше иметь на экране десяток проверок err != nil, чем сложнозавернутую конструкцию, которую и сам с трудом разберешь через пару месяцев. Не в PerlGolf же играем.
картинкой не нашёл, но есть текстом:
Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живёте

Стиль монад конечно подкупает if err != nil, но в одном из проектов я его попробовал (правда не знал, что это называется монадой)) ). Выглядит красивше, но фактическая последовательность выполнения у вас реализуется в рантайме, а не в коде, как следствие дебажить подобный код — занятие не из приятных, особенно в случае, если вызываемых функций штук 30+.
Посему к предложенному способу рекомендую относиться с большой осторожностью.

UFO just landed and posted this here
UFO just landed and posted this here
Ммм а есть ещё такой вариант:

err, a = getA()
if err = nil {
err, b = getB(a)
if err = nil {
return getC(b)
}
}
return err, nil

Не попроще будет?
Я бы не сказал, что с монадами стало проще. А раз полезного выхлопа нет, то зачем они такие нужны? Разве что для кодинга ради кодинга.

Monadic-style computation без синтаксической поддержки со стороны языка — это всегда печально и неудобно. В Haskell есть do-нотация. В JavaScript, к примеру, когда появились Promises (это по сути монады для асинхронных вычислений), все очень страдали, пока не появился async/await синтаксис для них.


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

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

panic + defer + recover. Во многих ситуациях этот механизм гибче и удобнее, чем классический throw + catch

> Во многих ситуациях этот механизм гибче и удобнее, чем классический throw + catch

Да ладно?
defer реально удобне finally, но с ним реальный код финализации определяется только на момент исполнения.
panic c throw — те же яйца вид в профиль.
А вот recover хуже catch: вся обработка конкретных ошибок и проброс выше только вручную.
В целом механизмы полностью эквивалентные с точностью до синтаксического сахара.

Но что есть, то есть: механизм исключений в Go имеется и вполне рабочий, просто авторы языка исключения сильно не любят.
А вот recover хуже catch: вся обработка конкретных ошибок и проброс выше только вручную.

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


авторы языка исключения сильно не любят.

согласен

Чего только люди не сделают, лишь бы не добавлять в язык null-safe цепочки в духе a1?.getA2()?.getA3()

Вот интересно, я двигался в обратном направлении. Писал диплом на функциональном языке (ГАРФ) в 1984-м году. Затем изучал лямбда-исчисление, категории и монады как математик. А последнее время стал немного программировать и из всех языков мне решительно нравится Lua!
Если вас сильно раздражает проверка на if err!=nil, то в Gogland уже давно впилен мой патч, скрывающий их нафиг. image

Костыльно-ориентированное программирование.

это очень смешно :-)
это как убраться запихав все вещи в шкаф. Типа шкаф открываешь и всё ооттуда вываливается.
Я лично не понимаю почему эти кнструкции так напрягают людей, код как код.
Потому что на 3 строчки полезного кода 12 строк iferr!=nil{returnerr}\n мантры. Это бессмысленная трата времени разработчика на печатание и затем на чтение всего этого бойлерплейта. Если их схлопывать, то хоть как-то это можно читать.
исползуйте панику. будет всего оин обработчик все хошибок и никаких проверок.
Почему же тогда никто их не использует, наоборот, все радостно пишут эту мантру, и ругают за паники? =)
у каждого инстурмента есть своё назначение, но в любом случае решение принимать вам. Хотите избежать цеопчек проверок на ошибку — есть иструмент для этого. Используйте его, учитывая его недостатки. Напимер не очень круто будет в бибиотеке паниковать по каждой мелочи.
Все правильно говорите. Все, что я хотел, это выразить мнение, что язык вынуждает разработчиков страдать, а предоставляемый им альтернативный вариант, по большей части, не применим. Соответственно остается только хотябы на уровне IDE схлопывать бойлерплейт. Не понимаю, что Вам тут было смешно. Это грустно.

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

func connect(addr string) (*connection, error) {
	con, err := db.Connect(addr)
	if err != nil {
		return nil, err
	}

	err = prepare(con)
	if err != nil {
		con.Close()
		return nil, err
	}

	return con, nil
}


Ошибка в нем следующая: если prepare() вывалится с паникой, коннект утечет (кто сказал, что fd закрывается в файналайзере?). Городить ли defer с recover? Наверное не городить. Месяца через три петух на продакшене хорошо прожарится и наточит клюв.

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

func connect(addr string) *connection {
	con := db.Connect(addr)
	onError con.Close()

	prepare(con)

	return con
}


Не положил в переменную — эквивалентно старой доброй мантре. У всех функций неявное последнее возвращаемое значение — ошибка. onError == defer с проверкой на err != nil. Остается единственный вопрос со стектрейсом, но собственно и все.

Но нет. Мы будем страдать. Спасибо google.
Вот вы так много времени тратите чтоб убедить меня в удобности вашего решения. Мне кажется круто было бы реализовать его и сделаьт PR. Это же опенсорс проект, так это работает.
Вы ставите общительность в претензию каждому своему собеседнику?

К сожалению, в go это не работает. И не очень ясно хорошо это, или нет. PR'ы и просто предложения разворачиваются на месте, потому что они противоречат религиозным убеждениям core team. Как противоположность — swift. Я не люблю swift, но там, в отличии от go, имеет смысл вкладывать силы и время. Их changelog каждый год впечатляет, и все это делает комюнити. Это круто.
И еще как временное решение использовать препроцессор.
Спасибо авторам языка — они настолько суровые практики, что проигнорировали все достижения теории примерно с середины 80-х годов.
А я просто завернул в
func e(error error) bool {
	if error != nil {
		return true
	}
	return false
}
//...
pool, error = pgx.NewConnPool(connPoolConfig)
if e(error) {
	log.Crit("Unable to create connection pool", "error", error)
	for e(error) {
		log.Debug("Try to create connection pool", "warning", error)
		pool, error = pgx.NewConnPool(connPoolConfig)
	}
}
И в моём мирке всё спокойно (очень уж вымораживает писать «error != nil»). Есть ещё парочка похожих обёрток, более специализированных (для логирования и вывода спец json-ответа об ошибке). Мне норм.

error != nil вымораживает писать, а if… вместо return error != nil нет. Странно..

Ну, как бы, предполагается, что ошибка ошибке рознь и не нужно их обобщать, вот и все… Если же вам плевать на обработку ошибок, кидайте panic, пользователи вообще любят стектрейсы и синие экраны смерти…
На обработку ошибок «плевать» в более чем 90% случаев. Случаи обработки конкретных ошибок крайне редки. Как часто вы при попытке вставить что либо в бд проверяете на конкретную ошибку о существовании записи и делаете в таком случае что-то другое? Обычно вы просто чистите за собой, куданибудь вываливаете это в лог и все.

Выпад про синие экраны смерти тут некорректен. Язык действительно тебя заставляет либо как идиоту писать после каждой 1 строчки мантру из 4 строк, либо использовать паники. Но в последнем случае тебя Go-сообщество справедливо закидает заранее подготовленным и свеже-подогретым говном. Вот сидим, пишем эту хреноту, да себя и всех вокруг убеждаем, что это хорошо.
Стандартная библиотека Go создана для написания достаточно низкоуровневых программ, работающих с диском, сетью и т.д. Если ваша программа более высокоуровневая, то, очевидно, вам нужен более высокоуровневый DSL. Значит нужен слой, который скроет работу, например, с сетью. Этот слой будет выдавать только те ошибки, которые участвуют в бизнес-логике, т.е. должны быть ею обработаны. В свою очередь, ему важны ошибки работы с сетью, как минимум, даже если речь исключительно о логгировании — это переписывание ошибок в зависимости от контекста в терминах вашего фреймворка, или, например, возможность переподключения при разрыве соединения, или повторный запрос при конкретной ошибке и т.д.
Все таки, лучше не писать хреноту, а понять почему так сделано и почему язык популярен.
Еще один тезис — синтаксис нужно оценивать, исходя из качества написанных библиотек, а не скорости написания кода. Качество библиотек на Go достаточно высоко, значит синтаксис достаточно хорош, осталось понять почему, а ответ на этот вопрос дан уже много раз, осталось поверить, что это не бредни полоумных работорговцев из гугла.
Еще один тезис — в конце концов синтаксис вообще мало что значит, решают возможности языка, компилятор и инструменты.
Для меня единственный спорный вопрос в Go — дженерики, но этот вопрос не решен нигде. Некоторые языки предоставляют возможности мета-программирования, потом декораторы, потом контракты и где-то на пятом уровне абстракции у нас возникает желание заняться машинным обучением, чтобы с этим кодом на Java/C# разбирался наш виртуальный друг.
func upgradeUser(endpoint, username string) error {
    getEndpoint := fmt.Sprintf("%s/oldusers/%s", endpoint, username)
    postEndpoint := fmt.Sprintf("%s/newusers/%s", endpoint, username)

    req, err := http.Get(genEndpoint)
    if err != nil {
        return err
    }
    data, err := ioutil.ReadAll(req.Body)
    if err != nil {
        return err
    }
    olduser, err := user.NewFromJson(data)
    if err != nil {
        return err
    }
    newuser, err := user.NewUserFromUser(olduser),
    if err != nil {
        return err
    }
    buf, err := json.Marshal(newuser)
    if err != nil {
        return err
    }
    _, err = http.Post(
        postEndpoint, 
        "application/json", 
        bytes.NewBuffer(buf),
    )
    return err
}

Функция upgradeUser реализует какой-то сценарий бизнес логики, соответственно она должна абстрагировать от деталей реализации. Но почему-то функция не абстрагирует от низкоуровневых ошибок, которые возвращают используемые в ней функций. Например: мы хотим проапгрейдить юзера, а в результате получаем ошибку ввода-вывода сети или http.ProtocolError или url.Error. Как я должен различать и обрабатывать их? Их даже заллогировать корректно нельзя, т.к. непонятен контекст. Часть ошибок может быть по моей вине, если я указал некорректный endpoint, с остальными ошибками, я скорее всего ничего сделать не могу. Также не забываем, что код, который будет использовать функцию upgradeUser, по хорошему, ничего не должен знать о деталях реализации. И что делать, если в процессе поддержки приложения мы отказались от http и перешли на бинарный протокол? Переписывать все места, где используется upgradeUser чтобы корректно обработать ошибки?
Делаем выводы:
1) Реализация этой функции далека от совершенства, т.к. в ней текут абстракции — не пишите так;
2) Если вы собираетесь вернуть полученную ошибку «наверх» без изменения (заворачивания в другую) трижды подумайте;
3) Следует четко понимать и определять какие ошибки может возвращать ваша функция, детали реализации функций скрывайте, в части обработки ошибок: заворачивайте их в более высокоуровневые либо логгируйте и возвращайте другую;
4) При правильной работе с ошибками разницы нет какой механизм вы используете, если писать корректный код на js с теми же async/await то рука будет болеть к вечеру от написания тонны try {} catch(), особенно если учитывать что там смешан поток обработки ожидаемый ошибочных и неожиданных исключительных ситуаций, и всегда надо будет прокидывать ошибку наверх вручную, если мы не знаем как ее обрабатывать.
ну вот знаю я хаскель. но мне реально не по себе от вашего кода. я использую Го только для решения практических задач и не более.
Вроде Го 2.0 готовят, никто не в курсе, собираются с обработкой ошибок что-то делать или всё?
Sign up to leave a comment.