If a function has multiple "successful" return values, I'll use if/else to select among them. If a function has a normal return value, but one or more ways that may abnormally exit early, I generally won't use an "else" for the normal path. For example, I think it's far more "natural" to say:
int do_something(int arg1)
{
if (arg1 > MAX_ARG1_VALUE)
return ARG1_ERROR;
... main guts of code here
return 0;
}
than to say:
int do_something(int arg1)
{
if (arg1 > MAX_ARG1_VALUE)
return ARG1_ERROR;
else
{
... main guts of code here
return 0;
}
}
or
int do_something(int arg1)
{
if (arg1 <= MAX_ARG1_VALUE)
{
... main guts of code here
return 0;
}
else
return ARG1_ERROR;
This distinction becomes especially significant if there are multiple things that can "go wrong", e.g.
int do_something(int arg1)
{
if (arg1 > MAX_ARG1_VALUE)
return ARG1_ERROR;
... some code goes here
if (something_went_wrong1)
return SOMETHING1_ERROR;
... more code goes here
if (something_went_wrong2)
return SOMETHING2_ERROR;
... more code goes here
if (something_went_wrong3)
return SOMETHING3_ERROR;
return 0;
}
Nested 'if/else' statements in such cases can get ugly. The most important caveat with this approach is that any cleanup code for early exits must be given explicitly, or else a wrapper function must be used to ensure cleanup.