Imagine you have a method that will return a state marked as success or failure, something that can be easily accomplished by returning true or false if the system supports booleans natively. Now imagine that you programming language does not support booleans, so you have to return another kind of value. What value should you return?
The first consideration is about the type of value: integer or string? While strings are easier to debug (they can easily be printed in the logs or in the console), integers are the best choice since they are easier to compare and require less memory than a string.
Ok, so you hav decided to return an integer; which value should represent the "success"? Well, for a strange convention, in programming languages like C each value different from zero represents a true condition, therefore success, while zero represents a false condition, so failure. In other words you method could look like:
int createFile( char* name ){
// do stuff here
if( ok )
return 1; // success
else
return 0; // failure
}
and you method could be used like the following:
if( createFile( "/tmp/blog.txt" ) == 0 )
perror("Error! Failure!");
There are few problems with this kind of approach. The first is a semantic problem: you usually have only one success condition, and could have different failures. In the above example the success is reached when the file is created, and the failures could range from a permission problem to a disk space limitation. So you have a single success condition and several causes of failures. But in the above code only one failure is available (return value 0) and several success conditions are possibile (each value different from 0), so the semantic is flipped!
This is also the cause why a lot of system calls and library methods exploit a global variable, ERRNO, to set a much detailed error code in case of failure. In other words, in case of failure (0) another variable contains the detail of the failure itself. While this can be beauty and elegant from a syntactic point of view, requires a double effort in writing and testing the code.
There is also another, technical, problem with the above approach: while 0 is represented the same on all machines of all architectures, values different from zero can be represented differently (little endian, big endian?). This is really important when dealing with very low level code, such as those of the operating systems (system calls especially).
For this reason, it is better to return a zero value on success, so that the above method can be rewritten as:
int createFile( char* name ){
// do stuff here
if( ok )
return 0; // success
else
return error; // can value 1 for permission problem, 2 for disk space, ...
}
and the code that uses the method call becomes:
if( createFile( "/tmp/blog.txt" ) != 0 )
perror("Error! Failure!");
So there is not any extra-typing in the caller code, but there is in the callee, since you have to keep a very good documentation in order to explain any return value which meaning has (when not zero, of course). Note also that the use of a global variable, like ERRNO, become obsolete with this approach, since the method can immediatly return a value that describes the exact cause of failure.
As a sidenote, consider that while C takes zero as failure, Bourne Shell and derivates consider zero as success. This is awkward since shells are much higher level interpreters than the C language!
The first consideration is about the type of value: integer or string? While strings are easier to debug (they can easily be printed in the logs or in the console), integers are the best choice since they are easier to compare and require less memory than a string.
Ok, so you hav decided to return an integer; which value should represent the "success"? Well, for a strange convention, in programming languages like C each value different from zero represents a true condition, therefore success, while zero represents a false condition, so failure. In other words you method could look like:
int createFile( char* name ){
// do stuff here
if( ok )
return 1; // success
else
return 0; // failure
}
and you method could be used like the following:
if( createFile( "/tmp/blog.txt" ) == 0 )
perror("Error! Failure!");
There are few problems with this kind of approach. The first is a semantic problem: you usually have only one success condition, and could have different failures. In the above example the success is reached when the file is created, and the failures could range from a permission problem to a disk space limitation. So you have a single success condition and several causes of failures. But in the above code only one failure is available (return value 0) and several success conditions are possibile (each value different from 0), so the semantic is flipped!
This is also the cause why a lot of system calls and library methods exploit a global variable, ERRNO, to set a much detailed error code in case of failure. In other words, in case of failure (0) another variable contains the detail of the failure itself. While this can be beauty and elegant from a syntactic point of view, requires a double effort in writing and testing the code.
There is also another, technical, problem with the above approach: while 0 is represented the same on all machines of all architectures, values different from zero can be represented differently (little endian, big endian?). This is really important when dealing with very low level code, such as those of the operating systems (system calls especially).
For this reason, it is better to return a zero value on success, so that the above method can be rewritten as:
int createFile( char* name ){
// do stuff here
if( ok )
return 0; // success
else
return error; // can value 1 for permission problem, 2 for disk space, ...
}
and the code that uses the method call becomes:
if( createFile( "/tmp/blog.txt" ) != 0 )
perror("Error! Failure!");
So there is not any extra-typing in the caller code, but there is in the callee, since you have to keep a very good documentation in order to explain any return value which meaning has (when not zero, of course). Note also that the use of a global variable, like ERRNO, become obsolete with this approach, since the method can immediatly return a value that describes the exact cause of failure.
As a sidenote, consider that while C takes zero as failure, Bourne Shell and derivates consider zero as success. This is awkward since shells are much higher level interpreters than the C language!
Nessun commento:
Posta un commento