Inleiding tot codekwaliteitsregels met FindBugs en PMD

1. Overzicht

In dit artikel zullen we enkele van de belangrijke regels belichten die voorkomen in tools voor code-analyse, zoals FindBugs, PMD en CheckStyle.

2. Cyclomatische complexiteit

2.1. Wat is cyclomatische complexiteit?

Codecomplexiteit is belangrijk, maar moeilijk te meten. PMD biedt een solide set regels onder de sectie Regels voor codegrootte, deze regels zijn ontworpen om overtredingen met betrekking tot de grootte van methoden en de complexiteit van de structuur te detecteren.

CheckStyle staat bekend om zijn vermogen om code te analyseren aan de hand van coderingsstandaarden en opmaakregels. Het kan echter ook problemen detecteren in het ontwerp van klassen / methoden door enkele complexiteitsstatistieken te berekenen.

Een van de meest relevante complexiteitsmetingen die in beide tools voorkomen, is de CC (Cyclomatic Complexity).

De CC-waarde kan worden berekend door het aantal onafhankelijke uitvoeringspaden van een programma te meten.

De volgende methode levert bijvoorbeeld een cyclomatische complexiteit van 3 op:

public void callInsurance (voertuig voertuig) {if (voertuig.isValid ()) {if (voertuig exemplaar van auto) {callCarInsurance (); } anders {delegateInsurance (); }}}

CC houdt rekening met het nesten van voorwaardelijke instructies en meerdelige booleaanse expressies.

Over het algemeen wordt een code met een waarde hoger dan 11 in termen van CC als zeer complex beschouwd, en moeilijk te testen en te onderhouden.

Enkele veelvoorkomende waarden die worden gebruikt door statische analyse-instrumenten worden hieronder weergegeven:

  • 1-4: lage complexiteit - gemakkelijk te testen
  • 5-7: matige complexiteit - aanvaardbaar
  • 8-10: hoge complexiteit - refactoring moet worden overwogen om het testen te vergemakkelijken
  • 11 + zeer hoge complexiteit - zeer moeilijk te testen

Het complexiteitsniveau heeft ook invloed op de testbaarheid van de code, hoe hoger de CC, hoe moeilijker het is om relevante tests uit te voeren. In feite toont de cyclomatische complexiteitswaarde precies het aantal testgevallen dat nodig is om een ​​score van 100% dekking van vestigingen te behalen.

De stroomgrafiek die is gekoppeld aan de callInsurance () methode is:

De mogelijke uitvoeringspaden zijn:

  • 0 => 3
  • 0 => 1 => 3
  • 0 => 2 => 3

Wiskundig gezien kan CC worden berekend met behulp van de volgende eenvoudige formule:

CC = E - N + 2P
  • E: Totaal aantal randen
  • N: Totaal aantal knooppunten
  • P: Totaal aantal exit-punten

2.2. Hoe de cyclomatische complexiteit te verminderen?

Om aanzienlijk minder complexe code te schrijven, kunnen ontwikkelaars verschillende benaderingen gebruiken, afhankelijk van de situatie:

  • Vermijd langdurig schrijven schakelaar uitspraken door gebruik te maken van ontwerppatronen, b.v. de bouwer en strategiepatronen kunnen goede kandidaten zijn om problemen met codegrootte en complexiteit aan te pakken
  • Schrijf herbruikbare en uitbreidbare methoden door de codestructuur te modulariseren en het Eén verantwoordelijkheidsbeginsel
  • Het volgen van andere PMD-codegrootteregels kan een directe impact hebben op CC, b.v. regel voor buitensporige lengte van methoden, te veel velden in een enkele klasse, buitensporige lijst met parameters in een enkele methode… enz

U kunt ook overwegen om principes en patronen te volgen met betrekking tot de grootte en complexiteit van de code, bijv. de KISS (Keep It Simple and Stupid) -principe, en DROOG (herhaal jezelf niet).

3. Regels voor het afhandelen van uitzonderingen

Defecten die verband houden met uitzonderingen zijn misschien gebruikelijk, maar sommige worden enorm onderschat en moeten worden gecorrigeerd om kritieke disfunctioneren in de productiecode te voorkomen.

PMD en FindBugs bieden beide een handvol regels met betrekking tot uitzonderingen. Hier is onze selectie van wat in een Java-programma als kritiek kan worden beschouwd bij het omgaan met uitzonderingen.

3.1. Gooi de uitzondering niet eindelijk in

Zoals u wellicht al weet, is de Tenslotte{} block in Java wordt over het algemeen gebruikt voor het sluiten van bestanden en het vrijgeven van bronnen, het gebruik voor andere doeleinden kan worden beschouwd als een code geur.

Een typische foutgevoelige routine gooit een uitzondering binnen het Tenslotte{} blok:

String content = null; probeer {String lowerCaseString = content.toLowerCase (); } eindelijk {gooi nieuwe IOException (); }

Deze methode zou een NullPointerException, maar verrassend genoeg gooit het een IOException, wat de aanroepende methode kan misleiden om de verkeerde uitzondering af te handelen.

3.2. Terugkerend in de Tenslotte Blok

Met behulp van de retourverklaring in een Tenslotte{} block is misschien alleen maar verwarrend. De reden waarom deze regel zo belangrijk is, is dat wanneer een code een uitzondering genereert, deze wordt verwijderd door de terugkeer uitspraak.

De volgende code wordt bijvoorbeeld zonder fouten uitgevoerd:

String content = null; probeer {String lowerCaseString = content.toLowerCase (); } eindelijk {terugkeer; }

EEN NullPointerException werd nog niet betrapt, nog steeds weggegooid door de retourverklaring in de Tenslotte blok.

3.3. Kan stream niet sluiten bij uitzondering

Het sluiten van streams is een van de belangrijkste redenen waarom we een Tenslotte blok, maar het is geen triviale taak zoals het lijkt te zijn.

De volgende code probeert twee streams in een Tenslotte blok:

OutputStream outStream = null; OutputStream outStream2 = null; probeer {outStream = nieuwe FileOutputStream ("test1.txt"); outStream2 = nieuwe FileOutputStream ("test2.txt"); outStream.write (bytes); outStream2.write (bytes); } catch (IOException e) {e.printStackTrace (); } eindelijk {probeer {outStream.close (); outStream2.close (); } catch (IOException e) {// Omgaan met IOException}}

Als het outStream.close () instructie gooit een IOException, de outStream2.close () wordt overgeslagen.

Een snelle oplossing zou zijn om een ​​apart try / catch-blok te gebruiken om de tweede stream te sluiten:

eindelijk {probeer {outStream.close (); } catch (IOException e) {// Omgaan met IOException} probeer {outStream2.close (); } catch (IOException e) {// IOException verwerken}}

Als je een leuke manier wilt om opeenvolgend te vermijden proberen te vangen blokken, controleer dan de IOUtils.closeQuiety-methode van Apache commons, het maakt het eenvoudig om streams af te handelen zonder een IOException.

5. Slechte praktijken

5.1. Klasse Definieert compareto () en gebruikt Object.equals ()

Telkens wanneer u het vergelijk met() methode, vergeet niet hetzelfde te doen met de is gelijk aan () methode, anders kunnen de resultaten die door deze code worden geretourneerd, verwarrend zijn:

Auto auto = nieuwe auto (); Car car2 = nieuwe auto (); if (car.equals (car2)) {logger.info ("Ze zijn gelijk"); } else {logger.info ("Ze zijn niet gelijk"); } if (car.compareTo (car2) == 0) {logger.info ("Ze zijn gelijk"); } else {logger.info ("Ze zijn niet gelijk"); }

Resultaat:

Ze zijn niet gelijk. Ze zijn gelijk

Om verwarring te voorkomen, is het raadzaam om ervoor te zorgen Object.equals () wordt nooit aangeroepen bij het implementeren Vergelijkbaar, in plaats daarvan zou je het moeten proberen te overschrijven met zoiets als dit:

boolean is gelijk aan (Object o) {return CompareTo (o) == 0; }

5.2. Mogelijke foutloze verwijzing

NullPointerException (NPE) wordt als de meest aangetroffen beschouwd Uitzondering in Java-programmering, en FindBugs klaagt over Null PointeD-dereferentie om te voorkomen dat deze wordt weggegooid.

Hier is het eenvoudigste voorbeeld van het gooien van een NPE:

Auto auto = null; car.doSomething ();

De eenvoudigste manier om NPE's te vermijden, is door een nulcontrole uit te voeren:

Auto auto = null; if (auto! = null) {car.doSomething (); }

Null-controles kunnen NPE's vermijden, maar wanneer ze uitgebreid worden gebruikt, hebben ze zeker invloed op de leesbaarheid van de code.

Dus hier zijn enkele technieken die worden gebruikt om NPE's te vermijden zonder nulcontroles:

  • Vermijd het trefwoord nul tijdens het coderen: Deze regel is eenvoudig, vermijd het gebruik van het trefwoord nul bij het initialiseren van variabelen of het retourneren van waarden
  • Gebruik @Niet nul en @Nullable annotaties
  • Gebruik java.util.Optioneel
  • Implementeer het null-objectpatroon

6. Conclusie

In dit artikel hebben we een algemeen overzicht gegeven van enkele van de kritieke defecten die zijn gedetecteerd door statische analysetools, met basisrichtlijnen om de gedetecteerde problemen op de juiste manier aan te pakken.

U kunt door de volledige set regels voor elk ervan bladeren door de volgende links te bezoeken: FindBugs, PMD.