Kuro5hin.org: technology and culture, from the trenches
create account | help/FAQ | contact | links | search | IRC | site news
[ Everything | Diaries | Technology | Science | Culture | Politics | Media | News | Internet | Op-Ed | Fiction | Meta | MLP ]
We need your support: buy an ad | premium membership

[P]
Error handling in object oriented languages

By Hopfrog in Op-Ed
Thu Apr 11, 2002 at 05:43:29 PM EST
Tags: Software (all tags)
Software

Error handling is an essential part of any software project, but an aspect that is often ignored or treated very rudimentarily. It is not at all uncommon to find code where a MessageBox("Function DoSomething() failed"); is present at every possible error point. This is unacceptable programming.

I would like to spur a discussion about good error handling with this article, and describe my personal favourite. My samples will be based on Win32 C++ programming, but should be generally applicable for other OO languages and for other platforms.


What should an error message look like?

An error message should be meaningful to the user and to the developer. A user should be able to look at an error message, and understand broadly what the cause of the error is. A developer should be able to look at an error, and know at what line in his code this error occurred, and why it occurred. At the same time, commercial programs would not want to reveal too much about their code in the error message.

Samples of bad error messages would be:

  • "File could not be played. Error!"
    This error message helps neither the user nor the developer understand what the problem is.

  • "Pointer m_pFilestream was NULL in file x:\projects\file_read.cpp, line 106. Behaviour unexpected. Deleting class object CStream, and returning!"
    This error message may make sense to a developers, but would confuse 95% of all users. But even for a developer it might lose its utility, considering that as the project evolves, line numbers change, and code may be moved from file to file.

  • "Access violation at address 0x44b32a. The program has performed an illegal operation and will be shut down."
    This is one of the worst possible error messages. It doesn't tell the user anything, and doesn't tell the developer much either.

  • Popping up multiple error messages when a chain of errors occur. For example, you click a button and get a message box : "Error, invalid filename". You click OK, and get a second message box : "Playback could not start, because file could not be opened." You click OK again, and get "Application error!"
This is how I think a good error message should look like:
  • An error message should be divided into an advanced and basic mode. The basic mode tells the user that there is an error, and why the error occurred. The advanced mode provides enough information for the developer to fix the problem with. In basic mode, a user should see something similar to :

    "The file you selected could not be played, because the format is probably not supported, or the selected file is corrupt. Please press the help button to view the list of supported input formats."

    If the user selects the advanced mode, he sees information such as :

    "Please contact technical support with the following information: HRESULT = E_POINTER, object m_pFilestream in function RenderFile(), class CStream*, was NULL. Error progression : Error triggered in RenderFile(), progressed to CStreamWrapper::StartFileRender(), ended at CMyApp::StartFileRender(), with error chain sealed at CMyApp::StartFileRender(). Release Build: 4997, OS=WinXP build 3342, MMDrivers=ReleaseVer 8.11, ParameterFlagDump=8772629"

You notice that function names and class names are included, and line numbers and filenames are left out. Also included is some general information about the OS, as well as the version of the required drivers. The parameter dump can specify a bit mask of all selected options. Also, and importantly, one sees the progression of the error. Just saying that the error occurred in RenderFile might sometimes not be sufficient if this function is called from various points in the application.

For commercial software companies, it might not be wished for to include the names of classes and functions in the error message. In this case, these names can be represented with numeric values which can then replace the names when the application is compiled as a release version. When an error report is received, the numbers can then be replaced with the function names using a reference file.

Implementation in C++

When designing an error handling mechanism in object oriented languages, there is always the problem of creating too many dependencies. A basis tenet of OOP is code reuse, and if you have a CErrorInfo class which every function returns, it becomes difficult to take a class out of this project and adding it to another project without having to change a good amount of code. As a result, whatever error handling mechanism we use should still allow our classes to be used in every other project, and adapted to other types of error handling without problems.

The method I prefer is to add a FireError macro at global scope, which calls a central error handling routine in our main class. The FireError macro could be declared this way:

#define FireFatalError(errNumber, errDescription) ::FireErrorImpl(errNumber, errDescription, typeid(this).name(), __func__, __FILE__, __LINE__, ErrLevelFatal);

[See Appendix 1 for information about the __func__ macro using Visual C++ 6.]

A piece of code could look like this:

_ASSERT(m_pFilestream);
If (m_pFileStream == NULL)
{
  FireFatalError(E_POINTER, "Object m_pFilestream in %location was NULL");
  return E_POINTER;
}

If this code was taken to a different project where popping up message boxes was the preferred error handling mechanism, all I would have to do is

#define FireFatalError(errNumber, errDescription) ::MessageBox(NULL, errDescription, NULL, NULL);

and my class is fully useable once again.

Note: __func__ returns the name of the function. __FILE__ returns the name of the file, and __LINE__ returns the line. When RTTI is enabled with C++ applications, typeid(this).name() returns the name of the class.

The OS information, as well as the build information is retrieved by the central error handling function.

What remains right now is the progression of errors. In the code above, E_POINTER, an error value is returned to the calling function. That function that receives this error value will also fire a fatal error, and its error will be added to the existing error being displayed as part of the error chain. The last function in the chain will seal the chain, indicating that it is not going to route the error anymore. One can either display the error box when the chain is sealed, or when the first error is fired.

Throw, Catch

C++ comes with it's own error handling mechanism, the throw and catch calls. However, these are not very often used. Though not difficult, the throw-catch methods are unnecessarily complex, and make it difficult for you to find out exactly where the error occurred. It is often a matter of personal preference, and I prefer not to use throw-catch, as do a large number of C++ developers. A discussion about the disadvantages is available here.

Appendix

1.) The __func__ (or __FUNC__) macro is available on some C++ compilers, but not on all. For these lacking compilers however, there are usually various hacks that simulate it. There is one for Visual C++ 6 here.

2.) For COM programmers, IErrorInfo is a good way to make error messages available to clients. A tutorial is available here.

3.) Information about using throw-catch as an error handling mechanism is available here and here.

Sponsors

Voxel dot net
o Managed Hosting
o VoxCAST Content Delivery
o Raw Infrastructure

Login

Poll
Is error handling boring?
o Yes. I don't like reading about it, and my software sucks 23%
o No. I am madly interested in it, and write perfect software 46%
o No opinion, but just can't resist voting on a poll 29%

Votes: 71
Results | Other Polls

Related Links
o here
o here [2]
o here [3]
o here [4]
o here [5]
o Also by Hopfrog


Display: Sort:
Error handling in object oriented languages | 179 comments (139 topical, 40 editorial, 0 hidden)
Have you been reading (4.25 / 4) (#2)
by wiredog on Thu Apr 11, 2002 at 02:24:40 PM EST

"Developing Windows Error Messages" from O'Reilly? It's been out of print for a few years, which is a pity. It should be part of every programming curricula. See the author's Bad Error Messages page.

Peoples Front To Reunite Gondwanaland: "Stop the Laurasian Separatist Movement!"
Speaking of bad error messages.... (none / 0) (#179)
by unDees on Fri May 10, 2002 at 06:50:57 PM EST

When I visit the site you referenced, I get the following one:

404 - File Not Found

:)

(Yeah, I know this discussion was a month ago, and links change, and you'll probably never see this anyway, but I couldn't resist.)


Your account balance is $0.02; to continue receiving our quality opinions, please remit payment as soon as possible.
[ Parent ]

Error messages (4.90 / 20) (#7)
by ucblockhead on Thu Apr 11, 2002 at 02:35:24 PM EST

First, the key I've found is not to have "advanced" error messages but to instead have a logging system that saves "advanced" error text someplace out of site of the user. It's there for the programmers when they need it, but it doesn't confuse the user.

I worked for a place that had a huge support problem with error messages. The users were minimum wage non-computer folks who would rarely tell you more than "it said 'error'". Many, many bugs remained for months because no one knew what the hell was going on. When I wrote a new system for them, I simply had the error reporting code log the exact text of the message reported to the user. After that, the problem was no longer a problem. We could just check the log to see what happened.

A couple comments on your messages:

"Please contact technical support with the following information: HRESULT = E_POINTER, object m_pFilestream in function RenderFile(), class CStream*, was NULL. Error progression : Error triggered in RenderFile(), progressed to CStreamWrapper::StartFileRender(), ended at CMyApp::StartFileRender(), with error chain sealed at CMyApp::StartFileRender(). Release Build: 4997, OS=WinXP build 3342, MMDrivers=ReleaseVer 8.11, ParameterFlagDump=8772629"
That is not a message a "user" should ever see, advanced or no. That's only meaningful to the program author. It should only be produced by debug builds, never in anything out in the field. This is the sort of info that should be logged but not displayed to the user.

"The file you selected could not be played, because the format is probably not supported, or the selected file is corrupt. Please press the help button to view the list of supported input formats."
This brings up an overall design issue. You shouldn't be displaying this sort of message because you shouldn't be allowing the user to select unsupported file types in the first place. One of the best way to handle user errors is to prevent user errors in the first place. If a field is a number, don't put up an error box when they enter alpha characters. Instead, simply ignore alpha keypresses. Don't say "entry must be 15 digits". Ignore keystrokes after 15 digits.

The overall problem I have with your article is that there is a muddling of the debug and release phases of the product. Error messages in release products should be generally about notifying the user about a user error. They should not be debug aids for the programmer. Those sorts of aids should be in the debug build, and used while testing.
-----------------------
This is k5. We're all tools - duxup

How do you do that? (5.00 / 5) (#27)
by seebs on Thu Apr 11, 2002 at 03:05:44 PM EST

How do I keep the user from selecting unsupported file types? The only way to know whether or not I support a file is to attempt to display it, and then verify that the user got the expected results.

The RCA "Lyra" MP3 player doesn't actually play MP3's; it plays special encrypted MP3's. If you give it a real MP3, it produces pops, clicks, and static.

Anyway, imagine that you're writing an MP3 player. What do you do if the file you're given doesn't look like an MP3? How does a utility that can read a stream of input check for file extensions, and what if the extension is wrong?


[ Parent ]
no perfect answer (4.75 / 4) (#39)
by ucblockhead on Thu Apr 11, 2002 at 03:45:33 PM EST

Obviously you can't prevent every single error. You sometimes have to fall back to the old standby error box. But just having the "don't report errors, prevent them" can save you a lot of hassles.

As far as MP3 players, Winamp, at least, simply skips over files it can't play in the playlist.

If it is a proprietary format, they really shouldn't be calling it an "mp3". They should pick a new extension, to make it more transparent. That way, all the select boxes can select only their own private filetype. Putting non-mp3 data in a file with an ".mp3" extension is just inviting error conditions.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

you presume too much (5.00 / 2) (#76)
by mulvaney on Thu Apr 11, 2002 at 07:17:14 PM EST

As far as MP3 players, Winamp, at least, simply skips over files it can't play in the playlist.

Yeah, but that's evil. The user doesn't get any feedback as to why the songs aren't playing.

Putting non-mp3 data in a file with an ".mp3" extension is just inviting error conditions.

You can't assume that everyone is going to use the same conventions that you do. Maybe I'm at work, and the IT guys scan my disk every night for .mp3 files? So I change all my songs to end in .dat. Or whatever.

Face it, there are times when you have to let the user do what he wants, and when that happens, you need to show him a dialog and say, "You messed up, here's why."

-Mike

[ Parent ]

Yes (4.00 / 2) (#87)
by ucblockhead on Thu Apr 11, 2002 at 10:26:49 PM EST

I'm not disputing that. The point is to work to minimize such user interactions. You will never get rid of them all, but throwing your hands up in the air and saying "I can't do it perfect so I won't do it at all" is what leads to poor UIs.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]
Agreed, but... (4.50 / 2) (#93)
by seebs on Fri Apr 12, 2002 at 01:51:19 AM EST

Skipping is a poor example. You should never silently skip anything unless the user has told you to.

Otherwise, you have the ultimate UI hell: It's not working and it won't even tell you it's failing.


[ Parent ]
UI design (5.00 / 1) (#95)
by katie on Fri Apr 12, 2002 at 05:03:04 AM EST

I think skipping them is a solution to the problem of popping up error boxes. If you hit a song you can't play, stopping and going "Error: reason" will annoy the people who now have to climb three flights of stairs to the system that spools the music around the building to click "OK".

I think it was in the book written by the guy who designed the VB UI which talked about not displaying error message boxes if possible because it interrupts the user's flow of thought.

(Of course, VB programmes then seem to use modal dialogs as the main interraction. I've seen them use long streams of question dialogs as a user interface. I thought that went out with CPM, but no...)

The MPG player's solution could be to cross out the bad songs and have clicking on them explain why they were crossed out...


[ Parent ]
Stopping would be inappropriate for MP3 player... (4.66 / 3) (#142)
by Karellen on Fri Apr 12, 2002 at 04:05:18 PM EST

You'd want to notify the user (possibly by way of a _non-modal_ dialog) that the track wouldn't play, and then continue straight on with the next one, leaving the dialog up for the user to dismiss in their own time, but not interrupting the flow of the music.

They could then remove the offending file from the playlist at their leisure.


[ Parent ]
That's not always acceptable (none / 0) (#178)
by Mr.Mustard on Tue Apr 16, 2002 at 01:10:26 PM EST

Sometimes, when rearranging my mp3 files, I'll load a playlist which contains no valid references. Every file has been moved. At least with the skipping method I just have it spinning through them quickly, one after the next. With non-modal dialog boxes things could quickly get out of hand.

That said, I think the skipping method isn't that great either. I actually wouldn't mind a modal dialog with the option of disabling it. (think "submitting an insecure form") At least then I get to make a decision.

Mr.Mustard [ fnord ]
[ Parent ]

How do you get the log file? (4.25 / 4) (#31)
by Hopfrog on Thu Apr 11, 2002 at 03:24:30 PM EST

Have you ever worked for tech support, and tried to explain to a grandma what a "folder" is?

Or asked her to read the last 3 items from a log file out to you? Complicated.

Not saying it isn't bad - I also use a log, but only in debug versions - but I prefer to think with the stupidest user in mind.

Hop.

[ Parent ]

samba/nfs (4.00 / 4) (#35)
by damiam on Thu Apr 11, 2002 at 03:29:59 PM EST

mount /users/grandma
tail /users/grandma/var/log/program/errors.log

What's the problem?

[ Parent ]

Welllllllll....Maybe Grandma is not on your local (3.75 / 4) (#36)
by Hopfrog on Thu Apr 11, 2002 at 03:33:11 PM EST

Network. Did that occur to you? You cannot do everything by being there by yourself. Tech support needs to know what the problem is, and log files are difficult for ordinary users to bring out.

Hop.

[ Parent ]

automate automate automate (4.60 / 5) (#43)
by ucblockhead on Thu Apr 11, 2002 at 03:50:07 PM EST

When I did this, it the user wasn't "grandma", but part of the same company, so we had a bit more control. The log file was automatically sent up every evening. We could trigger the send from the server. We often knew more about what errors they'd seen before they did. In more than one case, I fixed bugs that the users didn't even notice!

Obviously you can't do that in shrinkwrap software. But you can put a button on a dialog that says "send error information to technical support". There's no reason "grandma" has to do anything complicated. The designer's job is to make it all simple.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

I hate pressing that "Send to MS button" (3.33 / 3) (#48)
by Hopfrog on Thu Apr 11, 2002 at 04:01:17 PM EST

I'm scared that they send the license key of my warezed version of windows. 'sides, not all software is netted, and you should not have to write network code just to handle errors.

Not saying that log thing isn't bad, just defending my approach. In me opinion, and my opinion can always be corrected, this is the most painless way of showing an error message.

Hop.

[ Parent ]

Constrictive UI (5.00 / 2) (#174)
by wiml on Sun Apr 14, 2002 at 08:37:20 PM EST

You shouldn't be displaying this sort of message because you shouldn't be allowing the user to select unsupported file types in the first place. One of the best way to handle user errors is to prevent user errors in the first place. [...] Don't say "entry must be 15 digits". Ignore keystrokes after 15 digits.
I'm not at all convinced this is the correct approach. The problem is that it doesn't give the user an opportunity to find out what's wrong, and so the user is less likely to understand the situation and deal with it effectively.

Consider, scenario one: user downloads a sound file and attempts to play it. But they can't. Its icon is dim, and when they click on it, nothing happens. Result: user is confused and annoyed. Scenario two: user clicks on file, program produces a message saying "can't play that file. Unsupported format." Result: user is still annoyed, but at least they have a chance of understanding what's going on and what needs to be done to fix their problem.

Or, in the case of the field which should have a 15-character numeric-only value. Sure, ignoring extra keypresses is a reasonable way to deal with simple typos. On the other hand, if the user has a number that doesn't fit this format — maybe it was mistranscribed, or they read it from the wrong line on the invoice, or whatever — they have no idea what's going on. The computer silently refuses to accept their input. There's no indication of why. Is their keyboard broken? Is there some weird network problem? Bugs in the software? Who knows? The user will probably give up and assume it's an Unknowable Computer Weirdness. (This is bad, because that user will stop trying to understand their tools, leading to more work for everyone.) But if the program responds with, "That's an invalid entry; account numbers must be 15 digits with no letters or spaces or etc.", the user has a chance to track the problem back to its source and fix it.

"Preventing" errors in this way seems like a good idea from a purely program-centric point of view. But in the larger scheme of things, all it does is push the error back one level, and obscure its etiology. If your program is getting bad data, it's getting bad data for a reason. In the case of user input, you can't fix it yourself, but you need to leave enough information around so that someone, possibly the user, can fix it. Not just cover your ass with fields that balk and give no explanation.

(end mini-rant)

[ Parent ]

A small problem (3.50 / 6) (#11)
by trhurler on Thu Apr 11, 2002 at 02:41:24 PM EST

Usually, the error messages you get are useless because they were generated by system libraries that don't know what your program is trying to do. Often, these cannot be suppressed by any reasonable means, and even more often, your program can't determine what went wrong any better than the library code can, due to bad programmatic error reporting in system libraries. The end result is that you're telling people to do things that often simply are not possible on existing systems. Good error handling requires ALL code running on the system to "do it right."

--
'God dammit, your posts make me hard.' --LilDebbie

that is deep man (5.00 / 1) (#103)
by turmeric on Fri Apr 12, 2002 at 09:35:28 AM EST

its like, you know, the whole system is one thing, and the whole experience of the system is the experience of all its little parts, and like, its like, all one thing...

its like what is an 'error' really... its like in double-ledger accounting they enter every transaction twice, once as a credit once as a debit, this is like the 'balance' of the whole accounting budget thing you know, so like what is an 'error' ,why is that path of execution somehow 'inferior' to any other path, and must be treated separately? its like so not cool and discriminatory. its like, when you get people who have systems where there is an error, their whole experience has somehow been 'automated' so they dont get the same attention as someone whose system conforms to the 'expected path of execution', so like if you have any unusual system at all you just get a bunch of gibberish word errors.

like the other dude said, basically, if a car broke down i would want to have the car's computer send all its sensory data back to the manufacturer so the folks at the manufacturer could see if they had done anything wrong or if they could redo their design or factory so that the error would not happen again.

now in software, this is basically the 'automatic bug reporting tool'.. like bug-buddy or something. but the deal is that in the open source world,,,

  • 1. they do not want to fix every little bug sent in by every user. see 3 for the reason.
  • 2. they blow off bug reports as 'stupid users'
  • 3. they want to rewrite the whole thing in a new version instead of fixing bugs in the old version,
  • 4. they do not believe there are 'user interface' bugs rather they believe the users have genetic defects that makes them subhuman idiots.
  • 5. an automatic bug reporting tool doesnt have alot to work with since shared libraries are generally not shipped with debugging symbols. why? partly because gnu's debugging symbols bloat the binaries to gastronomic proportions so they are unusable by 'normal users' with normal amounts of RAM and CPU time. so, no shared library debug symbols, so, you never know where your crash came from, i mean, not only do you not have any data, you have no traceback through the code! no 'paper trail' if this were accounting!
so basically, it doesnt matter how you spit out error messages. the fact is that programmers dont care if their programs work or not, so why even bother spitting out an error message? and then, if youre a user, why even bother reading them? this is what users have learned by hard examples, sure they tried to report error messages once or twice, but after being treated like 'stupid retarded children' by the programmers, they quickly learned to ignore error messages and simply 'work around' the bugs in the program, because the bugs were never going to get fixed and the programmers were too arrogant to admit they had screwed up.

if the bug count was too high, like in open source, the users would simply give up and use something else.

solution. the entire mentality of software people has to be ripped out by the roots and replanted elsewhere. programs should be somewhere between business accounting and engineering with a paper-trail leading back to everything that happens, and strict guidelines and testing followed to ensure that the goddamn thing works properly. on the other hand, cars are not that great either. i mean, they constantly produce shoddy crap without caring too much whether it kills a bunch of people because the tires blow out, and after it happens they cant accept responsibility for their fuck ups. perhaps the entire industrialized production method of solving porblems with technology has some inherent feature that makes it totally oblivious to the effect it has on people, like the very nature of the intense focus required to build these contraptions also inherently means that the things that were 'defocused' to get the job done will always be left out, like pollution or safety or whether it works for everyone... it is almost like, there is a 'conservation of mental focus' that says that you can only pour so much mental-focus into solving a problem, and by necessity you will leave things out. but all spiritual health seems to come from defocus, or focusing on everything at once, the whole system working together, the entire massive complex of everything all existing in one symbiotic life force of everything. so what the heck.

ok i have the answer. programmers all need to do yoga and meditation.

[ Parent ]

Size of debug information irrelevant. (none / 0) (#120)
by hythloday on Fri Apr 12, 2002 at 12:29:49 PM EST

gnu's debugging symbols bloat the binaries to gastronomic proportions so they are unusable by 'normal users' with normal amounts of RAM and CPU time.

As most modern operating systems have demand-paged loaded executables, the size of the debug information is irrelevant.

if the bug count was too high, like in open source, the users would simply give up and use something else.

I'm not quite sure what you mean here: to contrast two similar examples, there was a patch for kmail's incorrect handling of dates within a day of the unix time rollover - I'm still waiting even for a reply email from microsoft about outlook express incorectly handling mail from pocomail.

[ Parent ]
Uh, what libraries are you using? (none / 0) (#141)
by Karellen on Fri Apr 12, 2002 at 03:58:26 PM EST

First of all, what do you mean by "...error messages ... generated by system libraries ... cannot be suppressed by any reasonable means and ... your program can't determine what went wrong ..."?


System libraries (well, all the ones I've come across have very well defined interfaces. The documentation for which tell you what you can do with them and what error codes (or exceptions) are returned in case of an error.

Of course the system libraries don't know what you're doing, but _you_ do. Get the result from the system library, check for errors and figure out what they mean in the context of what you were telling them to do.

(e.g. fopen(3) returning NULL means file couldn't be opened. So return well documented error codes (or exceptions) from your function indicating the file could not be opened, or, if appropriate, issue an error message to the user ("Unable to open file 'foo', please make another selection") and go on.

If your system libraries are not functioning as documented, then shout at your system provider and tell them to get their act together, or go to another provider.

Alternatively, write a wrapper library that does what the library is supposed to do, that handles cases your system can't, and gently guides it through the tough spots, document it well and then sell it to all the other poor suckers who are stuck with the same crappy library you are, telling them this will fix their problem (while fixing yours at the same time).

K.




[ Parent ]
well... (none / 0) (#145)
by pb on Fri Apr 12, 2002 at 04:10:17 PM EST

Yes, fopen() is sane; I was talking about Java, which won't compile your programs without considerable exception handling, depending on the functions you use.

And yes, writing a very large wrapper library might be the thing to do; either that, or writing another language that looks suspiciously similar except that it's actually decent.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Lacking java experience... (none / 0) (#160)
by Karellen on Sat Apr 13, 2002 at 11:39:10 AM EST

I'm not really sure about how java does stuff, but I'll take a stab that it's something like:


try {
    java.File fd = new java.File(filename, readflag);
}
catch (exception.file.not_exist e) {
    /* Do stuff */
}
catch (exception.file.perm e) {
    /* No permissions; Do other stuff */
}
catch (exception e) {
    /* Whoops */
    throw e;
}

How is that so different from...


FILE * fd = fopen(filename, "r");
if (!fd) switch (errno) {
case EEXIST:
    /* Do stuff */
    break;

case EPERM:
    /* Permissions; Do other stuff */
    break;

default:
    return -1; /* Error code according to this functions's API */
}


[ Parent ]
the difference (none / 0) (#161)
by pb on Sat Apr 13, 2002 at 01:13:20 PM EST

The difference is that in C, you are never required to check for error conditions from fopen(), and if you do, you aren't required to use a switch statement for it. You could write your own error handling function for everything, and then your calls to fopen could look like this:

FILE * fd = error(fopen(filename, "r"),"fopen");

Not so in Java; you need to do something with the exceptions whether you like them or not.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Neat! I wrote about that recently. (4.25 / 4) (#25)
by seebs on Thu Apr 11, 2002 at 03:02:54 PM EST

http://www-106.ibm.com/developerworks/usability/library/us-cranky14/. made similar points, but from a different (less technical) perspective.

Nice article [n/p] (2.00 / 1) (#34)
by Hopfrog on Thu Apr 11, 2002 at 03:27:51 PM EST



[ Parent ]
Problems (4.58 / 12) (#32)
by epepke on Thu Apr 11, 2002 at 03:26:39 PM EST

I sure hope this makes it to the front page, because I have a lot to say.

First problem. As others have pointed out, there are other languages than C++, and some of these are different. For example, while C++ try/catch is little more than glorified setjmp/longjmp, Java's try/catch is actually good, because information on what exceptions are thrown is syntactic and automatically kept track of by the compiler. I avoid C++ try/catch like the plague, but in Java I usually have a few try/catch blocks.

Second problem. Forgive me, but this is a bit of a soapbox of mine. As soon as I read "you have a CErrorInfo class which every function returns," I knew that you weren't doing O-O programming. You're doing class programming. I forgive you, because most of the books on C++, especially those with "Enterprise" in the title, really teach class programming but call it object programming. However, they're wrong. Class programming is a compromise between object and procedural programming. People usually do this in C++, but they don't have to. In class programming, an object mechanism within the language is used to represent glorified modules or structs plus functions. This somewhat reflects the history of C++ and the way it is taught. In O-O programming, similar language mechanisms can be used, but the philosophy is totally different. The program lives primarily in the interactions between objects, not in functions. Real O-O programmers almost never say the word "class." They don't write "interface classes"; they write interface objects, incidentally maybe using a mechanism with the syntactic keyword "class." Think about it; it's a subtle but important difference.

I was surpised at the difference, because I have been doing research in O-O back to the early 1980's, and I was taken aback the first time I read a book about programming C++ for business. The first example was a class as a bank account, and a balance check involved calling a function that returned a float. This is insane! You certainly don't want to do that, let alone teach such a practice first in a book. But I digress.

Anyway, as a result, you are viewing errors in an essentially procedural way, as a thing that happens in the course of running code. If you want to do that, that's fine, but don't call it O-O. In the O-O paradigm, there are other ways of viewing errors. Actually, the difference between errors becomes much clearer.

One particularly powerful way of viewing some errors is to consider an object as being in a state of error.

Here's an example of what I do. I'm writing a piece of Java code to handle a form. There is a kind of local validation error that can be detected by looking only at one field. Each field I have, say it's a text field or maybe a pull-down, isn't the Java API object. Instead, it's an object that I've written that encapsulates the Java API object but also presents an interface exactly like the Java API object and simply passes the information down. This object also maintains state and other information. In addition to the API-like interface, it supports another interface which includes a method called checkError.

Sounds like a lot of work, right? A little bit, but you get lots of magic. Magic 1: If you give it the ability to highlight itself (I use a red rectangle around the control), then, when an error is detected, you can put up a message somewhere and highlight the control. It's trivial. Magic 2: Now that you have the ability to highlight, you can use that elsewhere. You can highlight two controls for global interaction errors (good design minimizes this, but you often can't get rid of all of them). Magic 3: Notice that this is a different kind of error. It falls naturally out of the O-O design, but it also represents a significant cognitive difference in error handling in the brain of the user. Good O-O design almost miraculously produces this all the time. Magic 4: How about a tutorial mechanism, which steps the user through several fields, explaining what to do with each one?

OK, enough for now. I voted +1, even though I disagree, just so I could write this.


The truth may be out there, but lies are inside your head.--Terry Pratchett


Real OO programming? (4.66 / 3) (#79)
by greenrd on Thu Apr 11, 2002 at 07:39:41 PM EST

Real O-O programmers almost never say the word "class."

No, in my experience only OO programmers who don't know the difference between classes and objects almost never say the word "class".

I was surpised at the difference, because I have been doing research in O-O back to the early 1980's, and I was taken aback the first time I read a book about programming C++ for business. The first example was a class as a bank account, and a balance check involved calling a function that returned a float. This is insane! You certainly don't want to do that, let alone teach such a practice first in a book.

Sorry, but that sounds perfectly reasonable to me.

Please could you elaborate?


"Capitalism is the absurd belief that the worst of men, for the worst of reasons, will somehow work for the benefit of us all." -- John Maynard Keynes
[ Parent ]

Re: Real OO programming? (4.40 / 5) (#81)
by reflective recursion on Thu Apr 11, 2002 at 08:27:35 PM EST

I would say his comment was directed at the "float" part. He probably meant you shouldn't do this in financial applications, such as a bank account. You would want to use a BCD/fixed-point rather than float (rounding errors). I don't see why it should matter much if it's an intro book, but it could lead readers to actual incorrect use perhaps.

Look here for more info.

[ Parent ]
Sure (5.00 / 3) (#172)
by epepke on Sun Apr 14, 2002 at 07:08:19 PM EST

No, in my experience only OO programmers who don't know the difference between classes and objects almost never say the word "class".

My experience is different. Perhaps I could have been clearer. Real O-O programmers don't talk about writing classes; they talk about defining objects. A class is just a mechanism. Except, of course, some languages don't make a distinction, but even for those who do, the goal is to get the objects out there and working.

Sorry, but that sounds perfectly reasonable to me.

Open your wallet. Are there any floating point numbers in there? Nope. I don't know what's in your wallet, but in mine I have dollars, pesos, pounds sterling, Belgian francs, and gulder. Already, a floating point number is inadequate. So, there are things that money does/is that is not represented by a floating point number. Also, an interesting thing happened to my gulder and francs on January 1st, due to the Euro, they became non-negotiable. (They still have pretty pictures, though.)

There are also things that floating point numbers can do that aren't represented by money. 3 * 2 = 6. However, $3 * $2 does not equal $$6. The operation doesn't make any sense. A floating point number can also be overwritten or forgotten. Sure, you can patch in currency exchanges to a system, but it's far more work to do it in a procedural way, and it's a maintenance nightmare.

So, much better to have money in the system as objects. You can imagine a hierarchy of classes (an appropriate use of the word class) that encapsulates the behavior of money. It's even one of the rare legitimate uses of multiple inheritence: inherit dollars, Euros, and whatever along one hierarchy, and represent the role the money has to play along a different hierarchy (cash is different from a check which is different from money sitting in the account.) However, represent money in the system as objects which actually have a life cycle. Instead of having addition and subtraction of money, have a split and join operation. Split peels off some money from the rest (withdrawal from the account), and join puts money in (a deposit). Build accounting rules right into these operations. Maybe have split and join ghost off receipt or audit operations. How actually you want to do this in the language is a detail.

Consider an ATM. It spits out cash. When you make a withdrawl, say for $100, a split operation happens on your account. The $100 object is encrypted and makes its way over the wire to the ATM. Of course, a reciept happens for that operation, and it stays at the bank. Then your ATM decrypts it, counts out the cash, and when it spits out the cash, creates an internal reciept, and sends the money object to an account that takes care of the cash.

Consider that you use your ATM card in England. The process is more elaborate, of course. The ATM will have to get a quote for conversion from some service and negotiate the quote with your bank, then send the money to the conversion service (or, more likely, accumulate it until the end of the day). Also, a third-party ATM must work according to some loan agreement with the other bank. But it all flows directly out of the basic system of representing money as objects.

Now, this gets arbitrarily complex, but surely it isn't difficult at first to introduce money by defining, say, a Dollars class and making money by intantiating objects from this class, because at least refactoring flexibility is built in from day 1. Later on, you can refactor Dollars as a subclass of Money, or whatever it is that's appropriate.


The truth may be out there, but lies are inside your head.--Terry Pratchett


[ Parent ]
Your right... (1.00 / 1) (#177)
by codemonkey_uk on Mon Apr 15, 2002 at 08:28:19 AM EST

...but you might want to lay of the "Real OO Programmers do so-and-so" shit, its unnecessarily preachy, superior, and *ahem* wrong.

Lots of OO programmers say "class" because "class" is the keyword used for defining types of object in C++. No big deal, but it makes you look like a bit of an academic jerk.
---
Thad
"The most savage controversies are those about matters as to which there is no good evidence either way." - Bertrand Russell
[ Parent ]

survey sez .... BZZZZT!! (2.50 / 6) (#33)
by erp6502 on Thu Apr 11, 2002 at 03:27:31 PM EST

This is incredibly not interesting. Why? Because you're patching around a lack of reflection in C and C++. I do write plenty of C code, but I don't fool myself into thinking that good error handling is any more a topic of conversation than the proper techniques of rectal sanitation.

What would really be interesting would be if you spoke on the topic of run-time type- and bounds-checking, built-in lightweight debuggers/inspectors, and checkpointing facilities that would stash enough information to allow a proper post-mortem if the user should desire to do so.

You start to touch on this when you mention the availability of throw and catch, but dismiss it out of hand, along with that "large number of C++ developers" who believe that the stability and peace of mind that come with proper memory management and dynamic type checking isn't worth 10% overhead. Who are you to lecture us on proper programming practice?

p.s. -- please don't take this personally. It's not directed at you, but rather at the attitude of so many who have gone before you.

Indeed. (3.20 / 5) (#57)
by pb on Thu Apr 11, 2002 at 04:32:58 PM EST

Who ARE you to lecture us on proper programming practices? :)

If we programmers are anything, we are individuals. That's why there are so MANY ways for us to achieve the same goals.

I don't know about you, but back when I programmed in Pascal, I cranked up the optimization all the way, turned off all the range / bounds checking, and got very good at finding errors in my programs. :)

So... this programmer has a different approach. And he wrote about it, to SHARE it with us, in case we find it USEFUL. You, judging by your comments, may prefer a different approach. Therefore, you might want to write an article, to SHARE it with us, in case we find it USEFUL.....
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Oh, my... (1.50 / 6) (#74)
by erp6502 on Thu Apr 11, 2002 at 07:11:32 PM EST

What tremendously oversized apparatus you possess.

I, on the other hand, when forced to learn Pascal, chose to tutor myself in C instead and handed in my problem sets in that language. BFD.

Your anecdote very nicely illustrates one of my points, which is that most code jocks are just that: unthinking one-upsmen channelling their surfeit of machismo through a conviction that coding on the bare metal is the right thing to do ALL THE FUCKING TIME.

Thanks for your time, space monkey.

[ Parent ]

aha... (1.50 / 2) (#108)
by erp6502 on Fri Apr 12, 2002 at 10:32:11 AM EST

Looks like I hit a nerve with this comment.

Why are you reading this? Don't you have something better to do, like building the Great American Error Handler?

Whoops, too late for that. It came and went, and most people didn't notice. Suffer, you bastards.

:)

[ Parent ]

That's my opinion. (3.00 / 1) (#132)
by pb on Fri Apr 12, 2002 at 01:53:54 PM EST

I just didn't see anything worth replying to in that comment. I agreed with the sentiment, but perhaps not with the recipient, so posting a "Me too!" might have been misplaced. :)

If you'd like to discuss this further, I think I eventually link to a place for that in my User Profile somewhere, if you think you have anything to discuss...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
On exception handling (4.77 / 9) (#41)
by kuran42 on Thu Apr 11, 2002 at 03:48:01 PM EST

Consider:

long i;
char *str, *err = NULL;
...
i = strtol(str, &err, 10);
if ((i == LONG_MIN || i == LONG_MAX) && errno == ERANGE ||
    err != NULL) {
  // Handle error
} else {
  // Use i
}

as opposed to (assuming strtol2 throws an exception on error):

long i;
char* str;
...
try {
  i = strtol2(str, 10);
  // Use i
} catch (...) {
  // Handle error
}

Yes, this is a trivial example, but hopefully it illuminates one aspect of the extreme usefulness of exceptions. The exception based code is easier to write, easier to debug, easier to read. We can expand the usefulness as well. Consider this rewrite:

long i;
char* str;
...
try {
  i = strtol2(str);
  // Use i
} catch (OverflowException e) {
  // Handle overflow case
} catch (ValueError e) {
  // Handle invalid input case
}

Hopefully this makes clear how easy it is to deal with multiple error conditions resulting from a single piece of code without resorting to such clumsiness as passing data around through extra parameters and global variables. And since I can't resist an opportunity for Python advocacy, here's the same program:

try:
  i = int(some_string)
  # Use i
except ValueError, e:
  # Handle error

And if you want to extract the source of the problem:

try:
  i = int(some_string)
  # Use i
except ValueError, e:
  import sys
  print 'ValueError on line %d' % sys.exc_info()[2].tb_lineno

In conclusion: exception handling good.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect

My practical problems with exception handling (4.50 / 2) (#46)
by Hopfrog on Thu Apr 11, 2002 at 03:55:15 PM EST

Yes sure, exception handling may make your code smaller and neater, but I have discovered to date that:

When debugging, if there is an execption I am not explicitly prepared for, it gets caught by my outermost (...), and I don't know what line threw the error.

If you have multiple strtols in your code there, how do you know which one threw the error?

Is it not a large amount of work to always look up what kind of exception class each function you use is going to throw, and add it to your catch block?

When coding, I sometimes make a mistake, and some pointer is uninitialized. If I don't use exception handling, I access violate immediately and fix the error. With try-catch, this error gets caught, as a normal error, and not as a coding error. I then have to go hunting for it.

Hop.

[ Parent ]

Good point. (5.00 / 1) (#51)
by kuran42 on Thu Apr 11, 2002 at 04:09:31 PM EST

It is almost always an error to "catch (...)". Catch only the exceptions you are prepared to explicitly deal with. If others are raised, it is a programming error and should be exposed as such.

Multiple strtol() calls? I don't see the problem. I would do this:

try:
  a, b, c = int(str1), int(str2), int(str3)
except ValueError, e:
  print str(e)

Your exception can and should contain error about what went wrong. If str1 in the above example is "hello, world", the output will be ValueError: invalid literal for int(): hello, world. Does it matter which call raised the exception? Usually not, but if it does you have enough information to find the offending string and fix it.

Is it not a large amount of work to always look up what kind of exception class each function you use is going to throw, and add it to your catch block?

No more work than looking up a return type or a function parameter. In a well designed API, the exceptions will be intuitive anyway. I/O functions raise IOErrors, and so on.

As for uninitialized pointers, you should be catching such errors in a special block or, preferably, not at all. Exceptions are a runtime error mechanism and aren't really appropriate for use in the debugging process. You could also consider picking up a language without pointers ;)

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

I disagree... (4.00 / 1) (#96)
by katie on Fri Apr 12, 2002 at 06:28:00 AM EST

It's almost certainly an error to catch(...) and /not rethrow it/.

Catching all exceptions is a very useful metaphore for "Erk! Something went nasty, I don't know what, I don't care what, but I'm going to do <this> and then let something else deal with it."

It is not always correct for your system to just let go of life and give up. Nor is it always correct for any given subsystem to assume that that's the case. If something else can deal with the error, you might find yourself back here again yet... and now with unclosed stuff, undeallocated stuff...

This is the point of exceptions; handle the error here as best you can, pass it up if not. (...) is a case of "I don't know how to handle any given exception but I know what to do in the general case".

Anybody who catches (...) and doesn't rethrow it is going to have me scowl at them for a while unless they've got a good explaination.


[ Parent ]
Agreed (4.00 / 1) (#106)
by kuran42 on Fri Apr 12, 2002 at 10:05:04 AM EST

Though I think in a properly written program you should always know what exceptions can be thrown. The only case where this isn't true is in a plugin/module system where you are loading and executing unknown code, so you obviously can't know what exceptions it will raise.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]
The Java programmer's perspective... (4.75 / 4) (#58)
by sab39 on Thu Apr 11, 2002 at 04:41:49 PM EST

"What, exceptions in C++ don't give you a backtrace?"

When I have a global catch (Exception e) {} clause in my code, it *always* calls printStackTrace() on e. I can give it an arbitrary OutputStream to print to, including dumping it to a string via ByteArrayOutputStream. I don't necessarily have to display the backtrace to the user, but I can dump it to somewhere useful for debugging purposes.

Oh, and the backtrace includes the line number, which is sufficient the vast majority of the time to figure out which call was the problem.

Oh, and the other Java programmer's comment, "What, the compiler doesn't tell you what uncaught exceptions you have in your code?"

If I make an IO call and forget to either catch (IOException) or (more often) declare that my own method also throws IOException (because after all, if an IO error occurred, what can I really do about it except fail myself?), the compiler tells me explicitly which call can thrown IOException, and that it doesn't have a containing try...catch or a method with a throws clause.

Stuart.
--
"Forty-two" -- Deep Thought
"Quinze" -- Amélie

[ Parent ]
Yes... (2.50 / 2) (#65)
by pb on Thu Apr 11, 2002 at 05:00:40 PM EST

and then when you forget to catch an IOException, the compiler 'conveniently' won't compile your code.

I'd call that a bug, but apparently that's a 'feature'.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Two types of Exceptions (2.00 / 3) (#67)
by Ken Pompadour on Thu Apr 11, 2002 at 06:04:24 PM EST

Checked, and Unchecked. I'd tell you all about them, but suffice to say, once you know what they are, comments like yours look mighty embarassing.

...The target is countrymen, friends and family... they have to die too. - candid trhurler
[ Parent ]
IOException is checked.... (4.00 / 1) (#73)
by mulvaney on Thu Apr 11, 2002 at 07:08:02 PM EST

I'm not sure what you are getting at, because if you fail to catch an IOException, your code would not compile. (Which is a good thing, of course.)

-Mike

[ Parent ]

I know IOException is checked (3.00 / 2) (#77)
by Ken Pompadour on Thu Apr 11, 2002 at 07:24:49 PM EST

But he was clearly implying that all Exceptions were checked in Java.

...The target is countrymen, friends and family... they have to die too. - candid trhurler
[ Parent ]
No... (none / 0) (#97)
by pb on Fri Apr 12, 2002 at 07:44:28 AM EST

I'm just saying that there's no way to *stop* it from being checked, if you didn't write it...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Yes there is (5.00 / 1) (#110)
by sab39 on Fri Apr 12, 2002 at 11:09:53 AM EST

It's actually easy for 90% of cases, if you know that in your own code you never want to have to worry about checked exception warnings from the compiler.

Just pretend that the Java syntax for declaring a method isn't

returnType funcName(args) {

but rather

returnType funcName(args) throws Exception {

and voila! You no longer have to worry about exceptions in 90% of cases.

The remaining 10% are where your code implements an interface that will be called by someone else (eg a servlet or something) that doesn't obey your convention of specifying "throws Exception" on every method. And even then you only need to add a single try - catch in each method implementing that API, and decide what to do with the exception then (one option would be to wrap it in a RuntimeException - ie throw new RuntimeException().initCause(e); in JDK1.4 and above).

Java makes different tradeoffs than you'd like by default, but it isn't quite as inflexible in practice as you seem to think it is.
--
"Forty-two" -- Deep Thought
"Quinze" -- Amélie

[ Parent ]

no... (3.00 / 1) (#129)
by pb on Fri Apr 12, 2002 at 01:39:37 PM EST

I'm not talking about the methods that I create; I likely wouldn't throw anything in the first place. I run into this whilst trying to use Java's own built-in methods...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
That's what I mean... (5.00 / 1) (#131)
by sab39 on Fri Apr 12, 2002 at 01:53:48 PM EST

If you declare all your methods as "throws Exception", then the compiler won't complain about any exceptions that might get thrown by methods you call.

In many cases, that's my approach to exceptions too - especially IOExceptions. I usually don't want to handle them particularly - they're rare and when they *do* happen, something else is probably badly wrong anyway, so I want to just abort. So all I do is declare "throws IOException" on every method up to and including main() (I usually do this by running a dummy compile that I know will fail, looking at which uncaught exception errors I get and adding appropriate throws clauses at that point, so that I don't have to think too much).

Stuart.
--
"Forty-two" -- Deep Thought
"Quinze" -- Amélie

[ Parent ]
beautiful. (3.00 / 1) (#133)
by pb on Fri Apr 12, 2002 at 01:59:41 PM EST

Remind me to do that, then. Of course it makes all my declarations uglier, which is why Java should really have a macro system. But I guess I could run my code through the C preprocessor first, as part of the build... :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Best Software Implementation Practices (4.00 / 2) (#153)
by X3nocide on Fri Apr 12, 2002 at 09:09:46 PM EST

Obviously nobody's forcing you to use Java, but I feel that built-in support for Exceptions is a good thing, because it encourages (or in some cases, enforces ;) ) their use and software development. I'm in college right now, working torwards my degree, and the definite feeling is that CS is not a "real" engineering field. While my Mechanical Engineer roommate would disagree on what it will take to change that, I feel that, in addition to time, Exceptions, Assertions and the like will help elevate the field and work towards a "Best Practices" if you will. How exactly we get an industry relying on a large body of programmers, some not too keen on change, is beyond me. Assertions would be a prickly thing because in my experience a lot of entry level programmers wouldn't know where to draw the lines on preconditions. Hell, I'm not sure professionals would agree on that. But the only way we can get a consensus is with a workable way to test and utilize these things.

So yea, exceptions and assertions suck for tinkering. Obviously nobody wants to deal with that kind of thing when solving a toy problem or writing a game code. But when you're working with 10 other people to write a software package to sell to a company, I would hope that the need for correct and reliable code outweighs the cost of writing a bit more.

pwnguin.net
[ Parent ]

I agree... (5.00 / 1) (#158)
by pb on Sat Apr 13, 2002 at 10:10:20 AM EST

I got my CS degree at an engineering school, and I know I'm not an Engineer; I'd make a lousy one. However, I don't think the field as a whole is suited for it; in some ways, it would be like trying to make applied math or philosophy into an engineering field.

"Software development" is one area of CS that tries to be much more like an engineering field, however, and Java is great for it. Of course, I hated my software development courses, because everything was overly verbose, over-engineered, and rigid, and ended up producing needlessly large, complex projects. Sort of like MULTICS vs. UNIX...

And yes, exceptions are one of those features that is great for software development, but can really suck for coding in general. That's because CS isn't just an engineering field, it's an art (or a trade, if you will), too. And it will only become a 'real' engineering field when all (or most of) the artistry is stomped out of it, and replaced with textbooks and best practices. Java is a good step in this direction, IMO.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Clear benefits (4.66 / 3) (#75)
by RandomPeon on Thu Apr 11, 2002 at 07:16:38 PM EST

I once used class Foo written by somebody else. After being tested, Foo acquired several new exceptions. I didn't find out from documentation or somebody bothering to tell me, I found out from the compiler. Woulda let it slip otherwise.

[ Parent ]
Compilers (3.00 / 1) (#88)
by ucblockhead on Thu Apr 11, 2002 at 10:30:53 PM EST

This is the reason why C++ exceptions are broken. The compiler doesn't check such things.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]
um... (3.00 / 1) (#98)
by pb on Fri Apr 12, 2002 at 07:45:47 AM EST

It's a matter of opinion; C++ is good because it doesn't bug the hell out of me, and then STOP me from doing what I want to do!

Something like this should be a warning, sure; but never an error, IMO. :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Warning (5.00 / 1) (#109)
by ucblockhead on Fri Apr 12, 2002 at 11:07:17 AM EST

I can buy that. But if it isn't at least a warning, it is useless, because all a throw specification does as it stands now is deliberately throw away error information. "throw(int)" purports to mean "this function can only throw ints but what it really means is "this function will convert all non-int exceptions to unexpected_exceptions". Bleah. What's the point?

Here's what really makes it obnoxious: Suppose you have an "out of memory" handler. You put "catch(out_of_mem)" in your main. It does this really nifty cleanup and recover thing. You're really proud of it.

Furthermore, you also can handler out of diskspace conditions. So your main also has "catch(out_of_diskspace)".

Buried in your code there is a library module called "mungedisk" that says "throw(out_of_diskspace)". You didn't write it. You got it from somewhere else. Hell, maybe you don't even have the source. You don't bother handling its exceptions because you've got your nice global handlers out there to do everything.

Unbeknownst to you, "mungedisk" calls a function called "getrecords". Get records doesn't have a throw specification. Unbeknownst to the author of "mungedisk", it can throw an "out_of_mem" exception. This isn't documented.

So what happens? "getrecords" runs memory and throws "out_of_mem". This causes "mungedisk" to throw "unexpected_exception", because it promised not to throw out_of_mem. This exception blasts right by your handlers in main. You could have handled the out of memory condition, but your main doesn't even know about it because the exception information was deliberately thrown out.

What makes this truly obnoxious? If "mungedisk" didn't have any throw specification at all, your program would have worked perfectly. A throw specification never helps. It just makes things worse.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

umm... (4.00 / 1) (#124)
by pb on Fri Apr 12, 2002 at 12:48:07 PM EST

Like I said, make a global handler that always does a stack trace and die on an unhandled exception; have that be a built-in option.

Then, make the compiler generate warnings for throws that might be uncaught.

Finally, run your program; if it dies unexpectedly (and it gives you a line number and a trace here), add a try..catch. :)

And I guess we can't do this in Java...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
that's just it (5.00 / 1) (#125)
by ucblockhead on Fri Apr 12, 2002 at 01:04:08 PM EST

I don't want it to die on an "out of memory" condition! I've written a nifty handler that can deal with "out of memory"! I don't want it to crash on "out of memory"! But I can't prevent it if someone lower down on the chain screws up the specification.

On the ohter hand, if they hadn't bothered with the specification at all, everything would work.

In other words, a throw specification never helps. It can only make things worse.

The way C++ works now, you can't do the "intelligent" thing.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

ahh (4.00 / 1) (#127)
by pb on Fri Apr 12, 2002 at 01:28:10 PM EST

I see your confusion; the reason C++ doesn't bug me is because exceptions are entirely optional. I did not mean to imply that I ever have used their exception methods to do anything. My point is that I'm not required to use them at all.

The way C++ works now, you can write whatever code you want to handle errors, and your program (no matter how semantically flawed) will compile and run. In Java, you can write whatever code you want as long as you put try..catch blocks where the compiler wants them, and pretend to play along like a good little Java programmer; otherwise, your program will never compile...

And I'd like to see your handler for handling an out of memory exception. Does it busy-wait until the user goes to the store and buys some more RAM, or are you not talking about system memory? :)

Cheers.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
not confusion (5.00 / 1) (#136)
by ucblockhead on Fri Apr 12, 2002 at 02:19:15 PM EST

"out of memory" is just an example. The point is that the way the C++ throw specification works, throws that your program could handle can get changed to something entirely different just because so lower level library writer did something different.

Look at the example I gave again. Everyone in the chain does something reasonable. Despite this, an easily handleable exception becomes unhandleable.

Now imagine that, in the same example, the middle level guy had written "// throw(int)" instead of " throw(int)". My program would have worked better. Exception information would not have been lost.

Throw specifications only make sense if they are checked at compile time. Otherwise, they make programming harder, not easier, and are therefore worthless.

I'm not confused. I know how C++ does it. I know how Java does it. The way C++ does it is stupid. Out and out stupid. Java's way of exceptions is, while not perfect, definitely better.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

READ MY LIPS (4.00 / 1) (#139)
by pb on Fri Apr 12, 2002 at 03:14:45 PM EST

I am not using C++ exceptions. I don't have to.

I generally don't want to use Java exceptions either, but in Java, I have to.

This is what I take exception to. ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
catch(...), exception class hierarchy and assert (5.00 / 2) (#115)
by DodgyGeezer on Fri Apr 12, 2002 at 11:41:56 AM EST

Eek: catch(...). IMHO, they should be kept to a bare minimum. They're usually a sign that something went wrong badly and the state of the programme is probably suspect.

You might find this amusing, although we didn't at the time. We worked with a guy who kept getting bug reports from SQA about access violations. His solution was to wrap them all with catch(...). Somebody found out about this sometime later trying to track down some really wierd bugs. He argued endlessly that he'd fixed the problem. (His second endless argument was that it's okay to delete something multiple times in C++!) All he'd done was hide the problem (well, moved it to some other completely unrelated spot), waste SQA's time, and the time of some of the senior guys (they were the only ones who seemed capable of finding the bugs) on the development team.

So yes, there are times when you should use catch(...), but they can be abused, especially by poor programmers.

As for your comment about not knowing who threw the exception: exceptions can be objects, use what that gives you. Using a base exception class with at least a message data member, create sub-classes. You can choose to catch the base class, or the individual ones. This means you can do something like 'throw MyException("Message")'. Although Visual C++ doesn't honour the throw function decoration as per the ANSI C++ (e.g. doesn't call abort() for an unexpected exception), it's something to bear on mind.

Finally your comment about NULL pointers. Yes, forgetting to init them is easily done. Although if you make it a policy to init all your variables, it becomes second nature and reduces the risk. Staying stack based wherever possible reduces this problem (e.g. use std::string instead of char*). Also, make heavy use of assert.h, and perhaps even auto pointers. As I write the implementation to my functions/methods, it's second nature for me to declare pre-conditions, which includes asserting on NULL pointers where appropriate. It can be time-consuming adding pre- and post- conditions after the fact, but when done as part of the skeleton definition, it's quick and easily. After that, sprinkle assert liberally through the body of the code as neeeded.


[ Parent ]
exceptions (5.00 / 1) (#50)
by ucblockhead on Thu Apr 11, 2002 at 04:06:18 PM EST

They have their pluses and minuses. They aren't the be-all and end-all that some people make them out to be. They solve a particular problem well, that is, if you have ten functions, all of which must succeed, it is really nice not to have to check ten different return values.

However, in the opposite case, when you have ten functions, and all must be called regardless of errors in the others, will, suddenly exceptions are annoying as hell. You end up with all these damn try/catch blocks.

Neither is really perfect for all cases. Unfortunately, no language that I know of lets you transparently move between the different error handling methods.

So yeah, it is good sometimes. Except when it isn't.

Additionally, parts of C++'s exceptions are broken. "Throws" specifications are almost entirely useless because they aren't checked at compile time. Because of this, you don't really have a good idea what a function might through. Because of this, you end up doing a lot of "catch(...)", which is annoying.
-----------------------
This is k5. We're all tools - duxup
[ Parent ]

Simple control structure (4.50 / 2) (#54)
by kuran42 on Thu Apr 11, 2002 at 04:17:29 PM EST

# Lots of function definitions
....
....
funcs = (foo, bar, baz, spam, wee, python, be, cool, numberTen)
for i in funcs:
  try:
    i()
  except WhateverError, e:
    print i, ' failed with: ', e

Of course exceptions don't solve every problem, but can be used to solve the vast majority of them.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

Hmm... (4.00 / 1) (#64)
by sab39 on Thu Apr 11, 2002 at 04:58:59 PM EST

try {
  foo();
} finally {
  try {
    bar();
  } finally {
    try {
      baz();
    } finally {
      bop();
    }
  }
}

Okay, so it's not perfect, but no matter what happens, foo(), bar(), baz() and bop() will all get called. The outer code will see only a single exception, if multiple functions fail - probably whichever one happened last. If you want to actually handle exceptions separately for each function, then yes, you need lots of catch blocks there too.

For indentation, you could try something like this, which might be considered uglier or less ugly as a matter of taste:


try {
  foo();
} finally { try {
  bar();
} finally { try {
  baz();
} finally {
  bop();
}}}

Kinda like an else-if ladder but without the syntactic sugar provided by the fact that braces after if and else are optional. This example actually makes a good argument that braces after finally should be optional too:


try {
  foo();
} finally try {
  bar();
} finally try {
  baz();
} finally {
  bop();
}

--
"Forty-two" -- Deep Thought
"Quinze" -- Amélie

[ Parent ]
Minor point (none / 0) (#176)
by codemonkey_uk on Mon Apr 15, 2002 at 07:59:10 AM EST

No finally in C++
---
Thad
"The most savage controversies are those about matters as to which there is no good evidence either way." - Bertrand Russell
[ Parent ]
alternate approaches... (4.00 / 2) (#55)
by pb on Thu Apr 11, 2002 at 04:25:38 PM EST

Your example looks bad (overly verbose) in the first example (in both cases) and better (actually useful) in the second example.

My error functions generally looked something like this...

i = error(strtol(str, &err, 10), "strtol");

...and you'd probably get some out-of-range message if there was an error. I find this to be far simpler in the error catching and reporting department.

As for actual error handling, well, I'd have to write code for it. :) In that case, try..catch comes out to about even (or even superior! (?)) as whatever approach I would come up with to fix the error. Of course, I would in general try to minimize the conditions in which an error like this can occur first, (like trying to avoid "division by zero" errors in the old days) but there are always bugs. :)

I guess when my programs DO have errors, they're generally FATAL errors. :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Could you elaborate? (none / 0) (#56)
by kuran42 on Thu Apr 11, 2002 at 04:31:07 PM EST

i = error(strtol(str, &err, 10), "strtol");

strtol() returns the result, as well as a pair of values possibly indicating an error condition, and optionally sets the second parameter to a new value to indicate an error condition. How can you write a generic function error that handles this as well as other cases?

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]
sure (4.00 / 1) (#59)
by pb on Thu Apr 11, 2002 at 04:42:05 PM EST

I returned on the result if there wasn't an error, and checked errno, and later had a couple of macros to check different return values. Since I didn't do any real error handling (except for exit()) I didn't have to worry about checking anything else. So, yeah, this isn't a perfect error handling solution; for that, you might want function pointers and handlers, and end up with something logically equivalent and just as ugly as try..catch; but for error reporting and debugging, it worked fine for me. :)

#include <errno.h>
#define STRERR 0
#define NEGERR 1
#define serror(r,s) error(r,s,STRERR)
#define nerror(r,s) error(r,s,NEGERR)
error(int r, char *s, char n)
{
if ((errno) && ((n && (r < 0)) || (r == 0))) {
perror(s);
printf("%d\n", errno);
exit(errno);
}
return r;
}
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Interesting (4.50 / 2) (#60)
by kuran42 on Thu Apr 11, 2002 at 04:44:49 PM EST

... though it fails in this particular case for str = "0"

I'll stick with Python. ;)

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

right (4.00 / 1) (#62)
by pb on Thu Apr 11, 2002 at 04:55:48 PM EST

I never had to use strtol(); I guess I'd just want to check errno and not the return value, in that case. (or check for LONG_MIN or LONG_MAX, but why bother when errno always gets set)

Of course, if errno always gets set on errors, I'd never have to check the return value, and I could make another macro that made my error() function not check the return value, which might be good default behavior. Unfortunately, in C, not everything sets errno. :)

But in this case, the equivalent Python wouldn't even run. Because K5 eats your precious spaces, and preserves my blessed braces. ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Ruby has good exception stuff too (4.00 / 1) (#63)
by Freaky on Thu Apr 11, 2002 at 04:58:01 PM EST

begin
    i = Integer(foo)
rescue ArgumentError => e
    puts "Couldn't convert #{foo}: #{e.message}"
end

or even:

i = (Integer(foo) rescue Float(foo) rescue nil)

For me, being able to code more optimistically using exceptions is a signficant boon after having to check every single return code; if return code checks are desired you can easily wrap the exception throwing methods, but to be honest, I can't say I've ever felt the need.



[ Parent ]
brace for it! (2.00 / 1) (#80)
by zephc on Thu Apr 11, 2002 at 07:49:17 PM EST

your brace style offends my sensibilities! (I do however agree with you Python advocacy ;) )

[ Parent ]
great article; comments need help (3.60 / 5) (#44)
by pb on Thu Apr 11, 2002 at 03:51:18 PM EST

Most of the information in this article *is* generic, i.e., all the stuff about choosing your error messages carefully. As to the particular implementation, yes, it's specific to C++, but it's an IMPLEMENTATION, and he TOLD you that he was going to give that as an example.

Specifically to all you Java weenies, voting "-1, learn Java", or "use try..catch" instead (even though the author SPECIFICALLY mentioned that he wasn't going to, and LINKED to information on it just so that YOU would STFU...), I would like to offer this little tidbit.

I have tried to program in Java, and I have found it tedious, verbose, and unacceptable. Specifically, everything. On exceptions, I would much rather have ONE error function that does what I want it to do (report an error, and maybe die) than 8 billion try..catch BLOCKS everywhere and many REQUIRED exceptions. Couldn't they put a little eror-handling into their OWN functions instead of making ME, the programmer, do it for them?

As for the incredibly simple error function put forth in this article, I think it's pretty good. It's a simple solution, and it can't be implemented decently in Java as it is written, which says a lot about Java. If I had to use Java regularly, I would KILL for a macro system in Java. (probably killing the guys who specifically left it out of Java) Ditto for operator overloading. (tell me, which is it when I use + in Java; a macro, or operator overloading; it doesn't have either one!) How could you DESIGN a langauge that's SOO wordy, and then LEAVE OUT THE MACRO SYSTEM ???? It must be Sun's way of pushing their Data Warehousing business...

Oh, and what do I use for error handling? Well, perror() is pretty good, but I tend to write my own error function as a wrapper to that, so I can specify which function had the error, and just wrap functions with it, like so:

error(dostuff("a","b","c"),"dostuff");

This is sort of like try..catch, if try..catch weren't so @#(%)& wordy and required TWO BLOCKS around everything and a few VARIABLES... Yea, that's right, try..catch should be moved into an error() function, or maybe a macro. ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
Indeed (4.50 / 2) (#47)
by kuran42 on Thu Apr 11, 2002 at 03:56:22 PM EST

On exceptions, I would much rather have ONE error function that does what I want it to do (report an error, and maybe die) than 8 billion try..catch BLOCKS everywhere and many REQUIRED exceptions.

The default behavior is to propegate exceptions up from the frame in which they are raised. If they reach the top of the stack, they are printed out and the program terminates.

perror() is ok for reporting errors, but does nothing to correct error conditions. Using exceptions allows you to easily code up a solution for the error that has occured instead of going crying to the user about it.

I don't think the rest of your comments are relevant to error handling. You're right, Java is verbose and clumsy in most cases, but that has nothing to do with the subject at hand :) (That goes for all the "-1, learn java" "weenies" as well)

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]

different usages (5.00 / 3) (#53)
by pb on Thu Apr 11, 2002 at 04:15:56 PM EST

Ahh, but many programs won't compile without having exceptions implemented, and therefore there are system-level Java classes that impose their try..catch blocks on the programmer.

And yes, that's why I had my own error() function, for generally calling perror() and then exit(), similar to a try..catch block, but much shorter.

I agree that try..catch can be handy for error handling, but it is certainly overkill for error reporting, and having to use it everywhere, and all the time, is just painful. (I'd just give up and enclose the whole @#*% thing in a try..catch block; heck, I'd make a mac^H^H^Hclass template...)

And yes, I'm just as guilty as the rest of them; however, there are (sadly) many more of the Java weenies out there who can't shut up than us C weenies; I hope that by providing an alternative point of view, they will see that there IS a reason for this article, and there ARE people who don't use their beloved tool, and then they can shut up out of tolerance; barring that, they can just flame me instead of Hopfrog, which would be less instructive or productive, but more fun, and also lead to less useless top-level comments...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
On errors in type signatures (5.00 / 4) (#61)
by jacob on Thu Apr 11, 2002 at 04:46:42 PM EST

The reason Java forces you to handle certain errors is pretty reasonable, I think. For instance, a large category of exceptions come from input/output routines -- if a read error occurs, it needs to be dealt with, but how can the read function know what it's supposed to do? Anything from terminating the program to just ignoring the error might be appropriate depending on what the program is trying to do. So, Java percolates that decision up to whoever's using the I/O method; the caller can either handle it, or decide it doesn't know what to do either and percolate up again.

All reliable programs that call a read method will, at some point, have to decide what to do when there's a read error. If java didn't enforce that a program must make that decision, all it'd be doing is encouraging unreliable programs. The C family does enough of that, thanks, we don't need any more languages that encourage you not to write code to handle exceptional cases.



--
"it's not rocket science" right right insofar as rocket science is boring

--Iced_Up

[ Parent ]
ok, mostly (none / 0) (#100)
by pb on Fri Apr 12, 2002 at 08:10:01 AM EST

The thing is, I'd be perfectly happy if there was an option I could set in java to die and do a stacktrace on any unhandled exceptions. (or set a global 'catch' somewhere for this) But I can't do it without making try..catch blocks everywhere. Because Java won't let me.

Java is a very feature-rich language, but one of the features they left out (intentionally!) is any flexibility for the programmer. And that might be great for some environments, but it also ensures that I won't enjoy using it. :)

I don't mind handling exceptional cases, but not all of the cases that Java really wants me to handle are all that exceptional. And past that, I don't always want to declare two blocks and a variable to do it. Having the functionality is fine; requiring it is bad, and making the mechanism so verbose is really annoying.

Oh, and this is all my opinion, but it's a fact that there isn't much the programmer can do in Java to make Java more tolerable, besides implementing a Scheme interpreter. :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Mmmh (none / 0) (#105)
by kuran42 on Fri Apr 12, 2002 at 10:01:36 AM EST

If you declare your functions to propegate the exceptions that can be thrown by the functions they call, your exceptions will rise to the top and your program will die. But I still think you're missing the point. Exceptions shouldn't be ignored. They exist to allow you to handle error conditions more gracefully than by dying.

--
kuran42? genius? Nary a difference betwixt the two. -- Defect
[ Parent ]
yeah... (none / 0) (#112)
by pb on Fri Apr 12, 2002 at 11:15:49 AM EST

I agree with you in principle; I object more to the framework than the concept, and I think we're making different points here. I'm not saying that "error handling is bad"; I'm saying that requiring it, in a specific and complex system like Java, for stupid little system-level functions that won't make your program compile, is unreasonable. It's just as unreasonable as making system-level classes (that are essentially "first class" classes) 'final' (where's your inheritance now?).

When I'm using a system-level function that can throw an exception on failure, (but won't for whatever reason) and I'm required to stick a try..catch block around it, I get peeved. And even if I did declare my functions like that, it still has to get caught somewhere. Therefore, I'd at least have to put it in every class, or implement a class that I wrap *everything* with. Fortunately, I haven't had to program in (or around, rather) Java for a while.

Another good example of having to program around Java is when you're trying to get C++ style protected scope in Java. There are very good reasons for it, and protected by itself doesn't do it; neither does private. You have to (at least) make a new package for every protected class hierarchy. And you could make the same argument, that that's a good thing. But is it a good thing that it also requires me to segment my file and directory structure according to which variables I need protected?

It only takes a few silly requirements to make an unworkable (or unreasonably byzantine) system, and Java has quite a few already. I'm only asking for flexibility for the programmer. I wouldn't mind having to explicitly set any number of things to get it done. (like setting an environment variable like "I_KNOW_THIS_IS_BROKEN_SO_BITE_ME_ALREADY" to 1).

The issue I have is that these things are simply not possible by design, and I find that to be arrogance on the part of the developers, that they apparently know which features that no one should use, and require that 5% of us that need them to code around the language...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
tries and catches (none / 0) (#111)
by jacob on Fri Apr 12, 2002 at 11:10:34 AM EST

You don't need try/catch blocks everywhere. At worst you need 'throws' declarations in all your method signatures, but that's not really such a big deal. Of course, there is also such a thing as a Java RuntimeException; they don't need to be explicitly handled at all. They're for the always-lurking errors that can happen but that you don't want to have to specify what to do about every time: when you ask Java to allocate memory but it's run out, when there's a stack overflow, et cetera. They should be used with care, but they are there.

In general, though, I think you're touching on an important, oft-overlooked area of language design: how much control do you give the programmer? On one hand, exposing implementation can allow some neat tricks that wouldn't otherwise be possible; on the other, making your programming interface strict can really help head off programming errors and generally bad code. This problem pops up everywhere in language design: should you have a typesafe language? If yes, should you check for misapplication of types at runtime or compile time? Should you have a macro system? Should it be hygienic? If so, should users be allowed to circumvent that hygiene? And should they be allowed to 'tunnel' values created during macro expansion into program runtime? And so on. Nobody really knows the answers to these questions, but it's fun to think about.



--
"it's not rocket science" right right insofar as rocket science is boring

--Iced_Up

[ Parent ]
great... (none / 0) (#122)
by pb on Fri Apr 12, 2002 at 12:35:29 PM EST

I'll look into the "RuntimeException" the next time I venture into Java-land; if I could use that instead of try..catch at least for starters, that would be great.

And yes, that's exactly what I'm touching on; I like it when the programmer can have total control. I mean, why build an intentionally crippled language in the first place, when you could easily do better? Why require anything when you could easily make it optional? This is where languages like Perl (and even Scheme or C, although they are both more work, but in different ways) really shine.

I think that many people know the answers to these questions, but they don't all agree on them; Java is a great example of making someone else's preferences into requirements.

Java is like emacs vs. vi; most of the time the 'editor war' is about keybindings, but any decent editor should let you change *that*, and if it didn't, and you didn't want to use those keys, then you just wouldn't want to use that editor. But in the case of Java, tons of people use it, and attempt to convince you why you SHOULD use only what they use, and that being horribly restricted is actually the ONE TRUE WAY ...

And yes, you should have a macro system, and it should be optional. And there should be a mechanism for easily expanding someone else's macros, if for some reason you don't like macros... Java programmers without macros don't design elegant class solutions; they cut and paste. And without a system for templates, either, sometimes they really have to...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
there are no optional language features (none / 0) (#149)
by Shpongle Spore on Fri Apr 12, 2002 at 05:10:16 PM EST

You can't just say a programming language should give the user every possibly freedom, because giving one user the freedom to use a feature means taking away another user's freedom to ignore that feature. This is very important whenever more than one developer uses or works on the same code.

C++ exceptions are a good example of this problem. When reading or interfacing with other people's code, the fact that they could have used exceptions means you have to be aware of exceptions and how they work, even if you have no intention of using them yourself, since it's very difficult to be sure any function won't throw an exception. Giving some people the freedom to use exceptions forces everyone to deal with them.

From the other perspective, with Java's exception system, you are forced to declare which exceptions every function may throw. While this may seem like needless extra work, it's a Very Good Thing for anyone trying to read or use your code, because now anyone can tell at a glance that your code only throws the exceptions you've said it will. By making exceptions mandatory, Java also makes them much easier to deal with since it make it possible to know when you must deal with exceptions and when you don't have to.
__
I wish I was in Austin, at the Chili Parlor bar,
drinking 'Mad Dog' margaritas and not caring where you are
[ Parent ]

Familiarity... (none / 0) (#152)
by pb on Fri Apr 12, 2002 at 07:49:51 PM EST

This approach also gives Java a very steep initial learning curve. With the aforementioned C++ program, *of course* you should be familiar with any techniques used in the implementation of a program that you're working with; if it uses exceptions, then you'll need to know exceptions.

However, when Java requires a feature like exceptions to use other features (that have nothing to do with exceptions), it imposes its own learning curve, as well as forcing you in a particular implementation direction, which I find unforgivable.

So, the only question remaining is, what's a good motto for a language like this? "There's Less Than One Way To Do It", maybe? Or "I Wrote This In Java But Now It's Deprecated"? I'll have to ponder that one...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
I posted a "learn java" editorial commen (4.50 / 2) (#66)
by sab39 on Thu Apr 11, 2002 at 05:06:32 PM EST

I didn't mean to imply that Java was the One True Way to do error handling. I merely pointed out that an article hoping to stimulate discussion on error handling ought to be written from a viewpoint of someone who knows more than one tool to do that job.

I *agree* with you that advocating a single solution is a bad thing. That's *why* I didn't like this article - because it talked about a single solution as the One True Way rather than delving into the tradeoffs of several approaches.

And dismissing Exceptions, period, based on C++'s implementation struck me as suggesting that learning a language which did exceptions better might be a good idea. Java is one such, apparently python would be another.

(It seems that in fact my -1 was premature, since this article is stimulating good discussion regardless...)
--
"Forty-two" -- Deep Thought
"Quinze" -- Amélie

[ Parent ]
yes, well... (none / 0) (#102)
by pb on Fri Apr 12, 2002 at 09:04:22 AM EST

There are many ways to do errror handling, and try..catch is indeed one of them, and the author was gracious enough to mention that, even though it was not apparently the focus of his article. I'm sure you (or someone) could write a pretty good article on error handling (with try..catch or otherwise) in different languages, or how it could be implemented.

For example, Perl is flexible enough that you can implement a simple try..catch block structure, syntax and all, in the language. And of course Scheme has call/cc, which could be used to implement exceptions (and many other strange things), if desired. C++ supports try..catch as well as assertions. And all of these languages can be (and often are) implemented in C, so naturally you can build a framework for exceptions in C. Or you could use setlongjmp, goto, function pointers, interrupt hooks, or whatever other handlers your platform provides.

That would be interesting as well, but it wasn't what this article was trying to do, and there's no reason to vote -1 just because an article isn't THE article that you think it should be. Rather, vote -1 because it isn't a very good article by itself, not because there could exist a better article that discusses something completely different, but on the same topic.

And this isn't directed specifically at you, but it is at least directed at anyone who posted a "learn Java comment". :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
I don't think you understand exceptions (none / 0) (#72)
by mulvaney on Thu Apr 11, 2002 at 07:03:35 PM EST

Couldn't they put a little eror-handling into their OWN functions instead of making ME, the programmer, do it for them?
Exceptions are only thrown up when they can't be dealt with on a lower level. For example, if I write a library function that reads in a file, and returns some string from that file, what am I supposed to do if the file isn't there? Should I show a dialog? Generate an email? Print to STDOUT? Log to a file? As the library author, I have no idea, so I throw a FileNotFoundException, and you deal with it.

If you properly throw your exceptions up the chain, its not hard to have a try {} catch (Exception e) {} block which will act very closely to the system outlined by the author.

The rest of your rant isn't very interesting. It sounds more like just another "I can't write this C++ program the same way in Java" whine.

-Mike

[ Parent ]

Silly example (none / 0) (#84)
by walwyn on Thu Apr 11, 2002 at 09:17:38 PM EST

Opening a file is less than 1% of the code in any application. Whilst exceptions have their place they are mostly abused.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass
[ Parent ]
fair enough. (none / 0) (#130)
by pb on Fri Apr 12, 2002 at 01:50:14 PM EST

So what if, like a good programmer, I check for the existence of the file first? Should I still be *required* to write yet another useless try..catch block?

Or what if you *might* throw an exception under several other conditions that I know aren't going to happen. Is it all *really* necessary?

I'm just against the Java mentality that the compiler/language designers know better than the programmers. Maybe creating an idiot-proofed programming language would be a good idea for a "Learn Java in 21 days" webmaster, but not for a "real programmer". ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
No, that's acting like a *bad* programmer... (5.00 / 1) (#140)
by Karellen on Fri Apr 12, 2002 at 03:35:33 PM EST

Don't check to see if you can do something before trying to do it, it's just not worth it and you might be wrong.

Worried about opening a file and you don't know if it exists or not? OK, check for existence first.

What if you don't have read rights on the directory, but do have execute rights? You've just decided a file isn't there that might be.

What if the file exists, but beween your call to stat() and your call to open() your program is swapped out, and another process comes along and removes the file before you get swapped back in? Oooops, you've just encountered an internal "this can't happen" error.

What if you want to write a file, and you check for available space on the disk first? Only you're being run on a dynamically sized ramdisk that would have been expanded to accomodate your file had you just tried to write to it, instead of finding out that there are currently 0 free bytes on it. Or it could be a compressed volume that's unable to work out definitely how much free space there is, because until it tries to compress the data you're about to write, it doesn't know how much space is going to be taken up?

And that just a few examples where file reading/writing can go wrong on a multitasking OS that provides a nice clean interface to programs over the top of something that may be a lot more complex than you've thought. _Especially_ race conditions on a multitasking system. You might think "My program will never get swapped out just _here_, just when another program that's about to delete this file get swapped _in_ - that's just _too_ unlikely.". But why take the risk?

So to protect against all those "this can't happen" conditions, and to handle them gracefully, you need to put error checks in for all these failures anyway. Once you've done that, what's the point in doing your initial checks? _They're_ the wasted code.


K.



[ Parent ]
hehe (none / 0) (#143)
by pb on Fri Apr 12, 2002 at 04:07:43 PM EST

Yes, depending on the environment, file handling can get quite complicated. I ran into a bizarre situation with directory permissions recently, where vi did the unexpected, and went above and beyond the call of duty to write a read-only file...

However, what if you're working on an embedded system? What if there aren't multiple users, or what if you aren't on the network?

My goal here is not to show that there is no need for error handling, but simply to show that there are situations where it is unnecessary. Java does not recognize this, but I do.

But yes, file manipulation might be a bad example to illustrate this point; that's why it wasn't originally my example. :)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Not All Languages Suited to All Tasks (none / 0) (#150)
by Shpongle Spore on Fri Apr 12, 2002 at 05:14:29 PM EST

Film at 11.
__
I wish I was in Austin, at the Chili Parlor bar,
drinking 'Mad Dog' margaritas and not caring where you are
[ Parent ]
I wholeheartedly agree. (none / 0) (#154)
by pb on Fri Apr 12, 2002 at 11:54:55 PM EST

My problem is convincing other people of this... Including, but not limited to, many top-level posters in this article, and the developers of the Java language...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
'Virtual memory exhausted' races (none / 0) (#157)
by pin0cchio on Sat Apr 13, 2002 at 01:07:59 AM EST

Or it could be a compressed volume that's unable to work out definitely how much free space there is, because until it tries to compress the data you're about to write, it doesn't know how much space is going to be taken up?

For safety, most modern compressed filesystems will report the physical free space.

So to protect against all those "this can't happen" conditions, and to handle them gracefully, you need to put error checks in for all these failures anyway.

So how do you handle exiting an application with an unsaved document when you're running out of memory? You can't open a file because allocating an fstream (or its equivalent in any other language) throws a "Virtual memory exhausted" exception from new. And you can't hold say 128 KB of memory and then release it at the last moment because of a possible race: as soon as you release it, another process grabs it before you have a chance to do anything with it.

How does one gracefully handle "Virtual memory exhausted" exceptions?


lj65
[ Parent ]
How does one handle "memory exhausted"? (5.00 / 1) (#159)
by Karellen on Sat Apr 13, 2002 at 10:56:21 AM EST

I don't know.

But whether you do a memfree() (or whatever the call might be to tell you how much memory is left on the system) to determine if you're out of memory, or wait for a malloc() to fail, you're going to have to do something, and it might end up being the same thing.

My point is that why bother with a memfree() before the malloc(), when another process could grab all the available space between your call to memfree() and malloc() in a high-load, almost out of virtual memory situation anyway? You have to check for failure of the malloc() call anyway, so why bother with the memfree()? What's the difference between:

if (memfree() > size)
ptr = malloc(size);
else
ptr = NULL;

if (!ptr)
/* Out of memory */


... and ...

ptr = malloc(size);
if (!ptr)
/* Out of memory */

Apart from the fact that the first version is longer, hence slightly more prone to errors and adds absolutely nothing to your code.

If you want to know if you can do something, try to do it. If you can't do it, you couldn't do it. That's the only real way to tell.

K.


[ Parent ]
Macros? You're kidding, right? (2.00 / 1) (#83)
by DodgyGeezer on Thu Apr 11, 2002 at 09:05:39 PM EST

What's this talk of MACROs? They're horrible, they're a pain, and 95% of the time, there is a better solution. Macros so easily redefine the language and make code harder for other people to maintain. Macros take away some compile time checking. Macros, and especially long ones, are a complete pain in the arse to debug when something goes wrong in them.

I can see the need for the preprocessor, but it's presence alone ensures that it will be abused. In the long-run, time and effort is saved by not having it, especially on big projects. There are too many programmers with too little self discipline.

If you really need a global function, put it in a namespace and in a file with the same name as the namespace. Then it can be found easily. If you need the speed of a macro, make it inline.

But, I guess you like terse code that takes time for others to understand and maintain. Do you even like comments? As for exceptions, anybody who's used them extensively (for exceptional circumstances!) will attest to the fact that they are a very clean way of handling errors. Who cares if there are a lot of try/catch blocks? If it's a problem, break the method into smaller ones.

Jeez, you sound like one of those rabid C programmers who's been forced to use C++, but hasn't yet figured out OOP. (Note: I'm not saying all C programmers are like that). I can't wait to hear your take on the STL. As ever, you have to develop code in a way that is appropriate for the situation.

[ Parent ]
Funny (5.00 / 2) (#85)
by walwyn on Thu Apr 11, 2002 at 09:21:41 PM EST

I can't wait to hear your take on the STL.

You will find very few exceptions in the STL. Plenty of error return values, precious few throws.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass
[ Parent ]

Actually... (4.00 / 1) (#99)
by pb on Fri Apr 12, 2002 at 07:59:49 AM EST

Macros make great shorthand; if you don't like them, use a macro preprocessor, and you'll never have to see them.

Removing a feature because it can be abused is the height of arrogance; some of us might need it for something else. This is also why Java doesn't have a goto, and needs one. Yes, like a preprocessor, 95% of the time you shouldn't use it. But I'm not concerned about that; it has it's place. My question is, what the hell are you supposed to do for the other 5% of the time?? That's right, you're supposed to code AROUND the language...

Yes, inline functions do take away some of the need for macros; however, in Java, you still have to make a *class* for everything. So should I make a shorthand class that has functions that call system functions, and instantiate *that* all the time, even though I *know* the system functions are already there?

I resent your other comments implying what my other programming practices may be, based on your biases. Actually, I like commments; they are very useful for those of us who do write terse code. And I find my terse code easier to understand because it tries very hard not to do anything unnecessary. Therefore, *I* care that there are a lot of try/catch blocks, especially when I am required to implement them for conditions that I could ignore (or die). If they were more optional, or if I could replace them with an optional error-handling function, it would be a little better.

And I've used C++, and I've used OOP, but OOP in C++ is broken, and Java is only slightly better. Also, OOP is not the solution to anything, or even a very good paradigm for doing that much at all.

However, I like the STL, and wish that C++ courses spent more time teaching that than teaching OOP! That's because the implementations of the STL that I've seen are very efficient and provide a lot of useful functionality, and can even be faster than the equivalent C programs (!). (I'm sure I can find the paper on it if you're interested)

Note that this efficiency and usefulness is in direct contrast to the implementation of most Java system libraries; hence, why I like the STL, and why I don't like Java. Anyone who cares about speed or efficiency in their programs will probably come to similar conclusions here.

So, yes, you *do* have to develop code in a way that is appropriate for the situation. So you develop your code, and I'll develop mine, and I'll see you in the programming contests. ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
You're not convincing me (none / 0) (#107)
by DodgyGeezer on Fri Apr 12, 2002 at 10:12:29 AM EST

"Therefore, *I* care that there are a lot of try/catch blocks, especially when I am required to implement them for conditions that I could ignore (or die). If they were more optional, or if I could replace them with an optional error-handling function, it would be a little better. "

What a miss-leading crock!

Silently dying is just bad programming for all but personal projects. Exceptional handling in C++ is optional, and in Java, it's really not very painful, although it is inconsistent (certain exceptions don't have to be caught).

If you're writing good robust code, you have check every return value rather than just assume the call works. I've had to deal with too many time consuming bugs in my career because of this. That costs my employer money, and it's all down to sloppy/lazy programming and coding the happy path. If you are testing every return value, then you end up with code full of if statements and error handling, with the logic dispersed all over the place. With that mind, I would much rather have try/catches as this separates the error handling from the logic and makes the code easier to read. It also helps keep indentation down to a reasonable level, although that's often symptomatic of another coding problem.

If you want you program to die silently, you're going to have to call something like exit. How is that more terse than (in C++) not bothering with try/catch and letting the exception handler completely unwind the stack and shut down for you? It's actually better to have something in the code such as an if statement or try/catch that shuts down the programme as the intention is then clearly communicated to the person might have to debug and/or maintain the code, which could be ones self six or more months later.

Finally, exception handling tends to make it easier to handle the management of resources and clean-up. This is my experience, anyway.



[ Parent ]
(the right) macros can help resource management (5.00 / 2) (#114)
by hading on Fri Apr 12, 2002 at 11:34:04 AM EST

Finally, exception handling tends to make it easier to handle the management of resources and clean-up. This is my experience, anyway.

Perhaps I see differently because when I think of macros I think of Common Lisp macros. These are often used to aid in managing resources and making sure that things get cleaned up properly. The canonical example is the with-open-file macro. Instead of opening a file, doing something with it, and closing it, it allows one to specify the parameters for opening it and what is to be done, and is constructed in such a way as to assure that at the end the file gets closed (with no explicit extra thought on the part of the programmer) - even if an exceptional condition occurs. It's very common to use such macros to manage any resource in basically the same fashion: the macro defintion captures what needs to be done to acquire and release the resource, and after it is written, one can think about using the resource (via the macro) without having to worry about such things. Typically the unwind-protect form is used to assure cleanup even in the presence of exceptional situations. (unwind-protect seems to be one of the truly underappreciated features of Common Lisp - though much less so by Lisp programmers.)



[ Parent ]
Garbage collection? (4.00 / 1) (#116)
by DodgyGeezer on Fri Apr 12, 2002 at 11:50:20 AM EST

I have no experience with Common Lisp, although I fiddle around with my .emacs file once in a while, and did some Scheme at university. Isn't it garbage collected, thus making management and clean-up a lot easier anyway?

[ Parent ]
well, for memory (4.00 / 1) (#118)
by hading on Fri Apr 12, 2002 at 12:07:00 PM EST

The GC handles memory, of course, but much as in Java and the like, you don't really want to rely on it to handle other resource management. Implementations typically have hooks that allow you to run a function when something is GCed (like a finalize, I believe, in Java - I don't know it that well), but since run of the mill garbage collectors are somewhat unpredictable (as far as when they'll choose to reclaim something), it's similarly a bad idea to leave clean up of something like files to that.



[ Parent ]
Indeed (3.00 / 1) (#119)
by DodgyGeezer on Fri Apr 12, 2002 at 12:19:09 PM EST

I've experienced issues under Java before now. It was with a Corba object with a network connection. I can't remember the details now as it was a few years ago, but it carried on doing things (perhaps causing a race condition) after it was supposed to have gone away.

[ Parent ]
My kingdom for design by contract... (4.66 / 3) (#117)
by hawkestein on Fri Apr 12, 2002 at 11:59:35 AM EST

If you're writing good robust code, you have check every return value rather than just assume the call works.

Writing good robust code by checking every return value is, in itself, a tedious an error prone process. It means you have a lot of (possibly nested) if statements, and it can easy to occasionally forget to check a value.

Design by Contract (DBC) is a wonderful idea, and I don't understand why it hasn't been implemented in mainstream languages like C++ and Java. Basically, when you write a method, you document what you expect of your arguments with preconditions (e.g. non-null pointers, values within certain ranges), and you guarantee certain properties of your return values with postconditions if your arguments satisfy the preconditions.

With a language that supports DBC, the language can (optionally) insert code that checks preconditions and postconditions at run-time. That way, it will automatically check if your inputs are valid, and it will automatically check if your return value is valid. This way, you don't have to remember to put in all of your own checks for return values. Another nice bonus is that anybody reading your code can look at a method and see what sort of input it expects and what sort of output it will produce.

I've never personally coded in a language that properly supports DBC. The only one I know of offhand is Eiffel, although I believe there is a project to add it on top of Java. You can fake it a little bit in C/C++ with GNU Nana, and I've used that a little bit, but unfortunately Nana isn't portable: it's gcc-specific.

[ Parent ]

iContract = DBC in Java (4.00 / 1) (#168)
by mercenary on Sun Apr 14, 2002 at 12:54:08 AM EST

The DBC in Java project you mention is "iContract".



[ Parent ]
I'm not trying. (3.00 / 1) (#123)
by pb on Fri Apr 12, 2002 at 12:41:17 PM EST

My goal is not to convince YOU. And I never said I want the program to 'die silently'.

I simply want to not be forced to use try..catch all the time. You can do your little stack traces for unhandled exceptions; simply allow my program to COMPILE.

Is that so hard to understand?

Who says everyone is writing good robust code? Who says we're doing this for a living? Maybe you are, but I might not be.

I think that most error conditions SHOULD kill the program, because you should try to minimize the chance of having these errors in the first place. If you make sure that you always pass your functions sane data first, you might not have out of range errors. If you check to see if a file exists first, you might not have filehandle errors.

But Java assumes that you, the programmer, are incapable of doing so, so whether or not you do, if you don't implement a try..catch for its precious little function (because it knows better than you do here, about your code, oh yes...) then it won't compile your program.

So, yes, error handling might make a lot of things easier. And it might not. But that shouldn't require me to use it. And that is what I object to, mmmkay?
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Let's agree to disagree? (4.00 / 1) (#126)
by DodgyGeezer on Fri Apr 12, 2002 at 01:19:50 PM EST

"I simply want to not be forced to use try..catch all the time. You can do your little stack traces for unhandled exceptions; simply allow my program to COMPILE."

It sounds like you're objecting on the grounds that you're being forced to do it when you haven't before with another language. That's rebellion for rebellion's sake and hardly a strong argument. From my point of view, your argument is the same as the whinges of somebody who's only every used a weakly-typed language complaining that they're having problems getting a strongly-typed language to compile. As profficiency increases in a language, including those language constructs becomes second nature and thus irrelevant. But does add to faster and more reliable development. Really, who wants to spend their time constantly reacting to runtime problems? Comparing Java to C/C++ - the compile times are so much faster that even with some extra compile/edit cycles to handle missing try/catch still results in faster development time, in my experience.

"I think that most error conditions SHOULD kill the program, because you should try to minimize the chance of having these errors in the first place. "

That's rubbish. Most error conditions can be recovered from gracefully. Obviously some like running out of memory are a different matter, but need to be dealt with on a situation by situation basis. So, if you're saving data to disk and the save fails (e.g. it was to network file space and it went offline), do you kill the programme and lose the work, or handle it gracefully and provide another opportunity?



[ Parent ]
I think we already DO disagree :) (none / 0) (#128)
by pb on Fri Apr 12, 2002 at 01:34:27 PM EST

I am objecting that I'm being forced to do it, this is true; I don't see the need for it. It's like the fact that goto is a reserved, but unimplemented, keyword in Java. Or the fact that their graphics API has no function to draw a pixel on a screen... i.e., it's broken. :)

And I can sympathize with the weakly-typed language people; strongly-typed languages can get annoying too. Sure, it can be nice, but it shouldn't always be required. That's why C has pointers. :)

I don't mind reacting to run-time problems; the compiler can't know everything, especially not the Java compiler.

And yes, if a save fails, you should notify the user. I'm not objecting to all error handling, but I am encouraging testing. However, if a number might go out of range in some function, (even though it won't because I know what the data is) my program should still compile.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
We do agree some things though! (none / 0) (#135)
by DodgyGeezer on Fri Apr 12, 2002 at 02:16:18 PM EST

"I am objecting that I'm being forced to do it, this is true; I don't see the need for it. It's like the fact that goto is a reserved, but unimplemented, keyword in Java. Or the fact that their graphics API has no function to draw a pixel on a screen... i.e., it's broken. :) "

Yes, broken APIs are extremely annoying. However, from a quick search it appears that there are ways of setting individual pixels in Java: example, and, Sun docs. Perhaps not fully catering to your needs though.

As for goto, I have never used it since I started serious programming over 10 years ago. Not that I want to sound so arrogant that if I don't need, nor does anybody else ;) The need for it is very low. In fact, I think it's a blight on programming and makes things hard to read and understand. The only code I've every produced with goto in it was generated by Yacc, and that was a pain to debug when it had runtime problems.

"And I can sympathize with the weakly-typed language people; strongly-typed languages can get annoying too. Sure, it can be nice, but it shouldn't always be required. That's why C has pointers. :)"

Having spent at least 95% of my time with strongly typed languages, I dislike the lack of this feature when I have to do something JavaScript or VBScript, or even at times shell script. I'd probably miss it if I went back to my OO roots in Smalltalk. The compiler can catch so many silly mistakes and typos, which saves me time and effort, so why take that ability away?

As for C having pointers... now you're scaring me ;) That's one of the reasons why C++ added four casting operators - it clearly documents in the code the level of risk associated with a pointer cast and assignments. There are a number of times I've seen a runtime problem and found it immediately as I remembered seeing some reinterpret_casts in the code.

"I don't mind reacting to run-time problems; the compiler can't know everything, especially not the Java compiler.

And yes, if a save fails, you should notify the user. I'm not objecting to all error handling, but I am encouraging testing. However, if a number might go out of range in some function, (even though it won't because I know what the data is) my program should still compile."

Agreed: the compiler can't know everything, but I think that it should help as much as it can. It's a very powerful tool and one of the big steps in avoiding bugs, and it's all automated too. I like the fact that Java forces you to deal with mismatched numbers, etc. I've worked on too many C++ projects where there wasn't a zero tolerance towards warnings and size/sign mismatch warnings got ignored, only to cause runtime problems months later. I know, it is possible to get around it in Java by doing a cast, but that does make you pause and think a little, and leaves a documentation trail in the code for other people to see.



[ Parent ]
cool (none / 0) (#138)
by pb on Fri Apr 12, 2002 at 03:13:05 PM EST

Re, the pixel example--there's no way to do it with Graphics or Graphics2D, and I didn't want the overhead of creating a BufferedImage, although I probably could use some buffering.

Goto is great for a lot of things, like implementing exceptions <grin> or function calls, or proper tail recursion. To compensate for the lack of a goto, modern programming languages have many many constructs that simulate their use in limited areas, such as while() and do..while(), for(), switch/case, break/continue, try/catch/throw... But even with an extra, say, 5 - 10 keywords in the language, none of them come close to the full power of a goto.

That having been said, all of those keywords taken together probably will end up replacing 99% of your gotos. But sometimes, you'll still need a goto, and that's what they're there for.

You can replace line labels with function calls to some extent, but this leads to unnecessary overhead in some programming languages that a goto does not impose.

Weakly-typed languages require a certain amount of self-discipline to avoid bugs. First, there need to be functions for checking what type a variable actually is; using pointers in C, you'd have to build this yourself. Perl isn't very good about providing information about its built-in types, but does handle references rather well. But Scheme is great for this. Then you structure your functions around the type you get as input. Having functions (or closures!) as first-class data types is really neat, too; much better than "inner classes".

In a large project with a modular design and a lot of people collaborating, Java is a really appropriate tool; in fact, Java will tend to make large projects out of small projects! But for quick, efficient hacks, Java is horrible; it's painful to use for all sorts of system programming, text manipulation, etc... which is why we have C and Perl. And C++ should provide warnings for things like that, for those who care, whereas Java invents errors where there are none. ;)
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
I'll "race" you (3.00 / 1) (#156)
by pin0cchio on Sat Apr 13, 2002 at 12:58:03 AM EST

If you make sure that you always pass your functions sane data first, you might not have out of range errors.

What if you can't make sure that code written by a third party passes your classes sane data? What if you write something and then come back to it months later? What if you, being human, make a silly mistake? What if some 13-year-old passes your app deliberately non-sane data in an effort to deny service or elevate privileges without authorization?

If you check to see if a file exists first, you might not have filehandle errors.

Your app: file_exists("foo.txt"); => TRUE
Another process: remove("foo.txt");
Your app: fopen("foo.txt", "rb"); => NULL; errno = ENOENT
You have just experienced a "race condition."

if you don't implement a try..catch for its precious little function (because it knows better than you do here, about your code, oh yes...) then it won't compile your program.

That's called safety. You can catch(Exception e) {} if you want to ignore exceptions.


lj65
[ Parent ]
ehh... (none / 0) (#162)
by pb on Sat Apr 13, 2002 at 01:17:39 PM EST

I've already addressed some of this, but...

What if there are no other processes? What if you're running Java on a wristwatch?

If I want to ignore exceptions, I can't use 'catch'; that's an exception-handling function.

What I'm taking issue with here is *language design*.
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Java needs gotos? (none / 0) (#137)
by porkchop_d_clown on Fri Apr 12, 2002 at 03:05:18 PM EST

LoL. Right there you've just told me that your mentally set in your programming ways and unwilling to learn different techniques.

I use "goto abort" type things in C all the time; but Java was designed for different sorts of tasks and with different goals than C. You need to adapt to the strengths and weaknesses of the language your using, not force every language to behave like C.

Or do you write procedural programs in Lisp?


--
Uhhh.... Where did I drop that clue?
I know I had one just a minute ago!


[ Parent ]
I agree, but... (3.00 / 1) (#146)
by pb on Fri Apr 12, 2002 at 04:15:33 PM EST

When you're trying to do certain kinds of programming, Java might be the wrong tool for the job, and that's the kind of programming I enjoy. Implementing a Scheme interpreter in Java is a tricky business, for example, and the only decent one I've seen looks nothing like, say, a Scheme interpreter in Scheme. The simple approach of building on top of the existing language doesn't work in Java, because the existing language doesn't support a lot of desirable constructs...

Procedural programs in Lisp are fun! But no, after writing in Lisp and Scheme for a while, I ended up writing functional programs in C and Perl, and that's even more fun...
---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
Rotfl. (2.00 / 1) (#147)
by porkchop_d_clown on Fri Apr 12, 2002 at 04:40:49 PM EST

And then beat your head into the wall for giggles, no doubt.

Ah, well. To each his own. Personally, I've always adopted the "use the tool appropriate to the task" paradigm.

Of course, there are times when the "appropriate tool" is the one the boss gave you....


--
Uhhh.... Where did I drop that clue?
I know I had one just a minute ago!


[ Parent ]
a sample (none / 0) (#148)
by pb on Fri Apr 12, 2002 at 04:57:26 PM EST

Here's a snippet of Perl code for you...

$descending = sub {my $n = $_[0];
if (!$n){()}
else {($n, &$descending($n - 1))}
};

print "(", join " ", &$descending(10), ")\n";

---
"See what the drooling, ravening, flesh-eating hordes^W^W^W^WKuro5hin.org readers have to say."
-- pwhysall
[ Parent ]
inline with a broken compiler? (none / 0) (#151)
by pin0cchio on Fri Apr 12, 2002 at 06:11:19 PM EST

If you need the speed of a macro, make it inline.

OK, so I need the speed of a macro on my embedded system to write software that must run in (soft) real-time, but the system's expensive proprietary reference C++ compiler is broken with respect to inline, and GCC generates code that's twice as slow. What now?


lj65
[ Parent ]
You're working with broken tools (none / 0) (#169)
by magney on Sun Apr 14, 2002 at 02:23:48 AM EST

There's nothing wrong with doing things to work around broken tools, so long as you understand that that is what you are doing. It's if you don't have broken tools and write code in broken ways that you have a problem.

Do I look like I speak for my employer?
[ Parent ]

Verbosity and utility (4.00 / 1) (#70)
by holycola on Thu Apr 11, 2002 at 06:40:38 PM EST

First off, as a former tech writer and current business analyst, I love seeing an interest in useful software communication from the engineering side.

One comment about the proposed error messages you added is that even the non-technical error message is too long. A simple precis returns something like "[Application] could not play this file because the format is not supported, or the file may be corrupt. Click Help to view supported formats."

My point is that the more verbose an error message, the less likely the user is to read the whole thing. When an error occurs, the user's expectations and stream of thought have been interrupted. The best way to help is to provide the essentials of what will get the person back to realizing his or her intended goal.

Cheers - holycola

-----
This is not a sig.
A request. (4.00 / 2) (#78)
by eann on Thu Apr 11, 2002 at 07:31:03 PM EST

Given a choice, I'd prefer not to transcribe "HRESULT = E_POINTER, object m_pFilestream in function RenderFile(), class CStream*, was NULL. Error progression : Error triggered in RenderFile(), progressed to CStreamWrapper::StartFileRender(), ended at CMyApp::StartFileRender(), with error chain sealed at CMyApp::StartFileRender(). Release Build: 4997, OS=WinXP build 3342, MMDrivers=ReleaseVer 8.11, ParameterFlagDump=8772629" into an email message (or worse, read it over the phone) so you can fix my software.

On the other hand, if it was presented in such a way that I could copy and paste it into an email application (especially if the appropriate email address were listed very near the error code), then at least I can pretend I've done my part to help resolve the situation. I'm still annoyed that the software doesn't work, but I feel like you care, too.

Error handling is important when writing code, but it's at least as important when distributing the results to other people as well.


Our scientific power has outrun our spiritual power. We have guided missiles and misguided men. —MLK

$email =~ s/0/o/; # The K5 cabal is out to get you.


Error messages (3.00 / 1) (#82)
by walwyn on Thu Apr 11, 2002 at 08:50:43 PM EST

Error messages should reserved for problems concerning user input and aide the user in fixing the problem. Examples of how not to do it can be refered to here.

In most cases it is better to stop the user from making the mistake in the first place.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass

Exceptions again (4.80 / 5) (#86)
by Oblomov on Thu Apr 11, 2002 at 10:21:14 PM EST

Not using exceptions is bad advise. Exceptions are an integral - and very important - part of the language. If you don't know how they work or are uncomfortable with their use, you have a big gap in your c++ knowledge.

Except when forced by the environment (e.g. ATL), I haven't used return codes/deep-down dialog boxes in years. Popping up dialog boxes isn't really an option except in the most trivial cases.

A discussion about the disadvantages is available here.
Disagreeing with an article in "More Effective C++" is very dangerous territory but this article is very simplistic, it presents all kinds of problems without any solutions/alernatives. For some of the problems listed, the example is just buggy coding, for others, exceptions are the only solution (please keep in mind that the article is from 1994, the language and the understanding of it has progressed significantly):
  • Normal Execution Paths: These are just bugs
  • Stack::push: top++ should be moved down until the new buffer is allocated.
  • Stack::operator=: move the delete[] statement down until a new buffer has been allocated.
  • T throws in T::T()/T::T(const T&)/operator =: First of all, how are you going to handle a failure without using exceptions (note that a ctor doesn't have a return code), pop-up a dialog box in Stack::push and exit()? If Stack is supposed to be exception neutral, it should be implemented that way. Although not trivial the basic strategy is to try/catch around everything that can throw and properly clean-up by calling the dtors for the elements you have already constructed.
Also, I see this a lot:
_ASSERT(m_pFilestream);
If (m_pFileStream == NULL)
{
FireFatalError(E_POINTER, "Object m_pFilestream in %location was NULL");
return E_POINTER;
}
Either your function supports a NULL pFilestream by returing an E_POINTER and the _ASSERT/FireFatalError should go, or it doesn't and then the if() {} should go.
Sorry for the rant.

Unexpected error (4.00 / 2) (#104)
by Hopfrog on Fri Apr 12, 2002 at 09:59:50 AM EST

You don't expect it to occur, but it just might. So you assert, but still handle the failure. It's called good programming.

Hop.

[ Parent ]

Correction (none / 0) (#175)
by codemonkey_uk on Mon Apr 15, 2002 at 07:53:41 AM EST

What your advocating is called "Defensive Programming", the assertion is redundant, and defensive programming isn't always appropriate.

In fact, a defensive programming style is often used as a crutch for *bad* programming in general.

Consider:

  template<typename T>
    class array{
      public:

      //...

      //reserves memory ahead of time
      inline void reserve(size_t newSize)
      {
        if (capacity() < newSize) reserve_(newSize);
      }

      private:

      //...

      //sets capacity without safty checks first
      void reserve_(size_t newSize)
      {
        assert(capacity()<h;newSize);

        //...

      }

      //...

    };
Should there be an assertion in the first function? No, because the check is part of the service provided. Should there be a conditional in the second (private) function? No, the assertion was there to aid development of that class, and remains there 1) because removing it is a redundent code change, and 2) to catch future mistakes in maintanance of the class. It will never be false during normal program execution, and there is no reasonable responce to it failing..
---
Thad
"The most savage controversies are those about matters as to which there is no good evidence either way." - Bertrand Russell
[ Parent ]
No no no (4.50 / 2) (#134)
by walwyn on Fri Apr 12, 2002 at 02:11:00 PM EST

The first thing I have to do with new starters is wean them away from C++ exceptions. Well actually they are told "You will NOT throw an exception".

Unless the system being programmed has been fully specified with exceptions then you cannot simply bolt them on into new code. Adding exceptions to legacy code is nigh on impossible as is adding additional exception types. You are effectively making an API change which the compiler will not detect.

In the code example above, the assertion is all that is needed. It defines a contract and failure to meet that contract is a bug. Keep exceptions for exceptional conditions not to protect against sloppy programming.

Except when forced by the environment (e.g. ATL), I haven't used return codes/deep-down dialog boxes in years. Popping up dialog boxes isn't really an option except in the most trivial cases.

I have an interview question that involves a piece of code in a low level library that raises an error box. The question is what is wrong with this code? The answer is raising message boxes should be the sole preserve of top level interface code.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass
[ Parent ]

A simple enough solution's been developed. (4.50 / 2) (#165)
by Tau on Sat Apr 13, 2002 at 05:37:36 PM EST

It's called auto_ptr<>. If you change T* v into auto_ptr<T> v then when the containing object is destroyed the memory WILL be freed. I think Meyers even points this little template out in one of his books (probably More Effective C++ itself, I ought to read it again sometime). Moreover auto_ptr<> will even transfer ownership by making itself equal to zero and avoiding multiple link errors.

Well, actually this isn't quite true. auto_ptr<> calls new and delete as opposed to new[] and delete[]. If you want the latter you'll have to use Boost's array_ptr<> but you get the idea.

---
WHEN THE REVOLUTION COMES WE WILL MAKE SAUSAGES OUT OF YOUR FUCKING ENTRAILS - TRASG0
[ Parent ]
printStackTrace (3.00 / 1) (#90)
by bugmaster on Fri Apr 12, 2002 at 12:01:47 AM EST

It seems that you are essentially reimplementing "exception.printStackTrace()" in C++. printStackTrace() is one of the reasons that exceptions are essential: they let you know exactly where your program has failed, and why. In fact, I believe that most of the confusing and unhelpful error messages in Windows are due to the fact that Windows code is written in C, which does not support exceptions. Thus, it is a real pain to deal with error codes, and programmers are tempted to just pretend that errors will never happen.
>|<*:=
printStackTrace() doesn't work in GUIs (none / 0) (#155)
by pin0cchio on Sat Apr 13, 2002 at 12:56:06 AM EST

It seems that you are essentially reimplementing "exception.printStackTrace()" in C++.

But what if your C++ runtime doesn't direct cout anywhere useful? If you can't send the stack trace to any stream other than cout (not even a strstream), specifically to a dialog box on a graphical environment or somewhere else useful on an embedded system, it becomes useless.


lj65
[ Parent ]
printStackTrace()!? (none / 0) (#164)
by Tau on Sat Apr 13, 2002 at 05:13:12 PM EST

What the hell is that? It won't compile, I dont see it in the 'exception' header file anywhere and considering a throw destroys the stack on its way down I'd be interested to know how that would work in the first place

...

*belatedly realizes that this topic is now probably very very dead anyway.. sigh, nothing like Google I guess*

---
WHEN THE REVOLUTION COMES WE WILL MAKE SAUSAGES OUT OF YOUR FUCKING ENTRAILS - TRASG0
[ Parent ]
printStackTrace() (none / 0) (#173)
by wiml on Sun Apr 14, 2002 at 07:56:41 PM EST

I think the earlier poster was saying that the article's author was attempting to implement the functionality of printStackTrace(), which exists in other languages, in C++ (where it doesn't exist). Normally these other languages will accumulate the backtrace information into the exception object as they unwind the stack, but that's an implementation detail.

As for pin0cchio's remark, if your language forces you to use a certain output stream for something like that, then your language is broken. (As an example: Python, IIRC, will let you get a "traceback object" out of an exception, and you can examine this object in detail, get a string representation, etc.)

[ Parent ]

Here's how... (3.50 / 2) (#91)
by J'raxis on Fri Apr 12, 2002 at 12:55:51 AM EST

strerror(errno). Personally, I love errors like Operation now in progress, Not a typewriter, or perhaps my favorite... Error: No error. You and your object-oriented code... feh.

— The Raxis

[ J’raxis·Com | Liberty in your lifetime ]

How I do it. (4.00 / 1) (#92)
by nstenz on Fri Apr 12, 2002 at 12:56:13 AM EST

I code stuff in Microsoft Visual FoxPro, so it probably makes cleaning up messes like this easier... Here's what I have to work with:

ERROR() - error number of last error
MESSAGE() - error message description
MESSAGE(1) - the entire line of code as a string
PROGRAM([nLevel]) - currently executing program, or program executing at nLevel
SYS(16 [, nProgramLevel]) - like program(), but with procedure names and full paths
LINENO() - current line number

When an error occurs, I display the following info:

Error: Variable 'foo' is not found. (#12)
Program: MAIN.FXP
Procedure: SOMEPROCEDURE
Line: 263

Code: STRTOFILE(m.foo,m.path+"foo.txt")

Call Stack:
STARTUP.FXP
Procedure INIT STARTUP.FXP
Procedure MAKEWINDOW MAIN.FXP
Procedure LISTPOPULATE MAIN.FXP
Procedure SOMEPROCEDURE MAIN.FXP
ON ...

From there, the user gets a Retry button and a Quit button. Depending on the error, there may also be an Ignore button, or we can trigger it manually with a keyed code. Our error message has our tech support # right on it, and the information is listed in a useful order and is quite intelligible. The user may not understand what all of it means, but they can at least read it to us.

If necessary, a code can also be keyed in to drop the user to a command interpreter. Pretty much anything that can be run macro-interpreted can be run from there. It's useful occassionally to fix simple problems, but we don't really use it. I added it mainly so we could check the program state and get current variables and such. Those aren't usually necessary though.

The call stack and such is probably much easier to trace in VFP than it is in other languages because VFP is still single-threaded. =(

Condition Handling in Lisp (4.00 / 1) (#113)
by hading on Fri Apr 12, 2002 at 11:22:57 AM EST

I'm not going to go on about it (note that in Lisp we talk about "conditions" rather than "exceptions", but they are similar things). However, some people might be interested in some of Kent Pitman's papers on it (he played a large part of developing it, from what I can tell, and is unquestionably a foremost expert). Some of these papers can be found on his webpage. So anyone interested in learning a little bit about how it's done in CL might take a look.



As a programmer, my ideas. (3.00 / 1) (#144)
by /dev/trash on Fri Apr 12, 2002 at 04:09:54 PM EST

I like to see explicit errors. So when the call comes back from the customer I can track it down in code, more easily.
On the other hand, our QA department hated to see any kind of meaningful error. They said it confused the user and was not very user-friendly.
So we had to use very short and indescriptive error messages. Some were as descriptive as this:
An error has occured, please call your xxxcompany customer support team member.
Of course the support person had no clue what was wrong, and it got sent to me, and I had no clue, as it would be,9 times out of ten,not code I had worked on. So I spent more time than was necessary tracking it down, when I could have been fixing it and *gasp* making the customer happy.

---
Updated 02/20/2004
New Site
As a professional (3.00 / 1) (#163)
by walwyn on Sat Apr 13, 2002 at 02:28:55 PM EST

I'll assert every conditional every algorithm error. In a debug build you get a report of the exact line, method and file that the error occurs in.

The user never has to see some internal error nonesense. They just send in the appropriate data file and operation log and within a minute or two support can direct the problem to the appropriate developer.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass
[ Parent ]

good points but.. (3.00 / 1) (#167)
by /dev/trash on Sat Apr 13, 2002 at 09:38:16 PM EST

They just send in the appropriate data file and operation log and within a minute or two support can direct the problem to the appropriate developer.

You mean the data file the temp deleted and the operation log that they just can't find the tape of?

---
Updated 02/20/2004
New Site
[ Parent ]

Eh! (3.00 / 1) (#170)
by walwyn on Sun Apr 14, 2002 at 01:56:37 PM EST

Strange to say but we have never received a bug report on the basis that the 'temp deleted the CAD file' or 'we can't find our project directory'.
----
Professor Moriarty - Bugs, Sculpture, Tombs, and Stained Glass
[ Parent ]
Error Logs (4.00 / 1) (#171)
by prometheus on Sun Apr 14, 2002 at 02:32:43 PM EST

Typically I divide errors into two categories, sort of like Java. One is for errors which are expected as the normal course, like "File not found" or other errors in input or the environment which the user can do something about (or his administrator).

The other is for unexpected errors, like programming errors, or things we didn't think about when writing the program. For these, I'll typically log a full backtrace to a file (not possible in some languages), and depending on the environment, automatically e-mail the error to a developer. I'll also typically have a special debug mode programs can be run in that logs tons of information as well, and if the backtrace isn't enough to solve the problem, this typically will be.

Of course, most of my development is for things people never run on their computers when there is an interface at all, so most of the time, whatever web interface is using one of my systems will put up a completely uninformative 500 error page directing the user to try again later.
--
<omnifarad> We've got a guy killing people in DC without regard for his astro van's horrible fuel economy
Error handling in object oriented languages | 179 comments (139 topical, 40 editorial, 0 hidden)
Display: Sort:

kuro5hin.org

[XML]
All trademarks and copyrights on this page are owned by their respective companies. The Rest © 2000 - Present Kuro5hin.org Inc.
See our legalese page for copyright policies. Please also read our Privacy Policy.
Kuro5hin.org is powered by Free Software, including Apache, Perl, and Linux, The Scoop Engine that runs this site is freely available, under the terms of the GPL.
Need some help? Email help@kuro5hin.org.
My heart's the long stairs.

Powered by Scoop create account | help/FAQ | mission | links | search | IRC | YOU choose the stories!