lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


It was thus said that the Great Rena once stated:
> On Sat, Sep 28, 2013 at 5:37 PM, Sean Conner <sean@conman.org> wrote:
> 
> >     2. it  can be fixed, like EACCESS (bad privileges) or ELOOP (too many
> >        symbolic links when trying to resolve a filename) but that the fix
> >        has to happen outside the scope of the program, but once fixed,
> > tends
> >        not happen again unless someone made a mistake;
> > [...]
> >     4. and finally, the small category of errors that a program might be
> >        able to handle, like ENOENT (file doesn't exist) depending upon the
> >        context (it could then create the file, or ask the user for a
> >        different file, etc.).
> 
> 
> I feel like these are both the same category, really; in both cases the
> appropriate response is to somehow tell the user that there's a problem,
> and let them fix it. How you'll tell them depends on the program (show a
> dialog, write to stderr, write to a private log file and return an error
> page), and what they'll do to fix it is largely up to them (try another
> file, fix the broken symlinks, create the file, which might be done by a
> button on the error dialog), but in both cases the program handles it the
> same way: report the problem and abort the process (which might mean
> exiting the program, or just going back to the main loop waiting for
> another menu event, etc).

  I should perhaps clarify what I mean by category 4 errors.  These are
errors that are expected and can be handled by the program without having to
exit the program.  Take make---there's a function that takes a target and a
list of dependencies.  First check the dependencies, if any don't exist
(ENOENT), it's not an unrecoverable error (at least, not yet) because
there's an action the program can take---build the dependency.  And if a
target doesn't exist (again, ENOENT) it's not really an error (I mean, the
operating system reports it as an error because you tried to access a
non-existant file) but it's really not to the larger picture.  Make then
just makes the target (instead of maybe not making the target because it
hasn't changed).

  Same for errors like ETIMEDOUT.  In the cases I use it, it's a category 4,
but I can see where it might be a category 2 type error---it depends upon
the context.  

> There are definitely some types of errors that represent a bug in the
> program or a problem with the system, that can't really be handled - maybe
> this is a good place to consider the distinction between using error() and
> returning nil and an error code - but even then, it's nice to report the
> problem to the user if you can, instead of just dying with no explanation.
> Lua's default handler does a good job of this if your program is normally
> used in a terminal, but printing to stderr isn't useful for GUI programs
> launched from a menu or desktop icon, where that text is just going to go
> nowhere, and the user is just going to be frustrated and confused why the
> program just quit suddenly. That's why some programs (and even Windows
> itself) have a crash report dialog, to tell the user there's something
> wrong with the program, so at least they know *something*.
> 
> This is one of those things that I liked about Java, though wasn't fond of
> the implementation (so much boilerplate and red tape!) - when anything goes
> wrong, you throw an exception; code further up the call stack can catch
> exceptions it knows how to deal with while letting the rest continue to
> bubble up. So you can catch "no such file" and "permission denied"
> exceptions (IO exceptions) right when you try to open a file, but let "out
> of memory" and "invalid argument to open" exceptions bubble up to an outer
> layer (perhaps the main loop) that can handle them more appropriately, by
> just informing the user (using whatever method is appropriate) that there's
> a problem with the program/system itself and shutting down.

  The problem I have with exceptions is that it's another type of spagetti
code, only instead of an explicit GOTO, it's an implicit COMEFROM where the
COMEFROMs can be hard to find (that is, until the crashes, then you get a 10
page stack dump to trawl through).  

  I also have a problem with exceptions since they're overused for common
errors.  Exceptions are, well, exceptional error conditions (out of memory,
division by 0, illegal instruction, hitting memory that doesn't exist). 
They shouldn't be used for every little problematic situation.

  That said, the only bit of code I have that does use an exception-like
mechanism is in my MC6809 emulator [1], specifically, in the instruction
handling routine (mc6809_step() [2]).  Since I use callbacks to handle
memory reads and writes, allowing the callback to "throw an exception"
[3][4] and bail out of the instruction handling routine (which ends up just
returning a error code to the calling routine).  The use of an "exception"
in this codebase meant I could save having to write very convoluted code
around each call to read or write memory.  This is really the only time in
some twenty-plus years of coding that I felt using an "exception" like
mechanism was really called for.  The scope is very limited and does provide
a clarity in code and is overall, a performance win.  And it allows for
rather nice error recovery [5].

> To use this pattern in Lua, you have to use ugly syntax wrapping things in
> local ok, ex = pcall(function() ... end); if not ok then ... end; and you
> have to re-throw any error you can't handle right there, which adds another
> entire stack trace on, or discards it entirely if you aren't in an xpcall
> invocation.

  I'm still trying to figure out what's wrong with the current error
handling method.  Okay, so error() dumps stuff out to the TTY---write a new
error() function that writes to syslog or a dialog box or something, or am I
missing something?

  -spc (I never did get into Java or C++, which may explain my viewpoint)

[1]	https://github.com/spc476/mc6809

[2]	https://github.com/spc476/mc6809/blob/master/mc6809.c

[3]	For instance, you might want to run 6809 code under a special
	emulator that traps writes to certain regions of memory.  The
	callback for writing will check to see if the program can write to a
	given location and if it shouldn't, it throws an exception.

[4]	Since the emulator is in C, it uses setjmp()/longjmp() for
	"exception" handling.

[5]	It's not checked in, but I do have code that uses the "exception"
	mechanism in my MC6809 emulator to handle opcodes not defined by
	Motorola without having to change the core engine.  Yes, it is way
	slower to do it this way than by changing the core engine, but it is
	possible.

	But it's possible because of a very conscience design choice in how
	I did this.