Sztuczne dane

Do tej pory do testów używałem wyciągu z własnego konta, dlatego na zrzutach ekranu zakrywałem dane wrażliwe. O tym co powinno się zasłaniać, a czego nie trzeba jest kwestią dyskusyjną. Niemniej chciałbym pozbyć się takich rozterek. Pierwszy sposób na stworzenie sztucznych danych to skorzystanie z Autofixture. Używałem tej biblioteki przy tworzenia danych do testów, ale nic nie stoi na przeszkodzie, żeby użyć tego do innych celów. Zaletą tego rozwiązania jest efekt, który można uzyskać natychmiastowo.

Trudno od biblioteki oczekiwać, że od razu spełni nasze wymagania. Wartości nie są do końca takie jakich oczekuję. Wartości są dodanie, a daty z nie do końca jasnego okresu. Biblioteka daje się w miarę łatwo konfigurować, jednak nie próbowałem nigdy ustawić w niej zależności funkcyjnych. Są one potrzebne do wypłaty z bankomatu. Kwota wypłaty w najlepszym wypadku jest wielokrotnością dziesięciu złotych. Interesuje mnie też możliwość ustawienia kwoty będącej bilansem dla miesiąca, lub dodanie większej ilości wydatków w grudniu. Zagadnienie generowania sztucznych danych zaczęło mi chodzić po głowie jakieś 10 lat temu i ciągle nie mam odpowiednich zasobów, żeby sprawdzić ten pomysł. Tworzenie drobnego programiku do tworzenia sztucznych wyciągów zacznę od zdefiniowania typów operacji.

  • miesięczna pensja, może być stała w ramach danej próbki danych
  • rata kredytu, czynsz, telefon, internet, opłata za prowadzenie konta.. te wydatki mogą być również stałe, wymagane jest jedynie zdefiniowanie zakresu kwot i tytuły
  • wydatki na paliwo
  • zakupy w supermarketach
  • wypłaty gotówki
  • restauracje

Tyle powinno wystarczyć jak na dane testowe, nie staramy się tutaj przekonać fiskusa, że nie osiągamy dochodów. To pierwsza próba zdefiniowania jak powinien wyglądać miesięczny wyciąg.

Obecnie wzorcem projektowym, który ciągle siedzi mi w głowie jest Pipe. Ustawianie na początek opisu, wartości transakcji a następnie daty wydaje się w miarę intuicyjne i adekwatne. Po skończeniu pisania kodu generującego sztuczne dane doszedłem do wniosku, że może lepiej nadawałby się tutaj builder.

Do tworzenia tytułów dodałem prostą fabrykę.

Nic urywającego jaja. Z datami też było, też wielkiej magii nie ma. Generując wartości transakcji użyłem podobnej fabryki.

Generując wartości dla zakupów w sklepie lub na stacji paliw stworzyłem małą klasę, która generuje wartość powyżej lub poniżej zadanej wartości. Użyłem funkcji Gaussa, lecz po zastanowieniu się jest to zupełnie zbędna zabawa i na dodatek nie przynosi efektów o jakie ją początkowo podejrzewałem. Pozostawię sobie tą naukę na później, bo generowane dane są wystarczająco podobne do oryginału.

Mały update jak to ostatecznie wygląda generowanie.

Wspominałem o pipe, a użyłem linq. Praktycznie może chodzić o tą samą rzecz. Pomiędzy Skeetem, a Ayende na blogu tego drugiego powstała już jakiś czas temu dyskusja na temat powodów tworzenia interfejsów do tego wzorca, a użyciem linq. Użyłem tutaj linq w ramach eksperymentu, żeby zobaczyć jak wygląda jego użycie. Moje przypuszczenia jedynie się potwierdziły, choć miałem już praktycznie wyrobione zdanie w tym temacie. Linq nie pasuje mi jako pipe. Jest tutaj mały problem z nazewnictwem, bo oznacza, że coś ciągle wybieramy. Przyzwyczajony jestem raczej do przekształcania wyrażenia na inne, a nie do jego modyfikacji. Dodatkowo jest też mały side effect, czego staram się również unikać przy linq.

Pogoń za króliczkiem

Próbowałem bezskutecznie sprawdzić kto jest autorem popularnego stwierdzenia, że podróż jest ważniejsza niż cel. Rezultaty google sugerują, że to ktoś sławny, a ja się zastanawiam czy może chodzić o Radka Majdana. Cytat ten często nijak ma się do zadań programistycznych, które wykonujemy. Szukałem ostatnio buga i mam wrażenie, że każdy przyzna, że im szybciej się uda się go znaleźć tym lepiej. Przeciętny bug nie kryje w sobie wiedzy tajemnej, a jedynie znaną o omylności każdej jednostki. Mimo to z jakiegoś dziwnego powodu lubię szukać bugów.

Do projektu głównego stworzyłem projekt poboczny w celu zapoznawania się z wykorzystanymi bibliotekami i ku mojemu zdziwieniu nie mogłem sprawić, aby kawałek kodu działał. Zdziwienie brało się stąd, iż w głównym projekcie zrobiłem podobnie i działało. Po lekkich wątpliwościach czy w ogóle wiem co robię zacząłem szukać bugu. Z racji tego, że miałem dwie wersje: jedną działającą, a drugą nie najłatwiej było zastosować coś w rodzaju suwaka. Wersję nie działającą zmieniałem tak, aby była coraz bardziej podobna do działającej i obserwowałem, w którym momencie przestanie zacznie działać. Przebyłem całą drogę i niestety nic to nie dało. Na trop rozwiązania trafiłem zawiłą drogą. Korzystając z dobrodziejstwa dotPeeka, a dokładniej możliwości postawienia serwera symboli chciałem podebugować kod biblioteki. Już wcześniej miałem pliczek pdb, z tym że to była kupa. Jakby dllka i pdb nie były jednością.. ok to skasujmy pdb, żeby nie kopiowało się do katalogu bin i dajmy doPeekowi zrobić swoje. I tutaj nadeszło olśnienie. W katalogu z biblioteką nie ma pdb. Po wyszukani pliku okazało się, że zainstalowałem inną wersję tej samej biblioteki. Ktoś wrzucił ReactiveUI skompilowaną dla .net40 na nugeta i z jakiegoś powodu; który zupełnie mnie już nie interesuje; biblioteka ma błąd. Teoretycznie są to te same wersje kodu jednak nie działają. Witki mi opadły. Zraziło mnie to znów do nieco do korzystania z nugeta i następnym razem dwa razy się zastanowię co w ogóle ściągam.

ReactiveUI ciąg dalszy

Nawigacja

W przypadku WPF doradzane jest używanie modelu ViewModelFirst.Podczas przejścia do innego ‚widoku’ interesuje nas view model, a nie sam widok. Jako że view model nie wie nic o widoku musi istnieć coś co znajdzie odpowiedni widok i podepnie view model do data contextu. Jeśli chodzi o dokumentację to jest ona w tym temacie dość uboga i więc nie pozostaje nic innego niż eksperymentowanie.

Przeglądając źródła zacząłem od interfejsu IViewFor<T>, który jasno i wyraźnie mówi, że obsługuje ViewModele. Parę chwil i mam IViewLocator i jego domyślną implenetację. Swoją drogą zauważyłem tutaj rzadko spotykaną konwencję nazewniczą. Mianowicie zamiast nazywać rolę jaką pełni klasa bądź interfejs klasa mówi co robi. Trochę tak jakbyśmy wyobrazili sobie klasę jako krasnoludki, które odpowiadają na pytanie ‚a co Ty potrafisz robić’. Spotkamy więc typy IWantsToRegisterStuff, ICreatesObservableForProperty, ICanActivate. Widzę tutaj niewykorzystaną szansę, w przypadku pierwszej klasy wykorzystałbym prefix interfejsów, abyśmy mogli odczytać typ poprawnie gramatycznie czyli IWantToRegisterStuff. Przeglądanie implementacji skończyłem na kontrolce RoutedViewHost.

Post piszę na bieżąco.. i co widzę, projekt ma dwie dokumentacje 🙁 Prócz braku dokumentacji posiadanie dwóch rozjechanych jest moją drugą ulubioną rzeczą w projektach. Ech ech. Jedna dokumentacja jest w projekcie głównym, druga dokumentacja jest w osobnym projekcie na githubie. O dziwo wszystko czego chciałbym się dowiedzieć o nawigacji jest w starszej, nieaktualizowanej już od dwóch lat. Fatalnie.

W każdym bądź razie nawigacja, którą oferuje framework oparta jest o strony. Mamy abstrakcję zwaną IScreen, która udostępnia nam RoutingState. Ten z kolei umożliwia nawigowanie do widoku lub cofnięcie się do poprzedniego. Ewentualnie można wyczyścić ‚Historię’. Mam wrażenie, że jest to schemat dość popularny dla urządzeń mobilnych. Widok musi implementować IViewFor, a ViewModel IRoutableViewModel. Dalej pozostaje tylko użycie kontrolki RoutedViewHost i wszystko śmiga.

Rejestracja widoku dla ViewModelu odbywa się tak:

Podsumowanie

Stworzenie widoku, do którego można nawigować
  1. Widok implementuje IViewFor<AAAViewModel>
  2. AAAViewModel implementuje IScreen
  3. Domyślny Router = new RoutingState();
  4. Widok używa kontrolki reactiveUi:RoutedViewHost i ma zbindowanego Router’a z ViewModel
  5. Rejestracja widoku Locator.CurrentMutable.Register(() => new BBBView(), typeof(IViewFor<BBBViewModel>));
  6. BBBViewModel implementuje IRoutableViewModel
  7. Nawigowanie do widoku Router.Navigate.Execute(new BBBViewModel(this))

Raport z postępów – zabawy z wyglądem

Weekend spędziłem na poprawieniu wyglądu aplikacji. Dodałem trochę dziwną funkcjonalność: globalna zmiana wielkości czcionki. Zaczęło się od tego, że zwiększyłem jej rozmiar. Miało to w zamierzeniu wywołać poczucie innej; niż standardowa; estetyki i zwiększenie czytelności. Większy tekst to większa widoczność. Po drobnych bojach ze stylami stworzyłem styl bazowy, żeby zaraz po tym zobaczyć, że TextBlock nie jet kontrolką i trzeba do niego utworzyć nowy styl. Tutaj odezwała się moja mania chęci upraszczania wszystkiego i stwierdziłem, że chciałbym mieć jedno miejsce, w którym ustawiam wysokość czcionki. Pierwsze rozwiązanie to po prostu Resource o typie double.

Ale co jeśli ktoś będzie chciał zmienić rozmiar. Zmiana Resource w trakcie działania, raczej nie zadziała. Przypomniała mi się nieco hakerska technika bindowania z wykorzystaniem resource. Od teraz trzymałem rozmiar czcionki ciągle w resorsach, ale w takiej klasie:

Implementacja INotifyPropertyChanged prawdopodobnie w przyszłości zniknie. Tak czy siak podpiąłem pod przycisk ( tudzież guzik ) zmianę wielkości czcionki i działa. Nie wiem jak na to wpadłem, ale najważniejsze na świecie wydało mi się zmienianie czcionki dość znanym skrótem ctrl + scroll. Kiedy miałem już DependencyProperty wystarczyło tylko obsłużyć scrolla w oknie głównym i bam, parę linijek i działa. Postanowiłem też zapisać coś takiego, aby nie trzeba było przy każdym uruchomieniu zmieniać czcionki na inną.

Edit do treści poniżej: kolejny raz udało mi się skutecznie oszukać o zajebistości pomysłu i okazuje się zupełnie niepotrzebny.

Wymyśliłem sobie bibliotekę, która robi tak:

  • daje jej obiekt
  • zmiany property obiektu są monitorowane i zapisywane
  • przy starcie aplikacji odtwarzamy zapisany stan

Albo źle szukałem, albo nie ma czegoś takiego. Lubię tworzyć takie małe klaski. Nie jest to chyba jednak coś, co zasługuje na publikację. Na razie działa ok, ale dla nietrywialnych przypadków może wymagać więcej konfiguracji.

Edit: wystarczy zapisywać przy zamknięciu aplikacji :]

Poza tym szukałem jakieś palety barw do programu ale nic nie znalazłem. Jeśli chodzi o kolory to jedyną rzeczą jaką opanowałem to dobieranie kolorów ubrań dla syna 😉 Jeśli ktoś zna podstawy doboru kolorów w aplikacjach to dajcie znać.

Testowanie wymagań typu generycznego

W trakcie pisania małej klasy do zapisu ustawień aplikacji pierwszy raz spotkałem się z sytuacją, w której dołożenie constraintu do typu generycznego załatwiało mi walidację parametru. Chcąc obserwować encję dodałem sprawdzanie czy implementuje INotifyPropertyChanged, napisałem przed tym test i działa. Ale jakoś było tłoczno w kodzie i stwierdziłem, że dodam constraint i jest lepiej, bo kod waliduje się przy kompilacji. Został natomiast test i zacząłem się zastanawiać czy wywalić go i czy da się go napisać inaczej i oczywiście się da.

Wątpliwość budzą korzyści z robienia takich sprawdzeń, czy nie jest to zagalopowanie się w testowaniu.