Scripting Games 2012–Odliczanie: 8…

Zegar tyka…

Już tylko 8 dni…

Dwa dni temu przedstawiłem moje propozycje: co moim zdaniem powinno się robić, by sędziowie byli zadowoleni z pisanych przez Was skryptów. Oczywiście, każdy sędzia jest nieco inny i co innego będzie miało dla niego znaczenie, ale sądzę, że lepiej wiedzieć czego się spodziewać, nawet jeśli gdzieś może się trafić Hiszpańska Inkwizycja. Jej nie spodziewa się nikt. Winking smile

O czym dziś będzie mowa, już wspominałem:

8 rzeczy, których należy unikać

Są pewne rzeczy, które na PowerShellowych “purystów” działają jak płachta na byka. Dziś zamierzam wymienić te, które mi osobiście wadzą najbardziej i które, z własnego doświadczenia jako uczestnika, najboleśniej odpokutowałem. Winking smile

8… Write-Host.

O tym cmdlecie napisano już wiele. Problem z nim polega na tym, że często jest nadużywany do rzeczy, do których go nie stworzono. Można go czasem użyć, by wyświetlić na ekranie bajecznie kolorowe komunikaty o statusie (z tą bajecznością to niestety lekko przesadzam, jesteśmy zamknięci w 16-kolorowej klatce). Ale nigdy, przenigdy nie używajmy go do wyświetlania istotnych wyników. Taki wynik, jak sama nazwa cmdletu wskazuje, zostanie “pożarty” przez naszego hosta. Jeśli nasz skrypt/ funkcja mają komukolwiek posłużyć to nie mogą ograniczać się do jednego “ujścia”. Przekierowanie takiego wyniku gdziekolwiek (do innego polecenia, do pliku, na drukarkę) jest już praktycznie niemożliwe. Idealne ujścia dla informacji o statusie bieżącej operacji wewnątrz naszego skryptu, fatalne dla ostatecznego wyniku jego działania. A ponieważ niektórzy nabawili się przez to alergii na cmdlet w ogóle, proponuję status wyświetlać opcjonalnie (Write-Verbose/ Debug), lub używając poleceń Write-Progress (które mają tę przewagę, że łatwo je aktualizować nie zalewając użytkownika informacją).

7… Miksowanie typów na wylocie.

Pisałem, że na wyjściu naszej funkcji powinny znajdować się obiekty. Ale muszą to być obiekty jednego typu – mieszanie różnych typów zwykle kończy się fatalnie i powoduje nieoczekiwane rezultaty. Źródła “mieszania” są w zasadzie trzy głównie:

  • osadzone w treści stringi. Ładnie wyglądają, ale niestety lądują w wyniku funkcji.
  • korzystanie z metod .NET bez dbania o ich rezultat: często zwracają one jakieś liczby (indeks) lub wartości logiczne ($true/ $false)
  • założenie, że funkcja zwraca tylko to, co znajduje się po “return”, wiąże się z poprzednimi dwoma…

Dla przykładu:

function Get-List {            
param (            
    [Object[]]$Member            
)            
            
    $Collection = New-Object System.Collections.ArrayList            
    "Dodajemy elementy do kolekcji..."            
    foreach ($Item in $Member) {            
        $Collection.Add($Item)            
    }            
    , $Collection            
                
}            
            
$List = Get-List -Member Alfa, Beta, Gamma            
$List.Count

Wynik “powinien” być 1 (temu służy przecinek poprzedzający $Collection, w przeciwnym razie PowerShell “rozbije” nam kolekcję i zwróci zwykłą listę). Nie ma tu nawet jednego return, a mimo to wynik jest 5: mamy w wynikowej kolekcji stringa (nasz komunikat), liczby całkowite (rezultat użycia metody ‘Add’) i naszą kolekcję. Naturalnie, kolekcja nie ma nic wspólnego z ArrayList, ten typ jest tylko jednym z jej elementów. A wystarczyłoby:

  • użyć Write-Host lub jeszcze lepiej: –Verbose, –Debug lub –Progress zamiast wrzucania zwykłego stringa
  • przekierować metody .NET na $null, Out-Null, czy w inny sposób je “uciszyć”
  • używać return tylko do tego, do czego w PowerShellu służy (wcześniejsze wyjście z funkcji w oparciu o jakiegoś ifa np.)

6… Pisanie własnej pomocy.

Intencje są słuszne (pomoc jest przydatna, pisałem już o tym dwa dni temu). Rozwiązanie – fatalne. Dodanie parametru ‘?’ do naszej funkcji nie jest konieczne od wersji drugiej PowerShella. Funkcja, która coś takiego obsłuży będzie i tak albo zbyt wylewna, albo zbyt powściągliwa. Nie da nam możliwości (łatwej) podania parametrów takich jak Full, czy Examples. Napracujemy się bardzo, a i tak nikt tego nie doceni. Dlaczego? Ponieważ jeśli ktoś nie używa narzędzi skutecznych i wbudowanych to udowadnia, że ich nie zna. A to nigdy nie świadczy na naszą korzyść. Zamiast obszernej funkcji – kilka linii komentarza. Prościej, czytelniej i skuteczniej.

5… Samodzielne walidowanie parametrów.

Znów: prawdopodobnie w 9 przypadkach na 10 skutek nieznajomości funkcji języka. PowerShell umożliwia nam walidowanie parametrów na wiele różnych sposobów, włącznie z walidowaniem przy pomocy własnego skryptu. Jeśli więc początek Twoich funkcji to banda ifów sprawdzających wartość parametrów, to prawdopodobnie robisz to źle. Jest tylko jeden przypadek, gdy taka walidacja ma sens: jeśli walidować musimy kilka parametrów jednocześnie ($Pensja –gt $Wydatki). Tego PowerShell nie umożliwia, musimy więc go wyręczyć. Ale to właśnie te 1 na 10 przypadków. A może nawet 1 na 100…?

Jeśli więc chcecie walidować parametry (a robić to warto, by nie doprowadzić do nieoczekiwanych rezultatów), polecam lekturę about_Functions_Advanced_Parameters. Można też sięgnąć do mojego starszego postu, gdzie wymieniam dostępne opcje. Tu – prosty przykład. Podajemy funkcji ścieżkę do pliku, który musi istnieć by funkcja zadziałała, korzystając więc z ValidateScript upewniamy się, że tak jest w istocie:

function Get-FileStatistic {            
param (            
    [ValidateScript( {            
        Test-Path -Path $_            
    })]            
    [string]$Path            
)            
    Get-Content $Path | Measure-Object -Word -Line -Character |            
        Select-Object Words, Lines, Characters            
}

Przykład bardzo prosty ale obrazuje, jak niewiele trzeba, by nasz parametr miał sensowną wartość.

4… Aliasy, szczególnie mało czytelne.

Alias w konsoli to błogosławieństwo, w skrypcie – przekleństwo. Przede wszystkim może zmniejszyć jego czytelność. O ile aliasy takie jak ‘select’, ‘where’, czy ‘measure’ są dość powszechnie używane i zrozumiałe, o tyle rzadziej stosowane (sl?) mogą sprawić kłopot nawet osobom, które z PowerShellem pracują każdego dnia. Odradzam też powszechne skrótowce “%” i “?” – oczywiście, wiele osób zna je już dobrze, ale w skrypcie moim zdaniem są przeszkodą w jego łatwym odczytaniu i zrozumieniu. A jeśli ktoś posłuży się naszym skryptem jako inspiracją i zechce go zmodyfikować, to może się na nich “potknąć”. Pisząc skrypt nie musimy oszczędzać palców: piszemy go raz i później (przynajmniej z założenia) głównie korzystamy. Więc zysk jest iluzoryczny, lepiej jednak postarać się i użyć pełnej nazwy komendy. Jest też jeszcze jedno zagrożenie: polegamy w naszym skrypcie na istnieniu aliasu, który ktoś świadomie na swoim komputerze usunął. Wówczas użytkownik naszego skryptu może spędzić sporo czasu próbując ustalić, gdzie tkwi błąd. Na tyle dużo, że potencjalny sędzia uzna po prostu, że Wasz skrypt nie działa wcale. Po co ryzykować? Smile

3… Niezrozumiałe nazwy zmiennych.

Ocenianie skryptów czasem musi się ograniczyć do ich przejrzenia, testowanie wszystkich może się okazać niemożliwe. Używając nazw zmiennych, które trudno później śledzić (a, b, c, pc, pr) oszczędzacie nieco czasu w czasie pisania, ale praktycznie uniemożliwiacie rozumienie logiki “na pierwszy rzut oka”. Skrypt nie przyspieszy od nazwania zmiennej krótszą nazwą, a zarówno Wam, jak i osobom czytającym Wasz skrypt będzie łatwiej, jeśli zmienne będą miały sensowne nazwy. Oczywiście, zalecam nazwy angielskie… Nie dlatego, że nie lubię mowy ojczystej. Po prostu język angielski lepiej lub gorzej znamy (niemal) wszyscy, nikt więc nie będzie miał wątpliwości co kryje się pod zmienną “Path”. “Sciezka” może kogoś zbić z pantałyku… Winking smile

2… Łamanie linii przy pomocy backticka.

W PowerShellu koniec linii na ogół tożsamy jest z końcem wyrażenia, czego skutkiem jest jego wykonanie. By tego uniknąć często w skryptach korzysta się ze znaku “odwołującego” tę właściwość końca linii, “`”. Osobiście odradzam tę technikę. Wystarczy że ktoś nieumyślnie wstawi spację tuż po tym znaku i działająca komenda nagle zaczyna sprawiać problemy. Zamiast tego proponuję łamać linie na znakach, które tego feleru nie mają:

  • | –> czyli następna komenda w “rurce” ląduje w kolejnej linii
  • ( –> czyli dalsza część prostego wyrażenia przenosi się do linii kolejnej
  • { –> rozpoczynamy ScriptBlock w pierwszej linii, kontynuujemy w następnej
  • , –> lista można w prosty sposób “rozrzucić” na kilka linii

Gorzej, gdy pojedyncza komenda ma tak wiele parametrów, że można albo mieć linię o szerokości 300 znaków, albo użyć backticka. W takiej sytuacji – polecam metodę nazywaną “splatting”:

# Podajemy wartość "na sztywno" ale mogłaby być parametrem...            
$ComputerName = '.'            
            
$Opcje = @{            
    LogName = 'System'            
    Newest = 10            
    After = (Get-Date).AddDays(-1)            
    EntryType = 'Error'            
    Source = 'Service Control Manager'            
    ComputerName = $ComputerName            
}            
            
Get-EventLog @Opcje | select Message, TimeGenerated

Pozwala ona też prosto “współdzielić” parametry między komendami, jeśli są jakieś punkty wspólne. Jest też wiele innych korzyści wynikających z korzystania ze splattingu. Korzyści z backticka nie ma w zasadzie żadnych, przynajmniej do łamania linii (w innych miejscach nie niesie ze sobą takich zagrożeń).

1… Komentowanie rzeczy oczywistych.

Komentarze w funkcjach to rzecz dobra, warto czasem opatrzyć jakieś nieoczywiste operacje komentarzem, by osoba przeglądająca skrypt wiedziała dlaczego postępujemy tak, a nie inaczej. Ale trzeba wystrzegać się pisania rzeczy trywialnych i oczywistych. Prosty przykład:

# Czytam zawartość pliku...            
$Content = Get-Content -Path $Path            
            
# Filtruje tylko te linie, które zawierają słowo "error"            
$Errors = $Content | where { $_ -match 'error' }            
            
# Zapisuje do pliku $OutPath            
Set-Content -Path $OutPath

Pomijając jakość samego kodu: czy choć jeden komentarz coś wnosi? Wyjaśnia logikę naszego działania? Informuje nas o czymś, czego nie da się wyczytać ze samej składni? Teraz porównajmy to z przykładem pozytywnym…:

            
# Używam -FilterXml: tylko 2008 R2 i nowsze pozwala na użycie HashTable.            
Get-WinEvent -FilterXml $SomeXML -ComputerName $Computer

I już nie przyjdzie mi do głowy pytanie: czemu nie użyto łatwiejszego do zbudowania –FilterHashTable… Smile Skoro skrypt ma zadziałać na serwerze 2008, to nie można użyć parametru, którego ta wersja serwera nie akceptuje.

Podsumowanie.

Wiemy już czego unikać, co robić. Pytanie kolejne: po co w ogóle mam sobie zawracać tym głowę. Już pojutrze: 6 powodów, by uczestniczyć.

~ - autor: Bartek Bielawski w dniu 25 marca, 2012.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Wyloguj /  Zmień )

Zdjęcie na Facebooku

Komentujesz korzystając z konta Facebook. Wyloguj /  Zmień )

Połączenie z %s

 
%d blogerów lubi to: