Funkčný PowerShell s triedami — nie je to oxymoron, sľubujem vám to

Ahoj, Habr! Predstavujem vám preklad článku "Funkčný PowerShell s triedami.
Sľubujem, že to nie je oxymoron"
autora Christophera Kuecha.

Objektovo-orientované a funkcionálne paradigmy programovania sa môžu zdať nesúladné, ale obidve sú rovnako podporované v PowerShelli. Prakticky všetky programovacie jazyky, funkcionálne i ne, majú prostriedky na rozšírené prepojenie mien a hodnôt; Triedy, podobne ako štruktúry a záznamy, sú len jedným prístupom. Ak obmedzíme použitie tried na prepojenie mien a hodnôt a budeme sa vyhýbať takým "ťažkým" objektovo-orientovaným programovým konceptom, ako sú dedičstvo, polymorfizmus alebo mutabilita, môžeme využívať ich výhody bez zloženia nášho kódu. Ďalej, pridávaním nemenných (immutable) metód na konverziu typov môžeme obohatiť náš funkcionálny kód triedami.

Kúzlo konverzií

Konverzie sú jednou z najsilnejších funkcií v PowerShelli. Keď aplikujete hodnotu na konverziu, spoliehate sa na neimplicitnú inicializáciu a validáciu, ktorú prostredie dodáva vášmu aplikácii. Napríklad jednoduchá konverzia reťazca na [xml] prejde cez parserový kód a vygeneruje kompletný xml strom. V našom kóde môžeme použiť triedy na rovnaký účel.

Konverzia hash tabuliek

Ak nemáte konštruktor, môžete pokračovať ďalej bez neho, používajúc konverziu hash tabuliek na typ vašej triedy. Nezabudnite využiť validačné atribúty, aby ste plne využili tento vzor. Zároveň môžeme využiť typované vlastnosti triedy, aby sme spustili ešte hlbšiu logiku inicializácie a validácie.

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, konverzia pomáha získať čistý výstup. Porovnajte výstup poľa hash tabuliek Cluster, ktorý bol odoslaný do Format-Table, s tým, čo dostanete, ak predtým prevediete tieto hash tabuľky na triedu. Vlastnosti triedy sú vždy uvedené v tom poradí, v akom sú definované. Nezabudnite pridať kľúčové slovo hidden pred všetky tie vlastnosti, ktoré by nemali byť viditeľné vo výstupe.

Funkčný PowerShell s triedami — nie je to oxymoron, sľubujem vám to

Konverzia hodnôt

Ak máte konštruktor s jedným argumentom, konverzia hodnoty na váš typ triedy prenesie hodnotu do vášho konštruktora, v ktorom 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"

Konverzia na reťazec

Môžete tiež pretiahnuť metódu triedy [string] ToString(), aby ste definovali logiku reťazcového zobrazenia objektu, napríklad použiť interpoláciu reťazcov.

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 "Práve sme vytvorili model pre '$cluster'"

Konverzia serializovaných inštancií

Konverzia umožňuje bezpečnú deserialize. Nižšie uvedené príklady skončia chybou, ak údaje nespĺňajú našu špecifikáciu v Cluster.

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

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

Typy vo vašom funkčnom kóde

Funkčné programy najprv definujú štruktúry údajov a potom implementujú program ako sekvenciu transformácií nad nemennými štruktúrami údajov. Napriek rozporuplnému dojmu triedy skutočne pomáhajú písať funkčný kód, vďaka metódam konverzie typov.

Píšem funkčný Powershell?

Mnoho ľudí prichádzajúcich z C# alebo s podobnou minulosťou píše Powershell, ktorý vyzerá ako C#. Týmto sa vzdávate používania konceptov funkčného programovania a pravdepodobne získate výhodu, keď sa hlbšie ponoríte do objektovo orientovaného programovania v Powershell alebo sa lepšie naučíte funkčné programovanie.

Ak sa hlboko spoliehate na transformáciu nemenných údajov pomocou potrubí (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object a pod. — máte funkčnejší štýl a pomôže vám to využitie tried Powershell vo funkčnom štýle.

Funkčné využitie tried

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

V nasledujúcom príklade sa konštruktor Node vykonáva zakaždým, keď dôjde k cástingu na Datum, čo nám umožňuje napísať značné množstvo kódu. V dôsledku toho sa naše potrubie sústreďuje na deklaratívny dotaz údajov a agregáciu, zatiaľ čo naše triedy preberajú analýzu údajov a validáciu.

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

Balenie triedy na znovupoužitie

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

Bohužiaľ, triedy nemôžu byť exportované modulmi rovnakým spôsobom ako funkcie alebo premenné; ale existujú nejaké triky. Predpokladajme, že vaše triedy sú definované v súbore ./my-classes.ps1

  • Môžete urobiť dotSource na súbor s triedami: . ./my-classes.ps1. To vykoná my-classes.ps1 vo vašej súčasnej oblasti viditeľnosti a v definovaní všetkých tried z tohto súboru.

  • Môžete vytvoriť modul Powershell, ktorý exportuje všetky vaše používateľské API (cmdlety) a nastaviť premennú ScriptsToProcess = "./my-classes.ps1" v manifeste vášho modulu, s rovnakým výsledkom: ./my-classes.ps1 sa vykoná vo vašom prostredí.

Bez ohľadu na to, ktorú možnosť si vyberiete, nezabúdajte, že systém typov Powershell nemôže vyriešiť rovnomenné typy načítané z rôznych miest.
Aj keď ste načítali z rôznych miest dva identické triedy so rovnakými vlastnosťami, môžete naraziť na problémy.

Cesta vpred

Najlepší spôsob, ako sa vyhnúť problémom s rozlíšením typov, je nikdy neexponovať vaše triedy používateľom. Namiesto toho, aby ste očakávali, že používateľ importuje konkrétny typ v triede, exportujte zo svojho modulu funkciu, ktorá zbavuje potreby priamo sa odkazovať na triedu. Pre Cluster môžeme exportovať funkciu New-Cluster, ktorá podporí priateľské súpravy 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 prečítať

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

Zdroj: habr.com

Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Kúpte si spoľahlivý hosting pre webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster