Co złego w uproszczeniach języków?

Zastanawialiście się czasami w jakim kierunku idą kolejne rozwiązania języków? Czy to w ogóle idzie w dobrą stronę? Ja czasami myślę i o takich rzeczach. Nie do końca zgadzam się z takimi rozwiązaniami, ale całkowicie je rozumie.

Wszystkie chcą mieć wszystko

Firmy zajmujące się utrzymaniem danego języka chcą przyciągnąć do siebie jak największą liczbę programistów. Spowodowane jest to oczywiści możliwością rozwoju. W tym kierunku języki starają się zaspokoić potrzeby wszystkich programistów. To dlatego Java wprowadziła programowanie funkcyjne. C# wprowadził w ramach rozwoju parametry opcjonalne i nazwane. To miało przyciągnąć grono fanów innych języków tj. Scala, JS itp..

Co zyskujemy?

Bardziej funkcjonalne języki. Robimy dużo więcej rzeczy w jednej linijce. Kod tworzy się w tle. Gettery i settery automatyczne w C# to tylko zaleta. Jednocześnie wraz z usprawnieniami możemy osiągnąć łatwiej swoje cele. Pozwala to na szybkie pisanie logiki oraz rozwiązywanie problemów. Skusiłbym się nawet na stwierdzenie, że zyskujemy bardzo dużo czasu na implementację ważniejszej logiki biznesowej.

Ale czy na pewno?

Uważam, że owszem posiadamy języki coraz bardziej funkcjonalne. Jednakże za tym idzie jedna ważna dla mnie jako programisty rzecz. Tracimy czytelność kodu. I to mnie bardzo boli jako osobę, która działa w zespole. Osobę, która systemy napisane teraz będzie wspierała przez najbliższe 40 lat. Dlaczego aż tak bardzo? O tym w następnym wpisie. Jednakże chciałbym udowodnić, że aktualnie języki, które idą na prostotę stają się mało czytelne. Pozwólcie ze porównanie stworze na języku mi najbliższym czyli Javie.

Jak pewnie większość moich czytelników już wiecie, że powstała można powiedzieć, że nowa Java zwana Kotlin. Spójrzmy na deklaracje struktury danych:

Java

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Kotlin

data class Person(val name: String, val age: Int)

Nie wiem jak Wy, ale ja nigdy po takiej klasie nie spodziewałbym się, że posiada ona gettery i settery. W Javie też można zrobić pola publiczne, ale tego się nie robi. Trzeba się byłoby skupić na tej przyczynie, ale to nie wpis o tym. W kodzie Javy widzę co konkretnie autor miał na myśli pisząc ten kod, po stronie "nowej Javy" już nie za bardzo. Kolejny przykład.

Java

public void sendMessageToClient(
    @Nullable Client client,
    @Nullable String message,
    @NotNull Mailer mailer
) {
    if (client == null || message == null) return;

    PersonalInfo personalInfo = client.getPersonalInfo();
    if (personalInfo == null) return;

    String email = personalInfo.getEmail();
    if (email == null) return;

    mailer.sendMessage(email, message);
}

Kotlin

fun sendMessageToClient(
        client: Client?, message: String?, mailer: Mailer
) {
    val email = client?.personalInfo?.email
    if (email != null && message != null) {
        mailer.sendMessage(email, message)
    }
}

W tym przypadku uważam, że łatwo pomylić się z zabezpieczeniami. Trzeba pamiętać, aby za każdym użyciem jakiegoś pola wstawić później znak zapytania. A to jest tylko jeden, mało czytelny znak. To są proste przykłady i wiem, że są czytelne w obu przypadkach. Wydaje mi się jednak, że kod Javy bardziej do mnie przemawia. Podoba mi się koncepcja oraz styl języka Kotlin, ale pisząc dłuższy kod i logikę biznesową to po czasie zaczynam zastanawiać się co miałem na myśli pisząc ten fragment kodu. I skupiam swoją uwagę bardziej na składnie języka zamiast na samej logice. To zaczyna już mi się to nie podobać. Ciężko mi ocenić na ile to jest spowodowane tym, że język Kotlin jest dla mnie nowszy niż Java.

To są jednak przykłady normalne, które są po prostu różnicami w językach programowania. Pokaże Wam teraz programowanie funkcyjne, a zwykłe w Javie:

Za pomocą programowania funkcyjnego

public static void incrementPageVisit(Map<String, Integer> pageVisits, String page) {
    pageVisits.merge(page, 1, (oldValue, value) -> oldValue + value); 
}

Bez programowania funkcyjnego

public static void incrementPageVisit(Map<String, Integer> pageVisits, String page) {
    if(!pageVisits.containsKey(page)) {
       pageVisits.put(page, 0);
    }
    pageVisits.put(page, pageVisits.get(page) + 1);
  }

Gdyby nie nazwa funkcji miałbym problem z odgadnięciem "Co autor miał na myśli?". Dłuższy zapis jednak pokazuje od razu, że wrzuca wartość zero, gdy nie istnieje w mapie podany klucz, a potem dodaje jedynkę do wartości tego klucza. Krótszy zapis musiałbym się zastanowić nad składnią interfejsu. Czyli marnuje czas na rozkodowaniu co oznacza składnia zamiast skupić się na logice.

Przypadek problemu uproszczenia języka można także zobaczyć już stosując operator warunkowy.

if (expression1) {
 result = 1;
} else if (expression2) {
 result = 2;
} else if (expression3) {
 result = 3;
} else {
 result = 0;
}
result = (expression1) ? 1 : (expression2) ? 2 : (expression3) ? 3 : 0;

Takie sformułowania często zajmują mi czas, aby odszyfrować ten warunek. Nie lubię tego typu zapisów, ponieważ w swoim krótkim życiu spotkałem z nimi zbyt dużo problemów.

Ale to nie tak jak myślicie...

Po przeczytaniu tego wpisu, możecie odnieść wrażenie, że nie lubię zmian. Nie lubię wykonywać operacji w jednej linijce. Jestem przeciwny usprawnieniom oraz automatyzacjom kodu. To jednak nie jest prawdą. Chciałem zwrócić jedynie uwagę na to, że stosując tego typu łatwiejsze zapisy kodu tracimy na jego przejrzystości i czytelności. Osoba przeglądająca nasz kod może mieć problem z rozszyfrowaniem takiej, a nie innej składni. Zamiast skupić się jak to działa będzie musiała myśleć o tym co robi dana składnia lub co "uwielbiam najbardziej", który nawias oraz znak zapytania jest do dwukropka... Takie rzeczy trzeba wykorzystywać, ale z głową. W programowaniu funkcyjnym w Javie 8 bardzo podoba mi się operowanie na kolekcjach. Spowodowane jest to tym, że jest one o wiele czytelniejsze, niż to co było dotychczas do wykorzystywania. Jednakże pokazałem wyżej przykład w którym wolałbym go nie wykorzystywać. I rozświetleniem tej granicy miał zając się ten wpis. Zapraszam do komentowania i proponuje zastanowić się "Czy nowa funkcjonalność w języku którego wykorzystujecie jest słuszna w każdym przypadku?".

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot