-
Notifications
You must be signed in to change notification settings - Fork 288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Splatting vs. backticks #15
Comments
The Style Guide does not care if you're using notepad or Sublime, ISE or Visual Studio. In an ideal world, switching from a 600-character command line to a splatted hashtable (and back) should be as simple as right-clicking the line and picking "reformat as splatted hashtable" -- I suggest you file a feature request for ISE asking for that. Having said that: your tool's lack of "code completion" or "TabExpansion" is not a concern for the style guide. We just want it the code to look good when it's done. We can't let editor features dictate what good code looks like -- it's counter-productive. What I mean is: I don't think lack of TabExpansion is a valid argument in support of a particular style. Write it as one long line so your tool will help you, and then reformat it. Your other points are more valid ;-) |
I don't think introducing a variable is a problem -- in fact, I think there are plenty of other cases where you will have to introduce variables to make the code readable. For instance: when you need to call a command to get the value to pass to a parameter, it's probably good style to introduce a variable rather than nesting the call in a subexpression. This is much more significant than the splatting case, because you would introduce many variables, and can probably be avoided by using splatting 😉 I do think a command invocation is much easier to read when it starts with the command and then the parameters. I would even suggest that if you're splatting, it's good form to use the command name as (part of) your splatting variable name, to help with that. One issue is that backtick line-continuation is a code smell -- it not only makes it hard to read things, but it also makes things harder to edit (because you have to remember to keep fixing the backticks as you go). You can lessen that impact by writing EXACTLY one parameter per line (and never wrapping in the middle of one parameter), but it's still harder to read and edit code written that way. Another issue is that backticks don't help in the case where you need to pass the output of one command as a parameter to another -- you end up assigning a variable anyway. Perhaps a simple example I had to write today will help. I need to edit the content of a file, but the name of the file had to be determined by calling a function: $Version = Get-Version -Project $ModuleName
$ReleasePath = Join-Path $Pwd $Version
$FilePath = Join-Path $ReleasePath "${ModuleName}.psd1"
$ManifestContent = Get-Content -Path $FilePath
$ManifestContent = $ManifestContent -Replace "ModuleVersion = '[\d\.]+'", "ModuleVersion = '$Version'"
Set-Content -Path $FilePath -Value $ManifestContent I could have saved some lines (and a lot of variables) by doing some of the work in the same line: $FilePath = Join-Path $(Join-Path $Pwd $Version) "${ModuleName}.psd1"
Set-Content -Path $FilePath ((Get-Content $FilePath) -Replace "ModuleVersion = '[\d\.]+'", "ModuleVersion = '$Version'") Of course, that makes my lines too long, so I could wrap them: $FilePath = Join-Path $(Join-Path $Pwd $Version) "${ModuleName}.psd1"
Set-Content -Path $FilePath ((Get-Content $FilePath) `
-Replace "ModuleVersion = '[\d\.]+'", "ModuleVersion = '$Version'") But wait, that's deceptive. If we're going to allow line wrapping at all, we must require that you wrap only on each parameter: $FilePath = Join-Path $(Join-Path $Pwd $Version) "${ModuleName}.psd1"
Set-Content -Path $FilePath `
-Value ((Get-Content $FilePath) -Replace "ModuleVersion = '[\d\.]+'", "ModuleVersion = '$Version'") The problem is, that's still too long. $FilePath = Join-Path $(Join-Path $Pwd $Version) "${ModuleName}.psd1"
$SetContent = @{
Path = $FilePath
Value = (Get-Content $FilePath) -Replace "ModuleVersion = ['\d\.]+", "ModuleVersion = '$Version'"
}
Set-Content @SetContent From my point of view, the second one has a few advantages:
I would argue that the very first code sample is noticeably easier to read and follow, and is the best practice (rather than nesting commands). I don't actually write my code like that, usually, but in the back of my mind I feel it's better. In the real project, this code was written without line wrapping at all, and my lines are around 150 characters long (and I am ok with that length, and nobody has complained about it). |
So -- given all of that, if my project/team had a strict limit on line length, and the reason my command is too long is purely because of the number of parameters, then we must solve it one way or the other. The question is (basically, we just need to take a vote here): in the scenario where the command line is just too long, and you have to wrap the line -- do you truly recommend backticks over splatting? |
Just to add to the mix, here's an example of how I use backticks quite often:
Style-wise, with the vertically aligned pipe symbols, this makes for very readable one-liners. Many others argue against the backtick, and placing the pipe at the end instead, but that doesn't give me the same readability benefits. If I did happen to forget a backtick this way, PowerShell would give me an error because I'd have an empty pipe element, so that's not an issue. So in this case, where the command line is too long and I have to wrap the line, I truly recommend backticks over pipe symbols at the end of the line. |
I actually like the looks of that, except for the part where I still really dislike backticks! 😈 and they're really easy to screw up (because even an invisible space after them breaks them). I mean, I get that you mostly avoid that because they crash if you actually run the code, but it's still a break you can't spot in a code review. |
|
Need to call a vote on backticks. I don't like things (like here-strings) that make white space significant in a language where it normally isn't. With here-strings, I feel the added value is significant for a few scenarios, but with backticks I never have. |
My vote: allow backticks, with comments in the style guide clearly indicating that you should limit your use of backticks as a line continuance character to scenarios that improve code readability (when lines of script would otherwise be too long), and when doing so you should follow one of two models: a) a single backtick to spread one command across two lines, in which case the second line should be indented and the backtick should be in between parameters or pipeline elements; e.g.
b) multiple backticks to spread one command across more than two lines, in which case each line should be indented and each line should contain its own parameter or pipeline element e.g.
or
In the second scenario, when considering the former example (spreading parameters across multiple lines), splatting is generally the preferred style, however backticks may be used. |
Honestly, I'm starting to prefer backticks over splatting. I drank the Kool-Aid for a while too, but I prefer to see the command that I'm calling first, then the parameters. Like Kirk's first example, except I usually do it like this: Get-Something -Name ThisIsALongName `
-Type WhatColorIsYourParachute `
-SomeSwitch `
-SomeOtherParam AndItsValue `
-PassThru I'm still on the fence about whether to align the arguments to the parameters, as I've done here, or just align the parameter names. Either way, I find this much easier to read at a glance than splatting, which gives me a bunch of code to set up a hashtable (which doesn't always look intuitive), then sneaks in one quick little |
I agree with jrich523. Backticks should not be used. |
My only argument against backticks was that if you accidentally include whitespace after it, it stops functioning as a line continuation character. However, these days, we're putting code analysis tools in place that complain if you have trailing whitespace anyway, which makes this less of a problem. |
I am not in favor of backticks. I am saying no, but recognize Kirk’s points as valid. If you want a vote though, no. |
My vote no backticks
|
I say if you want the name there, put it the variable name, and if you want to line them up, do that too ;-) $GetSomethingParam = @{
Name = "ThisIsALongName"
Type = "WhatColorIsYourParachute"
SomeSwitch = $true
AnotherParam = "AndItsValue"
PassThru = $true
}
Get-Something @GetSomethingParam I think based on this poll, that people who like backticks are in the minority, not to mention the comments on PowerShell.org when they polled on this for the book. I also don't buy the argument that it's not really a big deal that whitespace screws up line continuation (especially when you're adding extra whitespace and trying to line things up). I don't think it's worth confusing the matter by discussing all these options in the guide -- and that's already the call that Don et. al. made in the first version of the Practices book. Which reminds me, this rule is in the style guide and the best practices book -- is everyone OK if I pull it from the best practices and leave it just in the style guide? |
I agree with @KirkMunro - allow backticks. When you have a bunch of parameters or an argument that is long (like a scriptblock) it is hard to avoid backticks. It is really too bad PowerShell overloaded backtick to be both line continuation and an escape char for spaces. Sorry, I just don't see myself using arg splatting to mitigate the long line scenario. A) it is just too different than other shells I've used (Korn, Bash, etc) B) I don't like the separation of the information (command and its parameters). C) Even if you use arg splatting you may have enough pipeline stages that you still need to use line continuation. So now you could have several arg hashtables stacked above a single pipeline spread over several lines and I have traverse up and down to see which hashtable goes with a particular command in one of the stages of the pipeline. Blech. |
Frankly, most of the time I'm going to argue you really just don't need to wrap the line #30 The contrived examples in the book are particularly egregious, since the line would be only 80 characters without quotes, if they just unwrapped it! 😉 However, the rationale in that chapter (and the caveat: "The backtick is not universally hated - but it can be inconvenient. If you have to use it for line breaks, well then, use it. Just try not to have to") is pretty much (still) how I feel about them, and I definitely prefer wrapping multi-command pipelines with just the pipe on the end and indented lines over wrapping with a completely superflous backtick and the pipe on the front. |
I prefer splatting. This maybe a specific case but, I have had a few scripts where I had to query multiple wmi classess. With splatting I need to define the common parameters only once.
|
I prefer splatting as well. I tend to use whitespace to mark where a splat + related command begin and end so readability is fine. I also often use splatting to dynamically build a set of parameter arguments before sending them off to a command, and I'm pretty sure this is what splatting was designed for. Therefore my vote would be against backticks simply for consistency. However, that will be a moot point if direct splatting is ever implemented: |
I think arguing splatting vs backticks is not the right argument. As Matt indicated, splatting was designed to facilitate dynamically I think the question should simply be: do backticks offer enough value to *Kirk Munro * Need a PowerShell SME for a project at work? Contact me On Mon, Jun 1, 2015 at 7:42 AM, mattmcnabb [email protected] wrote:
|
I agree with @KirkMunro. Here some things I don't like about the splatting approach. Let me use this script as an example: Get-Process |
Select-Object -Property @{Name='ProcessName'; Expression={$_.Name}},
@{Name='Id'; Expression={$_.Id}} `
-ExpandProperty Modules -ErrorAction SilentlyContinue |
Sort Id, BaseAddress |
Format-Table -Property @{Label='BaseAddress'; Expression={"0x{0:X16}" -f $_.BaseAddress.ToInt64()}},
ProcessName, Id, FileName -AutoSize Now let me convert that to the splatting approach: $selectObjParams = @{
Property = @{
Name = 'ProcessName'
Expression = {$_.Name}
},
@{
Name = 'Id'
Expression = {$_.Id}
}
ExpandProperty = 'Modules'
ErrorAction = 'SilentlyContinue'
}
$formatTableParams = @{
Property = @{
Label = 'BaseAddress'
Expression = {"0x{0:X16}" -f $_.BaseAddress.ToInt64()}
},
'ProcessName',
'Id',
'FileName'
AutoSize = $true
}
Get-Process | Select-Object @selectObjParams | Sort Id, BaseAddress | Format-Table @formatTableParams Now I've got to move my eyes up from the command invocations to look at various hashtables to see the params. That blows. I also don't get tab completion as @KirkMunro mentions. I also have to quote strings that didn't require quoting when used inline. Having to set a switch param to $true is a but of extra crud not needed when the parameter is specified inline. |
Exactly. In fact, that is what the very first post was about. |
@rkeithhill I might have written it like this, and avoided the conversation entirely Get-Process |
Select-Object -ExpandProperty Modules -ErrorAction SilentlyContinue -Property @{
Name='ProcessName'
Expression={$_.Name}
}, @{
Name='Id'
Expression={$_.Id}
} |
Sort Id, BaseAddress |
Format-Table -Property @{
Label='BaseAddress'
Expression={"0x{0:X16}" -f $_.BaseAddress.ToInt64()}
}, ProcessName, Id, FileName -AutoSize Or like this: $ProcessProperties = @{
Name = 'ProcessName'; Expression = {$_.Name} }, @{
Name = 'Id'; Expression = {$_.Id} }, @{
Name = 'BaseAddress'; Expression = {"0x{0:X16}" -f $_.BaseAddress.ToInt64()} }
Get-Process |
Select-Object -Property $ProcessProperties -ExpandProperty Modules -ErrorAction SilentlyContinue |
Sort Id, BaseAddress |
Format-Table -Property BaseAddress, ProcessName, Id, FileName -AutoSize Which I'm going to use as a segue to talk about #35 I want to eliminate as many practices as possible which are phrased as negatives, but when it's not possible, I want the guidelines to focus on the point. The point here is about readability, not about backticks, so the headline should not be Avoid backticks, but rather: Be thoughtful about line wrappingConsider this: Get-WmiObject -Class Win32_LogicalDisk `
-Filter DriveType=3 `
-ComputerName $server In general, you should avoid using line continuations when possible. Backticks can be hard to read, easy to miss, and they are easy to mistype (especially on US keyboards). Additionally, if you accidentally hit a tab after the backtick then it doesn't perform line continuation -- and the resulting error is confusing, and doesn't lead you to the solution (or even the line with the problem). Here's an alternative: Get-WmiObject -Class Win32_LogicalDisk -Filter DriveType=3 -ComputerName $server Put it on one line! Astonishingly, this line is only 80 characters long, and there was never any reason to wrap it in the first place! Another way to write it without needing to wrap lines would be to use splatting: $GetWmiObjectParams = @{
Class = "Win32_LogicalDisk"
Filter = "DriveType=3"
ComputerName = "SERVER2"
}
Get-WmiObject @GetWmiObjectParams Splatting lets you get the one-parameter per line without any line wrapping tricks like using backticks, but requires you to pre-define your parameters before you invoke the command. Of course, with more complicated commands you can wrap your line after almost any comma, pipe character, or semicolon without using a backtick. The backtick is not poison to be universally avoided, but it can cause problems. If you need to use it for line breaks, that's fine, just keep in mind that your goal is readable, maintainable code, and there are many ways to shorten a line. |
Sorry, that might be a little too snarky for the real document, but the blog posts which spawned the current book (and our discussions so far) are rife with contrived examples which lead to people arguing about backticks vs. splatting or pipelines vs. storing output in variables ... and missing the greater picture (Please read and comment onf #35) That example looks bad because it never needed line wrapping in the first place. Frankly, I'm sure that if I were writing what @rkeithhill wrote in #issuecomment-107749147, I would have written it using the second option I presented in my earlier comment, and looking at them now, I think that version is more readable than any of the other three versions -- however, a single counter-example doesn't change a best practice. We're trying to lead people to success. In a best practices document we're not trying to document the foibles of the language -- we want to help people completely avoid them by just following the road signs without needing to think about what would happen if they went the other way. In this case, we're trying to help people avoid writing lines that are hundreds of characters long (which is easy to do when you're writing pipelines) because they're hard to read and maintain. The only reason backticks even come into the picture is because of an earlier best practice (which is not in the book): avoid long lines. We have lots of parameters and we want to indent our code, so how do we avoid (really) long lines? Well, there's no single best way: you could start by putting each command in a pipeline onto it's own line -- if that's still too long, you can try wrapping on commas, parentheses, braces ... or parameter boundaries using backticks. |
At this point since we can't ALL (not that we all need to) agree do we even mention it at this point? I think we should remain quiet on what we feel is a language implementation "issue". (Issue is probably a bad word here.) Both have merits that u will admit I was short sighted on. So is it worth mentioning? On Jun 1, 2015, at 8:47 PM, Joel Bennett <[email protected]mailto:[email protected]> wrote: Sorry, that might be a little too snarky for the real document, but the blog posts which spawned the current book (and our discussions so far) are rife with contrived examples which lead to people arguing about backticks vs. splatting or pipelines vs. storing output in variables ... and missing the greater picture. That example looks bad because it never needed line wrapping in the first place. Frankly, I'm sure that if I were writing what @rkeithhillhttps://github.com/rkeithhill wrote in #issuecomment-107749147, I would have written it using the second option I presented in my earlier comment, and looking at them now, I think that version is more readable than any of the other three versions -- however, a single counter-example doesn't change a best practice. We're trying to lead people to success. In a best practices document we're not trying to document the foibles of the language -- we want to help people completely avoid them by just following the road signs without needing to think about what would happen if they went the other way. In this case, we're trying to help people avoid writing lines that are hundreds of characters long (which is easy to do when you're writing pipelines) because they're hard to read and maintain. The only reason backticks even come into the picture is because of an earlier best practice (which is not in the book): avoid long lines. We have lots of parameters and we want to indent our code, so how do we avoid (really) long lines? Well, there's no single best way: you could start by putting each command in a pipeline onto it's own line -- if that's still too long, you can try wrapping on commas, parentheses, braces ... or parameter boundaries using backticks. Reply to this email directly or view it on GitHubhttps://github.com//issues/15#issuecomment-107756342. |
This bit might be worth mentioning as a consideration with respect to the backtick's use as a line continuation character. I think it's reasonable to suggest to folks that if your line is too long and needs to wrap, try to first wrap on syntax that inherently supports wrapping. For example, ending a line with a comma, paren, brace or pipe symbol before reaching for the backtick. But when it does make sense to line wrap with a backtick, then do it. As for lines that are too long, I wonder if there needs to be a recommendation? Don't most folks just normally wrap their long lines anyway? I do like the following comment on an SO question about C# line length recommendation (if we feel we need to give line length guidance):
http://stackoverflow.com/questions/903754/do-you-still-limit-line-length-in-code |
We've been talking about the line length thing #30, but it's pretty clear ISE doesn't have a way to show a column indicator anyway, so it's really difficult to have a specific length recommendation, but even if I say:
The truth is that I assume we all agree we shouldn't have to scroll side-to-side to read code, right? But even if it's just so you can post a question on stack overflow, everyone needs to know a couple of strategies for manual line wrapping... So yes, let's leave the backticks vs. splatting out for now 💯 Let me write something about readability and line wrapping (slightly less melodramatic than what I wrote above), and I'll post it as a new issue or pull request and we can debate it then. 👍 Or I could just use Tobias' version: |
I slightly disagree with splatting, especially with the word "preferred". It is
a possible way, not preferred.
Splatting avoids backticks, this is probably a good thing. But it introduces
some drawbacks, too. It would be nice if the guideline mentions them, so that
people understand the consequences and make their choice being well informed.
The main drawback is absence of code completion of parameters and even some
parameter values on using the splatting approach. In contrast, TabExpansion
works fine across multiple lines with backticks. TabExpansion is a real time
saver.
Another drawback is necessity to introduce a variable because unfortunately
splatting requires a variable. It is not a big deal but it is just not natural.
Yet another minor thing is understanding the code. When a statement starts with a
command name then it's clear what it is even if it has lengthy continuation
with backticks. In contrast, if a statement starts with a lengthy hashtable
assigned to a variable then without looking at the command after it it is
less clear why is this needed.
The text was updated successfully, but these errors were encountered: