Java coding standard voor P3

Hieronder wat algemene tips en conventies voor Java code. Houd je aan deze conventies bij je ingeleverde werk!

Er zijn goede uitgebreidere coding standards te vinden op het web, en als je serieus Java programma's gaat schrijven, is het zeker de moeite waard deze te bestuderen. Hieronder staan de belangrijkste tips voor het soort werk dat we bij P3 doen. Veel hiervan is, mutatis mutandi, ook relevant voor andere programmeertalen.

Layout

Gebruik een consistente layout, met regelmatige indentering en positionering van haakjes.

Vermijd idioot lange regels die niet goed te lezen zijn op een scherm of op printout's. Geef in een klasse eerst de static velden, dan de instance velden, dan de constructoren, en dan de methoden.

Naamgeving

Hou je bij het kiezen van namen voor klasses, methoden, en variabelen aan de volgende conventies mbt het gebruik van hoofdletters en underscores:
Klassen en interfaces
Met hoofdletter: Klasse en LangeNaamVoorKlasse

Exception Klassen
Eindigend op ...Exception

Methodes
Zonder hoofletter: methode(...) en methodeMetLangeNaam(...)

Variabelen
Zonder hoofletter: variabele
Lange namen als variableMetLangeNaam óf variabele_met_lange_naam. Kies één van deze twee conventies en hou je daaraan.

Constanten, dwz final of final static velden
Helemaal in hoofletters: CONSTANTE en CONSTANTE_MET_LANGE_NAAM.

Methodes die de waarde van een veld field van type X teruggeven
Beginnend met get..., bijv.   getField()   of   getX()

(Void) methodes die waarde van een veld field van type X veranderen
Beginnend met set..., bijv.   setField(...)   of   setX(...)
Waarom? Code is beter leesbaar als iedereen zich aan dezelfde conventies houdt. Verder scheelt het bij het schrijven dat je minder vergissingen maakt met ontbrekende hoofdletters en underscores. Alle goedopgevoede Java programmeurs houden zich in elk geval aan de conventies hierboven.

Aanbevelingen

Maak instance velden nooit als public.

Waarom? De standaard OO- (encapsulatie-, modulariteits-,..) redenen: iedereen kan aan deze velden komen, zonder enige controle, en je kunt je representatie later niet meer veranderen.

Gebruik zo min mogelijk static velden, muv. final static constants (zie volgende punt).

Waarom? Static velden zijn als globale variabelen in niet-OO imperatieve talen.

Gebruik constanten!
Als je een constante waarde of object in je code tegenkomt, maak er dan een constante van, bijv.
   final static Color BLACK = new Color(0,0,);
   final static int BREEDTE = 23;

Waarom? Maakt code beter leesbaar en beter onderhoudbaar.

Gebruik zo min mogelijk * bij import. Geef expliciet aan welke klassen je importeert, en ga na dat alle geïmporteerde klassen ook daadwerkelijk gebruikt worden.

Waarom? Maakt de code makkelijker te begrijpen voor lezers, nb. óók voor jezelf als je na een paar uur weer naar je eigen code kijkt!
Bijvoorbeeld
   import java.awt.Canvas;
   import java.awt.Frame;
zegt meer dan
   import java.awt.*;

Vermijd overbodig commentaar. Zorg dat commentaar in de code iets toevoegt aan de code, en niet de code herhaalt in het Engels of Nederlands.
Dus niet:
   /** .... Deze klasse is een extensie van de klasse Frame ...
    **/
   public class MijnFrame extends Frame{
    ...
    /** ... Deze methode geeft een String terug ...
     **/
     public String getLabel(){ ...

    /** The main method of the program. This is method is called
        when the program starts.
     **/
     public void main(){ 
         ....
         // gooi een exception als x null is.
         if (x==null) throw new MijnException(); 
   }
   
Geef wel kort commentaar over dingen die niet direct duidelijk zijn uit de code.

Waarom? Zulk overbodig commentaar is langer en minder exact dan de code zelf, en op den duur vaak niet meer up-to-date. En het is natuurlijk een verspilling van tijd en energie om het te produceren of te lezen.

Vermijd overbodige get- en set-methoden. Maak alleen een get- en/of set-methode voor een veld als dat nodig is voor de functionaliteit van de klasse.

Waarom? Overbodige get- en set-methoden maken de `interface' van een object onnodig ingewikkeld voor gebruiker, kunnen soms gewenste relaties tussen velden (invarianten) verstoren, en zijn vaak moeilijk te documenteren zonder naar interne representatiedetails te verwijzen. Tenslotte is het alleen maar extra werk.

Gebruik altijd de methode equals ipv == om objecten te vergelijken, behalve als je test of een referentie null is.

Waarom? Als een klasse de methode equals implementeert (of eigenlijk, overschrijft), dan is dat met de bedoeling dat deze gebruikt wordt, omdat dit kennelijk de goede notie van gelijkheid is. En als een klasse de methode equals niet implementeert, krijg je gewoon de default implementatie van equals, dwz. ==.

Denk voor elke klasse die je schrijft na of je de methode equals() niet wil overschrijven.

Waarom? De default-implementatie van equals() is ==, dwz pointer-gelijkheid, en dat is meestal niet wat je wil.

Denk voor elke klasse die je schrijft na of je de methode clone() niet wil overschrijven. Als clone() nuttig zou zijn voor de klasse, implementeer (of eigenlijk, overschrijf) deze methode dan (en voeg implements Cloneable toe).

Waarom? De default-implementatie van clone() geeft een shallow copy en dat is meestal niet wat je wil.

Zet een casts altijd in een if-statement vd vorm
        if (x instanceof C) cx = (C)x;
        else "evasive action"
Waarom? Dit dwingt je na te denken over wat er moet gebeuren als de refenrence niet van de gegeven class is.

Zorg dat de main methode voor je applicatie in een eigen klasse apart van alle andere klassen.
Stop zoveel mogelijk functionaliteit in de andere klassen ipv in main.

Waarom? Alle code die in main staat is niet her te gebruiken. Te lange main methodes zijn symptomatisch van Java code die niet object-geörienteerd is.

Gebruik liever een interface dan een abstracte klasse. Gebruik nooit een abstracte klasse als het ook een interface kan zijn, omdat er helemaal geen implementatie inzit.

Waarom? Met een interface kun je meer kanten op; ihb, multiple inheritance wordt mogelijk. Bovendien is het duidelijker voor de gebruiker.

Tip mbt testen

Wen jezelf aan om standaard in elke klasse een methode main te definiëren die de klasse uitprobeert. Bijvoorbeeld
    public class TreeNode{
      ...
      public TreeNode(String string){ ... }
      public String   getLabel() { ... }
      public String[] getParams(){ ... }

      public static void main (String[] args){
          System.out.println("Testing de klasse TreeNode");
          TreeNode tn = new TreeNode("bla(arg1, arg2)");
          System.out.println(t.getLabel();
          for (int i=0; i<tn.getParams().length;i++)
                 System.println(tn.getParams()[i]};
      }
    }
    
Tijdens het ontwikkelen van code schrijf je vaak even hulpprogrammaatjes om iets te testen, die je later dan weer weggooit of uitcommentarieerd; op deze manier blijven zulke testprogramma's bestaan om later weer te gebruiken. Natuurlijk is het niet altijd mogelijk of zinnig dit voor alle klassen te doen.

Behalve als test, is dit handig als voorbeeld van hoe je de klasse gebruikt.

(In het voorbeeld hierboven zouden we natuurlijk ook het geval van een parameterloze TreeNode even moeten uitproberen.)

Er zijn manieren om het testen serieuzer en georganiseerder aan te pakken, bijv door het schrijven van unit tests mbv. junit.

Documentatie

Geef documentatie van elke klasse, methode, en veld in javadoc stijl:
Klassen en interfaces
Beschrijf voor elke klasse (of interface) wat het doel van de klasse is, of wat een object van deze klasse voorstelt. Bijvoorbeeld
    /**
     * De interface van een probleem dat de General Problem Solver (GPS) 
     * op kan proberen te lossen.
     * Een object van type ProbleemToestand is een toestand in de 
     * toestandsruimte waarin de GPS zoekt.
     *
     * @author 	Piet Venema 02123456
     * @see java.awt.Frame
     **/
  public interface ProbleemToestand implements Comparable {
	 ...
De eventuele tags, zoals @author, komen na de beschrijving. Zet de namen van de makers bovenaan alle files met @author in de javadoc documentatie, dat is voor ons handig bij het nakijken.

Methoden
Beschrijf voor elke methode wat-ie doet, en wat de parameters en het resultaat zijn.
    /**
     * Voeg een element toe in het begin van de rij.
     *     
     * @param element het toe te voegen element. 
     * @throws StackFullException als er geen ruimte meer beschikbar is 
     **/
  public void addFirst(Object element);
NB. de eerste zin van de beschrijving moet dus een op zichzelf staande, beknopte, beschrijving zijn van wat de methode doet. Javadoc gebruikt deze zin namelijk los van de complete beschrijving op sommige plaatsen. De eventuele tags, zoals @param, @result, en @throws, komen na de beschrijving.

Bij hele simpele gevallen mag je de @param (cq. @result) wel weglaten. mits je goedgekozen namen voor de parameters (cq. methode) kiest.

Velden
Beschrijf voor velden wat het veld voorstelt, bijv.
    /**
     * Aantal subtrees van deze Labtree 
     **/
  private int count;
tenzij er absoluut geen misverstand kan zijn over wat het veld voorstelt.

Documenteer ook eventuele verbanden tussen velden (zogenaamde invarianten), bijvoorbeeld:

  private boolean gevonden;
  private Vector  kortste_pad;
  //  (gevonden == false)  desda  (kortste_pad == null )

Code
Geef kort commentaar op plekken waar de code niet direct duidelijk is, of om gebruikte algoritmen uit te leggen.
Tot slot:

Een uitgebreidere documententie over javadoc is How to Write Doc Comments for the Javadoc Tool
Voor voorbeelden van javadoc documentatie, bekijk de documentatie van java API classen.

Voor je je code inlevert: