Friday, June 29, 2012

Using C# Named Parameters for Code Readability

In C# 4.0, Microsoft introduced a feature called 'Named Parameters', which lets you refer to parameters in a method that you are calling in a syntax like this:
 
public void MyMethod(firstParameter: firstValue, secondParameter: secondValue)

This lets you call the method with parameters in a different order, and also helps to facilitate optional parameters. Aside from these benefits, I've come to like using them to improve code clarity, particularly in unit tests.
 
Lets say you have a method like this one:
 
public decimal CalculateFuelCostPerKm(double kilometresTravelled, double litresInFuelTank, decimal costToFillTank)
 
If you were to call that method, you might do something like this:
 
CalculateCostPerKm(500D, 60D, 72.58M);

However if you are reading that and don't know the parameters, you're going to have to dive deeper to understand it. A way to improve it might be:
 
var kilometresTravelled = 500D;
var litresInFuelTank = 60D;
var costToFillTank = 72.58M;
CalculateCostPerKm(kilometresTravelled, litresInFuelTank, costToFillTank);

Which is much clearer, but is a fair bit of effort. But with C# named parameters you can do this:
 
CalculateCostPerKm(kilometresTravelled: 500D, litresInFuelTank: 60D, costToFillTank: 72.58M);

Simple to do, and clear to read. In fact, this is probably one of the rare situations where C# being written
more like Objective-C is a good thing. Take this example for creating an alert dialog in the iOS SDK:
 
- (id)initWithTitle:(NSString *)title message:(NSString *)message delegate:(id)delegate cancelButtonTitle:(NSString*)cancelButtonTitle otherButtonTitles:(NSString *)otherButtonTitles, ...
 
Which you would call like:
 
[[UIAlertView alloc] initWithTitle:@"Error" message:@"You stuffed up" delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];

Or in C# you might do something like this (ignoring the delegate business):
 
UIAlertView.InitWithTitle("Error", "You stuffed up", myDelegate, "OK", null);

As you can see, the verbose nature of Objective-C makes for clearer code in this instance, however, we could write the C# method like:
 
UIAlertView.InitWithTitle(title: "Error", message: "You stuffed up", delegate: myDelegate, cancelButtonTitle: "OK", otherButtonTitles: null);

Which makes it just as clear.
 
I've found this technique particularly useful in unit tests, which often tend towards using constant values. For instance I might often have a method to setup a certain state in my stubs where I take in the important parameters required, this technique makes this much easier to read.
 
I’d love to hear your thoughts on this technique, either in the comments on this post or via Twitter (@rodh257)

9 comments:

William said...

Instead of:

UIAlertView.InitWithTitle(title: "Error", message: "You stuffed up", delegate: myDelegate, cancelButtonTitle: "OK", otherButtonTitles: null);

I find it easier to read like:

UIAlertView.InitWithTitle(
title: "Error", message: "You stuffed up",
delegate: myDelegate,
cancelButtonTitle: "OK",
otherButtonTitles: null
);

Rod Howarth said...

Good call, I like that!

Sara said...

Hey, Good article. So I get what you're saying and yes it does make things pretty readable. But consider a method with more parameters than that say 7 or 8, does this then detract from the readability when you can just look at intellisence?

Sara :)

George Kinsman said...

The reason named parameters aren't a great idea, and why they held off implementing them for so long is because of versioning issues.

Imagine you call a method in another assembly with a named parameter, and then in a future version that parameter name changes.

Without changing anything in your code, its now broken with that new version where it wouldn't have with a normal method call.

It was really intended for com interop where method calls are far too long to start with.

Rod Howarth said...

I'm not sure I see that as being that much of a negative. If you're creating a public interface why are you renaming the variable names on that interface? I wouldn't think that should happen that often unless you are renaming them for a reason, in which case the people consuming your API would be better off knowing about it through a compiler error.

Take the fuel cost example:

public decimal CalculateFuelCostPerKm(double kilometresTravelled, double litresInFuelTank, decimal costToFillTank)

If you were to rename kilometresTravelled to metresTravelled, then the semantics of what is actually happening in your method have changed, and it will return a different result. In that case, if someone using your library upgrades it they will get a compiler warning tipping them off. However if they aren't using named parameters they won't and instead they'll just get the wrong result. Similarly if you reordered the first two parameters for some silly reason (or perhaps replaced the last parameter with another decimal parameter) the person with named parameters wouldn't have an issue, others would get a wrong result.

Of course, making changes like that to your API is silly, but then again, so is renaming the parameters, if you've got an API out and being publicly consumed, you'd want the method signature to stay consistent. Perhaps if you changed costToFillTank to dollarsToFillTank it would cause a compiler error, but you'd also need to update all online documentation and any blog posts/books about your API will now be out of date.

I don't think I've seen too many examples where people have just renamed their public API parameters without actually changing something important about the method call, and if I was using a library that regularly renamed parameters for no reason I'd question how solid it was. I'm of the opinion that in good API design you shouldn't update the public method signature unless something important has changed. You can change implementation details all you like but keep the method signature the same.

If its code in your own project (ie your unit tests are using named parameters), then VS/Resharper will handle the renames for you, or if its a separate department it wont take long to fix method names and it will alert you to the change.

George Kinsman said...

While what you say is completely true, I think it's more about the fact that previously, parameter names meant nothing while now, they have a great deal of meaning, and are now part of the API itself.

Hard-coding the parameter names of the callee at the callsite to me seems like too much cohesion and an easy cause of confusion.

Jon Skeet has a great example of their evil: http://msmvps.com/blogs/jon_skeet/archive/2009/07/03/evil-code-of-the-day.aspx

As well as part of his book online: http://codebetter.com/2011/01/11/c-in-depth-optional-parameters-and-named-arguments-2/

George Kinsman said...

While what you say is completely true, I think it's more about the fact that previously, parameter names meant nothing while now, they have a great deal of meaning, and are now part of the API itself.

Hard-coding the parameter names of the callee at the callsite to me seems like too much cohesion and an easy cause of confusion.

Jon Skeet has a great example of their evil: http://msmvps.com/blogs/jon_skeet/archive/2009/07/03/evil-code-of-the-day.aspx

As well as part of his book online: http://codebetter.com/2011/01/11/c-in-depth-optional-parameters-and-named-arguments-2/

Rod Howarth said...

Yeah it is important to realize they are a part of the API now, and I think its silly that Microsoft didn't add the warning from Skeets connect issue there as its clearly a mistake to reorder the parameters in the inheritance hierarchy.

But I don't think its bad for an API to include the parameters. Semantically it already does as they describe what you are sending through, and many other types of API's do that, ie REST, SOAP etc.

High cohesion is a good thing though yeah?

They do bring some new considerations but I think their benefits far outweigh their problems, either way we've got them now, so may as well get the benefits of them.

Mark Gibbons said...

Good blog post, cheers Rod! I guess it's like many things in programming, when there is more than one way to do / use something then it's up to the developer to follow good practices and not abuse it.