• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Paul Clapham
  • Bear Bibeault
  • Junilu Lacar
Sheriffs:
  • Jeanne Boyarsky
  • Tim Cooke
  • Henry Wong
Saloon Keepers:
  • Tim Moores
  • Stephan van Hulst
  • Tim Holloway
  • salvin francis
  • Frits Walraven
Bartenders:
  • Scott Selikoff
  • Piet Souris
  • Carey Brown

Exceptions vs. Enum error codes

 
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi,

I wonder who has ever battled choosing whether to use exceptions in between, say, library components, or to use enum values instead.

What did you choose?

The benefits of enum is that they are easy to understand and centrally defined and only require a return, now as many APIs would require you to catch some exception, the catching would happen inside the routine that triggers it after which you can cascade it on as an enum.

You might say: don't catch, but just declare as throws, then the original invoking routine can handle it.

But that is difficult to wrap your head around, the original routine might be an event dispatcher, and during the handling of the event, many exceptions might occur, e.g. some JsonParsingException, some user defined exceptions, some IO exceptions, you wouldn't know what exception belonged to which, etc.

If the event handler happened to call something that could create an IO error (e.g. some socket), now the event handler has to rethrow or pass on that IO exception, or change it into a more domain-specific one.

In the end you get the same amount of different exceptions, as you had possible enum return states, but now the original invoking method will not use a switch statement, but rather a big block of catch blocks to handle it all.

Meanwhile, you are arguing with yourself whether or not to use an API-given exception, or to change it into something else, the hunt for existing exception types, etc. etc. etc. while it is all very much easier

to just stick to a library error domain, isn't it?

Now exceptions are useful if you don't or can't have a return value but designing them feels insanely difficult until you can oversee your entire application, while enums would be rather limited in scope and only on the communcation link between caller and callee.

For instance currently some method returns a parse error, a data format error, a template error (programming error), a there-is-no-further-listener error, and a could-not-execute-response error.

But actually, further down, there is also a protocol corruption error.

That's 6 different exceptions if I want all of them to be handled by the calling code (for example for central error logging), of course chaining exceptions could provide detailed error reporting if required, that would be hard to reach with make-do solutions.

Yet I always think exceptions make things unnecessarily difficult (and a pain to catch).

What is your opinion or solution to this dilemma?
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
As an example to this dilemma, consider



This code would generate ClassCastException. However by using JSON-P's "readObject()" method, it would change into JsonException.

Ie. you're parsing something that is no object, asJsonObject() will generate ClassCast, while JsonReader.readObject() would generate JsonException instead for the same error.

In either case, you'd get something like:



To handle it. If you were to define your own application-meaningful exceptions, you'd get



Which seems okay but it is a lot of work for a single call.

So, you create an application-level helper that will make sure the appropriate exceptions are thrown and you let your method just not catch them.



That's convenient and pleasant.

The alternative is really to pass errors in objects and set an error message in them



or set the error message in a thread-bound object.

I think that personally I favour the latter method, but yeah.....
 
Marshal
Posts: 70223
282
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Welcome to the Ranch

That sounds a bit like exit codes in C, which still exist in the System.exit() method. The problem with that is, people can ignore error codes. It is quite possible to forget to query error codes, which means you don't know what has happened.
The notion behind throwing an exception is that it signals to a calling method that the called method has gone wrong. As a general rule of thumb, you shouldn't throw an exception and catch it in the same method; that would only constitute an inefficient replacement for an if‑else. The entire platform is designed on the assumption that errors are signalled by throwing an exception. which is why it is so easy to create subclasses of Exception.
Another thing which worries me is: what return type are those methods going to have? Are those going to be methods which usually do something distant from the current object and have void return type? In that case, you can't return an element of your enum? And what if they are supposed to calculate a result or create a result object and return that? If a void method returns the Error, well, it can't. If a method with a return type doesn't suffer an error, what is it supposed to return, maybe null? We all know it is usually a bad idea to return null if you can possibly avoid it.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Well the secret hidden agenda is that I'm porting this thing from C and that I want to be able to call it from C using the JNA/JNI.

In the sense that I am probably going to drop the C version (I should never have written it).

But that said,

Sure the C idiom was to return booleans or whatever else while error codes were passed through supplied ** parameters.

At least, that is a general C idiom.

I am currently talking event handlers, so they don't have return values.

Other methods that aren't event handlers might simply have enum returns; none of them return anything meaningful otherwise; sending messages uses parameters and callbacks that also use parameters, return objects are rarely used.

But more importantly, I was talking about within-library functions, ultimately none of this is currently exposed except for the fact that I want it to be easy to pass the Java-C barrier.

In other words, even if I use exceptions, I am going to translate them into the enums at the Java-C layer anyway (outside of the library).

So no, there are no "distant objects", and methods that otherwise would be void + exception are simply enum return value.

I am planning to turn them into exceptions anyway at the outer layer, but it's a lot more work than quickly munching up some enums, particularly because you have to mix Java-native exceptions with Application-designed ones OR translate all native ones into application specific ones, which is a lot of work.

However, your main point was that it forces the user to handle errors, that's a contentious point because you can just catch (Exception e) everywhere to get rid of that necessity.

But more importantly, I am the only user of my library components and I'm talking about the inter-component layers.

Thus, while some event handlers radiate outward, it would not be pleasant to ask them to raise exceptions to signal return states.

For instance, suppose a message-receipt handler.



The using application receives a message but asynchronously sends a response, for instance when the user sees the message, the application will simply send a new message, no return value required.

Conversely when the application sends a message, it does need to know about IO errors, but there is no return object; any response comes asynchronously through an event handler.

Thus there are no return methods thus far and there won't be any, that return objects, except for one, which only returns an error report.

I mean I have written a primitive JSON validation method that returns whether something was successfully verified or not.

We have these possible method signatures:







I don't think I have to explain.

Usage:









The latter is obviously the most Java-centric, after which it becomes



But what I would never do was throw exceptions upon validation errors, because then the method would exit and I don't catch all errors.
 
Marshal
Posts: 3180
466
Android Eclipse IDE TypeScript Redhat MicroProfile Quarkus Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

John Dryden wrote:I wonder who has ever battled choosing whether to use exceptions in between, say, library components, or to use enum values instead .. What did you choose?


You don't have to choose one over the other.  You could have your method throw a single exception which includes an enum indicating the specific reason:
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Ah yes, I figured as much, I had already implemented a version of that.

And then didn't use it because I was in a hurry and I let my validation function just do the checking :p.

But indeed, that makes transitioning from Exception to Enum very easy because you can just

 
Campbell Ritchie
Marshal
Posts: 70223
282
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

John Dryden wrote:. . .

That looks very like what one does with SQLExceptions already.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Campbell Ritchie wrote:That looks very like what one does with SQLExceptions already.



Right, so I have good forebears.

It also makes native to Java transitions a lot easier and I always hated the compulsory nature of exceptions to begin with, that you, Mister Campbell, cited as a benefit to Java (the pedantic aspect of it),

but alongside the compulsory "== null" tests everywhere those are the two main gripes I have with java you could say.

I like being able to ignore error codes .

I like error codes too, but I like being able to ignore them more .

I mean when you query the DNS in C, you can:
- discover whether you are trying for IPv6 when you don't have it
- discover whether the remote host is only IPv6 when you don't have it
- two different kinds of DNS failure errors
- host not found
- host has no configured IP address

I guess most of that is irrelevant or meaningless, but still, I have no clue whether information like that can be had in the Java exceptions for that thing.

In fact, Java has a NoRouteToHostException, but when you don't have a route to host, it throws a freaking SocketException instead of that one, at least on this Linux system.

So now I'm testing for "Network not reachable" which, to my conception, is the same thing :p.

Yes, Linux has no separate no route to host thing, It's called E_NETUNREACH.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I don't see how I can edit posts,

but apparently NETUNREACH and HOSTUNREACH are different things.

NoRouteToHostException only triggers on HOSTUNREACH.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
This is arguably a deficiency in the JRE, "UNIX Network Programming: The sockets networking API, Volume 1" notes that the distinction between ENETUNREACH and EHOSTUNREACH is obsolete and should be treated the same.
 
Saloon Keeper
Posts: 22483
151
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The old Exception versus return code debate. It's an old one. Both sides have their pros and cons, and only an ideologue would say that there's only one solution. In fact, if you really want to raise a storm, add checked versus unchecked Exceptions to the mix.

Some random comments on things others have said here:

I don't recommend ignoring responses. It's bad enough on a return code, but it absolutely enrages me when people "eat" Exceptions. Sooner or later, it's almost inevitable that you're going to wish you'd at least logged the information you discarded.

Exceptions are much "fatter" than return codes. That's true in that they incur more overhead, but also in that they can carry payloads. They also carry stack traces. Either or both of those is often a benefit. So my general rule is return codes for high performance, Exceptions where I need more internal context, and hybrid for cases where the norm is low overhead but where there can be (ahem) exceptions.

A general Exception with an "error code" or simular scalar property is something I'd avoid. I have no problem with sub-classing an Exception, but for me at least, it's preferable to be able to use the instanceof operator to determine which particular instance was thrown. I do usually have a fault code in there as well, but I find the instanceof test to be simpler and it requires you to assume less about the internals of the Exception.

People have mentioned error codes in SQLException, but that's an absolutely horrible example. SQL Exceptions are a mish-mash of general fault types with vendor-specific fault types. There's no clear rhyme nor reason to them. Frameworks such as Spring which do support fine-grained and consistent SQL (persistent store) exception types have to do a lot of work to normalize these characteristics. DB/2 SQLExceptions have been my bane more than once - many times the codes are of more value to the coders of the DB/2 server than to the clients.

There is, in fact a distinction in different network exceptions, even if current usage may not always be capable of expressing it. "Network not available" means no networking at all is possible. The NIC is defective, the network stack is down (in traditional Unix, that would be the case for runlevel 2 and below), or there's no carrier. A non-responsive switch, hub, or router is technically in that class as well, but harder to detect.

Host Unavailable is something you could get if the host you're talking to is online, but not responding to you. Generally either there's nothing listening on the target port or it's firewalled.

No Route to host means that the route mechanism cannot find a path to the indicated destination. If a host is down, then it's both unavailable and there's no route to it, so take your pick.

Determining which of the above conditions apply isn't always possible from a single network connection attempt, so whether or not it's worth making the distinction is in large part up to the OS network stack, which has a broader view.

Bear in mind that networking isn't getting any simpler. First we had direct-wiring. Then we added VPNs. Now cloud systems and other advanced technologies have VLANs, often more than one VLAN hermetically isolated from others in the same box, while appearing to be in the same box with selected nodes on the same VLAN. Plus, don't assume TCP/IP or Ethernet/WiFi. It's the general standard, but there's still occasional alternatives.

Finally, while Java is supposed to be write-once/run-anywhere, there's no denying that it lifted a lot of its mechanisms (networking included) straight from Unix. In fairness, so did Microsoft, but many of the warts on the Java networking subsystem are due to this.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tim Holloway wrote:The old Exception versus return code debate. It's an old one. Both sides have their pros and cons, and only an ideologue would say that there's only one solution.



Well I was more interested in practical concerns.

Sooner or later, it's almost inevitable that you're going to wish you'd at least logged the information you discarded.



Sooner can come when the thing at least compiles and runs at all.

So my general rule is return codes for high performance, Exceptions where I need more internal context, and hybrid for cases where the norm is low overhead but where there can be (ahem) exceptions.



Right, that's pleasant information to hear, thank you for that.

I do usually have a fault code in there as well, but I find the instanceof test to be simpler and it requires you to assume less about the internals of the Exception.



Well a public Enum would also be not much different than Exceptions, even if it is more exposure.

My stance is: get it running first, flesh out exceptions later.

"Network not available" means no networking at all is possible.



Network unreachable is a different error.

Host Unavailable is something you could get if the host you're talking to is online, but not responding to you.



No, connection refused is when a host actively sends a TCP "reset" packet, if it doesn't, you get a timeout.

NoRouteToHost is different, means no router on the chain reports being able to reach said IP address.

If a host is down, then it's both unavailable and there's no route to it, so take your pick.



That's not true, a host that's down is a host that's absent, you would not get "no route" when accessing your local (unrouted) subnet for instance.

Absent host is the same as packets being dropped.

But it can still have a route to it.
 
Tim Holloway
Saloon Keeper
Posts: 22483
151
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Two things.

First, the "git 'r Dun" approach makes me (and many of my friends) grind their teeth. Fred Brooks stated in his The Mythical Man-Month that one of the biggest mistakes made on the IBM OS/360 project was that they succumbed to the top-level management pressure to start grinding out code immediately over doing proper design. Failing to think things through is what gets a lot of people put on call nights and weekends.

Throw away diagnostic information especially when the code is still in development and you'll see me get extremely hostile. I have enough grief with mis-placed commas and improper capitalization without deliberate actions. This is because one thing I've learned is that for most projects I'll end up spending more days tracking down trivial errors than I will on the technically-demanding parts of the system.

Second, I can very definitely get a "no route to host" on my network when a host is unavailable and all too frequently do. It has multiple segments. But a lot of network errors get reported in different ways depending on topology, drivers, and operating systems. The conditions I outlined were for an abstract system and even it has fuzzy areas. The Real World is, of course, even worse.

On the subject of setting multiple catch clauses for related but different exceptions, I plead personal experience. It's just generally easier. Case in point: Many things can throw an SQLException, but some of them should be intercepted immediately, and others are typically tossed uphill. You might get an end-of-data exception that would terminate a transfer loop method, for example, and the transfer would clean up and return normally, perhaps to be called again for a new transfer. Or even multiple transfers in a transfer loop that each end with end-of-data exception. But an exception due to a broken link basically ruins the whole setup, so you might as well kill it from the top down instead of futilely wasting time in intermediate code that cannot work. Value tests are fine when everything's on the same level, but not so good when there are multiple levels involved.

But as I said in the beginning, there's no one-size-fits-all solution. It's partly going to be the context you're working in and partly personal preference. There's rarely one "right" answer here. Leave that for politics and religion.



 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tim Holloway wrote:First, the "git 'r Dun" approach makes me (and many of my friends) grind their teeth.



I am not a quick 'n dirty type of person. When in university a friend of mine got a working albeit ugly system running in Visual Basic to great effect, we enjoyed it for months while I stayed stuck in the design phase in Java because I wanted to do it prettier than that.

He used a 3D array to map a game world wasting huge amounts of RAM and disk space but the benefit was that the glitches in the map he would just fix by displacing the user to bridge the corruption.

Failing to think things through is what gets a lot of people put on call nights and weekends.



Well I'm not that kinda person but there's a time and place for everything.

In fact you won't find a more vocal critic of general Linux design philosophies where "Do-ocracy" means "no design whatsoever, just ugly systems".

Linux is so shabbily designed that everything hangs together by sheer volumes of duct tape.

With a retrograde init system that can't schedule shutdown tasks independently of bootup tasks and that academics say is last-decade design, huge complexes of C and C++ code barely working before it is shipped.....

I tend to appreciate your point of view .

As a simple example......

Libaccounts is a glib-based account storage system also used by Qt using libaccounts-qt which just wraps about libaccounts-glib.

This library reads service files from disk to build a model of available services and providers.

When you change a service file on disk, it won't actually reload it because it stores the results in SQLite and *doesn't check for changes*.

-.-

I am familiar with wasting time.

In fact I would argue that 90% of programmer time in Linux is wasted because they battle bad documentation, bad error reporting, and overall badly functioning systems that make their life so much harder that only 10% of their time is actually spent on something useful.

But in a different place that now makes me 30 enemies.

Throw away diagnostic information especially when the code is still in development and you'll see me get extremely hostile.



I think you're talking about concrete systems, it might not be applicable here.

I have enough grief with mis-placed commas and improper capitalization without deliberate actions. This is because one thing I've learned is that for most projects I'll end up spending more days tracking down trivial errors than I will on the technically-demanding parts of the system.



I know, programming is either hugely productive because you are in charge about the system, or hugely time-wasting because you are dealing with other people's code, documentation, or APIs you don't yet understand because they are so complex.

In a good day you can build a house, in a bad day it's lucky if the lights turn on.

Second, I can very definitely get a "no route to host" on my network when a host is unavailable and all too frequently do.



Really?

But a lot of network errors get reported in different ways depending on topology, drivers, and operating systems.



I guess. However, this was and is a simple matter, Linux just has EHOSTUNREACH and ENETUNREACH, apparently they *ought to* amount to the same thing, but the JRE throws one into NoRouteToHost, and the other into SocketException, irrespective of what other fuzziness may be going on, this is a clear "fix".

On the subject of setting multiple catch clauses for related but different exceptions, I plead personal experience. It's just generally easier. Case in point: Many things can throw an SQLException, but some of them should be intercepted immediately, and others are typically tossed uphill. You might get an end-of-data exception that would terminate a transfer loop method, for example, and the transfer would clean up and return normally, perhaps to be called again for a new transfer. Or even multiple transfers in a transfer loop that each end with end-of-data exception. But an exception due to a broken link basically ruins the whole setup, so you might as well kill it from the top down instead of futilely wasting time in intermediate code that cannot work. Value tests are fine when everything's on the same level, but not so good when there are multiple levels involved.



You're saying: let fatal errors cascade up the chain quickly without getting caught by intermediate levels, so that those levels also don't have to deal with, or pass on, those error states.

I agree.

But as I said in the beginning, there's no one-size-fits-all solution. It's partly going to be the context you're working in and partly personal preference. There's rarely one "right" answer here. Leave that for politics and religion.



I appreciate that point of view.

I am really mister error-reporting here though.

I mean, this is the first thing I did in the C version of connecting.



Creating a quick hashtable for...



Linux error reporting is too sparse for me.

I am translating "getaddrinfo" results to my domain specific errors there.

I spent 40% of my time writing the code and 60% the error system :p.
 
John Dryden
Greenhorn
Posts: 14
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator

Tim Holloway wrote:Second, I can very definitely get a "no route to host" on my network when a host is unavailable and all too frequently do. It has multiple segments.



Ah yes, those are ARP failures.



I am currently just throwing Errors for stuff that is programmatic failure anyway.

Sorry for the verbosity.
 
    Bookmark Topic Watch Topic
  • New Topic