Fluent Api

Jest wiele czynników, które mówią o jakości kodu, jednym z nich jest jego czytelność. W jaki sposób można, bez dużego nakładu pracy, poprawić czytelność kodu? Czy wielkość i poziom skomplikowania projektu mają wpływ na czytelność kodu? Kto jest odpowiedzialny z powstawanie nieczytelnego kodu i czy czytelność kodu mówi coś o profesjonalizmie programisty? W jaki sposób można zapobiegać a w jaki sposób leczyć zainfekowany kod? O tym wszystkim w dzisiejszym poście.

 „Lepszy jest kod dobrze napisany, ale niedziałający, niż kod działający, a źle napisany”

Nie pamiętam, kto jest autorem tych słów, ale osobiście się z nimi zgadzam. Smutne to, bo każdy programista chce by jego kod zrewolucjonizował świat, tak jak wprowadzony do użytku w roku 1978 system GPS zmienił sposób patrzenia na mapę.

Wiadomo, że dla „biznesu” świat się musi kręcić, kod pracować, a faktury wystawiać, dlatego warto by zadbać o czystość kodu, zanim jeszcze pojawią się problemy, a jednocześnie pamiętać, że stworzony przez nas kod z dużym prawdopodobieństwem będzie przez kogoś rozwijany. I o ile znajomość alfabetu jest niezbędna, o tyle nazywanie zmiennych dowolnymi, pojedynczymi literami nie doda nam punktów do respektu „na zakładzie”.

Dlaczego zwracam uwagę na czystość kodu? Bo jeśli czyta się kod, którego nie jest się autorem, albo, co gorsza pisało się ten kod samemu, tylko kilka miesięcy wcześniej, warto mieć możliwość szybkiego zorientowania się, co autor miał na myśli. Domyślanie się jest ciężkim procesem myślowym, który może skutecznie spożytkować zapasy glukozy we krwi, przez co wydajność mózgu spada w tempie wykładniczym, a to boli. Jak temu zaradzić?

Jest wiele technik dbania o czystość kodu: odpowiednie nadawanie nazw zmiennym/klasom/metodom, stosowanie konwencji odnośnie formatowania kodu, rozdzielenie funkcjonalności na mniejsze kawałki zgodnie z single responsibility principle itd. It.

Techniką nad którą chciałbym się dzisiaj pochylić jest tworzenie tzw. Fluent Api. Nie jest to technika nowa, bo pisał o niej Martin Fowler w 2005 roku, tym niemniej warto ją znać. Prosty przykład. Ustawianie stanu w obiekcie klasy Task, niby prosta domenowo klasa. Jakiś temat, jakiś opis itp.

 internal class Task
{
     public string Subject { get; set; }
     public DateTime StartDateTime { get; set; }
     public TimeSpan Duration { get; set; }
     public string Description { get; set; }
     public ICollection<Comment> Comments { get; set; }
}

I od razu mały konkurs: co czyta się lepiej? To?

public static void MakeNormal(Task task)
{
    task.Subject = "Fluent Api";
    task.Description = "Write blog post about Fluent Api";
    task.StartDateTime = new DateTime(2016, 01, 28);
    task.Duration = new TimeSpan(1, 15, 0);

    var comment1 = new Comment();
    comment1.Body = "First Comment";
    comment1.CreationDateTime = DateTime.Now;
    task.Comments.Add(comment1);

    var comment2 = new Comment();
    comment2.Body = "Second Comment";
    comment2.CreationDateTime = DateTime.Now;

    task.Comments.Add(comment2);
}

Czy może to?

     public static void MakeFluent(Task task)
        {
            task.Set
                .Subject("Fluent Api")
                .Description("Write blog post about Fluent Api")
                .StartNow()
                .DurationInMinuts(75)
                .Comment("First Comment")
                .Comment("Second Comment");
        }

Oczywiście znajdą się masochiści, który zagłosują na pierwszą opcję. Jak to pisał Kurt Vogenut w książce pt. „Rzeźnia numer pięć”, opisując dramat umierających w czasie nalotów Drezna przeprowadzonego w nocy 13-14 lutego 1945, „Zdarza się”.

Osiągnięcie efektu z drugiego przykładu ogranicza się jedynie do stworzenia klasy, która posiada referencję do obiektu klasy Task i upraszczając posiadała odpowiednie metody, zmieniające stan obiektu, które zwracają same siebie.

Poniżej pełen kod klasy Task

    internal class Task
    {
        private readonly TaskFluentInterface _set;

        public Task()
        {
            Comments = new List();
            _set = new TaskFluentInterface(this);
        }

        public string Subject { get; set; }
        public DateTime StartDateTime { get; set; }
        public TimeSpan Duration { get; set; }
        public string Description { get; set; }
        public ICollection Comments { get; set; }

        public TaskFluentInterface Set
        {
            get { return _set; }
        }
    }

Rozpoczęcie przypisania propertisów klasy Task, poprzez Fluent Api jest propertis „Set”, który zwraca nam referencję do obiektu klasy TaskFluentInterface. Żeby możliwe było składanie dalszych elementów zgodnie z zasadami Fluent Api, wszelkie kolejne wywołania api muszę zwracać ten sam typ, co przedstawię na przykładzie metody ustawiającej Temet w obiekcie klasy Task.

 internal class TaskFluentInterface
    {
        private readonly Task _task;

        public TaskFluentInterface(Task task)
        {
            _task = task;
        }

        public TaskFluentInterface Subject(string subject)
        {
            _task.Subject = subject;
            return this;
        }
        ...
    }

Kolejne właściwości, które ustanawiają stan obiektu klasy Task tworzy się analogicznie. Dzięki temu, że obiekt klas Task posiada referencję do obiektu klasy TaskFluentInterface, który to otrzymuje w konstruktorze referencję do obiektu klasy Task, wszelkie zmiany dokonane przy użyciu Fluent Api zmieniają stan obiektu początkowego. Taka konstrukcja zwalnia nas z obowiązku pisania metody w klasie TaskFluentInterface zwracającej obiekt klasy Task.  Jeżeli zrezygnujemy z przekazywania referencji do klasy Fluent Api, w celu uzyskania obiektu musimy napisać metodę, która ta takowy obiekt nam zwróci i co ważne taka metoda w ciągu wywołań Fluent Api musi się znajdować na samym końcu.

Podsumowując:

  • Dbanie o czystość kodu jest ważna, bo pozwala szybko zrozumieć intencje autora,
  • Tworzenie Fluent Api nie jest wyższą szkołą jazdy, ale w znaczący sposób ułatwia zrozumienie kodu
  • Metody zmieniające stan klasy we Fluent Api muszą zwracać ten sam typ
  • Przekazując referencję do Fluent Api nie musimy tworzyć metod zwracających obiekt „podstawowy”, jak to zrobimy to musimy stworzyć odpowiednią metodę i wywołać ją jako ostatnią w ciągu wywołań Fluent Api

Kod źródłowy z przykładów można pobrać z GitHuba https://github.com/kamiljozwiaknet/Blog.git

Share on Facebook0Share on Google+0Tweet about this on Twitter0Share on LinkedIn0

Kamil Jóźwiak

Nazywam się Kamil Jóźwiak i jestem programistą w firmie Sii, specjalizuję się w technologi .Net. W codziennej pracy staram się łączyć wzorce, style architektoniczne, ale przedewszystkim zdrowy rozsądek. Miłośnik czystego kodu i automatyzacji wszystkiego co się da zaatomatyzować.

  • Pingback: dotnetomaniak.pl()

  • Michał Gutsche

    Kamil jak zwykle ułańska fantazja do inżynierii oprogramowania. Co powiesz na to, że można to jeszcze prościej zrobić

    var task = new Task()
    {
    Subject = „subject”,
    Description = „description”,

    };

    • Dzięki Michał za trafny komentarz. Przedstawiony przykład jest trochę trywialny, ale taki miał być, miał tylko pokazywać mechanizm działania Fluent Api. Masz rację jeśli mamy klasę trywialną nie zawsze jest sens pisania dodatkowej klasy obsługującej Fluent Api, jednak jeśli jakaś metoda wymaga dodatkowych działań, w celu ustawienia stanu, np. połączenia z DB czy odpytania Web Service to jak najbardziej warto pisać Fluent Api

  • Zgadzam się co do Fluent Api. Daje to rzeczywiście bardziej czytelny kod. Ja ostatnio zachwyciłem się nad fluentowym api do wyrażeń regularnych za pomocą Verbal Expressions. Co z resztą opisałem u siebie na blogu: http://stapp.space/clean-code-regular-expression/

    • Faktycznie, użycie Fluent Api w Verbal Expressions jest MEGA

  • Radosław Maziarka

    Super podejście – bardzo ułatwia czytelność w przypadku trudniejszych obiektów.

    Jedyne co, to ja bym tutaj nie robił żadnego interfejsu tylko dodał metody do danej klasy. Nie rozumiem / nie zgadzam się z architekturą płaskich obiektów bez metod bo nie mają wtedy odpowiedniej logiki biznesowej którą realne byty posiadają.

    • Przykład, może „płaski”, bo i klasa „płaska” domenowo, tylko pytanie, czy dorzucanie do klasy metod odpowiedzialnych za ustawianie wartości nie kłóci się z zasadą pojedynczej odpowiedzialności? Wg. mnie tak i to bardzo

      • Radosław Maziarka

        Nie no, w jaki sposób? Tak rozumując to dana klasa nie będzie miała żadnych metod bo nic nie będzie do niej teoretycznie pasowało. Jeśli do ustawiania własnych pól nie pasują metody w tej klasie to IMO nie pasuje nic.

        • Fluent Api dodaje „inny” sposób robienia tego samego co oferuje klasa „bazowa” i osobiście będę bronił, że „domenowo” nie wnosi nic nowego do klasy, więc powinna stać z boku. Klasa Task jest dość trywialnym przykładem, więc „biznesowo” nie robi nic.

          • Arek Bal

            Dziedziczenie z Task rozwala cały ten misterny plan…

          • A w jaki niby sposób dziedziczenie ma na cokolwiek wpłynąć

          • Arek Bal

            Dodanie kolejnej właściwości oznacza konieczność dodania kolejnego „TaskFluentInterface”

  • Marcin

    Użycie Fluent Api bardzo fajne, ale to co zostało tutaj pokazane wygląda trochę na wzorzec projektowy Builder. Czy nie lepiej było by w takim razie nazwać tą klasę TaskBuilder? Gdy zobaczyłbym w kodzie klasę TaskFluentInterface, musiałbym się zastanowić co ona robi, a TaskBuilder jest oczywiste dla każdej osoby która zna podstawowe wzorce projektowe.

    • Michał Gutsche

      Racja na pierwszy rzut oka może się wydawać, że to jest podobne do Buildera, tylko to chudzi o sposób konstruowania api a nie samego obiektu, więc polecam użycie FA we api builder’a

      • Marcin

        Tak, wiem że chodziło tutaj o pokazanie samego Fluent API, a nie wzorca Builder. Widzę też że nie jest to do końca Builder, bo nie ma metody build, tylko mamy już wcześniej referencje do tworzonego obiektu. Po prostu wydaje mi się że jeśli już tworzymy jakąś klasę która służy do ustawiania wartości w innej klasie, to dobrze jest użyć do tego znanego wzorca projektowego, wraz z przyjętym w nim nazewnictwem.

        • ok, przygotowuję właśnie serię wpisów o „podstawowych” wzorcach projektowych i specjalnie dla Ciebie w Builderze będzie Fluent Api zgodne ze sztuką. Kiedy to się pojawi? 2-3 tygodnie. Jak chcesz być na bieżąco to polecam zapisanie się do newslettera, będę informował na żywo

    • We wzorcu Builder chodzi o to by utworzyć obiekt, we Fluent Api by go skonfigurować. I to jest mała, ale, znacząca różnica. Co do nazwy to jak najbardziej się zgadzam, że bez znania kontekstu trochę ciężko ogarnąć co robi, jednak w kontekście przykładu jest ok. To moja opinia.

  • Arek Bal

    Dopięcie referencji do obiektu, który trzyma referencje do naszego obiektu, tylko na potrzebę konstrukcji(/konfiguracji „whatever”) obiektu to jak strzał w stopę po to, by do szpitala przyjęli bo podoba nam się pielęgniarka.

    • A to racja, tylko to założenie na ten post było, żeby jak najprościej pokazać fluent api.

  • Pingback: FluentValidation – podstawy – RadBlog()

Zobacz też:
Zasady SOLID są dla programistów, jak dekalog dla chrześcijan. Każdy…