Rethrowing an exception only because you decided to log it using a catch block (meaning the exception has not changed at all) is a bad idea.
One of the reasons we use exceptions, exceptions messages and its handling is so that we know what went wrong and cleverly written exceptions can speed up finding the bug by a great margin.
Also remember, handling exceptions costs way more resources than let's say have an if
, so you shouldn't be handling them all to often just because you feel like it. It has impact on the performance of your application.
It is however good approach to use exception as a mean to mark the application layer in which the error appeared.
Consider the following semi-pseudo code:
interface ICache<T, U>
{
T GetValueByKey(U key); // may throw an CacheException
}
class FileCache<T, U> : ICache<T, U>
{
T GetValueByKey(U key)
{
throw new CacheException("Could not retrieve object from FileCache::getvalueByKey. The File could not be opened. Key: " + key);
}
}
class RedisCache<T, U> : ICache<T, U>
{
T GetValueByKey(U key)
{
throw new CacheException("Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: " + key);
}
}
class CacheableInt
{
ICache<int, int> cache;
ILogger logger;
public CacheableInt(ICache<int, int> cache, ILogger logger)
{
this.cache = cache;
this.logger = logger;
}
public int GetNumber(int key) // may throw service exception
{
int result;
try {
result = this.cache.GetValueByKey(key);
} catch (Exception e) {
this.logger.Error(e);
throw new ServiceException("CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: " + key);
}
return result;
}
}
class CacheableIntService
{
CacheableInt cacheableInt;
ILogger logger;
CacheableInt(CacheableInt cacheableInt, ILogger logger)
{
this.cacheableInt = cacheableInt;
this.logger = logger;
}
int GetNumberAndReturnCode(int key)
{
int number;
try {
number = this.cacheableInt.GetNumber(key);
} catch (Exception e) {
this.logger.Error(e);
return 500; // error code
}
return 200; // ok code
}
}
Let's assume someone has called the GetNumberAndReturnCode
and recieved the 500
code, signaling an error. He would call the support, who would open the log file and see this:
ERROR: 12:23:27 - Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: 28
ERROR: 12:23:27 - CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: 28
The developer then immediately knows which layer of the software caused the process to abort and has an easy way of identifying the issue. In this case it is critical, because Redis timing out should never happend.
Perhaps another user would call the same method, also recieve the 500
code, but log would show the following:
INFO: 11:11:11- Could not retrieve object from RedisCache::getvalueByKey. Value does not exist for the key 28.
INFO: 11:11:11- CacheableInt::GetNumber failed, because the cache layer could not find any data for the key 28.
In which case the support could simply respond to the user that the request was invalid because he is requesting a value for a non-existent ID.
Summary
If you are handling exceptions, make sure to handle them in the correct way. Also make sure your exceptions include the correct data/messages in the first place, following your architecture layers, so the messages will help you identify a problem which may occur.