środa, 28 grudnia 2011

Czy null jest potrzebny?

     W komentarzu do poprzedniego posta Bartosz napisał, że według niego nowo utworzony obiekt z wynullowanymi polami nie zawsze jest błędem i czasami może mieć sens. Nie zgadzam się z tym zupełnie. Powiem więcej: wg mnie, w językach wysokiego poziomu (chodzi mi o takie, gdzie już nie musimy ręcznie alokować i zwalniać pamięci) wartość null nie ma w ogóle racji bytu!
     Wartość NULL występuje w językach takich jak C, czy C++ właśnie po to, żeby reprezentować pusty wskaźnik. Natomiast w językach takich, jak C# czy Java nie zarządza się ręcznie pamięcią, a null w zasadzie nie ma żadnego sensownego uzasadnienia. Ktoś może w tym miejscu zaprotestować i powiedzieć, że null ma np. wiele zastosowań przy implementacji różnego rodzaju kolekcji. Odpowiem na to, że kolekcje da się w językach wysokiego poziomu zaimplementować bardziej sensownie. Zaprezentuję to później na przykładzie języka F#.
     Zanim to zrobię, pokażę cztery linijki kodu, które zszokują zatwardziałych Jawowców i C-Sharpowców :-) W F# poniższy kod:

type MyClass()=
  
member x.Foo () = ()

let mutable x = new MyClass()
<- null

...nie skompiluje się! Kompilator powie, że null nie jest wartością klasy MyClass. Analogicznie jest w przypadku każdej innej klasy zdefiniowanej w tym języku: null nie jest poprawną wartością. W F# słowo kluczowe null występuje tylko i wyłącznie po to, żeby móc korzystać ze standardowych bibliotek .NET-owych.
     Biblioteki F# zamiast wartości null używają unii dyskryminowanych (ang. discriminated union). Najprostszą z nich jest option, który jest zdefiniowany tak:

type 'a option =
  None
  
| Some of 'a

Czyli albo nie mamy wartości (None) albo ją mamy (Some). Option może być użyty w funkcji w następujący sposób:

let makeList opt =
  match opt with
    | None -> []
    | Some x -> [x]

Dla niewtajemniczonych: powyższa funkcja zwraca pustą listę dla braku wartości i jednoelementową listę, gdy poda się wartość.
A co z kolekcjami? Otóż jest równie prosto. Na przykład lista łączona, którą w C# zdefiniowalibyśmy tak:

public class LinkedList<T>
{
    private T head;
    private LinkedList<T> tail;
    [..]
}

w językach funkcyjnych zdefiniowana byłaby przy użyciu unii dyskryminowanej w taki sposób:

type 'a LinkedList =
  Empty
  | NonEmpty of 'a * 'a LinkedList 
 
Według mnie ta druga definicja jest o wiele jaśniejsza. Rekurencyjna definicja listy łączonej jest tu podana wprost - zupełnie jak w definicjach matematycznych. Natomiast w C# (lub w Jawie) trzeba żonglować referencjami i nullami a przez to kod przestaje tu być zrozumiały sam przez się.

poniedziałek, 19 grudnia 2011

Pasywny widok cd. i antywzorzec Magiczna Metoda

    W poprzednim poście opisałem wzorzec projektowy Pasywny Widok. Tomek w komentarzu do wpisu słusznie zauważył, że mój kod jest trochę przekombinowany. Ponadto gdy spojrzałem jeszcze raz na ten kod zauważyłem w nim pewien smrodek (nawiasem mówiąc, jak powinno się przetłumaczyć na polski angielskie "code smell"?). Myślę, że warto wrócić do tamtego kodu raz jeszcze.
    Najpierw opiszę, o co chodzi z "przekombinowaniem". Zdefiniowałem interfejs INameModel:


public interface INameModel
{
    string Name { getset; }
    event Action<string> NameChanged;
}


A potem konkretną klasę:


public class NameModel: INameModel
{
    
string name;
  
    
public string Name {
        
get { return name; }
        
set
        {
            name = 
value;
            
OnNameChanged(name);
        }
    }
  
    
public event Action<string> NameChanged;
  
    
protected virtual void OnNameChanged(string obj)
    {
        
if (NameChanged != null) {
            
NameChanged(obj);
        }
    }
}

 
    Po przemyśleniu uznałem, że choć nie jest to błąd, jest to niepotrzebny przerost formy nad treścią. Generalnie, interfejsy mają sens w dwóch przypadkach:
  1. gdy klasa implementująca jest serwisem,
  2. gdy dana implementacja klasy-wartości nie jest jedyną słuszną implementacją (np. interfejs IList jest implementowany przez LinkedList i ArrayList).
Wydaje się, że w przypadku klasy NameModel nie zachodzi żaden z powyższych przypadków. Sklasyfikowałbym :-) ją jako typową klasę-wartość. Odpalenie zdarzenia po wywołaniu settera to chyba nie jest żadna logika. Poza tym, w przypadku stosowania wzorca Pasywny Widok, dodalibyśmy sobie snippet generujący takie kilkanaście linii kodu. A jak coś jest generowane przez snippet, to to nie jest logika, tylko składnia języka :-) Czy ktoś czuje się jeszcze nieprzekonany, że NameModel nie różni się zasadniczo od takich klas, jak String czy Uri? Ja sam siebie przekonałem :-)
    To teraz uderzę się w piersi i opiszę smrodek, który napisałem. Użyłem antywzorca, który nazwałbym Magiczną Metodą (jeśli ktoś zna fachową nazwę, proszę o komentarz). Polega on na tym, że obiekt nie jest gotowy do użycia pomimo tego, że został legalnie utworzony przy użyciu konstruktora. Po prostu, konstruktor nie inicjuje wszystkich pól zostawiając część wynullowanymi. Na konkretnym przykładzie z poprzedniego postu:


public class NameController: INameController
{
    
private readonly INameValidator validator;
    
private INameModel model;
    
private INameView view;
  
    
public NameController(INameValidator validator)
    {
        
this.validator = validator;
    }
  
    
public void StartControl(INameModel model, INameView view)
    {
        
this.view = view;
        
this.model = model;
        view.
RegisterObserver(this);
        
RegisterFeedback();
    }
  
    
private void RegisterFeedback()
    {
        model.NameChanged += newName =>
            view.ErrorMessage = validator.
ErrorMessage(newName);
    }      
  
    
public string Name {
        
set {
            model.Name = 
value;
        }
    }

 
W powyższej klasie, dopóki nie wywołamy magicznej metody StartControl, nie możemy wywoływać innych metod, bo będziemy dostawać NullReferenceException. A tak nie powinno być w żadnym wypadku.
Kontroler powinien wyglądać albo tak:


public class NameController: INameController
{
    
private readonly INameValidator validator;
    
private readonly INameView view;
    
private readonly NameModel model = new NameModel();
  
    
public NameController(
        INameValidator validator, INameView view)
    {
        
this.validator = validator;
        
this.view = view;
    }
  
    
public void StartControl()
    {
        view.
RegisterObserver(this);
        
RegisterFeedback();
    }
    [..]
}



albo, ewentualnie, tak:


public class NameController<TView>: INameController
    where TView: INameView, new()
{
    private readonly INameValidator validator;
    private readonly INameView view = new TView();
    private readonly NameModel model = new NameModel();
  
    public NameController(INameValidator validator)
    {
        this.validator = validator;
    }
  
    public void StartControl()
    {
        view.RegisterObserver(this);
        RegisterFeedback();
    }
    [..]

}


    W powyższych kawałkach kodu widać, że widok również traktujemy jako wartość, a nie serwis, ale w przeciwieństwie do modelu, chcielibyśmy żeby był podmienialny jakimś dublerem testowym (bo prawdziwy widok jest ciężkim i wolnym obiektem; patrz: punkt b) na początku wpisu).


PS
Jeśli ktoś nie spotkał się z pojęciem dublera testowego, odsyłam do strony Martina Fowlera, który chyba jako pierwszy użył tego terminu.
W tym wpisie odwoływałem się do klasyfikacji obiektów na serwisy i wartości. Jeśli taki podział nie jest jasny, proszę dać znać w komentarzu - w razie potrzeby napiszę o tym coś więcej.

poniedziałek, 12 grudnia 2011

Pasywny widok, czyli MVC w środowisku eventowym

     Tak jak zapowiedziałem w poprzednim wpisie, chciałbym zaproponować architekturę aplikacji opartych na Windows Forms (lub na innym środowisku eventowym, np. na Javowym Swingu), która rozdzieli prezentację od logiki oraz umożliwi testowanie jednostkowe.
     Ktoś, kto na co dzień pisze aplikacje webowe może się w tym miejscu zdziwić, bo przecież w kontekście aplikacji webowych oczywistą oczywistością jest to, że jeśli chce się rozdzielić logikę od prezentacji należy zastosować klasyczny wzorzec projektowy MVC. Dlaczego nie jest tak w przypadku aplikacji okienkowych? Żeby to wyjaśnić, przypomnę, jak działa aplikacja webowa stosująca MVC.
     Do serwera przychodzi żądanie HTTP. Na podstawie adresu URL budowany jest odpowiedni kontroler, a na podstawie parametrów (najczęściej zawartych w żądaniu HTTP POST) budowany jest model. Następnie model jest przekazywany do kontrolera, który zmienia jego stan, a potem przekazuje model do widoku. Widok buduje od zera swoją zawartość na podstawie stanu modelu. Na koniec zbudowany widok jest odsyłany do klienta, a aplikacja może "wyrzucić do śmieci" wszystkie obiekty utworzone do obsłużenia żądania.
     Taki cykl życia nie nadaje się zupełnie do aplikacji okienkowych, bo musielibyśmy po każdym najdrobniejszym kliknięciu budować i renderować od zera całą warstwę prezentacji. Martin Fowler opisał na swojej stronie wzorzec Passive View (Pasywny widok), który jest wersją MVC nadającą się do aplikacji okienkowych.
     Wzorzec jest bardzo ciekawy, ale wg mnie, sposób w jaki pan Fowler go opisał jest dość zagmatwany, a przedstawione diagramy UML są dla mnie nieczytelne. Osobiście preferuję przedstawianie wzorców projektowych za pomocą działającego kodu i właśnie dlatego postanowiłem napisać prostą aplikację demonstrującą pasywny widok. Można ją pobrać stąd.
     Aplikacja jest bardzo prosta: wpisujemy imię i nazwisko w pole tekstowe, a gdy napis nie składa się z dwóch słów rozpoczynających się z dużych liter, to wyświetlany jest komunikat o błędzie.



     Jeśli chodzi o architekturę, to cały trick polega na tym, że klasa-widok nie wykonuje sama żadnych czynności i nie ma żadnych pól (nie licząc wygenerowanych przy użyciu Designera pól wizualnych kontrolek). W szczególności widok nie wie nic o modelu - i to jest zasadnicza różnica w stosunku do webowego MVC. Do kodu widoku dodaje się tylko settery i metodę podpinającą metody obserwujące zdarzenia na widoku (należące do kontrolera).
     Natomiast kontroler powinien być napisany tak, żeby mógł działać poprawnie nawet, gdy nie ma żadnej wizualnej reprezentacji. Dzięki temu można go przetestować jednostkowo, a to jest bardzo duża zaleta. W tym miejscu przypomniała mi się sytuacja, gdy mój przełożony robił złączenie dwóch rozgałęzień na SVNie i gdy przy niektórych ważnych kontrolkach pojawił się napis "merged" powiedział, że przydałaby mu się wódka na odwagę przed wrzuceniem złączonego kodu "na produkcję". Gdyby miał do dyspozycji testy jednostkowe złączonych klas, mógłby je odpalić i już by wiedział, czy złączenie było wykonane poprawnie.
     Do omówienia został jeszcze tylko model. Jest on najprostszym elementem tej układanki. Jedyne, o czym warto napisać, to że dobrze jest napisać model w oparciu o wzorzec Obserwator, czyli niech model powiadamia o zmianach swojego stanu, dzięki czemu kontroler nie musi sprawdzać go w sposób czynny.

poniedziałek, 5 grudnia 2011

Windows Forms i pokusa dwukliku

    Biblioteka Windows Forms została napisana w taki sposób, że komponenty przekazują informacje o zmianie swojego stanu przy użyciu zdarzeń (ang. events). Fachowo taką architekturę określa się wzorcem projektowym Obserwator. Jest to bardzo fajny wzorzec, bo pozwala na rozdzielenie warstwy prezentacji od logiki biznesowej. Logika biznesowa podpinana jest pod zdarzenia okienek i voilà! - mamy gotową aplikację w architekturze warstwowej.
    Żeby powyższe optymistyczne założenie stało się prawdziwe musi być spełniony jeden warunek: programista musi zaimplementować logikę biznesową POZA kodem kontrolki. Niestety, Visual Studio bardzo mocno kusi programistę, żeby wrzucił wszystko do kodu kontrolki: gdy dwukliknie się dowolną kontrolkę w designerze VS, środowisko samo wygeneruje nam kod do obsługi zdarzenia WEWNĄTRZ kodu kontrolki. Na dodatek ustawi kursor w ciele nowo wygenerowanej metody. Podświadomy przekaz od Microsoftu jaki dociera do wielu programistów jest następujący: „that's the way it should be done” :-(
    Chciałbym jeszcze zwrócić uwagę, że wzorzec projektowy Obserwator zupełnie traci swój sens i swoje zalety, gdy obiekt obserwowany jest równocześnie obserwatorem (polecam przeczytać zalety Obserwatora wymienione na Wikipedii).
    Żeby nie było, że jestem jakimś krytykującym Microsoft szpiegiem z krainy Javy, dodam jeszcze że zupełnie analogiczna sytuacja ma miejsce w javowym Swingu używanym w NetBeansie. Po dwukliknięciu np. JButtona generowana jest metoda obsługująca zdarzenie w kodzie klasy, która powinna być odpowiedzialna wyłącznie za renderowanie kontrolek.
    W kolejnym wpisie na tym blogu postaram się zaproponować taką architekturę aplikacji opartych na Windows Forms, która rozdzieli prezentację od logiki oraz umożliwi testowanie jednostkowe (no tak, zapomniałem dodać, że w przypadku wszystko robiących kontrolek możemy zapomnieć o testach jednostkowych, ale to chyba oczywiste?).



PS Wg mnie Visual Studio powinno generować takie metody po dwukliknięciu kontrolki w designerze:
private void button1_Click(object sender, EventArgs e)
{
// TODO: if your programming skills let you write
// multi-class solutions:
// remove this method & put your logic elsewhere
// otherwise:
// code here on your own responsibility
}