El Powershell funcional amb classes no és un oxímoron, ho garanteixo

Hola Habr! Presento a la vostra atenció la traducció de l'article "PowerShell funcional amb classes.
Prometo que no és un oxímoron"
de Christopher Kuech.

Els paradigmes de programació orientat a objectes i funcionals poden semblar en desacord entre si, però tots dos són igualment compatibles amb Powershell. Gairebé tots els llenguatges de programació, funcionals o no, tenen facilitats per a l'enllaç estès nom-valor; Les classes, com les estructures i els registres, són només un enfocament. Si limitem el nostre ús de Classes a l'enllaç de noms i valors, i evitem conceptes pesats de programació orientada a objectes com l'herència, el polimorfisme o la mutabilitat, podem aprofitar els seus avantatges sense complicar el nostre codi. A més, afegint mètodes de conversió de tipus immutables, podem enriquir el nostre codi funcional amb Classes.

La màgia de les castes

Les castes són una de les funcions més potents de Powershell. Quan emet un valor, confieu en les capacitats d'inicialització i validació implícites que l'entorn afegeix a la vostra aplicació. Per exemple, simplement llançar una cadena a [xml] l'executarà a través del codi analitzador i generarà un arbre xml complet. Podem utilitzar Classes al nostre codi amb el mateix propòsit.

Cast hashtables

Si no teniu un constructor, podeu continuar sense un emetent una taula hash al vostre tipus de classe. No oblideu utilitzar els atributs de validació per aprofitar al màxim aquest patró. Al mateix temps, podem utilitzar les propietats escrites de la classe per executar una lògica d'inicialització i validació encara més profunda.

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index
}

[Cluster]@{
    Service       = "MyService"
    FlightingRing = "PROD"
    Region        = "EastUS"
    Index         = 2
}

A més, la fosa ajuda a obtenir una sortida neta. Compareu la sortida de la matriu de taules hash del clúster que s'ha passat a Format-Table amb el que obteniu si primer emeteu aquestes taules hash en una classe. Les propietats d'una classe s'enumeren sempre en l'ordre en què s'hi defineixen. No oblideu afegir la paraula clau oculta abans de totes aquelles propietats que no voleu que siguin visibles als resultats.

El Powershell funcional amb classes no és un oxímoron, ho garanteixo

Elenc de significats

Si teniu un constructor amb un argument, enviar un valor al vostre tipus de classe passarà el valor al vostre constructor, on podeu inicialitzar una instància de la vostra classe

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index

    Cluster([string] $id) {
        $this.Service, $this.FlightingRing, $this.Region, $this.Index = $id -split "-"
    }
}

[Cluster]"MyService-PROD-EastUS-2"

Emet a línia

També podeu substituir el mètode de classe [cadena] ToString() per definir la lògica darrere de la representació de cadena de l'objecte, com ara utilitzar la interpolació de cadena.

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index

    [string] ToString() {
        return $this.Service, $this.FlightingRing, $this.Region, $this.Index -join "-"
    }
}

$cluster = [Cluster]@{
    Service       = "MyService"
    FlightingRing = "PROD"
    Region        = "EastUS"
    Index         = 2
}

Write-Host "We just created a model for '$cluster'"

Emet instàncies serialitzades

Cast permet una deserialització segura. Els exemples següents fallaran si les dades no compleixen les nostres especificacions al Clúster

# Валидация сериализованных данных

[Cluster]$cluster = Get-Content "./my-cluster.json" | ConvertFrom-Json
[Cluster[]]$clusters = Import-Csv "./my-clusters.csv"

Castes al teu codi funcional

Els programes funcionals primer defineixen estructures de dades i després implementen el programa com una seqüència de transformacions sobre estructures de dades immutables. Malgrat la impressió contradictòria, les classes realment ajuden a escriure codi funcional gràcies als mètodes de conversió de tipus.

El Powershell que estic escrivint és funcional?

Molta gent que prové de C# o antecedents similars està escrivint Powershell, que és similar a C#. En fer això, us allunyeu de l'ús de conceptes de programació funcional i probablement us beneficiareu d'aprofundir en la programació orientada a objectes a Powershell o d'aprendre més sobre la programació funcional.

Si confieu molt en la transformació de dades immutables mitjançant pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc. - teniu un estil més funcional i us beneficiareu d'utilitzar Powershell classes amb un estil funcional.

Ús funcional de les classes

Les castes, tot i que utilitzen una sintaxi alternativa, són només un mapeig entre dos dominis. En el pipeline, podeu mapar una matriu de valors mitjançant ForEach-Object.

A l'exemple següent, el constructor Node s'executa cada vegada que s'emet un Datum, i això ens dóna l'oportunitat de no escriure una bona quantitat de codi. Com a resultat, el nostre pipeline se centra en la consulta i l'agregació de dades declaratives, mentre que les nostres classes s'ocupen de l'anàlisi i la validació de dades.

# Пример комбинирования классов с конвейерами для separation of concerns в конвейерах

class Node {
    [ValidateLength(3, 7)]
    [string] $Name
    [ValidateSet("INT", "PPE", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope", "WestEurope")]
    [string] $Region
    Node([string] $Name) {
        $Name -match "([a-z]+)(INT|PPE|PROD)([a-z]+)"
        $_, $this.Service, $this.FlightingRing, $this.Region = $Matches
        $this.Name = $Name
    }
}

class Datum {
    [string] $Name
    [int] $Value
    [Node] $Computer
    [int] Severity() {
        $this.Name -match "[0-9]+$"
        return $Matches[0]
    }
}

Write-Host "Urgent Security Audit Issues:"
Import-Csv "./audit-results.csv" `
    | ForEach-Object {[Datum]$_} `
    | Where-Object Value -gt 0 `
    | Group-Object {$_.Severity()} `
    | Where-Object Name -lt 2 `
    | ForEach-Object Group `
    | ForEach-Object Computer `
    | Where-Object FlightingRing -eq "PROD" `
    | Sort-Object Name, Region -Unique

Classe d'embalatge per a la reutilització

Res és tan bo com sembla

Malauradament, les classes no es poden exportar per mòduls de la mateixa manera que les funcions o les variables; però hi ha alguns trucs. Suposem que les vostres classes estan definides al fitxer ./my-classes.ps1

  • Podeu dotsource un fitxer amb classes:. ./my-classes.ps1. Això executarà my-classes.ps1 al vostre àmbit actual i definirà totes les classes del fitxer allà.

  • Podeu crear un mòdul Powershell que exporti totes les vostres API personalitzades (cmdlets) i establir la variable ScriptsToProcess = "./my-classes.ps1" al manifest del vostre mòdul, amb el mateix resultat: ./my-classes.ps1 s'executarà a el teu entorn.

Sigui quina sigui l'opció que trieu, tingueu en compte que el sistema de tipus de Powershell no pot resoldre els tipus del mateix nom carregats des de llocs diferents.
Fins i tot si heu carregat dues classes idèntiques amb les mateixes propietats des de llocs diferents, correu el risc de tenir problemes.

El camí a seguir

La millor manera d'evitar problemes de resolució de tipus és no exposar mai les classes als usuaris. En lloc d'esperar que l'usuari importi un tipus definit per classe, exporteu una funció del vostre mòdul que elimina la necessitat d'accedir directament a la classe. Per al clúster, podem exportar una funció de nou clúster que admetrà conjunts de paràmetres fàcils d'utilitzar i retornar un clúster.

class Cluster {
    [ValidatePattern("^[A-z]+$")]
    [string] $Service
    [ValidateSet("TEST", "STAGE", "CANARY", "PROD")]
    [string] $FlightingRing
    [ValidateSet("EastUS", "WestUS", "NorthEurope")]
    [string] $Region
    [ValidateRange(0, 255)]
    [int] $Index
}

function New-Cluster {
    [OutputType([Cluster])]
    Param(
        [Parameter(Mandatory, ParameterSetName = "Id", Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string] $Id,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $Service,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $FlightingRing,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [string] $Region,
        [Parameter(Mandatory, ParameterSetName = "Components")]
        [int] $Index
    )

    if ($Id) {
        $Service, $FlightingRing, $Region, $Index = $Id -split "-"
    }

    [Cluster]@{
        Service       = $Service
        FlightingRing = $FlightingRing
        Region        = $Region
        Index         = $Index
    }
}

Export-ModuleMember New-Cluster

Què més llegir

Sobre les Classes
PowerShell defensiu
Programació funcional a PowerShell

Font: www.habr.com

Afegeix comentari