Mac Apps That Use Garbage Collection Must Move to ARC

H. S. Teoh via Digitalmars-d digitalmars-d at puremagic.com
Wed Feb 25 10:20:44 PST 2015


On Thu, Feb 26, 2015 at 02:39:28AM +1000, Manu via Digitalmars-d wrote:
> On 25 February 2015 at 09:02, ponce via Digitalmars-d
> <digitalmars-d at puremagic.com> wrote:
> > On Monday, 23 February 2015 at 09:51:07 UTC, Manu wrote:
> >>
> >> This is going to sound really stupid... but do people actually use
> >> exceptions regularly?

I wouldn't say I use them *all* the time, but I do use them. In
properly-designed code, they should be pretty rare, so I only really
have to deal with them in strategic places, not sprinkled throughout the
code.


> >> I've never used one. When I encounter code that does, I just find
> >> it really annoying to debug. I've never 'gotten' exceptions. I'm
> >> not sure why error codes are insufficient, other than the obvious
> >> fact that they hog the one sacred return value.
> >
> > I used to feel like that with exceptions. It's only after a position
> > involving lots of legacy code that they revealed their value.
> >
> > One (big) problem about error code is that they do get ignored, much
> > too often. It's like manual memory management, everyone think they
> > can do it without errors, but mostly everyone fail at it (me too,
> > and you too).
> >
> > Exceptions makes a program crash noisily so errors can't be ignored.
> 
> This is precisely my complaint though. In a production environment
> where there are 10's, 100's of people working concurrently, it is
> absolutely unworkable that code can be crashing for random reasons
> that I don't care about all the time.

It doesn't have to crash if the main loop catches them and logs them to
a file where the relevant people can monitor for signs of malfunction.


> I've experienced before these noisy crashes relating to things that I
> don't care about at all. It just interrupts my work, and also whoever
> else it is that I have to involve to address the problem before I can
> continue.

Ideally, it should be the person responsible who's seeing the
exceptions, rather than you (if you were not responsible). I see this
more as a sign that something is wrong with the deployment process --
doesn't the person(s) committing the change test his changes before
committing, which should make production-time exceptions a rare
occurrence?


> That is a real cost in time and money. I find that situation to be
> absolutely unacceptable. I'll take the possibility that an ignored
> error code may not result in a hard-crash every time.

And the possibility of malfunction caused by ignored errors leading to
(possibly non-recoverable) data corruption is more acceptable?


> > More importantly, ignoring an error code is invisible, while
> > ignoring exceptions require explicit discarding and some thought.
> > Simply put, correctly handling error code is more code and more
> > ugly.  Ignoring exception is no less code than ignoring error codes,
> > but at least it will crash.
> 
> Right, but as above, this is an expense quantifiable in time and
> dollars that I just can't find within me to balance by this reasoning.
> I also prefer my crashes to occur at the point of failure. Exceptions
> tend to hide the problem in my experience.
>
> I find it so ridiculously hard to debug exception laden code. I have
> no idea how you're meant to do it, it's really hard to find the source
> of the problem!

Isn't the stacktrace attached to the exception supposed to lead you to
the point of failure?


> Only way I know is to use break-on-throw, which never works in
> exception laden code since exception-happy code typically throw for
> common error cases which happen all the time too.

"Exception-happy" sounds like wrong use of exceptions. No wonder you
have problems with them.


> > Secondly, one real advantage is pure readability improvement. The
> > normal path looks clean and isn't cluttered with error code check.
> > Almost everything can fail!
> >
> > writeln("Hello");  // can fail
> 
> This only fails if runtime state is already broken. Problem is
> elsewhere. Hard crash is preferred right here.

The problem is, without exceptions, it will NOT crash! If stdout is
full, for example (e.g., the logfile has filled up the disk), it will
just happily move along as if nothing is wrong, and at the end of the
day, the full disk will cause some other part of the code to
malfunction, but half of the relevant logs aren't there because they
were never written to disk in the first place, but the code didn't
notice because error codes were ignored. (And c'mon, when was the last
time you checked the error code of printf()? I never did, and I suspect
you never did either.)


> > auto f = File("config.txt"); // can fail
> 
> Opening files is a very infrequent operation, and one of the poster
> child examples of where you would always check the error return. I'm
> not even slightly upset by checking the return value from fopen.

I believe that was just a random example; it's unfair to pick on the
specifics. The point is, would you rather write code that looks like
this:

	LibAErr_t a_err;
	LibBErr_t b_err;
	LibCErr_t c_err;
	ResourceA *res_a;
	ResourceB *res_b;
	ResourceC *res_c;

	res_a = acquireResourceA();
	if ((a_err = firstOperation(a, b, c)) != LIB_A_OK)
	{
		freeResourceA();
		goto error;
	}

	res_b = acquireResourceB():

	if ((b_err = secondOperation(x, y, z)) != LIB_B_OK)
	{
		freeResourceB();
		freeResourceA();
		goto error;
	}

	res_c = acquireResourceC();

	if ((c_err = thirdOperation(p, q, r)) != LIB_C_OK)
	{
		freeResourceB();
		freeResourceC(); // oops, subtle bug here
		freeResourceA();
		goto error;
	}
	...
	error:
		// deal with problems here

or this:

	try {
		auto res_a = acquireResourceA();
		scope(failure) freeResourceA();

		firstOperation(a, b, c);

		auto res_b = acquireResourceB();
		scope(failure) freeResourceB();

		secondOperation(x, y, z);

		auto res_c = acquireResourceC();
		scope(failure) freeResourceC();

		thirdOperation(p, q, r);

	} catch (Exception e) {
		// deal with problems here
	}


> > What matter in composite operations is whether all of them succeeded
> > or not.  Example: if the sequence of operations A-B-C failed while
> > doing B, you are interested by the fact A-B-C has failed but not
> > really that B failed specifically.
> 
> Not necessarily true. You often want to report what went wrong, not
> just that "it didn't work".

Isn't that what Exception.msg is for?

Whereas if func1() calls 3 functions, which respectively returns errors
of types libAErr_t, libBErr_t, libCErr_t, what should func1() return if,
say, the 3rd operation failed? (Keep in mind that if we call functions
from 3 different libraries, they are almost guaranteed to return their
own error code enums which are never compatible with each other.) Should
it return libAErr_t, libBErr_t, or libCErr_t? Or should it do a switch
over possible error codes and translate them to a common type
libABCErr_t?

>From my experience, what usually happens is that func1() will just
return a single failure code if *any* of the 3 functions failed -- it's
just too tedious and unmaintainable otherwise -- which means you *can't*
tell what went wrong, only that "it didn't work". My favorite whipping
boy is the "internal error". Almost every module in my work project has
their own error enum, and almost invariably the most common error
returned by any function is the one corresponding with "internal error".
Any time a function calls another module and it fails, "internal error"
is returned -- because people simply don't have the time/energy to
translate error codes from one module to another and return something
meaningful. So whenever there is a problem, all we know is that
"internal error" got returned by some function. As to where the actual
problem is, who knows? There are 500 places where "internal error" might
have originated from, but we can't tell which of them it might be
because almost *everything* returns "internal error".

Whereas with exceptions, .msg tells you exactly what the error message
was. And if the libraries have dedicated exception types, you can even
catch each type separately and deal with them accordingly, as opposed to
getting a libABCErr_t and then having to map that back to the original
error code type in order to understand what the problem was.

I find it hard to believe that you appear to be saying that you have
trouble pinpointing the source of the problem with exceptions, whereas
you find it easy to track down the problem with error codes. IME it's
completely the opposite.


> > So you would have to translate error codes from one formalism to
> > another. What happens next is that error codes become conflated in
> > the same namespace and reused in other unrelated places. Hence,
> > error codes from library leak into code that should be isolated from
> > it.
> 
> I don't see exceptions are any different in this way. I see internal
> exceptions bleed to much higher levels where they've totally lost
> context all the time.

Doesn't the stacktrace give you the context?


> > Lastly, exceptions have a hierarchy and allow to distinguish between
> > bugs and input errors by convention.
> >
> > Eg: Alan just wrote a function in a library that return an error
> > code if it fails. The user program by Betty pass it a null pointer.
> > This is a logic error as Alan disallowed it by contract. As Walter
> > repeatedly said us, logic errors/bugs are not input errors and the
> > only sane way to handle them is to crash.
> >
> > But since this function error interface is an error code, Alan
> > return something like ERR_POINTER_IS_NULL_CONTRACT_VIOLATED since
> > well, no other choice. Now the logic error code gets conflated with
> > error codes corresponding to input errors (ERR_DISK_FAILED), and
> > both will be handled similarly by Betty for sure, and the earth
> > begin to crackle.
> 
> I'm not sure quite what you're saying here, but I agree with Walter.
> In case of hard logic error, the sane thing to do is crash (ie,
> assert), not throw...
>
> I want my crash at the point of failure, not somewhere else.
[...]

Again, doesn't the exception stacktrace tell you exactly where the point
of failure is? Whereas an error code that has percolated up the call
stack 20 levels and underwent various mappings (har har, who does that)
or collapsed to generic, non-descript values ("internal error" -- much
more likely), is unlikely to tell you anything more than "it didn't
work". No information about which of the 500 functions 20 calls down the
call graph might have been responsible.


T

-- 
You are only young once, but you can stay immature indefinitely. -- azephrahel


More information about the Digitalmars-d mailing list