niedziela, 7 maja 2017

Jak w Javie można podkradać fajki czyli private nie takie prywatne

We wstępie do takiej jednej książki autorstwa Roberta Cialdiniego o obronie przed manipulacją jest przykład pewnego Indora (nie chodzi o popularne na siłowniach "Indor Cycling" ale o zwykłego żywego ptoka) i tamże naukowcy podrzucili temu prawdziwemu Indorowi sztucznego Indora, a w zasadzie nie indora tylko coś co wydawało indorowate dziwięki i to wystarczyło aby prawdziwy indor zaczął traktować odtwarzacz kasetowy jako swoje własne indorze piskle.

Innymi popularnymi przykładami bezrefleksyjnego zachowania bodziec-reakcja w świecie przyrody będzie oczywiście czerwona płachta na byka, wydzielanie śliny po usłyszeniu dzwonka przez psa Pawłowa oraz oburzenie niektórych programistów Java gdy przed polami w klasie nie pojawia się słowo private.

Pola (Niepotrzebnie)Prywatne

Jak kończy się bardzo często w praktyce dodawanie modyfikatora private do pól? Gdy chcemy stworzyć po prostu reprezentacje danych a język, w którym programujemy ma tylko klasy - wtedy tworzymy klasę, która ma być po prostu danymi - dodajemy pola i generujemy getter/settery.

Ewentualnie jeśli jesteśmy w tej branży odpowiednio długo to jednak nie dodajemy setterów. Można poczytać choćby w już 10 letniej Effective Java o zaletach (z angielska) "klas niemutowalnych" i stworzyć konstruktor, który będzie dbał o to by w naszym obiekcie nie pojawiły się śmieci w stylu sto-dwudziesty-ósmy dzień tygodnia, dajemy final przed polami i jest elegancko. A jak już jest obiekt i uda nam się uniknąć jałowej dyskusji czy to ma być rich/fat/poor/flat/anemic bo potrzebujesz rekord danych - a twój język zarówno technologicznie jak i ideologicznie nie dopuszcza możliwości stworzenia czegoś takiego - wtedy trzeba na końcu nazwy dodać "DTO" i następąpi kolejny cykl bodziec-reakcja gdzie na niektórych ludzi zadziała to jak mellissa i przestaną ci podsyłać posty Martina Fowlera z przed 15 lat.

Jak już mamy poprawnie stworzony obiekt (pamiętaj pamiętaj pamiętaj : "Data Transfer Object" a nie żaden "Record" - Wszystko jest obiektem a Record to takie nieobiektowe) wtedy trzeba dać możliwość dostępu do danych. No i jest jakaś teoria za geterami, która nazywa je (z angielska) "Akcesorami" i daje jakieś tam uzasadnienie że coś tam można prywatnie zmieniać w implementacji klasy bez zmiany jej interfejsu. O i tutaj zaraz na prawo jest taki post gdzie na obrazku w nagłówku jakaś ładna pani czyta wsadowy plik DOSa -> Accessors and Mutators. No i jak to w tego typu postach mamy trochę z dupy naciąganą sytuacje gdzie w Rekordzie obiekcie Person trzeba było zmienić sposób w jaki przechowuje się middle name ze Stringa na Tablice. No niezwykle praktyczny przykład.

Oczywiście może pojawić się krytyka, że przecież muszą być gettery bo pola sa prywatne a przecież musza być prywatne bo jak nie będą to każdy tam wepchnie byle syf. Nie nie muszą. Mamy coś takiego jak juz wspomniane final i jak pole jest final to ustawiasz sobie raz to pole public final String name w konstruktorze. Na pewno się to nie uda gdy utrzymujemy projekt z przed 150 lat gdzie standard JSP wręcz wymusza by dostęp do danych był przez "getCosTam". To się nazywa konwencja i taką sobie przyjęli - być może inne Zręby frameworki też wymuszają taką konwencję. Cóż jak chcesz ich używać to nie masz wyjścia ale takie uzasadnienie "bo framework wymaga" ma niewiele wspólnego z perspektywą projektowania/modelowania systemu.

Apropo frameworków to możemy czasem wpaść na pomysł, że w sumie chcemy gdzieś zapisywać dane a później np. chcemy wyświetlić listę "Personów" na jednej ze stron naszego CRUDa. No i jest ten fajny framework, który mi wyciąga dane z bazy i pakuje do klas i w ogóle. I wystarczy tylko dać annotacje tu i tam. A no i trzeba dodać bezparametrowy konstruktor -> no bo frejmłork stworzy sobie pustą klasę i bedzie tam wpychał dane jak mu wygodnie, być może przez refleksję będzie je wpychał, być może z totalnym wyjebaniem na takie słówka jak private. No i wtedy nie ma wyjścia - jak masz konstruktor bezparametrowy to już pola nie mogą być final i musza być private. Chyba nie ma wyjścia --> z tej sytuacji bo taki standard jest. No cóż.

¯\_(ツ)_/¯

Pola (potrzebnie)Prywatne

Kiedy tworzenie pola prywatnego ma sens? Zawsze kiedy masz stan, który musisz zmieniać. To czy musisz mieć ten stan i czy to dobrze czy nie dobrze, że go zmieniasz to już inna historia. Obiekt może przechowywać stan, który na skutek interakcji z resztą systemu może ulegać zmianie i zależy ci aby przede wszystkim uniemożliwić te zmiany z zewnątrz. Z racji wydajnościowych lub innych ważnych powodów możesz chcieć operować efektywnymi acz umożliwiającymi machloje strukturami danych i a na zewnątrz chcesz operować bezpiecznymi niezmiennymi strukturami. I tutaj na przykład obiekt może manipulować tablicą bajtów czy StringBuilderem a z zewnątrz będzie można dane tylko jak string odczytywać. Plus jak sobie włączymy 2,3,4 wątek to bardzo bardzo nam powinno zależeć aby stan zmieniać tylko z jednego zabezpieczonego miejsca.

No i dla przypomnienia raz jeszcze. Jak sobie napiszesz klasę Person i tam jest getName, które zwraca prywatne pole typu String to nic nie ukrywasz - taka sztuka dla sztuki ten geter, a jak jeszcze masz seter bo jakiś framework tego chce to ani nie ma żadnego ukrywania ani żadnej kontroli. Cześtym argumentem pojawiającym się tutaj jest - "bo jak mam settera to walidację sobie mogę zrobić" - spójrz w lustro , spójrz sobie w oczy w tym lustrze i odpowiedz sam przed sobą ile razy twoja interakcja z setterem to było coś więcej niż IDE->GENERUJ.

W każdy razie po tym wstępie (tak to był wstęp, długi ale wstęp) przejdziemy do zasadniczej części artykułu. Jeśli już masz taki uzasadniony prywatny stan - to niespodzianka - on wcale taki prywatny w tej Javie nie jest.

Pola (Nawet nie wiesz, że nie)Prywatne

Obiektem - a w zasadzie dwoma obiektami - naszych badań będą dwie instancje klasy Osoba. Klasa ta prezentuje koncept biednego człowieka relaksującego się przy pomocy używek.

class Osoba{
    private int fajki;
    public final String name;

    public Osoba(int fajki, String name) {
        this.fajki = fajki;
        this.name = name;
    }

    public String relax(){
            if(fajki==0)
                return name + ": o kurde gdzie moje fajki!";
            else{
                fajki=fajki-1;
                return name + ": zostało mi "+fajki+" fajek";
            }
    }

    void podpierdolFajki(Osoba ktosInny){
        this.fajki=ktosInny.fajki;
        ktosInny.fajki=0;
    }
}
Palenie szkodzi zdrowiu. Poniższy przykład służy tylko edukacji bo prościej sobie wyobrazić, że ktoś podprowadzi fajki a nie witaminę B6.

I co my tam mamy? Jest publiczne pole name czyli już będzie (David) lincz na forach javowywch. Ale jest też prywatne pole fajki i nie ma metody getFajki. Nie powinno nikogo interesować jak ktoś chce sobie puścić dymka. No i w zależności od stanu prywatnego fajek w kieszeni tenże relaks się uda albo nie uda.

Mamy także i jeszcze jedną niepokojąca metodę. Metoda ta ma zakres pakietowy (o czym wielu zapomina, że taki jest bo jest niewidzialny) czyli możemy założyć, że to metoda prywatna dla danej paczki co jest także dosyć wygodne by podzielić logike na kilka klas w pakiecie bez pokazywania ich na świat.

No i w tej metodzie tak trochę wygląda jakby jedna instancja mogła zmienić stan w innej. czy to się kompiluje? Niestety albo stety tak

Osoba stefan=new Osoba(0, "Stefan");
Osoba babkaStefana=new Osoba(10, "Babka Stefana");

stefan.podpierdolFajki(babkaStefana);

System.out.println(stefan.relax());
System.out.println(babkaStefana.relax());
I chociaż Stefan nie ma fajek to jednak w wyniku szfindlu i kompilacji.
Stefan: zostało mi 9 fajek
Babka Stefana: o kurde gdzie moje fajki!
Dlaczego tak się stało? Otóż private w Javie nie jest dla obiektów ale dla klas! Dlatego też dwie instancje tego samego obiektu mogą sobie te prywatne pola czytać. Może to się przydać przy tzw "konstruktorach kopiujących" kiedy tworzymy nowy obiekt na podstawie starego. No i oczywiście coś takiego nie przeszkadza kiedy mamy udawane-obiekto-dane gdzie można sobie ustawić cokolwiek skądkolwiek przy pomocy settera.

Kiedy takie podkradanie danych przez instancje tej samej klasy zaczyna być zauważalne? Kiedy w deklaracji klasy pojawia się generyk oraz zależność pomiędzy klasą a typem generyka jest Covariant (więcej może tu : kowariantne-konsekwencje.html")

Pola (Naprawdę)Prywatne

Do eksperymentu wystarczy nam taka deklaracja :
class Opakowanie[+A](private var mutable:ListBuffer[A])
No i co sie teraz stanie gdy będziemy tworzyć instancję?
val o=new Opakowanie[String](ListBuffer[String]())
Błąd !
Error:(14, 34) covariant type A occurs in invariant position in type => scala.collection.mutable.ListBuffer[A] of variable mutable
class Opakowanie[+A](private var mutable:ListBuffer[A])

o so chozi? Otóż z racji, że Opakowanie[String] jest podtypem Opakowania[Object] możliwe jest szachrajstwo gdzie Opakowanie[Int] podszywając się za Opakowanie[Object] podrzuci Inta do Listy Stringów - a tego nie chcemy. Nie chcemy usuwac tego +A bo chcemy miec relację Covariant - co zrobić? Otóż rozwiązaniem jest prywatny zakres instancji

class Opakowanie[+A](private[this] var mutable:ListBuffer[A])
Jest coś takiego w Scali - Zakres prywatny Instancji .No i teraz żodyn tu int anie podrzuci Żodyn!!!

Podsumowanie

O czym dokładnie był ten artykuł? Na poziomie mechanicznym o tym, że niektóre mechanizmy języków nie zawsze działają tak jak nam się wydaje. Na poziomie ideologicznym był to apel by jednak do pewnych rzeczy nie podchodzić jak maszynka bo przemysł nas zaprogramował by lubić annotacje i "Beany". Czasem warto zajrzeć jak coś działa w innych językach a już na pewno uważać z używaniem cytatów Fowlera czy Unkla Boba jako argumentów - każdy przez to przechodzi i ja też przechodziłem - to, że konsultant, który oferuje swoje usługi płatnie wydał kilkanaście lat temu opinie jeszcze nie jest argumentem