Czemu bugi są jak wino, im starsze tym droższe.

Każdy z nas/was słyszał o tym, że naprawienie buga z czasem staje coraz droższe, a jak kod pójdzie na produkcje to już w ogóle wszystko drożej miliard razy. Każdy słyszał, ale czy ktoś zdaje sobie sprawę czemu tak się dzieje?
Mi wydaje mi się że wiem i chce się tą wiedzą podzielić, także posłuchajcie pewnej bajki o remoncie mieszkania, a może znajdziecie analogie do IT, a przy okazji może podpowiecie jak dobrze zainstalować progi w mieszkaniu.
Pewnego razu, pewien programista zapragnął mieszkania, takiego pięknego mieszkania, a w nim pomalowanych ścian i sufitów, kafelek i paneli. I chciał (on – programista) aby to ładnie było w nim (tym – mieszkaniu). Słuchy chodziły, że inni koledzy z fachu tego samego już maja takie cuda i że ładnie u nich. Poradził się więc do kogo pójść, aby piękna takie i w jego domu zagościły.
Gdy wszystko już się ustaliło i zrobiło, złoto przeszło z ręki do ręki, wprowadził się on ze swoją białogłową i ich kwiatem miłości do nowego pałacu.
Ich zdziwieniom i radości z posiadania własnego miejsca nie było granic. A w zasadzie były i dosyć szybko się skończyła piękna radość. Pomijając większe i mniejsze odstępstwa od umowy/jakości. Skupię się w tej bajce na rzeczy, która może się wydać wręcz śmieszna, a taka nie jest.
Otóż oto przedmiot wywodu mojego:
Próg

Śmiechy i chichy teraz cichną i lecimy dalej. Otóż piękne plany, które snuliśmy to ograniczenie progów do minimum. Tutaj nastąpił kontakt z rzeczywistością:
Jak to bez panel bez progów, muszą być!
Nieświadomi wiedzy pokiwaliśmy głowami, jak muszą to muszą – fachowiec wie. 0:1 dla niego, bo tak naprawdę to nie muszą.
Gdy w mieszkaniu byliśmy i mieszkaliśmy, naszła nas taka fanaberia, żeby drzwi posiadać, bo czasem warto się zamknąć w jednym z nich i spokój mieć. Progi jednak spokoju nam dać nie chciały. Trzeba było je poprawić. Pewnego weekendu, postawiłem się zebrać i poprawić to co nas boli. Zerwałem progi, tu i tam razem z panelu kawałkiem, ale to nie problem, da się to zakryć nowymi, bardziej płaskimi ale też trochę szerszymi. Aż tak to nie boli. Przy pierwszej próbie założeniu nowego progu okazało się, że nie pasuje i trzeba trochę poprawić

Gdy już przy pomocy próbnika i pilnika dopasowałem szczelinę, tak aby nowy, właściwy próg pasował, wiedziałem że poprawką dwóch następnych na pewno nie zajmie im chwileczki.
Drap drap, szlif szlif udało się zrobić tak, że próg 2.0 pasował do szczeliny 1.0, co mniej więcej wyglądało tak:

Na internetach znana jest taka oto przyśpiewka:

99 little bugs in the code

99 little bugs in the code
Take one down, patch it around
117 little bugs in the code

A wiadomo, tradycja rzecz święta, także tego:

Gdy i tutaj udało mi się dopasować szczelinę do próbnika, okazało się że próg 2.0 nie jest aż tak kompatybilny ze szczeliną i należy wprowadzić drobny tunning sprzętowy:

Wreszcie, gdy wszystkie szczeliny 1.0 zostały spasowane z progiem 2.0, mógł nastać czas relesu. Ale! Przecież jeszcze drzwi. Ktoś opowiadał, że progi po drzwiach, hmmm. Tutaj uratowało nas szczęście, postanowiliśmy zaczekać na monterów ościeżnic i drzwi, co okazało się dobrym posunięciem, bo “progi po drzwiach” – tak rzekł pan montujący.

Ja spędziłem cały dzień, zrywając starą wersję progu, dopasowując szczelinkę i dopasowując nowe progi, Nie mogłem zdjąć podłogi i normalnymi narzędziami ściąć wystających elementów. Nie posiadając także narzędzi czy jakieś specjalistycznej wiedzy (w zasadzie nic szczególnego wiedzieć nie trzeba, ale zawsze) musiałem wprowadzić zmiany już w istniejącym systemie. Ok dałem rade, będzie działać, nauczyłem się pewnie paru rzeczy, ale zajęło mi to dzień – a muszę jeszcze dopasować progi do ościeżnic.
Gdyby od razu zrobić dobrze, tak że nie trzeba by w ogóle poprawiać, mając możliwości do manewrowania panelami, mając narzędzia to zamiast całego dnia, moją robotę można zrobić w pięć minut. W dodatku nie doszło by do uszkodzenia materiału, czy kupowania czegoś co nie pasuje i trzeba to wyrzucić.

Wszystkim czym mogę i jak mogę podpisuje się pod tym obrazkiem.
PS.
Jestem po releasie na produkcje, ale syf. Jestem strasznie nie pocieszony tym, jak mi poszło. Nie wiem czy nie będzie trzeba robić, kolejnego fixa i znowu robić releasa na produkcje, z programi 3.0.
Czas pokaże. Mam nadzieję, że teraz już będziecie wiedzieć, czemu im później tym drożej.

ToBeImplemented reboot

Projekty które robi się dla siebie mają pewna cechę, która jednocześnie jest czymś dobrym i złym. Taki pet-project można rozpoczynać milion razy i nikt nie robi z tego powodu afery – to plus, po milionowym rozpoczęciu szanse na zakończenie są małe – to minus.
Ja właśnie zwiększyłem cyferkę do 3, na szczęście nadal chce mi się to pisać. Rozpocząłem od nowa, bo strasznie pokomplikowałem sobie aplikację, to raz, i przy próbach prostowania doszedłem do wniosku, że chyba szybciej będzie napisać to jeszcze raz, bez takiej ilości magicznego pyłku, który użyłem wcześniej. A dwa, że nauczyłem się paru nowych rzeczy, które chciałem tutaj wykorzystać.
Tym sposobem zmierzam do warstw. O ile wcześniej dzieliłem projekt na proste części:


Jak widać, szaleństwa nie ma. Aplikacja (ASP MVC) korzysta z serwisów, serwisy z bazy oraz czasem z samych siebie (oczywiście przez interfejsy).
Ok, w zasadzie nie ma się czego czepiać, tak/nie? Mogło być lepiej, mogło być gorzej. Zamiast sześciu żółtych prostokątów, mógł być jeden. Zawsze powtarzam, że nie wolno porównywać się gorszym, pozostaje więc tylko czepiać się i szukać miejsca na poprawki.
Co skusiło mnie to tego, żeby to zmienić? W serwisach było w zasadzie wszystko co nie jest związane z bazą danych i z samym mvc, czyli: logika, mapowanie z model na view model, maile, hash, konta użytkowników, blablablabla, wszystko.
Czy to dobre? It depends. Jeśli robi się mały prosty projekt (taki książkowy), to może będzie to dobre rozwiązanie, jeśli robi się nowego fejsbuka to raczej nie, a przypominam, że już nie długo moja aplikacja zajmie jego miejsce – widzę i słyszę wasz śmiech, zobaczymy kto będzie się śmiać ostatni!

Co takiego pozmieniałem w swoim rozwiązaniu? Warstwy, więcej warstw, znacie to przysłowie, że każdy problem da się rozwiązań dodając kolejną warstwę abstrakcji?
Razem z tym zmieniłem także troszkę podejście do rozwiązywania zależności, które tworzę, wygląda mniej więcej tak:

Największe zmiany to wiele aplikacji które są wspierane, robię to żeby sprawdzić jak długo można utrzymywać wspólny kod, na razie bez cross-platform.
Idealnie jest aby aplikacje były maksymalnie lekkie, cała logika jest przeniesiona do części Bussines, która wykonuje właściwą pracę. W przypadku aplikacji WPF są jej dwa warianty, jeden oparty o Application.REST, drugi który referuje bezpośrednio do Bussines i działa “natywnie”.
Dodatkowo ważny jest brak zależności w poziomie, tzn. serwisy nie mogą zależeć od serwisów, infrastruktura od infrastruktury, czy biznes od biznesu. Poszczególne klasy w danej warstwach muszą być samodzielne, jeśli ma się pojawiać jakaś zależność, oznacza to że klasa chce wziąć na klatę zbyt dużą odpowiedzialność i że jest źle.
Prawdę mówiąc, jestem dopiero na początku tej drogi i wiem, że pojawią się bloczki (już są), które będą specyficzne dla poszczególnych aplikacji, ale jestem na to gotowy i na aktualnym stopniu mojej znajomości naszej programistycznej domeny jestem w stanie to zaakceptować.

TL;DR:
W projektach gdzie zależności są poziome, np. serwisA zależy od serwisuB są, złe. Dwa powody: może to prowadzić to zależności cyklicznych, oraz oznaczać to może, że jeden z nich ma za dużą odpowiedzialność, ponieważ część pracy przekazuje do sąsiada o którym nie powinien wiedzieć.
Słowo motywujące na dziś: nie bój się eksperymentować!

Ciekawy moich postępów czy rozwiązań, aktualny projekt możesz znaleźć tutaj:
https://jstadnicki.visualstudio.com/DefaultCollection/_git/ToBeImplemented
https://github.com/jstadnicki/tbi

Do dzieła!

Ach święta, czas jedzenie i nie policzalnych kalorii. A gdy ktoś ma szczęście, to także czas błogiego programowania bez żadnych zobowiązań. To także czas kiedy można przysiąść i poczytać. Udało mi znaleźć chwilę i posiedziałem, czytałem i czytałem i nie mogłem przestać, bo ciągle nie miałem rozwiązania swojego problemu. Chciałem zaimplementować “Owin Identity” w asp mvc, a w internetach chciałem znaleźć rozwiązanie podane na talerzu. Spędziłem cały dzień wpisując coraz to różne i kombinacje słów “owin, identity, mvc, asp” i wiecie co, nie znalazłem niczego więcej niż w ciągu pierwszych trzech minut. Ciągle chciałem znaleźć te talerz i rozwiązanie na nim. Koniec końców, przeczytałem to co było, włączyłem VS z przykładowym kodem gdzie jest wykorzystany OWIN oraz logowanie i zacząłem właściwą pracę poznawczą – programowanie. Miałem jeden projekt wzorcowy, oraz osobny gdzie chciałem to ponownie zaimplementować. Wykorzystując starodawną metodę kopiowania i zrzynania działającego kodu, przenosiłem powoli funkcjonalność z przykładu do celu, aż wreszcie miałem podstawową funkcjonalność. Moja radość nie trwała wiecznie, kod trzymałem na ramdrive, żeby się to wszystko szybciej kręciło. Chwilę po tym jak skończyłem naukę musiałem zrobić reboot, jakie było moje zaskoczenia gdy się zorientowałem kilka godzin mojej pracy poszło się. Może nie wyglądało to aż tak tragicznie:

A wiecie czemu, bo zostało mi w głowie z więcej niż z samego czytania blogów czy tutoriali. I wiecie co zacznę robić za chwilę? Napiszę to jeszcze raz i jeszcze raz, a każdym razem będzie to zrobione lepiej i lepiej.
Przestań liczyć na to, że znajdziesz idealne rozwiązanie swojego problemu na sieci, napisz je sam. Jeśli nie uda się za pierwszym razem, to na pewno za drugim, albo za trzecim, a już na pewno na czwartym. A jak nie to na pewno będziesz wiedzieć co zrobiłeś nie tak i następnym razem zrobisz to lepiej.
TL;DR;
Przestań czytać, zacznij pisać kod.

O jej, zapomniałem hasła.

Co się stało to się nie odstanie i hasło się zapomniało. Co możemy z tym zrobić? Trzeba mieć wcześniej ustaloną formę komunikacji, może email, może sms, może adres pocztowy.
Jeśli mamy coś takiego, to spoko, jesteśmy uratowani i nasz użytkownik też. Teraz już pozostało już tylko kilka kroków które należy wykonać, aby użytkownik mógł bezpiecznie zmienić swoje zapomniane hasło, na nowe które zapomni kiedy indziej.
Robimy tak, przynajmniej ja tak robię:
Grzecznie prosimy o podanie nam adresu na który mamy przysłać (mając nadzieję, że chociaż tyle pamięta).
Po tym jak poda nam adres, warto sprawdzić czy mamy taki adres. Jeśli go posiadamy, generujemy jednorazowy token, który zostanie przypisany do właśnie tego przypomnienia i tego użytkownika.
Ja robię to tak (co nie oznacza, że jest to jedyny słuszny sposób czy najlepszy):

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: Consolas, “Courier New”, Courier, Monospace;
background-color: #ffffff;
/*white-space: pre;*/
}

.csharpcode pre { margin: 0em; }

.csharpcode .rem { color: #008000; }

.csharpcode .kwrd { color: #0000ff; }

.csharpcode .str { color: #a31515; }

.csharpcode .op { color: #0000c0; }

.csharpcode .preproc { color: #cc6633; }

.csharpcode .asp { background-color: #ffff00; }

.csharpcode .html { color: #800000; }

.csharpcode .attr { color: #ff0000; }

.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}

.csharpcode .lnum { color: #606060; }

   1:  public ViewModelResult<RequestResetPasswordViewModel> RequestResetPassword(string emailAddress, string baseUrl)
   2:  {
   3:      var userToResetPassword = this.database.Users.Get(x => x.Email == emailAddress).FirstOrDefault();
   4:      if (userToResetPassword == null)
   5:      {
   6:          var result = new ViewModelResult<RequestResetPasswordViewModel>
   7:                           {
   8:                               Success = false,
   9:                               ViewModel = null,
  10:                               ErrorMessage =
  11:                                   Translations
  12:                                   .AccoutService_ResetPassword_Email_NotExists
  13:                           };
  14:          return result;
  15:      }
  16:      else
  17:      {
  18:          var token = Guid.NewGuid().ToString().Replace("-", string.Empty);
  19:          var passwordResetRequest = new PasswordResetRequest
  20:                                         {
  21:                                             Created = DateTime.Now,
  22:                                             Token = token,
  23:                                             Used = false,
  24:                                             UserId = userToResetPassword.Id
  25:                                         };
  26:          this.database.PasswordResetRequests.Insert(passwordResetRequest);
  27:          this.emailService.SendPasswordRequestEmail(
  28:              userToResetPassword.Email,
  29:              userToResetPassword.DisplayName,
  30:              baseUrl + token);
  31:          this.database.Save();
  32:          var result = new ViewModelResult<RequestResetPasswordViewModel>
  33:                     {
  34:                         Success = true,
  35:                         ViewModel =
  36:                             new RequestResetPasswordViewModel
  37:                                 {
  38:                                     Message =
  39:                                         Translations
  40:                                         .AccoutService_ResetPassword_EmailSend
  41:                                 }
  42:                     };
  43:          return result;
  44:      }
  45:  }

Co jest powyżej? Szukam gościa z podanego email’a, jeśli go znajdę, to generuje token. W moim przypadku jest to guid bez kresek (może warto to przemyśleć). Później taką informację zapisuje do bazy, a potem korzystając z dostępu do serwisu mailowego wysyłam informację.
W baseUrl ukrywa się podstawowy link go którego zostanie doklejony token – średnio ładne, jestem otwarty na propozycje.
Produkcja mail jest raczej prosta:

   1:  public void SendPasswordRequestEmail(string email, string displayName, string url)
   2:  {
   3:      var smtpClient = new SmtpClient(this.communicationConfiguration.Host, this.communicationConfiguration.Port);
   4:   
   5:      smtpClient.EnableSsl = this.communicationConfiguration.MailSsl;
   6:      smtpClient.Credentials = new NetworkCredential(
   7:          this.communicationConfiguration.HostUser,
   8:          this.communicationConfiguration.HostPassword);
   9:   
  10:   
  11:      var from = new MailAddress(
  12:          this.communicationConfiguration.PasswordResetRequestAddress,
  13:          this.communicationConfiguration.PasswordResetRequestName);
  14:   
  15:      var to = new MailAddress(email, displayName);
  16:   
  17:      var bodyFormat = Translations.EmailsService_PasswordResetRequestBody_DisplayName_Url;
  18:      var subject = Translations.EmailsService_PasswordResetRequestSubject;
  19:      var body = string.Format(bodyFormat, displayName, url);
  20:   
  21:      var message = new MailMessage(from, to);
  22:   
  23:      message.Subject = subject;
  24:      message.Body = body;
  25:      smtpClient.Send(message);
  26:  }

Zwykły plain text do wysłania informacji, część ludzi ładnie wysyła piękne maile, ja tego na razie nie robię. Pewnie tak będzie, jak już zdobędę miliony użytkowników.

To w zasadzie pierwszy etap, kolejna część do obsługa takiego żądania. Ja, nowicjusz robię to tak:

   1:  [HttpGet]
   2:  public ActionResult ResetPassword(string token)
   3:  {
   4:      var serviceResult = this.accountService.IsRequestTokenValid(token);
   5:      var viewModel = new ResetPasswordViewModel2();
   6:      viewModel.Token = token;
   7:      this.ModelState.AddModelError(string.Empty, serviceResult.ErrorMessage);
   8:   
   9:      return this.View("ResetPassword", viewModel);
  10:  }

Wybaczcie mi ResetPasswordViewModel2 – coś testowałem i mi zostało – oops!
Zwykły get do kontrolera, co ważne należy pamiętać żeby dostęp do tej metody był możliwy także dla nie zalogowanych użytkowników.
Sprawdzenie poprawności tokena wygląda tak:

   1:  public MessagesResult IsRequestTokenValid(string token)
   2:  {
   3:      var passwordResetRequest = this.database.PasswordResetRequests.Get(x => x.Token == token).FirstOrDefault();
   4:   
   5:      if (passwordResetRequest != null && passwordResetRequest.Used == false)
   6:      {
   7:          return new MessagesResult { Success = true };
   8:      }
   9:      else
  10:      {
  11:          return new MessagesResult
  12:                     {
  13:                         Success = false,
  14:                         ErrorMessage = Translations.AccoutService_HandlePasswordReset_TokenNotExists
  15:                     };
  16:      }
  17:  }

Opcje z przetrzymywaniem tokena są różne, ja aktualnie postanowiłem trzymać token w bazie i po użyciu oznaczać go jako użyty. Można też go usuwać, można go też trzymać w tabeli użytkowników jako dodatkowe pole. Tysiąc programistów, tysiąc problemów, tysiąc rozwiązań.

Jeśli token jest poprawny renderowany jest widok i czekamy aż pan zapominalski wprowadzi nowe hasło, po tym jak się zdecyduje zostanie obsłużony w taki sposób:

   1:  [HttpPost]
   2:  [ValidateAntiForgeryToken]
   3:  public ActionResult ResetPassword(ResetPasswordViewModel2 model)
   4:  {
   5:      if (ModelState.IsValid)
   6:      {
   7:          var serviceResult = this.accountService.HandleResetPassword(model);
   8:          if (serviceResult.Success)
   9:          {
  10:              return this.RedirectToAction("Login", "Account");
  11:          }
  12:          this.ModelState.AddModelError(string.Empty, serviceResult.ErrorMessage);
  13:      }
  14:   
  15:      return this.View("ResetPassword", model);
  16:  }

Oraz serwis, który wykonuje właściwą pracę:

   1:  public MessagesResult HandleResetPassword(ResetPasswordViewModel2 resetPasswordViewModel)
   2:  {
   3:      if (resetPasswordViewModel.NewPassword != resetPasswordViewModel.ConfirmNewPassword)
   4:      {
   5:          return new MessagesResult
   6:                     {
   7:                         Success = false,
   8:                         ErrorMessage =
   9:                             Translations.AccoutService_HandlePasswordReset_PasswordDoesNotMatch
  10:                     };
  11:      }
  12:   
  13:      var passwordResetRequest =
  14:          this.database.PasswordResetRequests.Get(x => x.Token == resetPasswordViewModel.Token && x.Used == false)
  15:              .FirstOrDefault();
  16:   
  17:      if (passwordResetRequest == null)
  18:      {
  19:          return new MessagesResult
  20:                     {
  21:                         Success = false,
  22:                         ErrorMessage = Translations.AccoutService_HandlePasswordReset_TokenNotExists
  23:                     };
  24:      }
  25:   
  26:      var user = this.database.Users.Get(x => x.Id == passwordResetRequest.UserId).First();
  27:      var hash = this.hashProvider.Get(resetPasswordViewModel.NewPassword);
  28:      
  29:      user.Hash = hash;
  30:      passwordResetRequest.Used = true;
  31:   
  32:      this.database.Users.Update(user);
  33:      this.database.PasswordResetRequests.Update(passwordResetRequest);
  34:   
  35:      this.database.Save();
  36:   
  37:      return new MessagesResult { Success = true };
  38:  }

Nie pamiętam czemu mam powtórzoną walidację pól view-modelu, pamiętam że miałem poważny powód żeby tak zrobić – deal with it!
Ale do rzeczy, jeśli podane hasła się zgadzają i token jest poprawny, generowany jest nowy hash dla hasła i jest ono zapisywane dla użytkownika, z którym powiązany jest token. Po wszystkim w moim przypadku MUSZĘ pamiętać o oznaczeniu tokena jako użytego.
Będzie?!

Code coverage bywa zdradliwy

Od kilku dni wdrażam w życie dopisanie testów do strony nad którą pracuje, bo lepiej późno niż wcale. W zabawie tej korzystam z dodatkowego narzędzia jakim jest ncrunch. Trochę więcej o nim na blogach Paweł i Arek Bardzo ciekawa sprawa, ale warto mieć dobry sprzęt, aby w pełni korzystać ze wszystkich funkcjonalności. Mi do gustu przypadła funkcjonalność mierzenia pokrycia kodu testami. Pomyślałem sobie – “spoko“, nie będę się musiał martwić, czy mam wszystko przetestowane – i właśnie o tej pułapce parę słów. Załóżmy taki kod, który będzie zmanipulowany na potrzeby przykładu:

public class UnderTest
    {
        private int someInternalVariable;
        public void Foo(int a)
        {
            if (a == 1 || a == 3)
            {
                this.someInternalVariable = a;
            }
        }
    }

A następnie taki test, który sprawdza czy zmienna została przypisana

[TestFixture]
    public class Test
    {
        [Test]
        public void T001()
        {
            var sut = new UnderTest();
            sut.Foo(5);
            Assert.AreEqual(0, sut.SomeVariable);
        }

        [Test]
        public void T001_With_Params()
        {
            var sut = new UnderTest();
            sut.Foo(1);
            Assert.AreEqual(1, sut.SomeVariable);
        }
    }

Patrząc teraz na metryki, wynik cieszy oko i aż prosi żeby pochwalić się światu, że testy mamy w jednym palcu, architektura u nas to kozak, bo właśnie wykręciliśmy 100% pokrycia kodu. Aaale potem kątem oka dostrzegamy pułapkę! O nie, a co z przypadkiem gdy a będzie 3, przecież nie mamy takiego testu – jak to możliwe, skoro cyferki mówią 100%, a serce mówi że brakuje. No tak dzieci, bo musicie pamiętać, że code coverage bywa zdradliwy i nigdy, przenigdy nie powinno się tylko na nim opierać wyników naszego testowania. . A to że kusi, to że menagierowie mówią i chcą żeby było przynajmniej 100/80/60/x procent to wiecie, oni są pragnienie, to my jesteśmy oranżada.