An important aspect of programming, and one that people don’t think of to often, is being able to express the intentions of the code clearly.
Most of the times we programmers get lost in the code we write. It is important to step back and take a look at the code we have written from another person’s perspective. One can say, “But that’s what documentation is supposed to do right? Provide information to others!”. Yes, but that’s not the only way. A good example of that is a situation we often face with functions.
Command Query Separation
Most functions can be generalised into 2 categories.
Command Functions
Functions that act on instructions sent to it and make changes to the underlying data/model. These are commands given to a function and the callee is not expecting a response.
Query Functions
Functions that are used as queries to examine the underlying data/model. The callee is most certainly expecting a response. The function should not modify the underlying model in any way.
It is not common to find a function that does both. In fact, to be consistent command functions must never return a response and a query function must only return a response. This is how ideal separation happens. This way programmers can easily distinguish between Commands & Queries and the objective of the function becomes clear.
The real world however is quite different. Most functions we write are not guaranteed to work the way we want. The likelyhood of an error occurring while a function is being run is very high. This can happen during data validation or some underlying process. Hence, most functions are very likely to return a response indicating the success of a function. This is done using a variety of techniques. It is this feature that throws Command Query Separation for a toss.
In this article we are going to look at some ways in which we can achieve Command Query Separation while still retaining error handling capabilities.
Let us start by looking at the example written below.
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: Value of type double. The result of the division is returned. If the denominator is 0 the function returns 0
*/
double division(const double &firstNumber, const double &secondNumber)
{
if (!floating_point_equality(firstNumber, secondNumber))
{
return firstNumber / secondNumber;
}
return 0.0;
}
The function is a rather simple implementation of division written in C++. It is meant to be a Query function. It immediately becomes clear that we are trying to do 2 things here:
- We are trying to perform a division
- We are trying to check if the division succeeded with the help of a return value
The problem is the fact that the function returns error codes and the result the same way. Any programmer using this function will have to write the code to distinguish between the two.
In this case the error is represented by the value ‘0’. There is no way for the caller to tell if the result of the division was 0 or if there was an error. It gets even worse if the function is a pure command function. A pure command function ideally should not return anything. However we will have to return a value to account for errors.
Here is an example of a Command Function:
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: An error code in the form of an integer. A '0' indicates success. '-1' indicates division by Zero error.
*/
int display_division_of_numbers(const double &firstNumber, const double &secondNumber)
{
if (!floating_point_equality(secondNumber, ZERO))
{
std::cout<<firstNumber<<" divided by "<<secondNumber<<" = "<<(firstNumber / secondNumber)<<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span><std::endl;
return 0;
}
else
{
return -1;
}
}
As we can see the function is a command function. It shouldn't be returning a value. But we are forced to return a value to communicate success.
Let us look at some alternatives.
Returning Error Codes
This approach is the one that was implemented above & has the obvious short comings.
Passing An Error Code variable
This is the next best approach. Instead of returning an error code pass in an object that represents error. After the call is completed, check to see if the error object is nil/NULL. This ensures that the return value always corresponds to an answer and nothing else.
//Potential Error Codes
typedef enum ErrorCodes
{
DIVIDE_BY_ZERO, NaN, NEGATIVE_NUMBER
} ErrorCodes;
//ErrorCode struct. This is the object that contains error information
typedef struct ErrorCode
{
public:
ErrorCode(const ErrorCodes &code, const std::string &description)
:errCode(code), errDescription(description)
{
}
std::string description() const
{
return errDescription;
}
private:
//Holds the code
ErrorCodes errCode;
//holds additional information
std::string errDescription;
} ErrorCode;
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Argument 3: Holds a pointer to the error code object. If the object is nil then there was no error.
Returns: Value of type double. The result of the division is returned.
*/
double division_of_numbers(const double &firstNumber, const double &secondNumber, ErrorCode **error)
{
if (!floating_point_equality(secondNumber, ZERO))
{
return firstNumber / secondNumber;
}
else
{
*error = new ErrorCode(DIVIDE_BY_ZERO, "Attempting to divide by zero");
}
return 0.0;
}
As is obvious from the code above, the return value always corresponds to the answer of the computation. All we have to do is check the ErrorCode pointer to see if it is NULL.
Here is the implementation for the Command Function.
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Argument 3: Holds a pointer to the error code object. If the object is nil then there was no error.
*/
void display_division_of_numbers(const double &firstNumber, const double &secondNumber, ErrorCode **err = NULL)
{
if (!floating_point_equality(secondNumber, ZERO))
{
std::cout<<firstNumber<<" divided by "<<secondNumber<<" = "<<(firstNumber / secondNumber)<<std::endl;
}
else
{
*err = new ErrorCode(DIVIDE_BY_ZERO, "Attempting to divide by Zero.");
}
}
As you can see the function looks like a true Command Function. There is no value being returned. However, the caller still has to check if the Error object is NULL.
Another implementation of this is to use a complex response.
//Potential Error Codes
typedef enum ErrorCodes
{
DIVIDE_BY_ZERO, NaN, NEGATIVE_NUMBER, NO_ERROR
} ErrorCodes;
//Response struct. It will hold either the error or a response.
typedef struct Response
{
public:
Response(ErrorCodes err)
: errCode(err), value(0.0)
{
}
Response(double answer)
: errCode(NO_ERROR), value(answer)
{
}
ErrorCodes getError() const
{
return errCode;
}
double getValue() const
{
if (NO_ERROR == errCode)
{
return value;
}
return 0.0;
}
private:
ErrorCodes errCode;
double value;
} Response;
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: A struct of type Response that either contains the value or the error. The caller must examine the struct before probing the value.
*/
Response* division_of_numbers(const double &firstNumber, const double &secondNumber)
{
if (!floating_point_equality(secondNumber, ZERO))
{
Response *answer = new Response(firstNumber/secondNumber);
return answer;
}
else
{
Response *error = new Response(DIVIDE_BY_ZERO);
return error;
}
}
This approach is a combination of the first 2 approaches. It immediately sends information to the caller that he/she must examine the object for errors before probing for the value. In the earlier example, there is no guarantee that the caller will examine the error. There is no guarantee with this approach either. But at least it simplifies the implementation for the caller and provides an easier mechanism to handle errors without having to manually create error objects.
Something similar is achieved in Swift using Associated Enums.
//Response Enum. It will hold either the error or a response.
enum Response
{
case Error(String)
case Value(Double)
}
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: An Enum Response that either contains the value or the error. The caller must examine the struct before probing the value.
*/
func division_of_numbers(firstNumber : Double, by secondNumber : Double) -> Response
{
if (!floating_point_equality(firstNumber : secondNumber, Equals: ZERO))
{
let answer : Response = Response.Value(firstNumber/secondNumber)
return answer;
}
else
{
let error : Response = Response.Error("Dividing by Zero")
return error;
}
}
Of course the Swift implementation does not need a struct as enums allow us to encapsulate a value in them.
Exceptions & Exception Handling
This is a much better approach. The idea is that you write you function to work as it is normally supposed to. If something goes wrong throw an exception. This approach completely eliminates the need to examine the return value or check to see if there are errors in the response object.
In exception based programming, your code will follow the correct path if there is no problem. If an issue occurs then your code jumps to the part where the error needs to be handled.
Here is an example:
#ifndef MathException_hpp
#define MathException_hpp
#include
#include
#include
namespace MathematicalExceptions {
class MathException : public std::exception
{
public:
virtual const char * what() const throw ();
MathException(const std::string &information);
private:
std::string description;
};
}
#endif /* MathException_hpp */
The next file:
#include "MathException.hpp"
const char * MathematicalExceptions::MathException::what() const throw ()
{
return description.c_str();
}
MathematicalExceptions::MathException::MathException(const std::string &information)
: description(information)
{
}
the next file.
#include <span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
#include
#include "MathException.hpp"
const double ZERO = 0.0;
//Floating point equality checker
/*
Argument 1: Holds the LHS value of type double
Argument 2: Holds the RHS value of type double
Returns: Boolean value
*/
bool floating_point_equality(const double &firstNumber, const double &secondNumber)
{
return fabs(firstNumber - secondNumber) < std::numeric_limits::epsilon();
}
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: A struct of type Response that contains the value.
This function throws an exception of type MathematicalExceptions::MathException
*/
double division_of_numbers(const double &firstNumber, const double &secondNumber)
{
if (!floating_point_equality(secondNumber, ZERO))
{
double answer = firstNumber / secondNumber;
return answer;
}
else
{
MathematicalExpections::MathException exception = MathematicalExceptions::MathException("Attempting to divide by zero");
throw exception;
}
}
int main(int argc, const char * argv[]) {
double numerator = 32.1;
double denominator = 0.0;
double answer = 0.0;
try
{
answer = division_of_numbers(numerator, denominator);
}
catch (MathematicalExceptions::MathException &err)
{
std::cout<<err.what()<<std::endl;
}
return 0;
}
Exceptions are about the closest we can come to achieving Command Query Separation. Anyone using functions that implement the Exception throwing and handling capability is clear as to whether it is a Command function or a query function without compromising on safety and error handling in any way.
Here is an example with Swift.
//Exception Enum. Will be used to throw an exception for mathematical operations
enum MathExceptions : Error
{
case Divide_by_Zero(String)
case NaN(String)
case NegativeNumber(String)
}
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
Returns: The value of type Double. The caller must handle any exceptions that might be thrown.
*/
func division_of_numbers(firstNumber : Double, by secondNumber : Double) throws -> Double
{
if (!floating_point_equality(firstNumber : secondNumber, Equals: ZERO))
{
let answer : Double = firstNumber / secondNumber
return answer;
}
else
{
throw MathExceptions.Divide_by_Zero("Attempting to Divide by zero.")
}
}
let ans : Double = 0.0
do
{
ans = try division_of_numbers(firstNumber: 22.3, by: 0.0)
}
catch let err
{
print(err.localizedDescription)
}
Again, the Swift implementation is rather Straightforward thanks to Associated Enums which conform to the Error protocol.
Here is how the Command function would look with exceptions.
//Division function
/*
Argument 1: Holds the numerator of type double
Argument 2: Holds the denominator of type double
*/
void display_division_of_numbers(const double &firstNumber, const double &secondNumber)
{
if (!floating_point_equality(secondNumber, ZERO))
{
std::cout<<firstNumber<<" divided by "<<secondNumber<<" = "<<(firstNumber / secondNumber)<<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span><std::endl;
}
else
{
MathematicalExceptions::MathException exception = MathematicalExpections::MathException("Attempting to divide by zero");
throw exception;
}
}
This produces a much better implementation of the function, while maintaining the error handling capabilities.
Conclusion
As we can see implementing perfect Command Query Separation is not easy. But by writing our functions properly and by using better error handling such as exceptions it becomes a lot easier to achieve that. Programmers should be able to look at a function & tell if it is a ‘Command’ or a ‘Query’ knowing that error handling is not part of the function signature in any way.
Like this:
Like Loading...