Funkčný Powershell s triedami nie je oxymoron, to garantujem

Čau Habr! Do pozornosti dávam preklad článku "Funkčný PowerShell s triedami.
Sľubujem, že to nie je oxymoron"
od Christophera Kuecha.

Objektovo orientované a funkčné programovacie paradigmy sa môžu zdať vo vzájomnom rozpore, ale obe sú v Powershell rovnako podporované. Takmer všetky programovacie jazyky, funkčné alebo nefunkčné, majú prostriedky na rozšírenú väzbu medzi názvom a hodnotou; Triedy, ako sú štruktúry a záznamy, sú len jedným prístupom. Ak obmedzíme naše používanie tried na viazanie mien a hodnôt a vyhneme sa náročným objektovo orientovaným programovacím konceptom, ako je dedičnosť, polymorfizmus alebo mutabilita, môžeme ich využiť bez toho, aby sme skomplikovali náš kód. Ďalej, pridaním nemenných metód konverzie typov môžeme obohatiť náš funkčný kód o triedy.

Kastová mágia

Castes sú jednou z najvýkonnejších funkcií v Powershell. Keď prenášate hodnotu, spoliehate sa na implicitné možnosti inicializácie a overovania, ktoré prostredie pridáva do vašej aplikácie. Napríklad jednoduchým prenesením reťazca do [xml] prebehne kód syntaktického analyzátora a vygeneruje sa kompletný xml strom. Na rovnaký účel môžeme použiť triedy v našom kóde.

Cast hashtables

Ak nemáte konštruktor, môžete pokračovať bez neho pretypovaním hašovacej tabuľky na váš typ triedy. Nezabudnite použiť overovacie atribúty, aby ste mohli naplno využiť tento vzor. Zároveň môžeme použiť typizované vlastnosti triedy na spustenie ešte hlbšej inicializačnej a validačnej logiky.

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
}

Okrem toho odlievanie pomáha získať čistý výstup. Porovnajte výstup poľa hašovacej tabuľky klastra odovzdaného do Format-Table s tým, čo získate, ak tieto hašovacie tabuľky prvýkrát prenesiete do triedy. Vlastnosti triedy sú vždy uvedené v poradí, v akom sú tam definované. Nezabudnite pridať skryté kľúčové slovo pred všetky tie vlastnosti, ktoré nechcete, aby boli vo výsledkoch viditeľné.

Funkčný Powershell s triedami nie je oxymoron, to garantujem

Obsadenie významov

Ak máte konštruktor s jedným argumentom, odovzdaním hodnoty do vášho typu triedy sa hodnota odovzdá vášmu konštruktorovi, kde môžete inicializovať inštanciu vašej triedy

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"

Cast to line

Môžete tiež prepísať metódu triedy [string] ToString(), aby ste definovali logiku za reprezentáciou reťazca objektu, napríklad pomocou interpolácie reťazca.

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'"

Cast serializované inštancie

Odliatok umožňuje bezpečnú deserializáciu. Nižšie uvedené príklady zlyhajú, ak údaje nespĺňajú naše špecifikácie v klastri

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

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

Casty vo vašom funkčnom kóde

Funkčné programy najskôr definujú dátové štruktúry, potom implementujú program ako postupnosť transformácií nad nemennými dátovými štruktúrami. Napriek rozporuplnému dojmu vám triedy vďaka metódam konverzie typov skutočne pomáhajú pri písaní funkčného kódu.

Je Powershell, ktorý píšem, funkčný?

Veľa ľudí z C# alebo podobného prostredia píše Powershell, ktorý je podobný C#. Týmto spôsobom sa vzďaľujete od používania konceptov funkčného programovania a pravdepodobne by vám prospelo, keby ste sa intenzívne ponorili do objektovo orientovaného programovania v Powershell alebo sa dozvedeli viac o funkčnom programovaní.

Ak sa vo veľkej miere spoliehate na transformáciu nemenných údajov pomocou kanálov (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object atď. – máte funkčnejší štýl a budete mať prospech z používania Powershell triedy vo funkčnom štýle.

Funkčné využitie tried

Kasty, hoci používajú alternatívnu syntax, sú len mapovaním medzi dvoma doménami. V potrubí môžete mapovať pole hodnôt pomocou ForEach-Object.

V nižšie uvedenom príklade sa konštruktor Node spustí vždy, keď sa prenesie Dátum, čo nám dáva príležitosť vyhnúť sa písaniu veľkého množstva kódu. V dôsledku toho sa náš kanál zameriava na deklaratívne dopytovanie a agregáciu údajov, zatiaľ čo naše triedy sa starajú o analýzu a validáciu údajov.

# Пример комбинирования классов с конвейерами для 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

Trieda balenia na opätovné použitie

Nič nie je také dobré, ako sa zdá

Bohužiaľ, triedy nemožno exportovať modulmi rovnakým spôsobom ako funkcie alebo premenné; ale je tam pár trikov. Povedzme, že vaše triedy sú definované v súbore ./my-classes.ps1

  • Môžete dotsource súbor s triedami:. ./moje-triedy.ps1. Tým sa spustí súbor my-classes.ps1 vo vašom aktuálnom rozsahu a zadefinujú sa všetky triedy zo súboru v ňom.

  • Môžete vytvoriť modul Powershell, ktorý exportuje všetky vaše vlastné rozhrania API (cmdlets) a nastaviť premennú ScriptsToProcess = "./my-classes.ps1" v manifeste modulu s rovnakým výsledkom: ./my-classes.ps1 sa spustí v vaše prostredie.

Bez ohľadu na to, ktorú možnosť si vyberiete, majte na pamäti, že systém typov Powershell nedokáže rozlíšiť typy s rovnakým názvom načítané z rôznych miest.
Aj keď načítate dve rovnaké triedy s rovnakými vlastnosťami z rôznych miest, riskujete, že narazíte na problémy.

Cesta vpred

Najlepší spôsob, ako sa vyhnúť problémom s rozlíšením typov, je nikdy nevystavovať svoje triedy používateľom. Namiesto toho, aby ste od používateľa očakávali importovanie typu definovaného triedou, exportujte funkciu z vášho modulu, ktorá eliminuje potrebu priameho prístupu k triede. Pre Cluster môžeme exportovať funkciu New-Cluster, ktorá bude podporovať užívateľsky prívetivé sady parametrov a vráti Cluster.

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

Čo ešte čítať

O triedach
Defenzívny PowerShell
Funkčné programovanie v PowerShell

Zdroj: hab.com

Pridať komentár