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 blokigiven
,when:
ithen:
.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 zapiswith
. Ten zapis nie jest niestety wspierany przez moje IDE, więc automatyczny refaktor go nie obejmie, i nie można korzystać w blokuwith
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 typuRuntimeException
.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, coObject
, 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.