Returning a result, vs Exceptions

Just a thought I’d like to suggest which might improve the robustness of Xojo apps by reducing the need to handle exceptions.

And I’ll apologise beforehand for suggesting this, because it was common practice in HyperTalk and SuperTalk - which don’t crash the app when a method fails for something so trivial as passing a an empty (nil) parameter.

At present in Xojo a lot of methods throw exceptions which must be handled, or the app crashes. Not negotiable. It’s annoying, pure and simple.

So every step of the way my code is constantly checking for nil, or exceptions, which I find very tedious. Sure it’s necessary to handle these cases to prevent an app crashing, but I think in many cases there is an easier way.

Let’s take Dictionaries, for example. Dictionary.Remove throws a KeyNotFoundException if the key was not found. Instead of doing that, suppose Dictionary.Remove returns a boolean result - true if it succeeded, false if it failed (ie the KeyNotFoundException).

So your code would look like:

result = Dictionary.Remove(aKey)

… and you can handle the result if you wish, or ignore it, if you don’t care (as is often the case).

But if you don’t handle the exception, the app crashes. Really dumb, in my view.

There are literally hundreds of examples where, if the Xojo syntax was changed to return a boolean result, instead of throwing an unhandled exception and crashing the app, the app won’t crash and the user could at least save their work.

And if you want to go the “extra mile”, and deal with an error, when the code returns a FALSE result, provide an ErrorCode (ie the exception) which can be interrogated and dealt with accordingly.

NB I am already subclassing many of the Xojo classes in this manner and, at the application level, it is just so much simpler.

Now you are debugging and get false as a result but don’t know what happened actually…

Exceptions are meant to give more information about the cause. And sure you can still ignore them or pre-check:

If mydict.hasKey("somekey") then mydict.remove("somekey")

No exceptions given unless mydict is nil, but you can also pre-check this…

Then, instead of booleans, get an integer for an error code.

I agree with you that I prefer results (error codes) rather than exceptions all around. For the record, it was more like you suggest, years ago, but Xojo specifically chose to move many things to the “exception” way :slightly_frowning_face:

Still, to handle exceptions widely (thus allowing your users to save anyway, as long as there’s no exception in the save method), use the app’s UnhandledException event (and return true).

3 Likes

Much more complex since you have to convert this error code to a string to show the user or understand yourself. Having a string and errornumber already makes handing faults better. Exceptions are classes, you can subclass even and add whatever you like. Say you can add a json string that shows the content of an jsonitem for example when it’s content failed. Then have the user send this to you easily.

Eighter way there is NO need to handle exceptions piece by piece, you can add a global exception handler, it just makes debugging harder.

I can see where exceptions are a good thing. But that doesn’t mean all Xojo methods must use them blindly.

How/where do exceptions take their string, anyway? And most of the time, the message property is empty on encountered exceptions.
On Windows, there’s an API to retrieve the error meaning (as a string) for error codes. Oddly enough, I’m not aware how this can be achieved on Mac.

Granted.

1 Like

Trust me, HyperCard and SuperCard had this down pat 30 years ago.

It really CAN be so much simpler.

And yes you could interrogate the errorCode to find out the details - if that matters.

There were two benefits:

  1. The app at least stayed alive so the user could save their work, instead of crashing and burning.

  2. Kiosks in public places, where no-one would respond quickly - it was possible to write apps that were bullet-proof and would stay alive so the user could try again and - ideally - enter the right data.

Trust me - when you have kiosk apps in a museum exposed to children - they will break your code. Guaranteed.

But things changes (not that I like that, but it’s a fact). Xojo (RealBasic) also had heavy use of error codes 20 years ago, and newly available languages (Swift, etc.) are also adopting exceptions.

You could have said “Basic had line numbering down past 40 years ago”, and it even worked fine like that!
My point is, even in software, rules always change. I hate that as most as I can, but I doubt my opinion will halt this process…

1 Like

Personally I use wrappers, in which I put the try/catch stuff so it doesn’t clutter up the rest of the code. That way I can examine the error and decide how to log it. Then I can return a boolean or Nil as appropriate.

3 Likes

The fact that exceptions force you to deal with them is the point. If you make a database query with a mistake, every other query should fail because the data no longer matched your expectations. This is an area where error codes really fall down. If you’re executing a dozen queries, do you really want to write the same error handler a dozen times? Exceptions handle this beautifully.

So this begged the question, why should a database use exceptions, but a folderitem use error codes? So for consistency, more exceptions have been used. They are trivial to deal with and forces you to consider “what if this goes wrong?”

Now, you could argue that Dictionary.Remove for a key that does not exist should not be an error scenario. I could buy into that. Though consider that a typo in your code may ask it to remove the wrong key, and no error would appear to be a success. At least this way, you’ll realize it while testing.

Regardless, for true errors, exceptions are more reliable and generally easier to deal with.

7 Likes

I can’t say as I agree here. I always had the wrappers for database access. Before API2 I has also built into them code to bind strings so I could use prepared statements under API1 with the same ease that one now can under API2. I also embed the logging of errors inside the wrapper, so that back where the wrapper methods are called I can test a boolean for success (when doing ExecuteSQL) or test for Nil (when doing SelectSQL). That keeps the error handling simple in the main line code. Having a try/catch inside the wrapper is no simpler or better than those Xojo methods returning an errorcode.

The only place where an exception makes sense is for an operation for which there is no way to return an errorcode. If you have a block of code doing a calculation which may give you a floating point error anywhere within it, then put all that block in a try/catch.

2 Likes

You have done that with your database. I have as well. But most users would not. With the base database class, it is much simpler to write

Try
  Database.SQLExecute(“BEGIN TRANSACTION;”)
  Database.SQLExecute(“INSERT INTO table (columns) VALUES (values);”)
  Database.SQLExecute(“INSERT INTO table_b (columns) VALUES (values);”)
  Database.SQLExecute(“COMMIT;”)
  Return True
Catch Err As DatabaseException
  Database.SQLExecute(“ROLLBACK;”)
  Return False
End Try

Than it is

Database.SQLExecute(“BEGIN TRANSACTION;”)
If Database.Error Then
  Return False
End If

Database.SQLExecute(“INSERT INTO table (columns) VALUES (values);”)
If Database.Error Then
  Database.SQLExecute(“ROLLBACK;”)
  Return False
End If

Database.SQLExecute(“INSERT INTO table_b (columns) VALUES (values);”)
If Database.Error Then
  Database.SQLExecute(“ROLLBACK;”)
  Return False
End If

Database.SQLExecute(“COMMIT;”)
If Database.Error Then
  Database.SQLExecute(“ROLLBACK;”)
  Return False
End If

Return True

This becomes even more apparent if you want code after the transaction, where a return would abort the entire method, but the exception allows you to abort just the block of code. To do that without exceptions would require something like a Do or While loop with no conditions.

But again, the bigger picture here is that the exception cannot be ignored. If there is an error in your query, you MUST handle it. If you don’t, you might be stuck in a transaction mistakenly left open, records might not have been inserted that you expect, or any number of other things. These are problems that must be handled one way or another, and error codes allow them to be ignored.

7 Likes

There’s a general rule for software: “Don’t fail, but if you fail, don’t fail silently”. So, if HyperTalk and SuperTalk does it, they aren’t a good software for the current standards. Better one user saying that “when I skip this field it crashes” because you failed to handle an edge case, than it silently subtracting one million dollars from an account were you in reality intended to add $10 dollars and when you noticed a stupid silent fail in your code millions in damages were already done.

Exceptions will make you a better developer.

6 Likes

That’s OK if you don’t mind not knowing which SQL statement the failure occurred on, but just want to rollback whatever state has been reached.

I only have about 4 places in my app where I explicity start/commit a transaction and several hundred where I don’t. However, I see that in those 4 places, I’m not doing any explicit rollback, and I should look at those to see whether I should. The SQLite doc seems to imply that closing a database with an open transaction enforces a rollback, but doing things explicitly is always preferable. So this thread (or part of it) has been useful - thanks.

This isn’t just about databases, though.

By your logic just about every event should be treated as an exception that has to be handled.

Here’s another example - the DesktopListBox.

If no item is selected, SelectedRowIndex returns ListBox.NoSelection, yet by your logic it should throw a “listboxNoSelectionException” and crash the app, if nothing was selected. Similarly if the user clicked below the last row in a listbox, it should throw a “clicked belowLastRowException”, and crash. Or drag & drop below the last row should throw a “droppedBelowLastLineException”, and crash.

But nooo… Xojo decided to return -1, instead, and no crash (thankfully).

Perhaps my biggest beef is that unhandled exceptions crash the app, which IMHO is tedious, frankly.

No, because calling properties do not trigger exceptions.

3 Likes

Although your example that uses errors is longer it is possibly more robust than your example that uses exceptions.

Why?

The error code version responds to all errors while the exception version only responds to DatabaseException. Who is to say that the commands can’t raise other exceptions such as an out of memory exception?

The problem with exceptions is that it is a complete guessing game in regard to which commands raise exceptions and what exceptions they raise. There is also no guarantee that the exceptions you are handling today are the correct exceptions in a future version. If the compiler had provided help it would have made the experience much better but as always with Xojo, the most important part of a feature is often forgotten about.

I would have much preferred if Xojo had stayed with error codes but rather than hiding them inside thr class, made them part of the function signature so that you were forced to write some code to handle them.

Then, you can do something in this case. This is not an error nor an exception.
You may Enable (or Disable) a Button,
you may Clear a DesktopTextArea Contents, (that is populated when a valid Row is selected)…
etc.

The documentation for DatabaseException points to the specific database error codes.

My understanding is that the ‘Catch error DatabaseException’ statement collects and provides the error code returned by the database and it further allows for reading the error stack.

For that we have a LastStatement property in SQLDatabaseMBS class for our plug-in.

1 Like