In the old days, functions just returned a value. This was fine if functions only did simple calculations.
For example, imagine a method that returns a hard-coded array of countries that can never fail.
public String[] getCountryNames()
{
:
}
What if that list was changed to come from a database instead of being hard-coded?
If everything goes well, the list is returned. But if there's a problem reading the database, the caller probably needs to know about it. In this case the programmer decided that a return value of null could show a database error.
/** @return a list of country names or null if there was an error */
public String[] getCountryNames()
{
:
}
Unfortunately there is no place left for an error code or a message to tell the caller what the nature of the problem is.
Worse, the return value is now being used to represent two different kinds of data:
a list of country names
an error flag.
It would be much better to leave the return value for returning values, and throw exceptions for problems, as below.
public String[] getCountryNames() throws DatabaseException
{
:
}
Better yet, the caller does not always need to know if there are more specific kinds of DatabaseException that can be thrown. eg. DatabaseUnavailableException vs DatabaseQueryException. Differentiating between the database being completely unavailable (perhaps to try again in ten minutes) and other problems is possible for calling code, but not mandatory.