Scripting Games: moje notatki – 1
Scripting Games trwają już przeszło tydzień. Właśnie wystartowała druga konkurencja, a pierwszą właśnie możecie oceniać (a jeśli zdecydowaliście się uczestniczyć – zbierać oceny i komentarze). Zgodnie z zapowiedzią, pora bym napisał słów kilka z mojego punktu widzenia: co mi się podobało, a co nie, w poszczególnych kategoriach. Dziś – zadanie pierwsze.
Najlepsiejsze: początkujący
Przeglądając skrypty pisane przez początkujących trafiłem na wiele elementów, które bardzo przypadły mi do gustu.
Na doskonały początek: wykorzystanie bloku skryptu przekazywanego do parametru Destination by tam “wyliczyć” docelowy folder w oparciu o obiekt wpadający z rurki. Wśród tych skryptów najbardziej przypadły mi do gustu te, których autorzy dodatkowo w tym samym miejscu zadbali, by folder na pewno w docelowym miejscu istniał:
Get-ChildItem Log\*\*.Log | Get-Random -Count 10 | Move-Item -Destination { mkdir -Path (".\Arch\{0}" -f $_.Directory.Name) -Force }
Oczywiście, właściwy kod opierał się na dacie utworzenia pliku, nie losował plików przy pomocy Get-Random.
Kolejna sprytna technika to wykorzystanie Select-Object by przekazać cmdletowi New-Item wszystkie wymagane przez niego parametry przy pomocy pipeline:
Get-ChildItem Log\*\*.Log | Get-Random -Count 10 | Select-Object @{ Name = 'Path' Expression = { $_.FullName } }, @{ Name = 'Destination' Expression = { ".\Arch\$($_.Directory.Name)" } } | Move-Item -WhatIf
Nie zabrakło też ludzi, którzy dzielą ze mną przekonanie, że PowerShell ma nazwę taką, jaką ma, nie bez powodu. Wszak zadanie takie to niemal idealna robota dla narzędzia robocopy. Wystarczy przekazać odpowiednie parametry i pliki fruną…
Robocopy.exe .\Log .\Arch *.log /MIR /MOV /MINAGE:90
Cieszy mnie też fakt, że wielu uczestników zamiast “na piechotę” wyliczać ścieżkę, korzystało z cmdletów takich jak Split-Path i Join-Path. I to w zasadzie doskonały moment, by zacząć narzekać…
Wręcz przeciwnie: początkujący
Nie trudno zgadnąć, że zacznę od wszelkich .Replace, .Split i .Substring., często w sposób wykluczający jakiekolwiek późniejsze zmiany (na przykład zafiksowana pozycja startowa wycinanego substringa). Dorzuciłbym tu [DateTime]::Now zamiast Get-Date. Ja rozumiem, że PowerShell nie ma pełnego pokrycia poleceniami wszystkiego co oferuje .NET, ale akurat określenie aktualnej daty nie jest jedną z tych rzeczy, do których takie sięganie do .NET bezpośrednio było kiedykolwiek konieczne.
Druga rzecz to potwornie nieczytelny kod. Szczególnie pseudo-jednolinijkowce. Jeśli piszecie kilka “realnych” linii kodu w jednej, przedzielając poszczególne średnikiem to nie tylko nie zbliżacie się nawet do idei jednolinijkowca, tworzycie po prostu potworka, który straszy. A nawet jeśli trzymacie się jednej “rurki” (a to właśnie w moich oczach zasługuje na miano jednolinijkowca) to śmiało, “połamcie” go w odpowiednich miejscach (tylko błagam, nie “backtickiem: `”). Jedna rurka to serce zwięzłych rozwiązań.
Kolejna rzecz to nadużywanie Write-Host, ale do tego jeszcze wrócę przy skryptach w drugiej kategorii.
Kolejna rzecz kosmetyczna to powtarzanie kodu. Pętle są w PowerShellu w kilku wymiarach i smakach – używajmy ich, zamiast przepisywać ten sam (niemal) kod trzy razy…
Na koniec rzecz najistotniejsza. Zadanie wymagało od nas uratowanie wolnej przestrzeni na naszym serwerze. Nie wiem jakim cudem niektórzy uczestnicy uznali, że skopiowanie logów na udział sieciowy w tym pomoże. Moim zdaniem ryzykujemy tylko tym, że zamiast jednego serwera z przepełnionym dyskiem – teraz będziemy mieli dwa. Powiecie: logi to nie duży problem? Cóż, dużo zależy od autorów aplikacji… Ostatnio odzyskałem kilkanaście gigabajtów (!?) kasując logi z jednej aplikacji. Oczywiście, mogłem je zamiast tego skopiować, ale dysk pozostałby pełny.
Najlepsiejsze: zaawansowani
Na pierwszy ogień: poprawna implementacja SupportsShouldProcess. Narzędzie zdecydowanie modyfikuje system, więc aż się prosi, by je dodać. Ale dodać w pełni, a nie na pół gwizdka! Na czym polegać będzie różnica, możecie przeczytać tu.
Druga sprawa to wspomniane wcześniej Write-Host. A raczej – fakt, że wielu uczestników wywaliło go ze swojego słownika. Zamiast tego polecenia o wiele lepiej jest użyć Write-Verbose i Write-Debug. Szczególnie gdy opis mówi wprost: nie zaśmiecać wyjścia informacjami, jeśli wszystko się powiodło. Druga sprawa istotna dla użytkowników PowerShella w wersji trzeciej: przekierowania. Wyjście Verbose i Debug możemy tam bowiem przekierować i przechwycić. Host pozostanie na zawsze w naszej konsoli i nigdzie indziej się nie wybiera.
Poza tym skrypty są pisane w stylu nadającym się do produkcji: pomoc w komentarzach, walidowanie parametrów, definiowanie parametrów jako wspierających rurkę czy wymaganych. Obsługa błędów też zwykle jest świetna: zarówno bloki try {} catch {} jak i rozsądne używanie opcji ErrorVariable by definiować własną kolekcję błędów, by nie polegać na $Error.
Wręcz przeciwnie: zaawansowani
Sporo grzechów i grzeszków z kategorii początkujących mógłbym tu przepisać. Ale skupmy się na “nowościach”.
Nazewnictwo funkcji. Naprawdę, nie ma tu miejsca na fantazję. Lista zaaprobowanych czasowników jest znana, myślnik – konieczny. Rzeczownik musi być w liczbie pojedynczej, tak samo jak w innych przypadkach: Get-Process by wyciągnąć processy, Get-ADUser by wyciągnąć użyszkodników. Dlaczego więc Archive-BackupLogs ? By później trzeba się było z tego rakiem wycofywać? Nie sądzicie, że to istotne? Porównajcie więc:
Get-Command -Module GroupPolicy -Noun GPPermission*
… na W2K8R2 i W2K12. Tak, nawet MS musi czasem po sobie “sprzątać” jak strzeli babola w tym miejscu.
Skoro już jesteśmy przy aliasach: w skryptach ich raczej nie używajcie, nawet tych oczywistych. Jeśli tworzymy narzędzie, to istnieje szansa, że kiedyś zechcemy je zmodyfikować. Jak nawrzucamy aliasów, parametrów pozycyjnych i innych świetnie się nadających do pracy interaktywnej “ułatwiaczy”, to może się okazać, że zamiast zmienić istniejące narzędzie, łatwiej będzie napisać nowe. A pisać “nowe” o trzeciej nad ranem z pożarem smyrającym końcówki naszych palców to taka średnia przyjemność. Innymi słowy: skrypt mający być narzędziem musi nie tylko działać, musi być czytelny i łatwy do modyfikacji.
Kolejna rzecz: wyręczanie wbudowanych narzędzi własnymi, zwykle mało udanymi. Dla przykładu, jeśli chcemy zagwarantować, że skrypt nie będzie uruchomiony na PowerShellu w niższej niż 3-cia wersji – korzystajmy z opcji #requires –version 3. Jeśli chcemy walidować parametry – użyjmy Validate*, nie piszmy walidatorów na piechotę.
A skoro już o walidacji mowa: bardzo niebezpieczne jest korzystanie w [ValidateScript()] z wartości innych parametrów. To nie działa! Lub inaczej: zadziałać może tylko błędnie. Spójrzcie sami:
function Test-Validate { param ( [ValidateScript({ $_ -gt $foo })]$bar, $foo ) "Zadzialalo!" } $foo = 1 Test-Validate -bar 2 -foo 4
Jaki będzie finał? W nadrzędnym zakresie mamy zmienną $foo, której wartość jest niższa niż przekazana do parametru $bar. Ale osoba tworzącą tą funkcję zakładała, że porównywać będzie z drugim parametrem. Jak widać – nic z tego. Mimo że $bar jest niższy niż $foo, uzyskamy na wyjściu “Zadzialalo!”
Kolejna rzecz to pobieranie danych wejściowych przy pomocy interfejsu graficznego. Zrozumiałbym, gdyby to był “backup”, jeśli użytkownik narzędzia nie skorzysta z istniejących parametrów. Ale jeśli tych parametrów po prostu na wejściu skryptu nie ma – to moim zdaniem ktoś właśnie zmusił mnie, by po prostu poszukał innego narzędzia. Jeśli miałby za każdym razem w okienku podawać wszystkie parametry… Fatality.
No i na koniec rzecz, której nigdy chyba nie zrozumiem. Tworząc konstrukcje if () {} than {} oczekujemy, że w nawiasie uzyskamy wartość logiczną “prawda” lub “fałsz”. Jeśli korzystamy z poleceń zwracających taką właśnie wartość (np. Test-Path) to po kiego grzyba porównywać to z inną wartością logiczną? if (Test-Path) załatwi nam sprawę w połowie przypadków! W drugiej połowie konieczne będzie if (-not (Test-Path). Wsio. Żadnych –eq i –ne nie trzeba do tego mieszać.
Tym kończę i zachęcam do dalszej zabawy – druga konkurencja już czeka. Zarówno na początkujących jak i zaawansowanych PowerShellowców.
[…] Polish version […]
Few notes written after event 1. | PowerShell.org said this on 2 Maj, 2013 @ 8:48 am |