Argument Name Role Hint by Mark Seemann
This article describes how object roles can by indicated by argument or variable names.
In my overview article on Role Hints I described how making object roles explicit can help making code more object-oriented. One way code can convey information about the role played by an object is by proper naming of variables and method arguments. In many ways, this is the converse view of a Type Name Role Hint.
To reiterate, the Design Guidelines for Developing Class Libraries provides this rule:
Consider using names based on a parameter's meaning rather than names based on the parameter's type.
As described in the post about Type Name Role Hints, this rule makes sense when the argument type is too generic to provide enough information about role played by an object.
Example: unit test variables #
Previously I've described how explicitly naming unit test variables after their roles clearly communicates to the Test Reader the purpose of each variable.
[Fact] public void GetUserNameFromProperSimpleWebTokenReturnsCorrectResult() { // Fixture setup var sut = new SimpleWebTokenUserNameProjection(); var request = new HttpRequestMessage(); request.Headers.Authorization = new AuthenticationHeaderValue( "Bearer", new SimpleWebToken(new Claim("userName", "foo")).ToString()); // Exercise system var actual = sut.GetUserName(request); // Verify outcome Assert.Equal("foo", actual); // Teardown }
Currently I prefer these well-known variable names in unit tests:
- sut
- expected
- actual
Further variables can be named on a case-by-case basis, like the request variable in the above example.
Example: Selecting next Wizard Page #
Consider a Wizard in a rich client, implemented using the MVVM pattern. A Wizard can be modeled as a 'Graph of Responsibility'. A simple example may look like this:
This is a rather primitive Wizard where the start page asks you whether you want to proceed in a 'default' or 'custom' way:
If you select Default and press Next, the Wizard will immediately proceed to the Progress step. If you select Custom, the Wizard will first show you the Custom step, where you can tweak your experience. Subsequently, when you press Next, the Progess step is shown.
Imagine that each Wizard page must implement the IWizardPage interface:
public interface IWizardPage : INotifyPropertyChanged { IWizardPage Next { get; } IWizardPage Previous { get; } }
The Start page's View Model must wait for the user's selection and then serve the correct Next page. Using the DIP, the StartWizardPageViewModel doesn't need to know about the concrete 'custom' and 'progress' steps:
private readonly IWizardPage customPage; private readonly IWizardPage progressPage; private bool isCustomChecked; public StartWizardPageViewModel( IWizardPage progressPage, IWizardPage customPage) { this.progressPage = progressPage; this.customPage = customPage; } public IWizardPage Next { get { if (this.isCustomChecked) return this.customPage; return this.progressPage; } }
Notice that the StartWizardPageViewModel depends on two different IWizardPage objects. In such a case, the interface name is insufficient to communicate the role of each dependency. Instead, the argument names progressPage and customPage are used to convey the role of each object. The role of the customPage is more specific than just being a Wizard page - it's the 'custom' page.
Example: Message Router #
While you may not be building Wizard-based user interfaces with MVVM, I chose the previous example because the problem domain (that of modeling a Wizard UI) is something most of us can relate to. Another set of examples is much more general-purpose in nature, but may feel more abstract.
Due to the multicore problem, asynchronous messaging architectures are becoming increasingly common - just consider the growing popularity of CQRS. In a Pipes and Filters architecture, Message Routers are central. Many variations of Message Routers presented in Enterprise Integration Patterns provide examples in C# where the alternative outbound channels are identified with Role Hints such as outQueue1, outQueue2, etc. See e.g. pages 83, 233, 246, etc. Due to copyright reasons, I'm not going to repeat them here, but here's a generic Message Router that does much the same:
public class ConditionalRouter<T> { private readonly IMessageSpecification<T> specification; private readonly IChannel<T> firstChannel; private readonly IChannel<T> secondChannel; public ConditionalRouter( IMessageSpecification<T> specification, IChannel<T> firstChannel, IChannel<T> secondChannel) { this.specification = specification; this.firstChannel = firstChannel; this.secondChannel = secondChannel; } public void Handle(T message) { if (this.specification.IsSatisfiedBy(message)) this.firstChannel.Send(message); else this.secondChannel.Send(message); } }
Once again, notice how the ConditionalRouter selects between the two roles of firstChannel and secondChannel based on the outcome of the Specification. The constructor argument names carry (slightly) more information about the role of each channel than the interface name.
Summary #
Parameter or variable names can be used to convey information about the role played by an object. This is especially helpful when the type of the object is very general (such as string, DateTime, int, etc.), but can also be used to select among alternative objects of the same type even when the type is specific enough to adhere to the Single Responsibility and Interface Segregation principles.