Which code is better:
// C++
void handle_message(...some input parameters..., bool& wasHandled)
void set_some_value(int newValue, int* oldValue = nullptr)
// C#
void handle_message(...some input parameters..., out bool wasHandled)
void set_some_value(int newValue, out int oldValue)
or
bool handle_message(...some input parameters...) ///< Returns -1 if message was handled
//(sorry, this documentation was broken a year ago and we're too busy to fix it)
int set_some_value(T newValue) // (well, it's obvious what this function returns, so I didn't write any documentation for it)
The first one doesn't have any documentation, but it doesn't need it. It's a self-documenting code. Output value clearly says what it means, and it's really hard to make a change like this:
- void handle_message(Message msg, bool& wasHandled) {
- wasHandled = false;
- if (...) { wasHandled = true; ...
+ void handle_message(Message msg, int& wasHandled) {
+ wasHandled = -1;
+ if (...) { wasHandled = ...;
When the code uses return values, it's easy to change them and leave the comments broken:
/// Return true if message was handled
- bool handle_message(Message msg) {
+ int handle_message(Message msg) {
...
- return true;
+ return -1;
Most of compilers don't (and can't) check the documentation which is written in comments. Programmers also tend to ignore comments when they edit the code.
So the question is:
if a subroutine has a single output value,
should it be a procedure with well-named self-documenting output parameter,
or should it be a function which returns an unnamed value with a comment describing it?
5 Answers 5
Edit (since the question has changed)
If your method only has one output, of course you return that value.
bool DoStuff(...)
is much more readable when you use it than
void DoStuff(..., out bool success)
Look at the sample usage:
if(DoStuff(....))
vs
DoStuff(..., out success)
if (success)
Also, it allows in-lining and chaining (if the language supports it):
ProcessRequest(GetRequest(reqID))
newval = ProcessString(oldval).Replace("/t","")
Converting these to "out" params would lead to much uglier code, plus it's just wrong.
For 1 return, always use return values.
-
1When I see
bool DoStuff(...)
I have no idea what thatbool
means. Is it "that stuff was good, let's do it again" or "we did enough of that stuff, let's stop" or something else - I don't know. Or maybe it has nothing to do with doing stuff? But withvoid DoStuff(..., out bool success)
orvoid DoStuff(..., out bool dontDoItAgain)
I know what that output value means.Abyx– Abyx06/27/2013 18:05:51Commented Jun 27, 2013 at 18:05 -
4@Abyx - That's a problem with the function name being
DoStuff()
, not with needing a return value.Bobson– Bobson06/27/2013 19:17:55Commented Jun 27, 2013 at 19:17 -
Yeah, if you want to return a failure condition from a function, you need to name it appropriately, so that it's clear what the return value is. DoStuff() returning a boolean would be "true" for success. FailToDoStuff() would return "true" for failure--but that's an entirely different pattern.Richard– Richard06/27/2013 19:30:59Commented Jun 27, 2013 at 19:30
-
Another example: if ProcessRequest() returns a boolean, "true" should always mean "success". Otherwise, it's just nonsensical.Richard– Richard06/27/2013 19:32:08Commented Jun 27, 2013 at 19:32
-
ProcessRequest
returningtrue
isn't a great example. Even if processing failed, it still processed as much as it could for as long as it could.true || false
is ambiguous. Are you trying to derive a conclusion from the input?isActionable
Or are you attempting to transform and record it?wasRecorded
And so on..JustinC– JustinC06/27/2013 22:02:30Commented Jun 27, 2013 at 22:02
Make functions return values. This isn't any sort of functional thing or even relatively modern. If you're unclear about what the function is returning, then your function needs a better name. If your code is doing the wrong things, fix and/or test it.
In a vacuum, the only time that modifying your inputs is acceptable is when that input is this
in object oriented languages or when the return value is taken by someother part of the function call (things like TryParse
).
edit: (because the question changed)
So, again, the question is: if subroutine has single output value, should it be a procedure with well-named self-documenting output parameter, or should it be a function which returns an unnamed value and have a comment describing it?
Neither. It should be a function that is well named so that its output is clear at the call-site. The comment at the declaration only helps in the declaration. Making comments at all the call-sites is inane and unmaintainable.
If you can't make a good function name to describe its output, rethink your design.
-
3@richard - Always (except for the two scenarios above). Frankly, distinguishing between functions, methods and procedures is fairly meaningless. They're all the same thing under the hood, and should follow the same design guidelines.Telastyn– Telastyn06/27/2013 13:19:34Commented Jun 27, 2013 at 13:19
-
2@richard - sorry about the edit. I will use them for the
Try<foo>
cases, but will work to avoid them. Multiple outputs are to be avoided, as they're a code smell for violating Single Responsibility (which I find is just as applicable in function design).Telastyn– Telastyn06/27/2013 13:24:25Commented Jun 27, 2013 at 13:24 -
3@Abyx In C++,
void
functions aren't functions. They are procedures by another name. C++ does have procedures, like you pointed out; it just doesn't single them out by name. So your question remains: "when should we prefer functions to procedures, and why?".Andres F.– Andres F.06/27/2013 15:33:56Commented Jun 27, 2013 at 15:33 -
2@Abyx - And the answer we're giving you is neither.Bobson– Bobson06/27/2013 15:44:43Commented Jun 27, 2013 at 15:44
-
2@abyx - functions that do not return values necessarily have side effects (even if that side effect is on the environment), or else they're meaningless.Telastyn– Telastyn06/27/2013 19:17:14Commented Jun 27, 2013 at 19:17
One reason to write proper functions with return values (as opposed to procedures-in-disguise with "output" variables) is composability.
If you have functions (excuse my pseudocode) void f(in: int x, out: int y)
and int g(in: int x)
, how do you compose them?
You cannot apply g
to f
applied to... say, 42:
int y = g(f(42));
Instead, you need to write:
int x = 0;
f(42, x);
int y = g(x);
Which is definitely clumsier and more error-prone. And composability matters a lot in languages with first-class functions.
Here is another example, hopefully more convincing: I'd like to be able to write
int maxSum = max(sumOfArray(array1), sumOfArray(array2)));
instead of jumping through hoops to achieve the same:
int sum1 = 0;
sumOfArray(array1, sum1);
int sum2 = 0;
sumOfArray(array2, sum2);
int maxSum = 0;
max(sum1, sum2, maxSum);
Hopefully this is a less contrived scenario illustrating why function composition is useful, and why it requires that the function returns its result instead of modifying an "out" variable.
-
3@Abyx yes they will have better names in reality but totally disagree that you don't want to compose functions IRLjk.– jk.06/27/2013 16:29:25Commented Jun 27, 2013 at 16:29
-
2@Abyx I thought it was clear
f
andg
are placeholder names, and not actual function names. Same with variable names. You are arguing minutiae instead of the actual point. Was the downvote yours?Andres F.– Andres F.06/27/2013 16:58:04Commented Jun 27, 2013 at 16:58 -
2@Abyx Haskell is used in real-world software, unlike brainfuck. Are you trolling? Your post was initially language agnostic; if you only care about C++ and C#, mark it as such.Andres F.– Andres F.06/27/2013 17:18:44Commented Jun 27, 2013 at 17:18
-
4If you can't edit the source code, then you can't do anything about preexisting functions, neither adding an output variable nor renaming the function, so the point is moot. If you can edit the source code, then the right course of action is renaming the function and having it return the proper value.Andres F.– Andres F.06/27/2013 17:24:08Commented Jun 27, 2013 at 17:24
-
2@Abyx I disagree about GHC. But if you prefer, here's Scala's map signature:
def map[B](f: (A) ⇒ B): TraversableOnce[B]
fromIterable
. Notice thef
in there? It means "any function with the right signature" ;)Andres F.– Andres F.06/27/2013 17:31:25Commented Jun 27, 2013 at 17:31
You can make your function return something that is obviously a return value. It's also useful to send error message back to the UI when something fails.
ReturnValue myFunction(object newValue)
class ReturnValue
{
public bool Success {get;set;}
public object OldValue {get;set;}
public string Message {get;set;}
}
-
Yes. But it often ends with
MyFunctionReturnValueType
, because you have a lot of different functions with different return types or different meanings of their return values.Abyx– Abyx06/27/2013 16:36:47Commented Jun 27, 2013 at 16:36 -
-
1@Ingo with tuple<string, string> you have no idea what output values are, or what is their order in tuple.Abyx– Abyx06/27/2013 18:20:58Commented Jun 27, 2013 at 18:20
-
@Abyx - Some documentation is unavoidable in any case.Ingo– Ingo06/28/2013 18:53:53Commented Jun 28, 2013 at 18:53
Your question is
If subroutine has single output value, should it be a procedure with well-named self-documenting output parameter, or should it be a function which returns an unnamed value and have a comment describing it?
and the answer to that is neither (or, perhaps, both). If you only have a single output value, you should definitely be using functions, not void
procedures. But the function's name should make it trivial to see what the return value is, so it's not an "unnamed" value and it doesn't need a comment.
Your example is called handle_value(Message msg)
. This is a bad function name, unless you don't care about the results (although see the last example below). Compare it to C#'s default for handling events: void btnCreateTransfer_Click
- the calling function doesn't care what happens inside this function, so we don't return anything, and the name just says that it does a "click" on "btnCreateTransfer".
Depending on what you want to get back out of your handle_value
logic, there's a number of ways you can rename it.
If you just want to check that it is able to be handled (i.e. the message was valid), you can define it as
bool is_valid_message(Message msg)
. The result is clearly true if it is valid, and false if not.- C#:
string.IsNullOrEmpty()
- C#:
If you just want to know whether it was successfully handled, you can name it
bool try_handling_message(Message msg)
, orbool handled_message(Message msg)
, or have a coding convention that anybool
result from an unclear function is expressly success or failure.- C#:
int.TryParse()
- this has anout
parameter for the value, but the function itself tells you whether it was successful.
- C#:
If you need more details from your response, you can use
int get_message_status(Message msg)
. Obviously, you'd need some way to know what theint
result meant. It could have meaning on its own or it could be defined in a constant.- C#:
Stream.Read()
returns the number of bytes read.
- C#:
Finally, you can return a custom type - either an
enum
or an actualclass
. Theenum
works just like theint
, except that it conveys meaning of its own so it gives you the most freedom of naming. Theclass
lets you encapsulate and return as much data as you need.- C#:
DialogResult myform.ShowDialog()
-ShowDialog()
isn't very explicit about what it returns, but because it's aDialogResult
value, you know exactly what it is returning. MessageResult process_message(Message msg)
- You process the message and get the result of doing so. This can also be your originalhandle_message
, but then it would be something likeHandledMessageResult handle_message(Message msg)
.
- C#:
Good code is self-documenting in that the functions, classes, and variables all explain what they are and what they do. If you can name an out
parameter clearly enough to know what's being returned, you can work that into the name of the function. And if you can't name your out
well and you can't name the function well, then you can still name the type of the return usefully. (see ShowDialog()
)
-
oh, WinForms events. Did you notice that all of them have
void
return type? Why do you ignore all the events which have output values? E.g. FormClosing, Drag&Drop events, etc?Abyx– Abyx06/28/2013 09:06:08Commented Jun 28, 2013 at 9:06 -
in
handle_message(out bool wasHandled)
, false value ofwasHandled
doesn't mean "failure" or "success". It means that there is no need to pass message to next handler. Something like "stop event propagation" if we'd use term "event" instead of "message". As for Windows window messages, DOM events, etc. And yeah,wasHandled
has nothing to do with message result and actual message processing. Handler can process message, but returnwasHandled == false
if it wants to pass the message to a next handler.Abyx– Abyx06/28/2013 09:33:39Commented Jun 28, 2013 at 9:33
Explore related questions
See similar questions with these tags.
bool is_valid_message(Message msg)
.bool try_handling_message(Message msg)
.int get_message_status(Message msg)
.MessageResult process_message(Message msg)
.... The first is clearly true if it's a valid message. The second is (by convention) true if the message was handled successfully. The third is clearly some type of status value (which you'd have to look up regardless if it was from areturn
or anout
). The last is very clear about what it is.