Skip to content

Instantly share code, notes, and snippets.

@freman
Last active September 18, 2018 12:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save freman/0b372e46c72f6a27652538b9930ee851 to your computer and use it in GitHub Desktop.
Save freman/0b372e46c72f6a27652538b9930ee851 to your computer and use it in GitHub Desktop.
Error handling as it can already be done

I'd like to argue that to a great degree, we can already do error handling the way the proposal demonstrates it in the 'corrected' new example through the tools we already have without introducing new keywords or new magic.

The following achieves the same error handling in almost exactly the same way without being much longer or uglier than the reference material.

func CopyFile(src, dst string) (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("copy %s %s: %v", src, dst, err)
		}
	}()

	r, err := os.Open(src)
	if err != nil {
		return err
	}
	defer r.Close()

	w, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			w.Close()
			os.Remove(dst) // (only if a check fails)
		}
	}()

	if _, err := io.Copy(w, r); err != nil {
		return err
	}
	return w.Close()
}
@networkimprov
Copy link

A major goal of the proposal is to avoid if err != nil { ... } after every function call.

@JavierZunzunegui
Copy link

JavierZunzunegui commented Aug 29, 2018

The proposal implies there is some gain in using handle over defer besides avoiding the repetitive if err != nil { ... }.
If removing the repetitive if err != nil { ... } syntax is it's only purpose it should state it clearly and remove the misleading examples.

Copy link

ghost commented Aug 30, 2018

defer actually calls a function, and if you are using it in a loop, it will impact the performance hard. The draft handle works lexically, so it does not use a stack.

Beyond eliminating if err!= nil { ... }, check actually allows chaining - now you can write json.Unmarshal(check ioutil.ReadFile(filename),&something).

@freman
Copy link
Author

freman commented Sep 4, 2018

Why would you be using it in a loop?
And chaining isn't so hard

func SomethingAwful() (err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("something awful happened: %v", src, dst, err)
		}
	}()

	checkReadfile := func(b []byte, e err) b []byte {
		if e != nil {
			err = e
			return nil
		}
		return b
	}

	// Break out of bad places :D
	for err == nil {
		var something Something
		jsonErr := json.Unmarshal(checkReadfile(ioutil.ReadFile(filename)),&something
		if  jsonErr != nil || err != nil {
			// do something?
	        }
	}
}

Not saying it's pretty, but I've not seen any real suggestions that would not be solvable with go1

@gomarcus
Copy link

gomarcus commented Sep 17, 2018

I would hereby like to follow @freman's line of reasoning and would like to express my dislike of the new error handler proposals. Personally, I still consider the central principles of GO to be extremely precious and fear that the demands for syntactic sugar only lead to a dilution of the previous clarity and purity. The manual and explicit error checking/handling is in my opinion one of GO's core strengths.

Currently, type error is an interface like any other. A special syntax for handling errors would be a fundamental change.

Interesting observation
Many who start learning GO complain about the repetitive explicit error checking, but most get used to it and soon appreciate it in the vast majority of cases.

cases:

  1. Should the repeated use of if err != nil {…} be a visually disturbance for some users… a different color scheme in the editor could easily solved this problem for them…

  2. Should the introduction of error handler be focused on enrichment of error information… It may be better to think about why the received error messages are incomplete. It might be beneficial to improve the code of the error returning function, instead of creating new syntax to iron out the initial fault.

  3. @leafbebop argued that error handler may improve chaining. And even though this may be true in some cases, I would like to question the premise here. In my opinion, chaining does not necessarily result in less writing effort, more comprehensible structures nor easy to maintain program code.
    In addition, chaining is only possible if all functions involved have a exact argument order. This would result in chaining being used in some cases and the conventional way in others, creating two parallel paradigms.

  4. The proposal of @gregwebs suggests that one of the goals is reducing the program code and/or the writing effort for the programmer, as well as a new syntactic expression for reformatting a received error.
    It is particularly difficult for me to follow this reasoning, because it is already optional to use a formatting function and also the amount / readability of the program code is not improved.
    The proposal introduces a new function type type errorHandler func(error)error. However the proposal seems to disregard that such a formatting function has only a limited selection of information, which is a bad prerequisite as a formatting function (only e). Unless such a function would be specially incorporated in the calling function and get access to all variables (with all disadvantages).
    The use of errorHandler's not only changes the language appearance, but also the reading flow. This effect is reinforced by errorHandler's defined outside of the calling function body.

Current

func A() error {
   x, err := f()
   if err != nil {
      return fmt.Errorf("…%v…", err.Error())
   }
}

proposal of @gregwebs

func B() error {
   errorHandler := func(e error) error {
      return fmt.Errorf("…%v…", e.Error())
   }
   x := f() ? errorHandler
}

The confusion becomes more evident when different formatting functions are called.
Current

func A() error {
   x, err := f1()
   if err != nil {
      return fmt.Errorf("…%v…", err.Error())
   }
  y, err := f2(x)
   if err != nil {
      return fmt.Errorf("…%v…", err.Error())
   }
}

proposal of @gregwebs

func C() error {
   errorHandler1 := func(e error) error {
      return fmt.Errorf("…%v…", e.Error())
   }
   errorHandler2 := func(e error) error {
      return fmt.Errorf("…%v…", e.Error())
   }
   x := f1() ? errorHandler1
   y := f2(x) ? errorHandler2
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment