%Status vs Other Return Values in Caché ObjectScript Methods

Answers

Errors can be encountered during the normal execution of any code and the cause of those errors is not always known or predictable. Caché has three primary error reporting mechanisms two are passive, meaning the user code is responsible for checking some error indicator and one is active, meaning the error triggers a change in the code path. The passive error reporting, %Status and SQLCODE, both require the user code to check for error conditions after executing some code. The code author must know before hand that these error indicators can be changed by the code that is being executed.

The third mechanism is exceptions. ($ZE with ZTRAP, etc. exist of course). With exceptions, the user code only needs to know that an exception could be thrown and to properly structure the code to deal with that possibility. If Caché user code is not already properly structured then $ZTRAP errors can cause problems so structuring code to deal with exceptions should not be considered a hardship.

The advantage of exceptions over the two passive types is that all three types of errors - %Status, SQLCODE, and exception - can be integrated into a single error handler. The passive checks can be used to construct exceptions using exception class constructors - CreateFromStatus and CreateFromSQLCODE - which can then be thrown. The error handling code lives in the CATCH block and can use the exception instance without further need to parser of otherwise process the exception in order to identify the type of exception, the exact error code and name, error context values, and so on. There is no need to litter your code with a lot of error handling code, detecting different types of errors, formatting, parsing, and so on. Just do it all in one place.

There are also exception methods to cast an exception as %Status or SQLCODE error values should there be a contractual obligation to report errors using one of the passive methods.

Another advantage to using exceptions is that there is virtually no runtime overhead. Also, unless you must return a %Status value (it happens), your code is free to return a useful value from a function, improving code readability and efficiency.

%Status or %Library.Status provides a number of useful capabilities.

Following is documentation from the %Library.Status Class.

The %Status data type class is used to represent an error status code.

Many of the methods provided by the Caché Class Library return error status information using the %Status data type. The include file, %occStatus.INC, contains several macro definitions that are useful in testing the value of an error code in %Status format.

These macros include:

  • $$$ISOK(status:%Status) returns true (1) if the status code status does not represent an error condition.
  • $$$ISERR(status:%Status) returns true (1) if the status code status represents an error condition.

You can get a more detailed description of an error by using the system-provided DecomposeStatus^%apiOBJ(status:%Status,&err,flag) routine.

This routine takes a status code and returns an array of error strings.

For example: 

Set status=obj.%Save() If $$$ISERR(status) Do DecomposeStatus^%apiOBJ(status,.err,"-d") For i=1:1:err Write err(i),! 

A method returning a %Status code representing an error will throw an exception in a client Visual Basic (ActiveX) or Java application.

Right.

But if your method does something meaningful.

E.g. what about the method which checks, if a class exists, like in another neighboring post:

ClassMethod ClassExists(ClassName as %String) as %Boolean {

return ##class(%Dictionary.CompiledClass).%ExistsId(ClassName)

}

Why would you need %Status here?

Since booleans can be only true or false, you usually don't need to know about anything else, so let the caller handle what it should do.

 

ClassMethod AssertClassExists(ClassName As %String) As %Status
{
   if '..ClassExists(ClassName) return $$$ERRROR($$$ClassDoesNotExist, ClassName)
   return $$$OK
}

ClassMethod ClassExists(ClassName as %String) as %Boolean {

return ##class(%Dictionary.CompiledClass).%ExistsId(ClassName)

}

I only return the result when I'm absolutely sure that the method cannot throw any error. Otherwise I follow the rule:

1 - Obligatory arguments first.

2 - Result by second.

2 - Parameter with initial values third.

3 - Rest parameters for last.

(obligatoryParamA,  obligatoryParamB, obligatoryParamC, result,  optionalA, optionalB,  rest...)

My personal preference is %Status: 1 = OK,
0 = something went wrong + standard or handmade Error Code
+ ability of $system.Status.Append(Status) to get a chain of error messages
which gives you the chance to drill down to the source. Which is especially important when
you get it from some embedded action.

The opposite to me is
<ZSOAP>  that leave you alone with no hint what went wrong
or the famous <ZSG> homed in %Save() Method

to pass return value I prefer objects or variables passed byRef od Output
 
The other opposite is SQLCODE (would be better named SQLerror)  0 is OK. Anything else needs action
But that's tradition in SQL world since more than half a century

When you receive a <ZSOAP> or <ZGTW> error, throw it away and take whatever comes into %objlasterror as your final %Status code. For <ZSOAP>, %objlasterror will have the real error description such as the timeout, XML parsing errors, authentication errors, etc. For <ZGTW> errors, %objlasterror will have the complete Java stack trace of the error.

Kind regards,

AS

ISC advises against using %objlasterror in production code.

citation from http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

Using the %objlasterror error status variable
The Test class includes references to %objlasterror, which should be used as a debug resource only (for example, in development code that does not yet report errors properly), so that the underlying problem can be diagnosed and the offending code's error reporting can be corrected. It is appropriate for such code to kill %objlasterror whenever it uses an error status that is an expected condition and not a reportable error.

Thank you Alexey, I corrected the URL in my comment.

you are right on %objlasterror.  and that's how I work it around
but if you want to keep a log and chase sporadic error events this is quite an extra effort.  

I believe this recommendation you linked on our documentation is outdated and wrong. One must use %objlasterror on several instances. Examples:

Java Gateway: http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

.NET Gatewayhttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

SOAP Error Handlinghttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

Caché ActiveX Gatewayhttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

%New() constructorhttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

Using %Dictionary Classeshttp://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=...

There are many instances where %objlasterror is the ONLY place where you can find out what REALLY happened. Not using this information on production is, IMHO, unwise.

Kind regards,

Amir Samary

Yes, like... %DynamicObject and %DynamicArray.

I still don't quite get why to use %objlasterror instead of returning the error.

Method %ToJSON(outstrm As %Stream.Object) As %String
{
if $D(outstrm) {
if $isobject(outstrm) {
if '(outstrm.%IsA("%Stream.Object")) {
throw ##class(%Exception.General).%New("%ToJSON - argument passed is not an instance of %Stream.Object")
}
try {
set ans = $zu(210,27,outstrm)
catch {
do  $$$APPERROR1($$$LASTERROR)  <- This appends the error to %objlasterror and that's it.
}

So we are back to Documentation vs. Reality.
And another excellent example for the importance of this community. 

Agreed. I believe this information should have come inside the main exception. Many developers probably have a hard time debugging errors without the real root cause. But then, the documentation explains how to get to the root cause and even give code snippets on how you should code to always have the root cause (that is on %objlasterror).

Comments

Many guides to "good programming" (in any language) would advise that the return from a function/method should be used for "real" data only, and any "exception" situations should be flagged as an error. While I'm not convinced this is always the best way, I can see the advantages. Code with repeated tests of returned status values can be messy and hard to read, and if the only thing it can do when the status is a fail is to quit out again with a status of "failed", then there is not a lot to be gained.

Mike