Was ist der empfohlene Codierungsstil für PowerShell?

79

Gibt es einen empfohlenen Codierungsstil zum Schreiben von PowerShell-Skripten?

Es geht nicht darum, wie man den Code strukturiert (wie viele Funktionen, wenn man ein Modul verwendet, ...). Es geht darum, wie man den Code so schreibt, dass er lesbar ist .

In Programmiersprachen gibt es einige empfohlene Codierungsstile (was einzurücken ist , wie einzurücken ist - Leerzeichen / Tabulatoren, wo neue Zeilen zu erstellen sind , wo geschweifte Klammern zu setzen sind , ...), aber ich habe keinen Vorschlag für PowerShell gesehen.

Ich interessiere mich besonders für:


Wie schreibe ich Parameter?

function New-XYZItem
  ( [string] $ItemName
  , [scriptblock] $definition
  ) { ...

(Ich sehe, dass es eher wie 'V1'-Syntax ist)

oder

function New-PSClass  {
  param([string] $ClassName
       ,[scriptblock] $definition
  )...

oder (warum leeres Attribut hinzufügen?)

function New-PSClass  {
  param([Parameter()][string] $ClassName
       ,[Parameter()][scriptblock] $definition
  )...

oder (andere Formatierung, die ich vielleicht in Jaykuls Code gesehen habe)

function New-PSClass {
  param(
        [Parameter()]
        [string]
        $ClassName
        ,
        [Parameter()]
        [scriptblock]
        $definition
  )...

oder ...?


So schreiben Sie eine komplexe Pipeline

Get-SomeData -param1 abc -param2 xyz | % {
    $temp1 = $_
    1..100 | % {
      Process-somehow $temp1 $_
    }
  } | % {
    Process-Again $_
  } |
  Sort-Object -desc

oder (Name des Cmdlets in der neuen Zeile)

Get-SomeData -param1 abc -param2 xyz |
  % {
    $temp1 = $_
    1..100 |
      % {
        Process-somehow $temp1 $_
      }
  } |
  % {
    Process-Again $_
  } |
  Sort-Object -desc |

Und was ist, wenn es -begin, -processund -endParameter? Wie mache ich es am lesbarsten?

Get-SomeData -param1 abc -param2 xyz |
  % -begin {
     init
  } -process {
     Process-somehow2 ...
  } -end {
     Process-somehow3 ...
  } |
  % -begin {
  } ....

oder

Get-SomeData -param1 abc -param2 xyz |
  %  `
    -begin {
      init
    } `
    -process {
      Process-somehow2 ...
    } `
    -end {
      Process-somehow3 ...
    } |
  % -begin {
  } ....

Die Einrückung ist hier wichtig und welches Element auch in eine neue Zeile gesetzt wird.


Ich habe nur Fragen behandelt, die mir sehr häufig in den Sinn kommen. Es gibt einige andere, aber ich möchte diese Frage zum Stapelüberlauf kurz halten.

Alle anderen Vorschläge sind willkommen.

stej
quelle
1
Ich denke, das Fehlen eines gemeinsamen Codierungsstils für Powershell-Skripte ist darauf zurückzuführen, dass es eher mit der Verwendung durch Administratoren als mit "echter" Codierung zusammenhängt.
Filburt
4
Du hast recht. Imho-Administratoren benötigen jedoch Skripte, die leicht zu lesen sind. Zum Beispiel mag ich die Backticks nicht, deshalb versuche ich sie zu vermeiden.
Stej
2
Das ist eine gute Frage.
Steve Rathbone
Es ist seltsam, dass es trotz haufenweise Verachtung immer noch Code ist. und kann somit mit einem Standard- und vertrauten Einrückungsschema besser lesbar gemacht werden.
user2066657

Antworten:

88

Nachdem ich ein paar Jahre damit verbracht habe, ziemlich tief in PowerShell v2.0 einzutauchen, habe ich mich für Folgendes entschieden:

<#
.SYNOPSIS
Cmdlet help is awesome.  Autogenerate via a template so I never forget.

.DESCRIPTION
.PARAMETER
.PARAMETER
.INPUTS
.OUTPUTS
.EXAMPLE
.EXAMPLE
.LINK
#>
function Get-Widget
{
    [CmdletBinding()]
    param (
        # Think about which parameters users might loop over.  If there is a clear
        # favorite (80/20 rule), make it ValueFromPipeline and name it InputObject.
        [parameter(ValueFromPipeline=$True)]
        [alias("Server")]
        [string]$InputObject,

        # All other loop candidates are marked pipeline-able by property name.  Use Aliases to ensure the most 
        # common objects users want to feed in will "just work".
        [parameter(Mandatory=$true, Position=0, ValueFromPipelineByPropertyName=$True)]
        [alias("FullName")]
        [alias("Path")]
        [string[]]$Name,

        # Provide and document defaults for optional parameters whenever possible.
        [parameter(Position=1)]
        [int]$Minimum = 0,

        [parameter(Position=2)]
        [int]$ComputerName = "localhost",

        # Stick to standardized parameter names when possible.  *Especially* with switches.  Use Aliases to support 
        # domain-specific terminology and/or when you want to expose the parameter name of the .Net API you're wrapping.
        [parameter()]
        [Alias("IncludeFlibbles")]
        [switch]$All,
    )

    # The three main function blocks use this format if & only if they are short one-liners    
    begin { $buf = new-list string }

    # Otherwise they use spacing comparable to a C# method
    process    
    {
        # Likewise, control flow statements have a special style for one-liners
        try
        {
            # Side Note: internal variables (which may be inherited from a parent scope)  
            # are lowerCamelCase.  Direct parameters are UpperCamelCase.
            if ($All)
                { $flibbles = $Name | Get-Flibble }   
            elseif ($Minimum -eq 0)          
                { $flibbles = @() }
            else
                { return }                       

            $path = $Name |
                ? { $_.Length -gt $Minimum } |
                % { $InputObject.InvokeGetAPI($_, $flibbles) } |
                ConvertTo-FullPath
        }
        finally { Cleanup }

        # In general, though, control flow statements also stick to the C# style guidelines
        while($true)
        {
            Do-Something
            if ($true)
            {
                try
                {
                    Do-Something
                    Do-Something
                    $buf.Add("abc")
                }
                catch
                {
                    Do-Something
                    Do-Something
                }
            }            
        }    
    }    
}

<# 
Pipelines are a form of control flow, of course, and in my opinion the most important.  Let's go 
into more detail.

I find my code looks more consistent when I use the pipeline to nudge all of PowerShell's supported 
language constructs (within reason) toward an "infix" style, regardless of their legacy origin.  At the 
same time, I get really strict about avoiding complexity within each line.  My style encourages a long,
consistent "flow" of command-to-command-to-command, so we can ensure ample whitespace while remaining
quite compact for a .NET language. 

Note - from here on out I use aliases for the most common pipeline-aware cmdlets in my stable of 
tools.  Quick extract from my "meta-script" module definition:
sal ?? Invoke-Coalescing
sal ?: Invoke-Ternary
sal im Invoke-Method
sal gpv Get-PropertyValue
sal spv Set-PropertyValue
sal tp Test-Path2
sal so Select-Object2        
sal eo Expand-Object        

% and ? are your familiar friends.
Anything else that begins with a ? is a pseudo-infix operator autogenerated from the Posh syntax reference.
#>        
function PipelineExamples
{
    # Only the very simplest pipes get to be one-liners:
    $profileInfo = dir $profile | so @{Path="fullname"; KBs={$_.length/1kb}}
    $notNull = $someString | ?? ""        
    $type = $InputObject -is [Type] | ?: $InputObject $InputObject.GetType()        
    $ComObject | spv Enabled $true
    $foo | im PrivateAPI($param1, $param2)
    if ($path | tp -Unc)
        { Do-Something }

    # Any time the LHS is a collection (i.e. we're going to loop), the pipe character ends the line, even 
    # when the expression looks simple.
    $verySlowConcat = ""            
    $buf |
        % { $verySlowConcat += $_ }
    # Always put a comment on pipelines that have uncaptured output [destined for the caller's pipeline]
    $buf |
        ? { $_ -like "*a*" }


    # Multi-line blocks inside a pipeline:
    $orders |
        ? { 
            $_.SaleDate -gt $thisQuarter -and
            ($_ | Get-Customer | Test-Profitable) -and
            $_.TastesGreat -and
            $_.LessFilling
        } |
        so Widgets |        
        % {                
            if ($ReviewCompetition)
            {
                $otherFirms |
                    Get-Factory |
                    Get-ManufactureHistory -Filter $_ |
                    so HistoryEntry.Items.Widgets                     
            }
            else
            {
                $_
            }
        } |            
        Publish-WidgetReport -Format HTML


    # Mix COM, reflection, native commands, etc. seamlessly
    $flibble = Get-WmiObject SomethingReallyOpaque |
        spv AuthFlags 0xf -PassThru |
        im Put() -PassThru |
        gpv Flibbles |
        select -first 1

    # The coalescing operator is particularly well suited to this sort of thing
    $initializeMe = $OptionalParam |
        ?? $MandatoryParam.PropertyThatMightBeNullOrEmpty |
        ?? { pwd | Get-Something -Mode Expensive } |
        ?? { throw "Unable to determine your blahblah" }           
    $uncFolderPath = $someInput |
        Convert-Path -ea 0 |
        ?? $fallback { tp -Unc -Folder }

    # String manipulation        
    $myName = "First{0}   Last{1}   " |
        ?+ "Suffix{2}" |
        ?replace "{", ": {" |
        ?f {eo richard berg jr | im ToUpper}            

    # Math algorithms written in this style start to approach the elegance of functional languages
    $weightedAvg = $values |
        Linq-Zip $weights {$args[0] * $args[1]} |
        Linq-Sum |
        ?/ ($weights | Linq-Sum)
}

# Don't be afraid to define helper functions.  Thanks to the script:Name syntax, you don't have to cram them into 
# the begin{} block or anything like that.  Name, parameters, etc don't always need to follow the cmdlet guidelines.
# Note that variables from outer scopes are automatically available.  (even if we're in another file!)
function script:Cleanup { $buf.Clear() }

# In these small helpers where the logic is straightforward and the correct behavior well known, I occasionally 
# condense the indentation to something in between the "one liner" and "Microsoft C# guideline" styles
filter script:FixComputerName
{
    if ($ComputerName -and $_) {            
        # Handle UNC paths 
        if ($_[1] -eq "\") {   
            $uncHost = ($_ -split "\\")[2]
            $_.Replace($uncHost, $ComputerName)
        } else {
            $drive = $_[0]
            $pathUnderDrive = $_.Remove(0,3)            
            "\\$ComputerName\$drive`$\$pathUnderDrive"
        }
    } else {
        $_
    }
}

Der Syntax-Textmarker von Stack Overflow gibt mich völlig auf. Fügen Sie es in die ISE ein.

Richard Berg
quelle
Vielen Dank für die umfassende Antwort; Ich werde es wahrscheinlich als akzeptierte Antwort markieren, anscheinend interessiert sich niemand für den noblen Codierungsstil: | Haben Sie irgendwo Ihre Hilfsfunktionen veröffentlicht (??,?:,? +, Im, ...)? - Es wäre wertvoll für viele Leute, denke ich;)
Stej
Nein, ich habe nicht ... ja, ich sollte ... eines Tages ...!
Richard Berg
3
Ok, v0.1 irgendwo öffentlich begangen. Gehen Sie zu tfstoys.codeplex.com/SourceControl/changeset/view/33350#605701 und navigieren Sie zu Modules \ RichardBerg-Misc
Richard Berg
Ein Punkt, der zu diesem großartigen Leitfaden hinzugefügt werden sollte: Verwenden Sie bei Bedarf Validatoren! Sie sparen Code und verbessern die Benutzerfreundlichkeit.
JasonMArcher
Das Bereitstellungsskript eines Kollegen ist einmal für mich kaputt gegangen, weil ich einen benutzerdefinierten 'ls'-Alias ​​in meinem Profil hatte. Seitdem war meine Praxis "benutze keine Aliase in Skripten"
John Fouhy
13

Ich glaube, die umfassendste Ressource für Codierungsstile für PowerShell ist immer noch The PowerShell Best Practices and Style Guide .

Aus ihrer Einführung:

Wie die englischen Rechtschreib- und Grammatikregeln haben auch die Best Practices und Stilregeln für die PowerShell-Programmierung fast immer Ausnahmen. Wir dokumentieren jedoch eine Basis für Codestruktur, Befehlsdesign, Programmierung, Formatierung und sogar Stil, mit deren Hilfe Sie häufig auftretende Probleme vermeiden und Hilfe leisten können Sie schreiben mehr wiederverwendbaren, lesbaren Code - da wiederverwendbarer Code nicht neu geschrieben werden muss und lesbarer Code beibehalten werden kann.

Sie haben auch diese GitBook- Links verfügbar gemacht:

rsenna
quelle
404: Links sind defekt.
Ashish Singh
Fest. Dieser neue Leitfaden wurde erstellt, indem der alte PowerShell Style Guide von Carlos Perez (den ich ursprünglich verlinkt hatte) und das Community Book of PowerShell Practices von Don Jones und Matt Penny zusammengeführt wurden.
Rsenna
4
Diese Antwort sollte jetzt wirklich höher sein.
Bacon Bits
8

Ich bin kürzlich auf einen hervorragenden Punkt zum Einrückungsstil in PowerShell gestoßen . Beachten Sie, wie im verknüpften Kommentar angegeben, den Unterschied zwischen denselben Syntaxen:

1..10 | Sort-Object
{
    -$_
}

und

1..10 | Sort-Object {
    -$_
}

Während meine Neigung darin besteht, "wie die Römer zu tun" und den Standard-C # -Einrückungsstil ( mehr oder weniger Allman ) zu verwenden, habe ich Probleme mit dieser und ähnlichen Ausnahmen.

Dies veranlasst mich persönlich, mein bevorzugtes 1 TB zu verwenden , aber ich könnte davon überzeugt sein. Wie haben Sie sich aus Neugier niedergelassen?

Tohuw
quelle
2
Ich bin ziemlich neu in schick. Danke für die Vorwarnung! Anfangs mochte ich die separate Zeile, aber jetzt öffne ich sie gerne in der Setup-Zeile.
AnneTheAgile
Bei .NET-Codierern, die den C # -Standard verwenden, kann es zu Feindseligkeiten kommen. Wenn jedoch die Funktion zum Einrücken von Einrückungen geändert wird, gehe ich jederzeit davon aus, was konsequent getan wird, was von religiösen Vorlieben erwartet wird. Ich bevorzuge 1 TB für alles, aber wenn das obige Beispiel ein umgekehrtes Verhalten zeigt, ist mein PoSh sofort allmanisiert. :)
Tohuw
Vorsicht, Sie verbinden Stil mit Verhalten.
Keith S Garner
@KeithSGarner eher, ich impliziere Verhalten sollte Stil diktieren. Oder besser, die Sprache sollte stilunabhängig sein.
Tohuw
1
Wenn Sie sich mit dem Beispiel if (& lt; test & gt;) {StatementBlock} befassen, lässt die Sprache beide Stile zu (1 TB oder Allman), es handelt sich nicht um ein Verhaltensproblem. (Ich bevorzuge Allman selbst, aber "Wenn in Rom ...") Was das obige Beispiel "Sortierobjekt" betrifft, handelt es sich nicht um ein Stilproblem, es gibt nur eine richtige Antwort, abhängig vom erforderlichen Verhalten. Stil! = Verhalten
Keith S Garner