LINUX.ORG.RU

Каскады iferr: как живете с ними?

 ,


2

3

Не секрет, что практически все функции в Golang возвращают ошибки. И тут два пути: или игнорировать или обрабатывать в iferr. Игнорировать почти никогда не получается, так что выбор невелик и код превращается в

val, err := SomeFunc()
if err != nil {
..do some..
}
. Да, это правильно, но это ж на каждый чих проверка получается. Нельзя ли сделать для этого условный defer или как-то иначе скрыть явную обработку ошибок? Может, кодогенерация поможет или еще что? Как вы пишете максимально чистый и компактный код?

Ответ на: комментарий от alysnix

вы знаете хоть, что делается при входе в блок try и выходе по исключению в один из блоков catch? какой там код будет дополнительный?

LOL. Сейчас лоровские школьники меня научат плюсам)) Ну давай, рассказывай. Тему кейворда noexcept раскрыть не забудь. XD

PRN
()
Ответ на: комментарий от PRN

как оформляется код операторов try… catch, уважаемый. и сравните это с обработкой ошибок на месте, с накладными расходами равными нулю.

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

alysnix ★★★
()
Ответ на: комментарий от alysnix

обработкой ошибок на месте, с накладными расходами равными нулю.

Если что этот код имеет не «нулевые расходы»

    if err := datastore.Get(c, key, record); err != nil {
        return &appError{err, "Record not found", 404}
    }

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

datastore.Get(c, key, record)

может работать быстрее, пока исключение не кинет, но и тут есть нюансы))

то есть генерить исключение в функции и немедля его обрабатывать в вызывающей функции - это ахтунг.

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

PRN
()
Последнее исправление: PRN (всего исправлений: 1)
Ответ на: комментарий от PRN

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

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

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

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

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

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

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

а чем iferr решает проблему разных типов ошибок? Правильно, ничем.

В golang мы стираем тип у err и вообще не особо паримся из-за аллокаций в куче - ну так и в C++ никто не запрещает наследовать исключения от std::exception и потом ловить этот базовый класс.

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

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

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

Lrrr ★★★★★
()
Последнее исправление: Lrrr (всего исправлений: 1)
Ответ на: комментарий от alysnix

то есть в оформлении оператора try-catch происходят такие вещи - код толкания текущего хадлера обработки ошибки в стек или список(зависит от реализации)

Ого как сложно в список «указатель» то запихнуть. Ну все перехожу на счеты, там и просто и наглядно XDD

PRN
()

Даже тип Result полноценный не сделать, потому что методы не могут быть generic, и метод

func (r Result[A]) Then[B](f func(A) Result[B]) Result[B]

не написать. Только свободную функцию Compose, но тогда получим вложенность вида

Compose(f, Compose(g, Compose(h, ...)))
// You can edit this code!
// Click here and start typing.
package main

import "fmt"

func Compose[A, B, C any](f func(A) (B, error), g func(B) (C, error)) func(A) (C, error) {
	return func(a A) (C, error) {
		var b, err = f(a)
		if err != nil {
			var c C
			return c, err
		}
		return g(b)
	}
}

type Void struct{}

func Nothing() (Void, error) {
	return Void{}, nil
}

//  ----------------------------------------------------------------

type Request struct {
	ID string
}

type Data struct {
	ID    string
	Value string
}

type Response struct {
	Value string
}

type Controller struct {
	// db connection
	// ... other dependencies
}

func (c *Controller) fetch(req Request) (*Data, error) {
	if req.ID == "foo" {
		return &Data{
			ID:    "foo",
			Value: "bar",
		}, nil
	}
	if req.ID == "gee" {
		return &Data{
			ID:    "gee",
			Value: "qux",
		}, nil
	}
	return nil, fmt.Errorf("not found")
}

func (c *Controller) render(data *Data) (*Response, error) {
	if data.Value == "bar" {
		return &Response{
			Value: data.ID + ":" + data.Value,
		}, nil
	}
	return nil, fmt.Errorf("access denied")
}

func (c *Controller) reply(resp *Response) (Void, error) {
	fmt.Println("REPLY:", resp.Value)
	return Nothing()
}

func (c *Controller) Handle(id string) {
	var flow = Compose(c.fetch, Compose(c.render, c.reply))

	var req = Request{
		ID: id,
	}

	var _, err = flow(req)

	if err != nil {
		fmt.Println("ERROR:", err.Error())
	}
}

//  ----------------------------------------------------------------

func main() {
	var c = &Controller{}
	c.Handle("foo")
	c.Handle("bar")
	c.Handle("gee")
}

-- https://go.dev/play/p/TVJCeOjXGKx

По-моему, более «Go-way» будет что-то типа такого: https://go.dev/play/p/vzVyxKYeUCa

// You can edit this code!
// Click here and start typing.
package main

import "fmt"

func Do[Env any](env Env, steps ...func(Env) error) error {
	for _, step := range steps {
		if err := step(env); err != nil {
			return err
		}
	}
	return nil
}

//  ----------------------------------------------------------------

type Request struct {
	ID string
}

type Data struct {
	ID    string
	Value string
}

type Response struct {
	Value string
}

type Flow struct {
	Request

	data *Data
	resp *Response
}

type Env struct {
	// db connection
	// ... other dependencies
}

func (f *Flow) fetch(_ *Env) error {
	if f.Request.ID == "foo" {
		f.data = &Data{
			ID:    "foo",
			Value: "bar",
		}
		return nil
	}
	if f.Request.ID == "gee" {
		f.data = &Data{
			ID:    "gee",
			Value: "qux",
		}
		return nil
	}
	return fmt.Errorf("not found")
}

func (f *Flow) render(_ *Env) error {
	if f.data.Value == "bar" {
		f.resp = &Response{
			Value: f.data.ID + ":" + f.data.Value,
		}
		return nil
	}
	return fmt.Errorf("access denied")
}

func (f *Flow) reply(_ *Env) error {
	fmt.Println("REPLY:", f.resp.Value)
	return nil
}

func (f *Flow) Handle(env *Env) {
	var err = Do(env, f.fetch, f.render, f.reply)
	if err != nil {
		fmt.Println("ERROR:", err.Error())
	}
}

//  ----------------------------------------------------------------

func flow(id string) *Flow {
	return &Flow{
		Request: Request{
			ID: id,
		},
	}
}

func main() {
	var env = &Env{}
	flow("foo").Handle(env)
	flow("bar").Handle(env)
	flow("gee").Handle(env)
}
korvin_ ★★★★★
()
Ответ на: комментарий от PRN

В гугдле даже в плюсах исключения не используют

Еще ни один человек, который на этот документ ссылается, не осилил прочитать дальше первых 3 строчек.

А я их приведу.

https://google.github.io/styleguide/cppguide.html#Exceptions

On their face, the benefits of using exceptions outweigh the costs, especially in new projects. However, for existing code, the introduction of exceptions has implications on all dependent code. If exceptions can be propagated beyond a new project, it also becomes problematic to integrate the new project into existing exception-free code. Because most existing C++ code at Google is not prepared to deal with exceptions, it is comparatively difficult to adopt new code that generates exceptions.

Because we’d like to use our open-source projects at Google and it’s difficult to do so if those projects use exceptions, we need to advise against exceptions in Google open-source projects as well. Things would probably be different if we had to do it all over again from scratch.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

Things would probably be different if we had to do it all over again from scratch.

Где-то в параллельной вселенной есть гугл, который использует исключения и язык го с исключениями))

PRN
()
Ответ на: комментарий от anonymous

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

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

ya-betmen ★★★★★
()
Ответ на: комментарий от alysnix

Things would probably be different if we had to do it all over again from scratch.

«would probably» на русский переводится примерно как - «когда рак на горе свистнет» или «мы ничего не обещаем, но …»

Если кому-то нужна иллюстрация тезиса «некоторые люди читают жопой», то вот она.

eao197 ★★★★★
()
wg := sync.WaitGroup{}
errCh := make(chan error, 10) 
for i := 0; i < 10; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
                if err = fu(i); err != nil {
                   errCh <- err
                }
	}()
}
var err error
wg.Wait()
close(errCh)

for err1 := range errCh {
	err = errors.Join(err, err1)
}
fmt.Println(err)

Совсем недавно использовал такую конструкцию, как это сделать с try…catch или еще как то, не представляю.

alnkapa
()
Последнее исправление: alnkapa (всего исправлений: 2)

Вообще не понимаю этих претензий.

  1. В C нет исключений, и надо проверять код возврата на каждый чих, и никого это не напрягает, более того все, ядрённые писатели (которые ядро пишут) на него дрочат, и говорят, что так и надо.
  2. То, что многие в C забивают на обработку ошибок - ну так и в go забивай - такие-же проблемы получишь.
  3. В go есть паники - это очень похоже на исключения, но нам как бы намекают - ошибка - это не есть исключительная ситуация, после исключительной ситуации лучше завершить работу. Это делают функции типа Must…(), которые принято вызывать на инициализации, и падать, если что-то пошло не так.
  4. Если очень пригорело - реализуй обработку паник - будет тебе щастье, ведь Роб Пайк не будет делать тебе ревью. Твой проект - твои правила.

ПС. Я пилю пет проект на go. Привык. Но смотрю в сторону Rust понемногу.

her_s_gory
()
Последнее исправление: her_s_gory (всего исправлений: 1)
Ответ на: комментарий от alnkapa

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

func _log_err(err error) {
	if err != nil {
		log.Println(err)
	}
}

func main() {
	err, res := errors.New("ошибка!"), 42
	_log_err(err)
	log.Println(res)
}

А если предполагается более тонкое поведение, то куда его запихивать?

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Странное решение на мой взгляд. У Вас нет желания писать if или код должен быть прямой как стол?


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

Задача собственно такая, вам надо параллельно выполнить несколько несвязанных задач и вернуть результат куда-то наверх. Там принимается решение об общем успехе или неудаче. При неудаче сказать об этом клиенту стандартную ошибку + подробное описание. Я хотел показать что if err это как ни странно не какой-то скучный кусок кода, а вполне себе бизнес логика.

alnkapa
()