Czas Mavena minął

Faks firmy NEC

Aż ciężko uwierzyć, jak mocno ludzie potrafią trzymać się rozwiązań, które kiedyś podbijały rynek i ułatwiały życie, a obecnie są już tylko groteskowym reliktem przeszłości i hamulcem rozwoju. Japończycy cały czas używają faksów, Amerykanie czeków, a programiści Javy — Mavena. Od razu trochę sprostuję, żeby nie być niesprawiedliwym w ocenach: przywiązanie do czeków może nie być takie złe, bo zapewne nie pozwala na społeczne wykluczenie ludzi, którzy mają kłopoty z nauczeniem się korzystania z komputerów lub są zbyt biedni, żeby z łatwo z nich korzystać. Skupmy się jednak na programowaniu.

Tło historyczne

Maven ma nieocenione zasługi dla ekosystemu Javy. Dokonał epokowego przełomu, kończąc czasy, gdy programowanie w tym języku niewiele przypominało nowoczesny software development, dziś przyjmowany za coś naturalnego. Java wyrastała bardzo mocno z tradycji C/C++, gdzie w czasach, gdy Maven zdobywał popularność, panowała pełna wolna amerykanka jeśli chodzi o organizację projektu. Każdy uważał, że jest najmądrzejszy na świecie i wymyślał swój najlepszy układ katalogów, następnie oprogramowywał kompilację wszystkiego do kupy we własnym Makefile’u. Java długo powielała jedynie pomysły języków „bazowych”, wystarczy spojrzeć jak Ant przypomina zwykły Make. Maven zerwał z tym dziedzictwem: koniec z fantazją w organizacji projektu, źródła miały być w src/, testy w test/, a zbudowane rzeczy w target/, co znacząco ułatwiało wejście w cudzy kod. Ustanowił też uruchamianie testów jako stałą część budowania projektu, co wcale nie było wtedy oczywiste – środowisko programistów dopiero walczyło o popularyzację pisania testów automatycznych; projekt bez testów nie był wtedy niczym dziwnym, podobnie jak testy, o których odpaleniu trzeba było specjalnie pamiętać. Maven kończył też z wrzucaniem binarnych zależności do sytemu kontroli wersji, oferując deklaratywną składnię do zapisu zależności i ich automatyczne pobieranie.

Pamiętam pierwszy kontakt z Mavenem i mój zachwyt nad tym, że jako programista dostaję zbiór spisanych jasno wytycznych, jak zorganizować projekt, a w nagrodę narzędzie zrobi bardzo dużo za mnie automatycznie, w sposób powtarzalny i przewidywalny. Maven odniósł sukces, stając się w świecie Javy podstawowym narzędziem, a z nim sukces odnieśliśmy my, programiści lubiący pracę z kodem, który jest utrzymywalny.

Maven rozwiązał problemy, do których był zaprojektowany 15 lat temu. Tyle że teraz świat poszedł do przodu, rzeczy które wtedy były marzeniem (jak automatycznie odpalające się testy i zaciągające się zależności) teraz przyjmowane są za elementarną podstawę, chcemy wytwarzać oprogramowanie jeszcze szybciej, pewniej i na większą skalę. Nasze współczesne potrzeby to Continuous Deployment, pakowanie aplikacji nie tylko do WAR-a, ale do obrazu Dockera, odpalanie w trakcie buildu zewnętrznych narzędzi do analizy kodu (często spoza świata JVM), stabilne zależności bez JAR hell. Maven nie był zaprojektowany do mierzenia się z takimi problemami, a ponieważ przez 15 lat jego filozofia działania nie zmieniła się, nie jest w stanie zaproponować spójnego rozwiązania.

Problemy ze zrozumiałością

Moją krytykę Mavena podzieliłem na 2 większe części. Zacznę od opisania problemów ze zrozumiałością, później przejdę do wydajności. Nie będę odnosił się do sprawy czytelności i rozwlekłości XML-a albo Groovy’ego — nie podoba mi się sprowadzanie problemu z Mavenem do kwestii gustu i sugerowanie, że każdy ma prawo do swojego, więc wara od moich narzędzi. Pokażę, jak według mnie Maven przez swój bagaż historyczny dokłada programistom dodatkowych problemów przez złe modelowania zarządzania projektem. Łatwość odczytywania przez człowieka konfiguracji projektu jest sprawą znacznie mniejszej wagi niż łatwość rozumienia, co w trakcie budowania dzieje się z projektem i jak to kontrolować.

Konflikty zależności

Weźmy dwa proste i wydawało by się identyczne projekty, jeden w Mavenie:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>maven-dependencies</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.10</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.11</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.3</version>
            </plugin>
        </plugins>
    </build>
</project>

i drugi w Gradle’u:

plugins {
    id 'java-library'
    id 'war'
}

repositories {
    jcenter()
}

dependencies {
    implementation 'org.slf4j:slf4j-api:1.7.10'

    runtimeOnly 'ch.qos.logback:logback-classic:1.1.11'
}

Wynik ich zbudowania jest inny. Projekt mavenowy w wynikowym WEB-INF/lib/ zawiera:

logback-classic-1.1.11.jar
logback-core-1.1.11.jar
slf4j-api-1.7.10.jar

natomiast gradle’owy:

logback-classic-1.1.11.jar
logback-core-1.1.11.jar
slf4j-api-1.7.22.jar

Maven dosłownie zastosował nasze życzenie pobrania SLF4J w wersji 1.7.10. Gradle natomiast wykrył konflikt pomiędzy naszym życzeniem a wymaganiami Logbacka:

% gradle dependencies --configuration=runtimeClasspath

runtimeClasspath - Runtime classpath of source set 'main'.
+--- org.slf4j:slf4j-api:1.7.10 -> 1.7.22
\--- ch.qos.logback:logback-classic:1.1.11
     +--- ch.qos.logback:logback-core:1.1.11
     \--- org.slf4j:slf4j-api:1.7.22

W wypadku braku dodatkowych ograniczeń (BOM, constraints, metadane Gradle’a) w Gradle’u konflikt wygrywa nowsza z wersji – i jest to najbezpieczniejsze założenie przy braku danych, dużo lepsze niż podejście Mavena. Skoro Logback w swoich wymaganiach ma SLF4J 1.7.22, to nie można bezpiecznie założyć, że wcześniejsze wersje działają – Logback może ładować klasy, które w starszych wersjach SLF4J jeszcze nie istniały. Przykładowo, release notes Logbacka ostrzegają, że użycie Logbacka 1.1.4 z SLF4J niższym niż 1.7.15 może spowodować NoClassDefFoundError. Narzędzie do budowania powinno pomagać programiście, na przykład wykrywać błędy w jego działaniach. Gradle automatycznie naprawia sytuację. Maven po cichu ignoruje wymagania bibliotek, akceptuje bezsensowną konfigurację i naraża nas na trudne do wykrycia błędy w działaniu aplikacji.

Zależności niemieszczące się w głowie

Kolejny przykład. Weźmy następujące zależności:

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.9</version>
</dependency>

<dependency>
    <groupId>com.datastax.cassandra</groupId>
    <artifactId>cassandra-driver-core</artifactId>
    <version>3.8.0</version>
</dependency>

Używamy tych dwóch bibliotek w zupełnie oddzielnych celach (przeglądanie klas refleksją i korzystanie z bazy danych), więc moglibyśmy oczekiwać, że nie mają ze sobą interakcji. Tymczasem możemy zdestabilizować nasz projekt dopisując pierwszą z zależności do pom.xml zawierającego początkową wyłącznie drugą zależność. Obie biblioteki korzystają z Guavy, tyle że reflections z 15.0 a sterownik Cassandry z 19.0. Przy bibliotece traktującej serio usuwanie kodu oznaczonego jako deprecated (Guava do takich należy) 4 „duże” numerki różnicy mogą oznaczać różnicę między kodem poprawnie działającym na produkcji, a kodem, który czasem rzuca NoClassDefFoundError.

Kod z listingu spowoduje użycia Guavy 15. Zmieniając kolejność tych dwóch zależności uzyskujemy zupełnie inny system, działający na Guavie 19. Zaskakuje to ludzi, wystarczy spojrzeć na Stacka. Ktoś może mieć sensowną motywację: na przykład trochę wyżej jest inna zależność bazodanowa, więc nasz kolega lub koleżanka przesunie cassandra-driver-core do góry (bardzo słusznie realizując podstawową zasadę programowania „powiązane rzeczy powinny być blisko siebie”).

Nie kupuję tłumaczenia „przecież dokumentacja Mavena mówi wyraźnie, że pierwsza deklaracja wygrywa”. Po pierwsze, przez to zależności przestają być deklaratywne; stają się listą, gdzie kolejność ma kluczowe znaczenie. Nie jesteśmy w stanie zastosować żadnego przyjaznego dla człowieka grupowania zależności i trzymać związanych rzeczy obok siebie — jesteśmy skazani na konfigurację przyjazną tylko i wyłącznie dla maszyny.

Po drugie, nie skaluje się to ze wzrostem liczby zależności. Tu mieliśmy kłopot już przy 2 użytych bibliotekach; jak w ogóle ogarnąć sytuację, gdy mamy ich 20, a każda wewnątrz dokłada swoje zagnieżdżone drzewo zależności? Jest to niemożliwe. Z mojej obserwacji ludzie metodą prób i błędów dochodzą do kolejności, która wydaje się działać i ta kolejność zostaje „święta”, nienaruszalna. Biada nowicjuszowi, który spróbuje naruszyć status quo, dokładając nową zależność między istniejące albo wyrzucając gdzieś ze środka nieużywaną od dawna bibliotekę. Starannie pielęgnowana konstrukcja rozsypuje się jak domek z kart i trzeba pracowicie od nowa szukać nowej stabilnej konfiguracji.

Bezpośrednie zależności i kod write-only

Jest typowo mavenowe rozwiązanie powyższego problemu: specyfikujemy wprost zależność od Guavy.

<dependency>
    <groupId>org.reflections</groupId>
    <artifactId>reflections</artifactId>
    <version>0.9.9</version>
</dependency>

<dependency>
    <groupId>com.datastax.cassandra</groupId>
    <artifactId>cassandra-driver-core</artifactId>
    <version>3.8.0</version>
</dependency>

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>21.0</version>
</dependency>

Kod z listingu da nam system działający na Guavie 21. Jest to świetna sytuacja, bo nie musimy czytać POM-ów wszystkich naszych zależności i sprawdzać, jakiej wersji Guavy używają oraz powtarzać to przy każdym podbiciu wersji. Wpisujemy wprost „naszą” Guavę i żadna zależność tego nie ruszy. Odsuwamy tym samym od nas naprawdę spory cognitive load rozumienia relacji między zależnościami, POM staje się znowu bardziej możliwy do ogarnięcia przez zwykłego człowieka.

Chyba każdy większy projekt napisany w Mavenie robi mniej więcej coś podobnego. Z tego, co widziałem, użytkownicy Mavena dobrze zdaje sobie sprawę z korzyści takiego podejścia, gorzej jest natomiast ze świadomością negatywnych konsekwencji. Spójrzmy wysokopoziomowo na to, co się zdarzyło w naszym przykładzie: mieliśmy system z 2 bezpośrednimi zależnościami a teraz ten sam system ma 3 bezpośrednie zależności. Nic nie zmieniło się w kodzie Javy, nie korzystamy w ogóle z Guavy. Czy wygląda to jak rozwiązanie czy jak hack?

Dołożenie bezpośredniej zależności, by rozwiązać problem z przechodnią (transitive) zależnością to trochę jak leczenie dżumy cholerą. Jeśli każdy konflikt zależności rozwiążemy w taki sposób, to wkrótce zostaniemy z POM-em, w którym nie sposób będzie odróżnić prawdziwej zależności od zależności dodanej tylko po to, by zgadzały się wersje. Do projektu trafi nowy programista i będzie zastanawiał się, czemu do kompilacji jest używana Guava, skoro nie widzi importów w kodzie (a sprawdzenie, że nie ma importów wymaga często sporo zachodu, bo np. Guava umieszcza klasy w pakietach zaczynających się od com.google.common, które nie zawierają ani group ID ani artifact ID tej biblioteki, więc ciężko wychwycić związek). A może coś ładuje te klasy dynamicznie i dlatego potrzebna jest Guava? Jeśli ktoś jest cierpliwy i zacięty, może sprawdzić to dla jednej, dwóch bibliotek. Ale na więcej raczej nie starczy czasu.

Jest to prosta droga do zaśmiecenia projektu nieużywanymi bibliotekami. Wracając do naszego przykładu, nowsze wersje zarówno reflections jak i sterownika Cassandry nie używają Guavy. Podbijając wersję obu bibliotek zostajemy z Guavą, która niczemu już nie służy — ale trzymamy ją, bo nikt już nie pamięta, po co taka bezpośrednia zależność została dodana, a zbyt pracochłonne jest upewnienie się, że niczemu już nie służy.

Taki POM jest więc trochę jak kod write-only: gdy dopiszemy jakąś zależność, to zostanie tam aż do końca.

Jest możliwe rozwiązanie tego problemu w lepszy sposób z użyciem Mavena: skorzystanie z bloku <dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.reflections</groupId>
        <artifactId>reflections</artifactId>
        <version>0.9.9</version>
    </dependency>

    <dependency>
        <groupId>com.datastax.cassandra</groupId>
        <artifactId>cassandra-driver-core</artifactId>
        <version>3.8.0</version>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>21.0</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Wtedy kontrolujemy wersję Guavy, ale nie wprowadzamy zależności od niej. Czemu nie jest to popularne w mavenowych projektach? Nie wiem, może dlatego, że czasem <dependencyManagement> nie działa, więc ludzie nie ufają tej funkcji (przy okazji: przerażające jest, jak zawzięcie w podlinkowanym pytaniu StackOverflow ludzie bronią nieintuicyjnego zachowania, to chyba jakiś syndrom sztokholmski).

Kod z enkapsulacją, ale biblioteki bez

Autorzy bibliotek korzystający z Mavena do publikacji nie mają możliwości ukrywania swoich zależności — wszystko, z czym się kompilują, trafia na compile classpath ich użytkowników. Jeden ze skutków opisuję w artykule o pracy z importami w IntelliJ: IDE sugeruje nam ciągle pełno śmieciowych klas, ponieważ w Mavenie zależności biblioteki są zawsze „publiczne”.

Skończyliśmy już dawno z programowaniem w stylu „wszystko globalne i publiczne” (nawet w JavaScripcie). Czemu więc używamy narzędzia do budowania pracującego w takiej filozofii?

Gradle zachowując ten sam format publikowanych paczek jest w stanie lepiej odizolować prywatne i publiczne zależności bibliotek wprowadzając koncepcję zależności typu api i implementation.

Jeszcze dziwniejsze przypadki z bibliotekami

Twórcy Guavy musieli zmierzyć się z problemem wersji dla Androida i nowoczesnej Javy, wydzielania części biblioteki do osobnych modułów, JAR-ów z adnotacjami niepotrzebnie wyciekających do użytkowników. Publikowanie tej biblioteki z Mavena nie umożliwia eleganckiego rozwiązania — skończyło się na dziwacznych hackach. Pouczającą analizę sytuacji oraz przedstawienie lepszego podejścia można znaleźć na blogu Gradle’a.

Wszystko w pluginach

Maven powstawał w czasach, gdy szczytem mody była architektura wtyczek: supergeneryczny produkt z otwartym interfejsem do tworzenia dodatków, dzięki czemu dało się do niego dodać każdą funkcjonalność przez napisanie wtyczki. Tak działał Eclipse, tak działał ówczesny Firefox — tak działa do tej pory Maven. Pomysł powstał w kontrze do własnościowych monolitycznych narzędzi, których zachowania nie dało się w żaden sposób zmienić — masz robić to, co przewidział producent i ani się waż marzyć o czymś więcej.

Otwarty interfejs dla pluginów brzmi świetnie, ale w praktyce okazało się, że nie działa to najlepiej. Producent mógł stwierdzić, że nie dostarcza produktu, a jedynie framework do uruchamiania pluginów i umywać ręce od reszty. Nie działa ci coś — napisz do twórcy odpowiedniej wtyczki. Nie ma jakiejś funkcji — napisz sobie do tego plugin. Wtyczki gryzły się ze sobą i z nowymi wersjami platformy. Nie pasowały do interfejsu użytkownika, dodawały własne pozycje menu zamiast integrować się z istniejącymi, stosowały własne dziwne ikony.

To nie tylko moje osobiste preferencje, ekstremalne podejście do wtyczek jest obecnie w odwrocie. Eclipse poległ w starciu z IntelliJ — który ma bazę wtyczek pisanych przez niezależnych twórców, ale poprawne zachowanie IDE przy pisaniu czegoś tak skomplikowanego jak webowa aplikacja oparta o Springa gwarantuje nam producent. Nawet jeśli niektóre funkcje, jakich wtedy używamy, są dostarczane przez wtyczkę, to jest to wtyczka rozwijana przez twórców IDE razem z IDE i nie musimy jej pobierać ani aktualizować. Firefox zyskał blokowanie śledzenia zaszyte we własnym kodzie mimo sukcesu dodatków blokujących reklamy; dostarczył też własny wbudowany odpowiednik chrome’owych DevToolsów mimo dużej tradycji używania dodatku Firebug. W Gradle’u obsługa Javy jest wtyczką, ale nie da się wybrać jej wersji — jest osadzona w produkcie.

Tymczasem w Mavenie szczegóły wtyczek infekują POM: zapisujemy tam rzeczy tak szczegółowe, jak wersje wtyczki kompilującej kod. Nawet jeśli aktualizujemy samego Mavena, nie oznacza to, że automatycznie skorzystamy z lepszego działania projektu — konieczne może być podbicie wersji kluczowych wtyczek. Elementarne rzeczy, bez których nie sposób wyobrazić sobie pracy z projektem, jak kompilacja i odpalanie testów, są wtyczką i mogą cierpieć od wszystkich wtyczkowych przypadłości. Przykładowo plugin do odpalania testów przestał w październiku 2018 działać na Ubuntu i Debianie, poprawka powstała, ale tylko do wersji niestabilnej i do dnia pisania niniejszego artykułu plugin wersja stabilna nie ukazała się.

Problemy z wydajnością

Clean na wszelki wypadek

Weźmy dowolny wielomodułowy projekt Mavena. Uruchamiamy mvn package, wszystko działa. Otwórzmy teraz w źródłach dowolny interfejs (ważne, żeby miał implementację, ale nie w tym samym module) i zmieńmy nazwę jakiejś metody. Zepsuliśmy właśnie nasz kod. Uruchamiamy znowu mvn package:

[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) --- [INFO] Nothing to compile - all classes are up to date 
[INFO] ----------------------------------------------------------- [INFO] BUILD SUCCESS
Według Mavena wszystko działa.

Większość programistów używających Mavena dla świętego spokoju dodaje „clean” przed każdym poleceniem, żeby nie martwić się, że będzie pracować z częściowo tylko przebudowanym projektem. Zalecają to nawet tutoriale (warto zobaczyć, jak żałosne jest podane tam uzasadnienie). Jest to tak nagminne, że ludzie deklarują aliasy w linii komend typu mci (mvn clean install). Z drugiej strony, jeśli komuś się spieszy, to wszędzie dodaje flagę do pominięcia testów (mvn install -DskipTests). Chcąc robić tą samą rzecz używamy zupełnie innych poleceń, kalkulując w głowie, które jest szybsze. Moment, od czego mamy narzędzia, czy to nie komputer powinien liczyć za nas, ile roboty trzeba wykonać i robić tylko tyle i nic więcej? Tak właśnie działa Gradle: wpisujemy gradle build i nie przejmujemy się resztą.

Możliwe są dalsze optymalizacje wydajności, które ludziom znającym wyłącznie Mavena pewnie nie mieszczą się w głowie. Gradle od 2017 udostępnia dla Javy compilation avoidance: projekty zależne nie są przekompilowywane w sytuacji, gdy zmiana w kodzie nie dotyka publicznego interfejsu klas, na przykład modyfikuje prywatne zmienne albo ciała metod.

Wolny start

Mavenowy projekt, zwłaszcza oparty na snapshotowych zależnościach, startuje koszmarnie wolno. Najprostsze skompilowanie kodu wisi w oczekiwaniu na pobranie z sieci informacji, czy snapshoty się nie zmieniły. Ciągle coś się ściąga, mimo że nie było zmian w POM-ie. Doprowadzało mnie to czasem do takiej szewskiej pasji, że pracowałem z przełącznikiem mvn --offline.

Gradle unika tej sytuacji przez cache’owanie wersji snapshotowych.

Na koniec

Wiele razy słyszałem od ludzi „u nas Maven działa dobrze, czemu mamy coś zmieniać?”. To nie jest żaden argument. Paryskie lotnisko w roku 2015 używało Windowsa 3.1, międzynarodowa korporacja telekomunikacyjna używała ClearCase’a (systemu kontroli wersji o fatalnej opinii) i jakoś od tego nie zbankrutowała. Jest wiele fatalnych praktyk, których stosowanie nie pociąga za sobą natychmiastowego bankructwa, powoduje natomiast mniejszy lub większy paraliż rozwoju. Czy nie jest marnotrawstwem spędzanie czasu na wdrażanie nowych programistów w gąszcz workaroundów na powszechnie występujące problemy zamiast przejść na narzędzia, w których dany problem po prostu nie występuje? Oraz z drugiej strony: jeśli potencjalny pracodawca proponuje Ci pracę z Mavenem, zastanów się, czy nie marnuje Twojego czasu na uczenie się narzędzia rozwiązującego problemy programistów sprzed 15 lat. Mnie zdarzyło się odrzucić ofertę pracy, gdy firma, co do której miałem parę niezależnych wątpliwości, wykazywała mocne przywiązanie do Mavena.

Nadszedł czas, żeby skończyć z używaniem Mavena w nowych bibliotekach opensource’owych, tutorialach i naszych projektach w pracy — podobnie jak w żadnych nowych rzeczach nie powinna pojawiać się Java 7 albo Joda Time. Jeśli ktoś nie zna alternatyw dla Mavena — niech się nauczy. Czy można traktować serio kogoś, kto jest świetnym fachowcem w swojej działce, ale przykłady wciąż pisze w Javie 7? Nie, z taką osobą ewidentnie coś jest nie tak, skoro nie jest w stanie dostosować się do zmieniających się czasów. Tak samo powinniśmy podchodzić do kurczowego trzymania się Mavena.

Nie mówię, że każdy stary kod trzeba przerobić tak, by był „nowocześniejszy”. Czasem jest to nieopłacalne. Nie mówię też, że nie powinny powstawać już żadne artykuły o Mavenie czy Javie 7 — cały czas są stare projekty używające starych narzędzi, projekty te trzeba utrzymywać w dobrej formie i w związku z tym dobrze rozumieć, jak działają ich części składowe, przydatne jest, gdy ktoś wyjaśni ich zawiłości w artykule. Chodzi mi o pisanie nowości na bazie Mavena. Wstyd mi, gdy widzę nowe artykuły na blogach prezentujące rozwiązania współczesnych problemów używające Mavena, traktujące to narzędzie jako domyślne dla Javy i oczywistą oczywistość bez alternatywy oraz naturalnie bez wad. To mieszanie nowicjuszom w głowach i wpajanie na starcie złych nawyków. To tak, jakby uczyć w 2020 roku ludzi Javy 7 albo wystawiania API przez WSDL.

Problem jakości blogosfery

Jest zresztą więcej złej jakości porad: przykładowo promowanie podziału aplikacji na najwyższym poziomie w stylu controller-dto-service (pisał o tym Milen Dyankov). Widziałem też posty pokazujące ekstremalne podejście do mockowania, ze sprawdzaniem liczby wywołań metody niemodyfikującej stanu.

Myślę, że jako środowisko ludzi chcących podnosić poziom naszego zawodu i pasjonujących się dzieleniem się wiedzą mamy obowiązek reagować. Nie uważam, żeby dobrym pomysłem było potępianie, wszczynanie kłótni albo oczekiwanie od ludzi, że przepiszą od nowa materiały, w które włożyli przecież dużo pracy. Można wysłać wiadomość prywatną do autora i zasugerować choćby dopisanie zdania lub dwa korekty na końcu (nie wiem, coś w stylu „z przyzwyczajenia użyłem tu Mavena, ale prawdopodobnie prościej da się to napisać w Gradle’u” albo „podany tu podział na pakiety jest niedobry, polecam podejście package by feature [LINK]”). Sam tak zrobiłem i spotkałem się z pozytywną reakcją autora. W razie braku możliwości porozumienia możemy sami dodać uprzejmy publiczny komentarz ze sprostowaniem. Możemy popierać takie komentarze innych.

Warto tak czy inaczej działać. Jeśli nic nie zrobimy, będziemy musieli i tak pracować nad naprostowanie złych nawyków u ludzi — ale już nie gdzieś daleko, ale u nowicjuszy przychodzących do naszego zespołu.


Zdjęcie faksu autorstwa Abc10 z Wikimedia Commons wykorzystano na licencji CC BY-SA 4.0.