Skip to content

Commit bd21572

Browse files
committed
Split up TODO between Style Guide and Best Practices
1 parent 81feab0 commit bd21572

File tree

5 files changed

+360
-362
lines changed

5 files changed

+360
-362
lines changed

Best Practices/ExampleExample.md

Lines changed: 0 additions & 36 deletions
This file was deleted.

Best Practices/TODO.md

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
These documents are in an extremely rough state, not suitable for inclusion in the main guide yet.
2+
3+
### Using The .Net Framework
4+
<!-- MarkdownTOC depth=4 autolink=true bracket=round -->
5+
6+
- [Use RequiredAssemblies rather than Add-Type](#use-requiredassemblies-rather-than-add-type)
7+
- [Use Add-Type rather than Reflection](#use-add-type-rather-than-reflection)
8+
- [Use Add-Type for small classes or PInvoke calls](#use-add-type-for-small-classes-or-pinvoke-calls)
9+
- [Prefer shipping binaries over large compilations](#prefer-shipping-binaries-over-large-compilations)
10+
- [Performance](#performance)
11+
- [Prefer language features over cmdlets](#prefer-language-features-over-cmdlets)
12+
- [Know when to use .Net](#know-when-to-use-net)
13+
- [Error Handling](#error-handling)
14+
- [General Design Principles](#general-design-principles)
15+
- [Use custom objects](#use-custom-objects)
16+
- [Scripts vs Functions](#scripts-vs-functions)
17+
- [Always write CmdletBinding](#always-write-cmdletbinding)
18+
- [Include Help](#include-help)
19+
- [Always ship a about_ModuleName with modules](#always-ship-a-about_modulename-with-modules)
20+
- [Prefer PipelineByPropertyName parameters.](#prefer-pipelinebypropertyname-parameters)
21+
- [Specify aliases for pipeline binding](#specify-aliases-for-pipeline-binding)
22+
- [Never forget the Common Parameters](#never-forget-the-common-parameters)
23+
- [Specify positional parameters, but don't use them](#specify-positional-parameters-but-dont-use-them)
24+
- [Specify short aliases, but don't use them](#specify-short-aliases-but-dont-use-them)
25+
- [Never specify Default values for mandatory parameters](#never-specify-default-values-for-mandatory-parameters)
26+
- [Always specify HelpText for mandatory parameters](#always-specify-helptext-for-mandatory-parameters)
27+
- [Use PsCmdlet.ThrowTerminatingError rather than throw](#use-pscmdletthrowterminatingerror-rather-than-throw)
28+
- [Use PsCmdlet.WriteError rather than Write-Error](#use-pscmdletwriteerror-rather-than-write-error)
29+
- [Use SupportsShouldProcess when appropriate](#use-supportsshouldprocess-when-appropriate)
30+
- [Modules ARE the Best Practice](#modules-are-the-best-practice)
31+
- [Use RequiredModules](#use-requiredmodules)
32+
- [Persisting Configuration](#persisting-configuration)
33+
- [Provide aliases in your modules](#provide-aliases-in-your-modules)
34+
- [GOTCHAS](#gotchas)
35+
- [Beware string concatenation with +](#beware-string-concatenation-with-)
36+
- [Beware -match and -like](#beware--match-and--like)
37+
- [Beware -contains and -in](#beware--contains-and--in)
38+
- [Use Language Features](#use-language-features)
39+
- [You should understand the .Net underpinnings](#you-should-understand-the-net-underpinnings)
40+
- [AVOID appending to arrays in a loop](#avoid-appending-to-arrays-in-a-loop)
41+
- [EXCEPTIONS:](#exceptions)
42+
- [RATIONALE:](#rationale)
43+
- [AVOID appending to string in a loop](#avoid-appending-to-string-in-a-loop)
44+
- [EXCEPTIONS:](#exceptions-1)
45+
- [RATIONALE:](#rationale-1)
46+
- [Strongly type parameters](#strongly-type-parameters)
47+
- [Don't reinvent the wheel](#dont-reinvent-the-wheel)
48+
- [Let's talk about Logging](#lets-talk-about-logging)
49+
- [Let's talk about code signing](#lets-talk-about-code-signing)
50+
- [Don't reinvent the wheel](#dont-reinvent-the-wheel-1)
51+
- [Let's talk about Logging](#lets-talk-about-logging-1)
52+
- [Let's talk about code signing](#lets-talk-about-code-signing-1)
53+
54+
<!-- /MarkdownTOC -->
55+
56+
57+
#### Use RequiredAssemblies rather than Add-Type
58+
#### Use Add-Type rather than Reflection
59+
Avoid using `[System.Reflection]` to load assemblies when possible. Particularly avoid `LoadWithPartialName` (specify the full name instead).
60+
61+
#### Use Add-Type for small classes or PInvoke calls
62+
TODO: Is this better than PowerShell Classes, for compatibility?
63+
64+
#### Prefer shipping binaries over large compilations
65+
With PowerShell 5, security is tighter, and compiling code in memory will be frowned upon. Now that we have PowerShellGet and the PowerShell Gallery, there are few reasons 's no reason to avoid binaries.
66+
67+
TODO: Discuss: when does embedding C# code makes sense more sense than just compiling it every time?
68+
69+
70+
### Performance
71+
72+
#### Prefer language features over cmdlets
73+
Prefer foreach(){} over ForEach-Object
74+
Prefer .foreach and .where over cmdlets
75+
Prefer functions with process blocks over ForEach-Object
76+
77+
#### Know when to use .Net
78+
Prefer writing wrapper functions to just calling .Net APIs
79+
Discuss: System.IO file loading vs Get-Content (large files)
80+
Discuss: Other examples of .Net API calls that are clearly faster?
81+
Discuss: Casting for creating objects
82+
83+
### Error Handling
84+
85+
TODO
86+
Discuss: Avoid depending on `$?` -- why?
87+
Discuss: Never use `$Error` in scripts (always use -ErrorVariable)
88+
Discuss: Interactively, always copy $Error[0] to $e or something
89+
90+
91+
### General Design Principles
92+
93+
#### Use custom objects
94+
Discuss: Add-Type vs PowerShell classes vs creating PSCustomObjects
95+
Discuss: Casting for creating objects
96+
Discuss: PSTypeNames and formatting rules
97+
98+
#### Scripts vs Functions
99+
Discuss: In PowerShell 3+, performance is such that there's very little penalty for using scripts.
100+
Discuss: In PowerShell 3+, module autoloading means there's no discoverability penalty for writing functions (if you put them in a module) instead (except loading sets of them at a time).
101+
Discuss: During development, always write scripts, which are automatically re-parsed if you edit the file.
102+
103+
#### Always write CmdletBinding
104+
105+
This is in the Style Guide too, but we should discuss it in more depth here, and link to it from the Style Guide. Scripts should start life as something like this:
106+
107+
```
108+
[CmdletBinding()]param()
109+
process{}
110+
end{}
111+
```
112+
113+
You can always ignore one of the blocks, and add parameters and such, but you should never write a script without CmdletBinding, and you should never write one without at least considering making it take pipeline input
114+
115+
116+
### Include Help
117+
118+
TODO: Link to StyleGuide about formatting help
119+
Discuss: Minimum acceptable comment based help: Synopsis, Parameters, and an example for each parameter set (plus pipeline examples if you can contrive one)
120+
Discuss: Benefits of MAML help files
121+
122+
#### Always ship a about_ModuleName with modules
123+
124+
Discuss: Other reasons to write about_topics
125+
126+
127+
128+
#### Prefer PipelineByPropertyName parameters.
129+
Discuss: This allows the most flexibility: piping objects and using scriptblocks to shape it for parameters. Unless you absolutely need to write a `begin` block and use this parameter in it, you probably should accept it on the pipeline.
130+
131+
Discuss: There is some testing overhead involved in that decision.
132+
133+
Discuss: You MUST do all your work in the process block if you do this.
134+
135+
#### Specify aliases for pipeline binding
136+
You can use aliases to map parameters to property names of objects which might be piped to you while still keeping your parameter names clear and meaningful.
137+
138+
#### Never forget the Common Parameters
139+
Particularly when splatting PSBoundParameters to the next function, if that function isn't `[CmdletBinding()]` (it should be!) you must remember to strip the common parameters if they're present.
140+
141+
#### Specify positional parameters, but don't use them
142+
When writing at the command line, positional parameters are a blessing, but they can be confusing for future readers. You should always expose your parameters positionally when it makes sense, but you should rarely share a script that pases parameters positionally.
143+
144+
#### Specify short aliases, but don't use them
145+
Again, for the sake of typing, it's particularly useful if you can provide two-letter aliases for each of your parameters such that every parameter has a two-letter or less name which is unique.
146+
147+
#### Never specify Default values for mandatory parameters
148+
It doesn't do anything, and it confuses future readers.
149+
150+
#### Always specify HelpText for mandatory parameters
151+
When prompted for a mandatory parameter, a user can request HelpText, but can't look at the documentation. It's frequently useful to duplicate at least the first sentence or two of the parameter help.
152+
153+
```
154+
[Parameter(Position=1, Mandatory=$true, ValueFromPipeline=$true,
155+
ValueFromPipelineByPropertyName=$true, HelpText='The name of the file to read')]
156+
[Alias('PSPath','FullName','Path')]
157+
[String]$File
158+
```
159+
160+
161+
#### Use PsCmdlet.ThrowTerminatingError rather than throw
162+
#### Use PsCmdlet.WriteError rather than Write-Error
163+
Discuss: These need example output to explain why they're better
164+
Discuss: a common template (from the Microsoft team) for throwing errors
165+
166+
```
167+
# Utility to throw an errorrecord
168+
function ThrowError
169+
{
170+
param
171+
(
172+
[parameter(Mandatory = $true)]
173+
[ValidateNotNullOrEmpty()]
174+
[System.Management.Automation.PSCmdlet]
175+
$CallerPSCmdlet,
176+
177+
[parameter(Mandatory = $true)]
178+
[ValidateNotNullOrEmpty()]
179+
[System.String]
180+
$ExceptionName,
181+
182+
[parameter(Mandatory = $true)]
183+
[ValidateNotNullOrEmpty()]
184+
[System.String]
185+
$ExceptionMessage,
186+
187+
[System.Object]
188+
$ExceptionObject,
189+
190+
[parameter(Mandatory = $true)]
191+
[ValidateNotNullOrEmpty()]
192+
[System.String]
193+
$ErrorId,
194+
195+
[parameter(Mandatory = $true)]
196+
[ValidateNotNull()]
197+
[System.Management.Automation.ErrorCategory]
198+
$ErrorCategory
199+
)
200+
201+
$exception = New-Object $ExceptionName $ExceptionMessage;
202+
$errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject
203+
$CallerPSCmdlet.ThrowTerminatingError($errorRecord)
204+
}
205+
```
206+
207+
208+
#### Use SupportsShouldProcess when appropriate
209+
Discuss: when is this critical (-whatif) and optional (-confirm_
210+
Discuss: when should you call PSCmdlet.ShouldProcess vs PSCmdlet.ShouldContinue (-Force)
211+
212+
```
213+
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="Medium")]
214+
param([Switch]$Force)
215+
216+
$RejectAll = $false;
217+
$ConfirmAll = $false;
218+
219+
foreach($file in ls) {
220+
221+
if($PSCmdlet.ShouldProcess( "Removed the file '$($file.Name)'",
222+
"Remove the file '$($file.Name)'?",
223+
"Removing Files" )) {
224+
225+
if($Force -Or $PSCmdlet.ShouldContinue("Are you REALLY sure you want to remove '$($file.Name)'?", "Removing '$($file.Name)'", [ref]$ConfirmAll, [ref]$RejectAll)) {
226+
"Removing $File"
227+
228+
229+
}
230+
}
231+
}
232+
```
233+
234+
#### Modules ARE the Best Practice
235+
Discuss: Scope isolation, hiding helper functions, sharing state between functions
236+
Discuss: Organizing related functions
237+
Discuss: Discoverability, Packaging, Sharing
238+
Discuss: Dependency Management, Versioning
239+
Discuss: Internationalization
240+
241+
#### Use RequiredModules
242+
The problems with this have gone away with autoloading, and this is the only way to let PowerShellGet manage dependencies for you.
243+
244+
#### Persisting Configuration
245+
My choice: Configuration module. Otherwise, use clixml (or XAML) to persist to AppData (TEST: you shouldn't store configuration in your module folder, as it may not survive upgrades (in PowerShell 3 & 4 there was no side-by-side loading))
246+
247+
248+
#### Provide aliases in your modules
249+
You should feel free to create and use aliases within your modules. In some cases, you can even improve readability by using an alias without the verb, or shortening command names.
250+
251+
For exported aliases, follow the guidance of Microsoft ("ip" for import, "s" for set, "g" for get, "r" for remove, etc.), make up somethign for your nouns.
252+
253+
Use `New-Alias ... -ErrorAction SilentlyContinue` to avoid overwriting existing aliases.
254+
255+
256+
257+
258+
### GOTCHAS
259+
260+
#### Beware string concatenation with +
261+
You should always wrap this with parentheses, because otherwise it can break (e.g. when passing a string as a parameter.
262+
263+
#### Beware -match and -like
264+
They quietly cast objects to strings (or arrays of strings)
265+
266+
#### Beware -contains and -in
267+
They work on ARRAYS not strings
268+
269+
270+
### Use Language Features
271+
When writing scripts (as opposed to at the command line), you should almost always choose language features over cmdlets. This includes using if instead of where-object, foreach instead of ForEach-Object, etc.
272+
273+
The language features are always faster, and almost always more readable. Of course, there are always exceptions, and one exception to this rule is when using foreach will force you to collect a lot of items into an array instead of iterating them as they stream through a pipleine.
274+
275+
276+
### You should understand the .Net underpinnings
277+
278+
#### AVOID appending to arrays in a loop
279+
##### INSTEAD assign output from the loop
280+
#### EXCEPTIONS:
281+
* Appending to multiple collections
282+
* Using Lists instead of arrays
283+
284+
#### RATIONALE:
285+
* Copying is slow
286+
* Pipeline output uses ArrayList
287+
* Easier to read and understand
288+
289+
290+
#### AVOID appending to string in a loop
291+
##### INSTEAD assign output from the loop using $OFS
292+
#### EXCEPTIONS:
293+
* When it's a really short loop
294+
295+
#### RATIONALE:
296+
* Copying is slow
297+
* Joining an array of strings is fast
298+
* Easier to read and understand
299+
300+
301+
### Strongly type parameters
302+
Although PowerShell is a dynamic language, we have can specify types, and in Parameters, it's particularly useful because it hints to users what they can pass to your command.
303+
304+
Strong types on parameters is also crucial because it's a user-input point. Strong types can help you avoid script injection and various other problems with user inputs, and will allow failures to happen as early as possible (even before your command is called).
305+
306+
Additionally, avoid using `[string]` with ParameterSets because anything can be cast to it, so PowerShell can't distinguish one parameter set from the other.
307+
308+
When passing on parameters to another command, you should be _at least_ as strongly typed as the other command, to avoid casting exceptions within your script.
309+
310+
One notable exception is when you could accept more than one type. In PowerShell you can speficy parameter set overloads, but you can't change the type of a parameter.
311+
312+
313+
### Don't reinvent the wheel
314+
### Let's talk about Logging
315+
### Let's talk about code signing
316+
### Don't reinvent the wheel
317+
### Let's talk about Logging
318+
### Let's talk about code signing

CONTRIBUTING.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## Contributing
2+
3+
Markdown documents on GitHub support linking to any header, so when editing, please observe the following conventions:
4+
5+
1. Keep rules within the section where they make sense.
6+
2. Sections must be header level 3 (have three hashes): `### Naming Conventions`
7+
3. Rules must be headers with an explanatory paragraph
8+
5. Rules should have examples and counter examples
9+
6. Rules should be phrased as the positive, rather than the negative.
10+
* Don't say: "Avoid using aliases"
11+
* Do say: "Use full command names"
12+
7. When writing a negative rule, you should always start with "avoid" and end with an "instead" sentence, like: <blockquote><h6>Avoid the use of `~` to represent the home folder.</h6><p>The meaning of ~ is unfortunately dependent on the "current" provider at the time of execution. This isn't really a style issue, but it's an important rule for code you intend to share anyway. <strong>Instead</strong>, use `${Env:UserProfile}` or `(Get-PSProvider FileSystem).Home`</p></blockquote>
13+

README.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,8 @@ The PowerShell Style Guide is in PREVIEW.
1010
We are still actively working out our disagreements about the rules in this guide, so please don't be suprised if, over then next few weeks we change rules to contradict what they say at this current moment.
1111

1212
## Contributing
13+
Please use the issues system (and/or GitHub pull requests) to make corrections, contributions, and other changes to the text - we welcome your contributions!
1314

14-
Markdown documents on GitHub support linking to any header, so when editing, please observe the following conventions:
15+
For more information, see [CONTRIBUTING](contributing.md)
1516

16-
1. Keep rules within the section where they make sense.
17-
2. Sections must be header level 3 (have three hashes): `### Naming Conventions`
18-
3. Rules must be headers with an explanatory paragraph
19-
5. Rules should have examples and counter examples
20-
6. Rules should be phrased as the positive, rather than the negative.
21-
* Don't say: "Avoid using aliases"
22-
* Do say: "Use full command names"
23-
7. When writing a negative rule, you should always start with "avoid" and end with an "instead" sentence, like:<blockquote><h6>Avoid the use of `~` to represent the home folder.</h6><p>The meaning of ~ is unfortunately dependent on the "current" provider at the time of execution. This isn't really a style issue, but it's an important rule for code you intend to share anyway. <em>Instead</em>, use `${Env:UserProfile}` or `(Get-PSProvider FileSystem).Home`</p></blockquote>
2417

0 commit comments

Comments
 (0)