PowerShell funcional com classes - não um oxímoro, eu garanto

Olá Habr! Apresento a você a tradução do artigo "PowerShell funcional com classes.
Eu prometo que não é um oxímoro " de Christopher Kuech.


Os paradigmas de programação funcional e orientada a objetos podem parecer incompatíveis entre si, mas ambos são igualmente suportados no Powershell. Quase todas as linguagens de programação, funcionais e não, possuem meios de ligação estendida de nomes e valores; Classes, como estruturas e registros, são apenas uma abordagem. Se restringirmos o uso de Classes a nomes e valores e evitarmos conceitos de programação orientada a objetos "pesados" como herança, polimorfismo ou mutabilidade, poderemos tirar proveito deles sem complicar nosso código. Além disso, adicionando métodos imutáveis ​​de conversão de tipos, podemos enriquecer nosso código funcional com Classes.


Magia de castas


Castes é um dos recursos mais poderosos do Powershell. Ao converter um valor, você conta com o ambiente sendo adicionado ao seu aplicativo para inicialização e validação implícitas. Por exemplo, uma conversão simples de uma string em [xml] a executará no código do analisador e gerará uma árvore xml completa. Podemos usar Classes em nosso código para a mesma finalidade.


Hashtables personalizados


Se você não tem um construtor, pode continuar sem ele usando uma tabela de hash para o tipo de sua classe. Lembre-se de usar atributos de validação para aproveitar ao máximo esse padrão. Ao mesmo tempo, podemos usar as propriedades digitadas da classe para acionar uma lógica de inicialização e validação ainda mais profunda.


class Cluster { [ValidatePattern("^[Az]+$")] [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 } 

Além disso, o elenco ajuda a obter uma conclusão clara. Compare a saída da matriz de tabelas de hash do cluster passada para Format-Table com o que acontece se você converter essas tabelas de hash pela primeira vez em uma classe. As propriedades da classe são sempre listadas na ordem em que são definidas lá. Não se esqueça de adicionar a palavra-chave oculta na frente de todas as propriedades que não devem estar visíveis na saída.


imagem

Valores personalizados


Se você tiver um construtor com um argumento, converter os valores para o seu tipo de classe passará o valor para esse seu construtor, no qual você pode inicializar a instância da sua classe


 class Cluster { [ValidatePattern("^[Az]+$")] [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" 

Linha personalizada


Você também pode substituir o método de classe [string] ToString () para determinar a lógica da representação de string do objeto, por exemplo, usando a interpolação de string.


 class Cluster { [ValidatePattern("^[Az]+$")] [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'" 

Instâncias serializadas personalizadas


O elenco permite desserialização segura. Os exemplos abaixo falharão se os dados não atenderem às nossas especificações no Cluster


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


Castes no seu código de função


Os programas funcionais primeiro definem estruturas de dados, depois implementam o programa como uma sequência de transformações sobre estruturas de dados imutáveis. Apesar da impressão conflitante, as classes realmente ajudam a escrever código funcional, graças aos métodos de conversão de tipo.


O Powershell é funcional que eu escrevo?


Muitas pessoas que vêm de C # ou com um passado semelhante escrevem o Powershell, que é semelhante ao C #. Ao fazer isso, você reluta em usar os conceitos de programação funcional e provavelmente obtém o benefício de estar imerso na programação orientada a objetos no Powershell ou em uma melhor compreensão da programação funcional.


Se você confiar fortemente na transformação de dados imutáveis ​​usando pipelines (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, etc., terá um estilo e um uso mais funcionais Aulas de PowerShell em estilo funcional.


Uso funcional de classes


As castas, embora usem sintaxe alternativa, estão apenas mapeando entre dois domínios. No pipeline, você pode mapear uma matriz de valores usando o ForEach-Object.


No exemplo abaixo, o construtor Node é executado toda vez que ocorre uma conversão para o Datum, e isso nos permite não escrever uma quantidade razoável de código. Como resultado, nosso pipeline se concentra na consulta e agregação declarativa de dados, enquanto nossas classes cuidam da análise e validação de dados.


 #       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 "([az]+)(INT|PPE|PROD)([az]+)" $_, $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 


Reutilizar embalagens de classe


Nada é tão bom quanto parece


Infelizmente, as classes não podem ser exportadas por módulos da mesma maneira que funções ou variáveis; mas existem alguns truques. Digamos que suas classes sejam definidas no arquivo ./my-classes.ps1


  • Você pode dosar um arquivo com classes:. ./my-classes.ps1. Isso executará my-classes.ps1 no seu escopo atual e definirá todas as classes do arquivo.


  • Você pode criar um módulo do Powershell que exporte todas as suas APIs de usuário (cmdlets) e defina a variável ScriptsToProcess = "./my-classes.ps1" no manifesto do seu módulo, com o mesmo resultado: ./my-classes.ps1 será executado em seu ambiente .



Qualquer que seja a opção escolhida, lembre-se de que o sistema de tipos Powershell não pode resolver os tipos com o mesmo nome carregados de lugares diferentes.
Mesmo se você baixar duas classes idênticas com as mesmas propriedades de locais diferentes, você corre o risco de ter problemas.


Caminho a seguir


A melhor maneira de evitar problemas de resolução de tipos é nunca expor suas classes aos usuários. Em vez de esperar que o usuário importe um tipo definido na classe, exporte do seu módulo uma função que elimine a necessidade de acessar diretamente a classe. Para o Cluster, podemos exportar a função New-Cluster, que oferecerá suporte a conjuntos de parâmetros amigáveis ​​ao usuário e retornará o Cluster.


 class Cluster { [ValidatePattern("^[Az]+$")] [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 que mais ler


Sobre Classes
PowerShell defensivo
Programação funcional no PowerShell

Source: https://habr.com/ru/post/pt484300/


All Articles