Yesterday we had a lively debate across PowerShell experts how to best deal with parameter default values in PowerShell code. I’d like to share with you the key points, and why and how we evolved one of the rules in PSSharper in ISESteroids 126.96.36.199 to best reflect this.
Do you know your default values?
When you define parameters for a script or a function, you probably use a param() block and define the parameters, for example like this:
param ( $Path, $Filter )
When you do this, the parameters are optional by default, so a user may or may not submit them. This is a point that often slips when creating PowerShell code under time pressure. You define the parameters for all arguments your code needs, not necessarily taking into consideration that a user may simply not submit an argument for your parameters. Which raises the question: what exactly are the default values when a user does not control them?
How PowerShell picks Auto-Default Values
When the user assigns no value to an optional parameter, PowerShell initializes it with an automatic default value. This automatic default value depends on the datatype. If your parameter was a [String], the default is an empty string. If it was an [Int], it is the value 0. A [Bool] gets initialized to $false, and most other types receive a $null.
A true PowerShell professional may know all of this by heart, and count on it to produce short and concise code. For many others, it is not always obvious what the actual default value is, and even more important, code may break when it is unexpectedly confronted with null values instead of some other meaningful information.
A Simple Rule…
There is a simple rule that caused a lot of debate: “A parameter should either be mandatory or have a defined default value”. What does it mean?
In above example, if you want to keep the parameters optional, you could choose to define sensible default values:
param ( $Path = $env:windir, $Filter = '*.exe' )
This way, the code could work even when a user chooses to not submit arguments to it.
Or, you could mark the parameters as mandatory, ensuring that the user is forced to submit some value:
param ( [Parameter(Mandatory)] $Path, [Parameter(Mandatory)] $Filter )
Now the parameter value is always defined by the user because PowerShell prompts for it if the user does not specify the value voluntarily.
Note that in either case, you (the code author) would still have to validate the user input to see if it makes sense, i.e. that a submitted path really exists.
…but is the rule true?
This simple rule would identify most issues where authors accidentally forgot to define sensible defaults, but what if you do want to set the default value to a null value?
Well, you could just assign $null, mimicking what PowerShell does under the hood if there is no default value at all:
param ( $Path = $null, $Filter = $null )
param ( [String]$Path = '', [String]$Filter = '' )
Please keep in mind that this is an edge case. In typical scenarios, you want to assign a default value other than a null value anyway.
This approach produces redundant code. As you have seen, you could as well have skipped the assignment, and left it to PowerShell to set the null value. Plus, some data types do not accept null values.
So the rule should really be: “If a parameter is optional, it should have a default value unless a null value is acceptable“
Implementing a better rule
The improved rule has one culprit: how do you automatically check the statement “unless a null value is acceptable”? You can’t. PowerShell Script Analyzer and ISESteroids PSSharper would have to analyze your code and try and determine whether your code can accept null values. That’s simply not doable. It’s the author who needs to come up with that information.
This way, the rule would read: “If a parameter is optional, it should have a default value unless the code author has declared that a null value is acceptable“. So what we need is a way for the author to declaratively state that he (or she) is aware of a possible null value.
How can you consent with null values?
What are good ways for a PowerShell code author to declare that a null value is ok for a parameter? You could assign $null to the parameter no matter what. This way, you would indicate that you know that this parameter is null when no value is submitted by the user.
However, this approach has issues:
- It is technically not necessary to assign a null value. Jaykul suspects performance issues when doing this, and also does not want redundant code.
- Keith points out that some data types such as DateTime do not accept $null values in the first place.
They both are right.
So for the time being, we changed the rule in ISESteroids 188.8.131.52 and use an attribute. When you want to declare that a parameter can have a null value, simply add the attribute [AllowNull()]. Once you do this, ISESteroids PSSharper no longer flags this issue. So in this example, ISESteroids would not flag the parameter as having no default value:
param ( [AllowNull()] $Path )
The attribute [AllowNull()] was actually introduced for mandatory parameters. Mandatory parameters do not accept null values submitted by the user unless you add this attribute. Optional parameters do not care about this attribute, they always accept null values.
So technically, again, adding this attribute for optional parameters is not needed, but it causes no harm either. It seems to be the best compromise to declare valid null defaults, and it has advantages:
If it really is ok for you to accept null values for a particular parameter, adding the attribute helps other users immediately spot this fact, and should you ever turn your optional parameter into a mandatory parameter, it would still continue to accept null values – if that was what you wanted in the first place.
In addition, we turned the rule into a manual rule. It can no longer be bulk-applied to a script because we feel that each case should be considered individually before code is refactored.
A final word…
Debates and discussions about best practices are awesome. It is fascinating to see all the well articulated and sophisticated points and arguments tossed in and shared by participants. At the same time, it is probably safe to say that there will never be the one-and-only “definite best practice guide”. Code composition as well as code formatting is governed by plenty of personal preferences, backgrounds, and priorities.
This is why an important part of PSSharper development centered around customizations. If you don’t like a rule, turn it off. Never feel bossed around. All of this is intended to help, not to force you to do something you are not comfortable with.
ISESteroids 184.108.40.206 with the updated rule is available immediately.
Please submit feedback and your views on this. As you see, your input is important to us, and we try to improve features the best we can.