Powershell fungsional dengan kelas bukanlah sebuah oxymoron, saya jamin

Hei Habr! Untuk perhatian Anda, saya persembahkan terjemahan artikel tersebut "PowerShell Fungsional dengan Kelas.
Aku berjanji itu bukan sebuah oxymoron"
oleh Christopher Kuech.

Paradigma pemrograman berorientasi objek dan fungsional mungkin tampak bertentangan satu sama lain, namun keduanya sama-sama didukung di Powershell. Hampir semua bahasa pemrograman, berfungsi atau tidak, memiliki fasilitas untuk pengikatan nama-nilai yang diperluas; Kelas, seperti struct dan record, hanyalah satu pendekatan. Jika kita membatasi penggunaan Kelas pada pengikatan nama dan nilai, dan menghindari konsep pemrograman berorientasi objek yang berat seperti pewarisan, polimorfisme, atau mutabilitas, kita dapat memanfaatkan manfaatnya tanpa mempersulit kode kita. Selanjutnya, dengan menambahkan metode konversi tipe yang tidak dapat diubah, kita dapat memperkaya kode fungsional kita dengan Kelas.

Keajaiban kasta

Kasta adalah salah satu fitur paling kuat di Powershell. Saat Anda memberikan nilai, Anda mengandalkan kemampuan inisialisasi dan validasi implisit yang ditambahkan lingkungan ke aplikasi Anda. Misalnya, memasukkan string ke dalam [xml] akan menjalankannya melalui kode parser dan menghasilkan pohon xml lengkap. Kita dapat menggunakan Kelas dalam kode kita untuk tujuan yang sama.

Keluarkan tabel hash

Jika Anda tidak memiliki konstruktor, Anda dapat melanjutkan tanpa konstruktor dengan memasukkan tabel hash ke tipe kelas Anda. Jangan lupa untuk menggunakan atribut validasi untuk memanfaatkan pola ini sepenuhnya. Pada saat yang sama, kita dapat menggunakan properti kelas yang diketik untuk menjalankan logika inisialisasi dan validasi yang lebih dalam.

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
}

Selain itu, casting membantu mendapatkan hasil yang bersih. Bandingkan output dari array hashtable Cluster yang diteruskan ke Format-Table dengan apa yang Anda dapatkan jika Anda pertama kali memasukkan tabel hash ini ke dalam kelas. Properti suatu kelas selalu dicantumkan sesuai urutan definisinya di sana. Jangan lupa untuk menambahkan kata kunci tersembunyi sebelum semua properti yang Anda tidak ingin terlihat di hasil.

Powershell fungsional dengan kelas bukanlah sebuah oxymoron, saya jamin

Pemeran makna

Jika Anda memiliki konstruktor dengan satu argumen, memberikan nilai ke tipe kelas Anda akan meneruskan nilai tersebut ke konstruktor Anda, tempat Anda dapat menginisialisasi turunan kelas Anda

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"

Transmisikan ke garis

Anda juga dapat mengganti metode kelas [string] ToString() untuk menentukan logika di balik representasi string objek, seperti menggunakan interpolasi string.

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

Transmisikan instance serial

Pemeran memungkinkan deserialisasi yang aman. Contoh di bawah ini akan gagal jika data tidak memenuhi spesifikasi kami di Cluster

# Валидация сСриализованных Π΄Π°Π½Π½Ρ‹Ρ…

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

Kasta dalam kode fungsional Anda

Program fungsional pertama-tama mendefinisikan struktur data, kemudian mengimplementasikan program sebagai rangkaian transformasi pada struktur data yang tidak dapat diubah. Meskipun kesannya kontradiktif, kelas sangat membantu Anda menulis kode fungsional berkat metode konversi tipe.

Apakah Powershell yang saya tulis berfungsi?

Banyak orang yang berasal dari C# atau latar belakang serupa menulis Powershell, yang mirip dengan C#. Dengan melakukan ini, Anda beralih dari penggunaan konsep pemrograman fungsional dan kemungkinan besar akan mendapat manfaat dari mendalami pemrograman berorientasi objek di Powershell atau mempelajari lebih lanjut tentang pemrograman fungsional.

Jika Anda sangat bergantung pada transformasi data yang tidak dapat diubah menggunakan saluran pipa (|), Where-Object, ForEach-Object, Select-Object, Group-Object, Sort-Object, dll. - Anda memiliki gaya yang lebih fungsional dan Anda akan mendapat manfaat dari penggunaan Powershell kelas dalam gaya fungsional.

Penggunaan kelas secara fungsional

Kasta, meskipun menggunakan sintaksis alternatif, hanyalah pemetaan antara dua domain. Di dalam pipeline, Anda dapat memetakan array nilai menggunakan ForEach-Object.

Pada contoh di bawah, konstruktor Node dieksekusi setiap kali Datum dilemparkan, dan ini memberi kita kesempatan untuk tidak menulis kode dalam jumlah yang cukup. Hasilnya, pipeline kami berfokus pada kueri dan agregasi data deklaratif, sementara kelas kami menangani penguraian dan validasi data.

# ΠŸΡ€ΠΈΠΌΠ΅Ρ€ комбинирования классов с ΠΊΠΎΠ½Π²Π΅ΠΉΠ΅Ρ€Π°ΠΌΠΈ для 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

Kelas pengemasan untuk digunakan kembali

Tidak ada yang sebaik kelihatannya

Sayangnya, kelas tidak dapat diekspor oleh modul dengan cara yang sama seperti fungsi atau variabel; tapi ada beberapa trik. Katakanlah kelas Anda didefinisikan dalam file ./my-classes.ps1

  • Anda dapat melakukan dotsource file dengan kelas :. ./kelas-saya.ps1. Ini akan mengeksekusi my-classes.ps1 dalam lingkup Anda saat ini dan mendefinisikan semua kelas dari file di sana.

  • Anda dapat membuat modul Powershell yang mengekspor semua API khusus (cmdlet) dan menyetel variabel ScriptsToProcess = "./my-classes.ps1" di manifes modul Anda, dengan hasil yang sama: ./my-classes.ps1 akan dijalankan di lingkungan Anda.

Opsi mana pun yang Anda pilih, perlu diingat bahwa sistem tipe Powershell tidak dapat menyelesaikan tipe dengan nama yang sama yang dimuat dari tempat berbeda.
Bahkan jika Anda memuat dua kelas identik dengan properti yang sama dari tempat berbeda, Anda berisiko mengalami masalah.

Jalan lurus

Cara terbaik untuk menghindari masalah resolusi tipe adalah dengan tidak pernah memaparkan kelas Anda kepada pengguna. Daripada mengharapkan pengguna mengimpor tipe yang ditentukan kelas, ekspor fungsi dari modul Anda yang menghilangkan kebutuhan untuk mengakses kelas secara langsung. Untuk Cluster, kita dapat mengekspor fungsi New-Cluster yang akan mendukung kumpulan parameter yang mudah digunakan dan mengembalikan 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

Apa lagi yang harus dibaca?

Tentang Kelas
PowerShell Pertahanan
Pemrograman Fungsional di PowerShell

Sumber: www.habr.com

Tambah komentar