CodeNarc – linter do Grooviego

Gdy zaczynałem pracę nad Star Trek API, naturalnym wydało mi się zastosowanie jakiegoś lintera do Javy. Wybór padł na popularny Chekstyle. To żadne zaskoczenie, ale ponad połowa kodu, który w tej chwili znajduje się w repozytorium projektu, to kod testów napisanych w Spocku, czyli w efekcie w Groovym. Udało mi się znaleźć tylko jeden linter do Grooviego, CodeNarc, który opiszę w tym poście.

Instalacja

Jeśli budujemy projekt za pośrednictwem Gradle’a, jak w moim przypadku, instalacja ogranicza się do dodania do Gradle’a pluginu:

apply plugin: 'codenarc'

I skonfigurowania:

codenarc {
configFile = "$rootDir/codenarc.groovy" as File
toolVersion = '0.26.0'
}

Następnie trzeba stworzyć plik codenarc.groovy w katalogu głównym projektu, lub w dowolnym innym, jeśli wskażemy go w konfiguracji. Gdy to zrobimy, mamy dostępne dwie nowe komendy:

gradle codenarcMain
gradle codenarcTest

Pierwsza komenda sprawdza, czy produkcyjny kod w Groovim jest zgodny z aktywowanymi w CodeNarcu regułami, a druga robi to samo dla kodu testów. Ponieważ w projekcie nie mam żadnego kodu produkcyjnego w Groovym, przy walidacji kodu posługuję się tylko tą drugą komendą.

Dodatkowo teraz, gdy wywołujemy komendę gradle check, wykonywane są też walidacje pochodzące z CodeNarcu.

Konfiguracja

Cała konfiguracja Codenarcu odbywa się we wspomnianym już pliku codenarc.groovy. Alternatywnie można skorzystać z konfiguracji przez plik z propertisami, ale ja wybrałem plik Groovy.

Postanowiłem zacząć od wrzucenia pliku ze wszystkimi regułami domyślnie włączonymi, i po kolei rezygnować z tych, które uznałem za bezcelowe lub niemające znaczenia. Mimo wszystko nie zrezygnowałem z dużej liczby reguł, ponieważ kod testów jest z natury prostszy, niż kod produkcyjny, a więc nie tak wiele rzeczy trzeba było poprawiać, żeby walidacje przechodziły.

Ostatecznie mój plik z regułami wygląda tak.

Wyłączone reguły

Oto wybrane reguły, które wyłączyłem w Star Trek API, wraz z powodami wyłączenia:

  • NoTabCharacter – ponieważ w całym projekcie używam tabów.
  • AbstractClassWithoutAbstractMethod – ponieważ czasami moje klasy abstrakcyjne mają tylko pola, których wartości używane są w testach.
  • TrailingComma – w testach rzadko zmienia się kolejność w listach po ich utworzeniu.
  • ClassJavadoc – zgodnie z regułami clean code’u, piszę bardzo mało dokumentacji do kodu. Jeszcze mniej sensu miałoby pisanie jej w testach, gdzie, IMO, dostateczną dokumentację stanowią nazwy metod testowych pisane językiem naturalnym.
  • SpaceAroundMapEntryColon – wydawało mi się nienaturalne, by konstruktor mapy zapisywać jako [ : ], a nie [:]. CodeNarc jest pierwszym miejscem, gdzie spotkałem się z takim zapisem.
  • MethodSize – domyślnie metoda może mieć sto linii. W przypadku metod testowych nie ma sensu robić wygibasów, by je podzielić, zwłaszcza że są już podzielone przez bloki given, when: i then:.
  • AUnnecessaryObjectReferences – reguła zabrania odwoływania się do jednego obiektu więcej, niż domyślne 5 razy. Często natomiast w testach mam sytuację, gdy robię assercje nawet na kilkudziesięciu polach jednego obiektu. Jako alternatywę CodeNarc podaje zapis with. Ten zapis nie jest niestety wspierany przez moje IDE, więc automatyczny refaktor go nie obejmie, i nie można korzystać w bloku with z podpowiadania składni.

Przekonfigurowane reguły

Następujących reguł nie wyłączyłem, ale je przekonfigurowałem:

  • LineLength – dopuszczalna długość linii została ustawiona na 150 znaków, podobnie jak w Checkstyle’u.
  • BuilderMethodWithSideEffects – CodeNarc traktował wszystkie metody, które zaczynały się od create jako metody wytwórcze, które nie mogą mieć efektów ubocznych. Ja natomiast mam metody testowe, których opis w języku naturalnym zaczyna się od create, więc zmieniłem regułę tak, aby były brane pod uwagę tylko te metody, które zaczynają się od create, po którym następuje wielka litera.
  • PackageName – musiałem dopuścić camel case w nazwach paczek.
  • Najczęściej ignorowane reguły

    Oto lista reguł, które najczęściej ignoruję w pojedynczych metodach lub klasach:

    • LineLength – w niektórych miejscach lepiej nie łamać linii, gdy są za długie, bo czytelność jest gorsza. Używam też tego wykluczenia w przypadku testów z datatables.
    • RuntimeException – w niektórych testach asercje wykonuję w pętlach, więc rzucam z nich wyjątki. Nie widziałem potrzeby tworzenia nowego typu wyjątku tylko na ten przypadek, więc dozwoliłem rzucanie wyjątków typu RuntimeException.
    • BracesForMethod – czasami metoda ma długi opis, i wtedy jest zapisywana z trzema cudzysłowami, a nie z jednym. W tym przypadku trzeba wyłączyć regułę BraceForMethod.

    Czego można się dowiedzieć

    Podczas implementowania CodeNarcu dowiedzieć się można kilku ciekawy rzeczy. Ja dowiedziałem się następujących:

    • Ostatnie domknięcie w ciągu metod może być zapisane bez nawiasów okrągłych. Czyli .do { it.stuff() } zamiast .do({ it.stuff() }).
    • Nie powinno się zapisywać metod voidowych jako def something(), ponieważ def oznacza tyle, co Object, a metody voidowe, takie jak metody testowe, niczego nie zwracają.
    • Nie ma sensu zapisywać stringów w podwójnych cudzysłowach, jeśli nie robimy w nich interpolacji. Także z powodów wydajnościowych.

    Podsumowanie

    Zastanawiam się, ile osób używa CodeNarcu w stosunku do produkcyjnego kodu w Groovim. Na GitHubie ma on zaledwie 120 gwiazdek, w porównaniu do 1985 gwiazdek, które ma Checkstyle.

    CodeNarc na pewno poprawia jakość kodu, który piszemy. Pewnie miałbym z niego więcej użytku, gdybym w Groovim nie pisał tylko testów. Mimo wszystko pomaga, choćby w usuwaniu nieużywanych zmiennych i formatowaniu kodu.