Skryptologia – shim on you

Tym razem trochę o tym jak działa zmienna $PATH i co to jest shim. Zanim jednak przejdę do mięska, to kilka słów o wzorcach projektowych.

Wśród wzorców mamy trzy, które działają podobnie i często są mylone.

  • Adapter – pozwala na podmianę jednego API na inne. Pełni rolę tłumacza pomiędzy API. Całkowicie zmienia oryginalne API.
  • Fasada – ogranicza API do wybranych elementów. Zubaża je, „podpowiadając”, z czego w danym kontekście należy korzystać.
  • Proxy – opakowuje API w celu ochrony lub zasłonięcia dodatkowych czynności potrzebnych przy wywołaniu (np. wywołaniu zdalnym). Nie zmienia jednak samego API (sygnatur).

Jak widać, różnica polega na tym, ile z pierwotnego API zostaje. Pomyłki związane z pomyleniem tych wzorców wynikają zazwyczaj z potrzeb językowych. Jeżeli mamy cztery klasy-adaptery ogarniające nam komunikację ze starym API, to przecież nazwanie piątej klasy fasadą czy proxy spowoduje tylko zamieszanie. W dodatku na CR mogą pojawić się niewygodne pytania, które będą prowadzić do jałowej dyskusji o niuansach w kodzie. Szczególnie że istnieje jeszcze wzorzec mostu, który pozwala na separowanie separację interfejsu i implementacji. Zatem lepiej użyć niewłaściwego słowa i mieć spokój niż wojować o to właściwe opóźniając św. Wartość Biznesową.

A teraz dołożę wam jeszcze jeden wzorzec – shim. Nie ma on ładnej polskiej nazwy, a bezpośrednie tłumaczenie „podkładka” nie oddaje do końca sensu tego wzorca. Najprościej można go opisać jako odmianę adaptera, którego zadaniem jest kompensowanie błędów i różnic w API. Zazwyczaj używa się go w celu zapewnienia kompatybilności wstecznej, w tym kompatybilności z wszystkimi bugami, albo w celu naprawy istniejącego kodu, do którego nie możemy dobrać się bezpośrednio. Można go też użyć w celu zapewnienia kompatybilności w przód, ograniczając nowsze API, w taki sposób, by stare rozwiązania mogły z niego korzystać.
Shim działa zatem trochę jak adapter, trochę jak proxy, trochę jak fasada, czerpiąc z każdego tych wzorców pewne elementy funkcjonalne. Nic więc dziwnego, że głównym użytkownikiem tego rozwiązania są ofiary Windowsa, gdzie mamy cały zestaw narzędzi w ramach Application Compatibility Toolkit.

A teraz mięsko.

Mechanizm tego wzorca można wykorzystać w bardzo ciekawy sposób. Mam kilka projektów, które korzystają z mavena jako głównego narzędzia do budowania. Problem polega na tym, że pewne projekty korzystają z prywatnych repozytoriów, a te w mavenie konfigurujemy w pliku settings.xml. Nie chcę też dopuścić do sytuacji, gdzie w głównym pliku ustawień będę miał widoczne repozytoria „z różnych parafii”. Po pierwsze klienci nie powinni o sobie wiedzieć. Po drugie znacznie łatwiej jest wtedy zarządzać ustawieniami. Kończę projekt, usuwam kod, nie muszę nic usuwać z innych miejsc.

Rozwiązaniem jest zatem użycie lokalnego pliku settings.xml i uruchamianie mavena z przełącznikiem -s. Dodatkowo zazwyczaj korzystam z -U, który pozwala na dociągniecie aktualnych snapshotów. Bardzo przydatne w projektach z wieloma modułami i zależnościami wewnątrzkorporacyjnymi. Użycie to wiąże się z każdorazowym wpisywaniem -U -s settings.xml. Mocno niewygodne. W dodatku trzeba pamiętać, że może to zepsuć skrypty, bo tu się zapomni, tam się nie doda i tragedia gotowa.

Rozwiązania są teoretycznie trzy.

Po pierwsze możemy stworzyć alias:

Listing 1. Aliasowanie mavena

alias mvn='mvn -U -s settings.xml'

Ale to nie zadziała w momencie gdy pliku nie ma w danym katalogu. Można oczywiście alias rozwijać do mikroskryptu z IFem na pokładzie, ale to jest mega niewygodne.

Po drugie możemy stworzyć funkcję, która będzie miała identyczną nazwę i będzie wywoływała właściwy program.
Po trzecie możemy stworzyć skrypt, który będzie tak jak wymieniona wyżej funkcja.
Zarówno skrypt jak i funkcja będą realizować wzorzec shim. Co wybierzemy, to zależy, poniekąd, od naszych preferencji. Ja preferuję skrypt, ponieważ ma on być samodzielnym programem i nie jest przeznaczony do używania w innych programach/skryptach.

Listing 1. Shim mavena

#!/bin/bash

args="$@"
added=0

while [ $# -gt 0 ]
do
        case "$1" in
                (-s) added=1; break;;
                (*);;
        esac
        shift
done

if [[ $added == 0 ]]  # nie dodano argumentu ręcznie
then
        if [[ -f "settings.xml" ]]
        then
                exec /home/koziolek/.sdkman/candidates/maven/current/bin/mvn $args -s settings.xml
        else
                exec /home/koziolek/.sdkman/candidates/maven/current/bin/mvn $args
        fi
else
        exec /home/koziolek/.sdkman/candidates/maven/current/bin/mvn $args
fi

Jak widać mam tutaj dwie „sekcje”. Pierwsza sprawdza, czy już nie przekazano pliku z konfiguracją. Druga uruchamia właściwego mavena. Teraz trzeba TYLKO dodać to do zmiennej $PATH.

Listing 3. Konfiguracja $PATH

export PATH=$HOME/.bin/:$PATH

Ścieżkę do naszego skryptu dodałem na początku zmiennej $PATH, ponieważ ta jest ewaluowana od lewej do prawej i uruchamiany jest pierwszy pasujący program. Należy o tym pamiętać, ponieważ w przeciwnym wypadku, gdy dodamy skrypt na końcu, pierwszym „trafieniem” będzie maven z SDKmana i nasz skrypt nie zostanie uruchomiony.

Dwie lekcje do zapamiętania:

  • shim – czyli wzorzec projektowy, służący do „maskowania niedociągnięć” kodu.
  • $PATH – i kolejność ewaluacji.

Koniec 😀

Napisz odpowiedź

Twój adres e-mail nie zostanie opublikowany.

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax