Felhasználói eszközök

Eszközök a webhelyen


oktatas:programozas:tiszta_kod

< Programozás

Tiszta kód

A tiszta kód

Robert C. Martin a Tiszta kód című könyvével forradalmi módszer mutatott be, hogyan álljunk a szoftverfejlesztéshez.

A programozók sokkal több kódot olvasnak mint írnak, ezért fontos, hogy kód ne legyen zavaros, mint egy felkavart mocsár. A haladó programozók tudják, hogy a zavaros kódok, amiket vissza kell olvasni, nagyban lassítják a munkát. Mégis, sokszor úgy érzik, nekik is ilyet kell írniuk, csakhogy haladjon a munka. Az ilyen kód lehúz mint a mocsár, és nem fogjuk tudni tartani a határidőt.

A kódot ezért úgy kell megírni, hogy mindig olvasmányos legyen, kevés ránézéssel tudjuk mit csinál, meglepetések nélkül.

A kód tisztán írása és tartása művészet.

Elnevezések

Beszédes nevek

A változók, állandók, osztályok és függvények létrehozása során a rossz névadással, a kódmocsár irányába lökjük a kódunkat.

A jó elnevezéshez három dolgot vizsgáljunk meg:

  • mi az oka létezésének
  • mit csinál, mi a szerepe
  • hogyan használjuk

Ha úgy érezzük, megjegyzést kell fűzni egy változóhoz vagy állandóhoz, biztosan rossz az elnevezése.

int a; //A háromszög alapja
public class Em {
    String n; //name
    double sa; //salary
}

Félrevezetés

Kerüljük a félrevezető elnevezéseket. A közismert nevek például félrevezetők.

A bin, például Unix alapú rendszerekben a bináris szóra utal, illetve kettes számrendszerben megadott számokra is utalhat. Ha mi a belső indexet így szeretnénk rövidíteni, az félrevezető.

Példák az ismert azonosítókra, közismert és egyéni értelemmel:

  • bin - binary
    • belső index
  • pc - personal computer
    • pozitív cél
  • tv
    • tiltott viselkedés
    • tevékenység

A List szó félrevezető, ha olyan gyűjtemény nevében használjuk, ami nem List szerkezetben van tárolva. Például:

dolgozoList
class Employee {
    public String[] getEmployeeList() {
        String[] array = {"Pali", "Dani"};
        return array;
    }
}

Ilyenkor válasszuk például a következőket:

dolgozoCsoport
dolgozok
employeeGroup
employees
class Employee {
    public String[] getEmployees() {
        String[] array = {"Pali", "Dani"};
        return array;
    }
}

Az O használata is megtévesztő lehet:

if( 0 == 35)

A nevek kimondhatósága

Válasszunk olyan neveket, amit könnyű kimondani. Ha egy névben nincsenek magánhangzók, nem könnyű azokat kimondani.

A dkn, a dolgozó keresztnevének rövidítésre például nem ajánlott:

dkn - dolgozó keresztnevek

Legyen helyette:

dolgozoKeresztnev
Product.java
class Product {
    String nm;
    double prc;
    public Product(String nm, double prc) {
        this.nm = nm;
        this.prc = prc;
    }
}

A nevek kereshetősége

Az egybetűs azonosítók keresése rémálom. Kerüljük a használatát.

Egy m nevű állandóban tároljuk a maximális értéket, ami megadható árnak. Ha betöltjük a programot egy kódszerkesztőbe, keressünk rá Ctrl+F billentyűkombinációval. Mi az első találat? És a következők?

App.java
import java.util.Scanner;
 
class Product {
    String name;
    double price;
    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
}
 
class Register {
    public Product inputProduct() {
        Scanner sc = new Scanner(System.in);
        System.out.print("Név: ");
        String name = sc.nextLine();
        if (!isGoodName(name)) {
            System.exit(1);
        }
        System.out.print("Ár: ");
        String priceStr = sc.nextLine();
        double price = Double.parseDouble(priceStr);
        sc.close();
        Product product = new Product(name, price);
        return product;
    }
    public boolean isGoodName(String name) {
        boolean good = true;
        if (name.length()<1) good = false;
        if (!name.matches("[a-zA-záéíóöőúüűÁÉÍÓÖŐÚÜŰ]+"))
            good = false;
        return good;
    }
    public boolean isGoodPrice(double price) {
        final int m = 3000000;
        boolean good = true;
        if (price < 1) good = false;
        if (price > m) good = false;
        return good;
    }
 
}
 
public class App {
    public static void main(String[] args) throws Exception {
        Register register = new Register();
        Product product = register.inputProduct();
        System.out.println(product.name);
        System.out.println(product.price);
    }
}

Az az állomány nem olyan nagy. Nagyobb fájlban a keresés jóval nehezebb.

Típuskódolás

A névben a típus kódolása kerülendő. Régebben indokoltak voltak ezek az eljárások, mint a „magyar jelölés”, vagy az tagváltozó jelölése. A tagváltozót egy m_ karakter előtag párral jelölhetjük.

Employee.java
public class Employee {
    //magyar-jelölés:
    String sName;
    String sCity;
    double dSalary;
}
Employee.java
public class Employee {
    //tagváltozó-jelölése:
    String m_Name;
    String m_City;
    double m_Salary;
}

Helyette:

Employee.java
public class Employee {
    String name;
    String city;
    double salary;
}

A felületek (interfész) nevébe sem ajánlott beletenni az „i” vagy „I” karaktereket. Helyette tegyük a hozzátartozó megvalósításba mondjuk Imp szót, vagy magyarul Megval, vagy hasonló szót.

DolgozoGyarMegvalositas
DolgozoGyarMeg
interface iDolgozo {
    public void munkavegzes();
}
class Dolgozo implements iDolgozo {
    public void munkavezges() {
 
    }
}

Helyette:

interface Dolgozo {
    public void munkavegzes();
}
class DolgozoMegvalositas implements Dolgozo {
    public void munkavezges() {
 
    }
}

Osztálynevek

Az osztálynevek legyenek mindig főnevek vagy főnévi kifejezések.

Robert Martin szerint kerülendők még a következő szavak az osztályok neveiben: Manager, Processor, Data, Info és hasonlók.

Helytelen:

class Calc { //helytelen mert ige
 
}

Legyen főnév:

class Triangle {
 
}

Tagfüggvények

A tagfüggvények nevei mindig igék, vagy igei kifejezések.

Az elérő függvények előtagjai legyenek:

  • set
  • get
  • is
class Dolgozo() {
    String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
    public boolean isEmptyName() {
        boolean empty = true;
        if (this.name.length() > 0) {
            empty = false;
        }
        return empty;
    }
}

Ha magyar tagfüggvény neveket használunk, akkor nem valami szép az ilyen név:

getNev()

Ez hunglish nyelven van írva, azaz félig magyar, félig angol. Ha mégis mellette döntünk, akkor használjuk következetesen. Jobb ötlet megkeresni a szavak angol megfelelőjét:

getName()

Esetleg a „get”, „set” és az „is” helyett magyar változatok keresése:

lekerNev()
beallitNev()

Az „is” helyett nehéz a magyar nyelvben megfelelő szót találni.

A konstruktorok túlterhelése helyett, használjunk gyártó metódust.

Dolgozo dolgozo = Dolgozo.letrehozNevvel("Nagy János");
class Dolgozo {
    String nev;
 
    //gyártómetódus:
    public static Dolgozo letrehozNevvel(String nev) {
        Dolgozo dolgozo = new Dolgozo();
        dolgozo.nev = nev;
        return dolgozo;
    }
}
 
class Program01 {
    public static void main(String[] args) {
        Dolgozo bela = Dolgozo.letrehozNevvel("Para Béla");
        System.out.println(bela.nev);
    }
 
}

Jófejkedés

Kerüljük a jófejkedést, például szülőfüggvényecske.

class Dolgozo {
    String nevecske; //kerülendő
    String alvohej; //kerülendő
    double amitkap; //kerülendő
}

Helyette:

class Dolgozo {
    String name;
    String city;
    double salary;
}

Egy fogalom, ugyanaz a szó

Ha egy fogalomra használunk egy szót, akkor legyünk következetesek és mindig ezt használjuk.

fetchName()
fetchCity()
fetchSalary()

Vagy:

getName()
getCity()
getSalary()
Employee.java
public class Employee {
    String name;
    String city;
    double salary;
    public String getName() {
        return name;
    }
    public String fetchCity() { //Helytelen
        return city;
    }
    public double takeSalary() { //Helytelen
        return salary;
    }    
}

Nem a fetch vagy a take a probléma. A probléma, hogy mind más.

Helyesen:

Employee.java
public class Employee {
    String name;
    String city;
    double salary;
    public String getName() {
        return name;
    }
    public String getCity() {
        return city;
    }
    public double getSalary() {
        return salary;
    }    
}

Szóviccek

A szóvicceket is kerüljük, de mit értünk szóvicc alatt? Két eltérő fogalmat ugyanazzal a szóval jelölünk.

class Employee {
    String name;
    public void addName(String name) {
        this.name = name;
    }
}
class Cat {
    String name;
    pubic void addName(String name) {
        this.name = name;
    }
}
class Doctor {
    String name;
    public void addName(String name) {
        this.name = "Dr " + name;
    }
}

A Doctor addName() metódusa valami mást is csinál. Ezért legyen a neve valami más, mondjuk addExtName().

Megoldás és feladat tartományok

Ha van megoldástartományból a szakmában használatos név, használjuk azt. Ha nincs maradhat a feladattartomány.

class Vizsgalat {
    String betegEloelet;
}

Helyette:

class Vizsgalat {
    String anamnezis;
}

Angolul:

class Examination {
    String patientHistory;
}

Helyette:

class Examination {
    String anamnesis;
}

Gyakorlat 01

Feladat 01

Adottak a következő elnevezési hibák:

  • Nem beszédes nevek
  • Félrevezetés
  • Nem kimondható
  • Nehezen kereshető
  • Típuskódolás
  • Osztálynév nem főnév
  • Tagfüggvény nem get, set
  • Jófejkedés
  • Egy fogalom több szó
  • Szóvicc (két külön fogalom, ugyanazzal a szóval)
  • Nem használja a megoldás tartományt (van ismert név)

A következő programban keressem meg, milyen hibákat talál.

Az osztályban tároljuk egy dolgozó nevét, települését, címét és fizetését.

Working.java
public class Working {
    String n; //Név
    String sCity;
    String adr;
    double littleGirl; //Fizetés
    public String getName() {
        return n;
    }
    public String fetchCity() {
        return city;
    }
    public double takeSalary() {
        return littleGirl; 
    }    
}

Feladat 02

Vajon mit csinál a következő függvény?

trinangleCalc();

Többet mond, ha paraméterrel kell hívni?

triangleCalc(30, 35, 45);

Írja meg a függvény megvalósítását helyesen.

Feladat 03

Adott egy metódus, amely beállítja egy jármű sebességét, véletlenszerűen. Vagy gyorsít vagy lassít.

public Car extends JComponent {
    int d;
    Integer e;
    Car() {
 
    }   
    public void doit(){
        Random a = new Random();
        boolean b = a.nextBoolean();
        int c = a.nextInt(3) + 1;       
 
        if(b) {
            if(d>(this.e + c)) {
                this.e = this.e + c;
            }
        }else {
            if((this.e - c) >0){
                this.e = this.e - c;
            }
        }       
    }
 
}

Tisztítsa meg a kódot.

Feladat 04

Adott a következő kód:

App.java
class App {
    public static void main(String[] args) {
        String[] plateList = {
            "AA-AB-342", 
            "AA-CA-183",
            "AA-AD-834",
            "AA-AA-814"
            };
    }
}

Keresse meg, miért nem felel meg a tisztakód elveinek? Javítsa.

Feladat 05

A következő függvény a háromszög kerületét számítja ki.

public void triangleCalc(double a, double b, double c) {
	System.out.println(a+b+c);
}
  • Írja át, hogy megfeleljen a tisztakód elveinek.
  • Írja meg a teljes programot.

Függvények

Méret

A függvények mérete legyen minél kisebb. Ha 3-4 sor, akkor az már jó. Ennek eredménye lehet, hogy egy if vagy while törzsében csak egy függvényhívás van. Ez jó. Ha ilyen rövid függvényeink vannak, akkor valószínűleg elkerültük az egymásba ágyazott kifejezéseket is.

Helytelenül nagy függvény:

Program01.java
import java.util.Scanner;
 
public class Program01 {
    public static void main(String[] args) throws Exception {
        String name = inputName();
        System.out.println(name);
 
    }
    public static String inputName() {
        Scanner sc = new Scanner(System.in);
        System.out.print("Név: ");
        String name = sc.nextLine();
 
        boolean goodName = true;
        if (name.length()<1) goodName = false;
        if (!name.matches("[a-zA-záéíóöőúüűÁÉÍÓÖŐÚÜŰ]+"))
            goodName = false;
 
        if (!goodName) {
            System.exit(1);
        }
        return name;
    }
}

Jó méretek:

Program01.java
import java.util.Scanner;
 
public class Program01 {
    public static void main(String[] args) throws Exception {
        String name = inputName();
        checkExitRequired(name);        
        System.out.println(name);        
    }
    public static void checkExitRequired(String name) {
        if (!isGoodSize(name) || !isGoodString(name)) {
            System.err.println("Hiba! Rossz név!");
            System.exit(1);
        }
    }
    public static String inputName() {
        Scanner sc = new Scanner(System.in);
        System.out.print("Név: ");
        String name = sc.nextLine();   
        return name;     
    }
    public static boolean isGoodSize(String name) {
        boolean goodName = true;
        if (name.length()<1) goodName = false;
        return goodName;
    }
    public static boolean isGoodString(String name) {
        boolean goodName = true;
        if (!name.matches("[a-zA-záéíóöőúüűÁÉÍÓÖŐÚÜŰ]+"))
            goodName = false;
        return goodName;
    }
 
}

Egyetlen feladat

Egy függvény csak egyetlen feladatot csináljon, de azt jól csinálja.

public class Employee {
    String name;
    int length;
    public void setName(String name) {
        this.name = name;
        this.length = name.length(); 
    }
}
import java.util.Random;
 
public class Game {
    public int getRandomNumber() {
        Random random = new Random();
        int num = random.nextInt(8);
        System.out.println("Szám: " + num);
        return num;
    }
}

Sorrend

A függvényeket fentről lefele írjuk egymás után. Ezt nevezik leszállószabálynak.

Triangle.java
package haromszog;
 
public class Haromszog {
 
    public static void main(String[] args) {
 
        double perimeter = calcPerimeter(30, 35, 40);
        System.out.println("Perimeter: " + perimeter);
    }
 
    public static double calcPerimeter(double sideA, double sideB, double sideC) {        
        double perimeter = -1;
        if(isEditable(sideA, sideB, sideC)) {
            perimeter = sideA + sideB + sideC;
        }
        return perimeter;
    }
 
    public static boolean isEditable(double sideA, double sideB, double sideC) {
        boolean editable = true;
        if(sideA + sideB < sideC) {
            editable = false;
        }
        if(sideA + sideC < sideB) {
            editable = false;
        }
        if(sideB + sideC < sideA) {
            editable = false;
        }
        return editable;
    }
}

Beszédes nevek

Keressük meg a legbeszédesebb neveket. Ha bizonytalanok vagyunk, próbáljunk meg több nevet is. Használjunk bátran hosszú neveket. A nevek megválasztásánál legyünk itt is következetesek.

Rossz példák:

public void c() {
    //...
}
public void cs() {
    //...
}
public void csinal() {
    //...
}

Paraméterek

A legideálisabb ha egy függvénynek egyetlen paramétere sincs.

Előfordul, hogy van egy paraméterünk, amivel kapcsolatban valamilyen kérdésre szeretnénk választ adni, vagy azon egy műveletet szeretnénk elvégezni. A függvény nevében következetesen szét kell választani ezt két tevékenységet.

Vannak a jelzőparaméterek, amelyek használatát kerülni kell. A jelző paraméter, amikor például átadok egy logikai paramétert, amely megmondja, hogy amin művelete kell végezni az milyen állapotban van. Az ilyen függvények helyett írjunk két függvényt, amely mindkét állapotra megoldást nyújt.

Tegyük fel, hogy van egy függvényünk, ami képes PostgreSQL és MariaDB adatbázishoz kapcsolódni. Hogy melyikhez kapcsolódik egy állandóval jelezzük.

DatabaseCon.java
class DatabaseConn {
    public Connection connect(DatabaseType.PostgreSQL) {
       ...
    }
}

Helyette:

DatabaseCon.java
class DatabaseConn {
    public Connection connectPostgresql() {
       ...
    }
    public Connection connectMariadb() {
       ...
    }
 
}

Az ilyen szerkezetek kerülendők. Írjunk helyette két külön függvényt. Az egyik PostgreSQL adatbázis kapcsolatához, a másik a MariaDB-hez.

Ha egy függvénynek 2-3 paraméternél többre van szüksége, csomagoljuk egy osztályba.

Javított verzió

A „Sorrend” fejezetben leírt példa, javított verziója:

Sides.java
class Sides {
    double a, b, c;
    public Sides(double a, double b, double c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
}
Triangle.java
public class Triangle {
 
    public static void main(String[] args) {
        Sides sides = new Sides(30, 35, 40);
        double perimeter = calcPerimeter(sides);
        System.out.println("Perimeter: " + perimeter);
    }
 
    public static double calcPerimeter(Sides sides) {        
        double perimeter = -1;
        if(isEditable(sides)) {
            perimeter = sides.a + sides.b + sides.c;
        }
        return perimeter;
    }
 
    public static boolean isEditable(Sides sides) {
        boolean editable = true;
        if(sides.a + sides.b < sides.c) {
            editable = false;
        }
        if(sides.a + sides.c < sides.b) {
            editable = false;
        }
        if(sides.b + sides.c < sides.a) {
            editable = false;
        }
        return editable;
    }
}

Mellékhatások

Ha egy művelet hatással van olyan változókra, objektumokra amely kívül esik tervezett célon, akkor azt mellékhatásnak nevezzük.

Adott egy függvény, ami MariaDB adatbázishoz kapcsolódik.

A következő cennect() függvénynek az a célja, hogy kapcsolódjon egy adatbázishoz. Azonban van egy mellék hatása is, megváltoztatja az objektum állapotát.

DatabaseCon.java
class Mariadb {
    boolean connected = false;
    public Connection connect() {
       ...
       connected = true;
    }
}

Matematikai értelemben 1), ha egy függvénynek nincs visszatérési értéke, biztosan van valami mellékhatása. Például megváltoztatja az objektum állapotát.

Nekünk most, fontosabb mi a függvény létrehozásának célja. Ha megváltoztatja egy objektum állapotát, az lehet csak hatás, ha ez volt a függvény létrehozásának célja. Ha visszaad valamit a függvény, akkor általában nem az a célja, hogy megváltoztasson valahol egy objektumot.

Kimeneti paraméterek

Ha egy függvénynek olyan paramétert adok meg, amit szeretnék megváltoztatni, akkor kimeneti paraméterről beszélünk. De a paramétereket jobban szeretjük bemeneti paraméterként használni.

A következő példában, a dolgozó nevéhez szeretnék fűzni egy településnevet, és a településhez is szeretnénk nevet fűzni.

Program01.java
class Program01 {
    static StringBuffer dolgozo;
    static StringBuffer city;
    public static void main(String[] args) {
        dolgozo = new StringBuffer("Pala Béla:");
        city = new StringBuffer("Szeged:");
 
        /* A hívás helyén nem tudom eldönteni, melyik fog történni.
         * A dolgozóhoz fűzök települést, vagy a dolgozót fűzöm településhez */
        appendCity1(dolgozo);
        appendCity2(dolgozo);
 
 
        System.out.println(dolgozo);
        System.out.println(city);
    }
    /* A dolgozót kimenetnek használom, a dolgozóhoz fűzök valamit */
    public static void appendCity1(StringBuffer dolgozo) {
        dolgozo.append("Szeged:");
    }
    /* A dolgozót fűzöm valamihez */
    public static void appendCity2(StringBuffer dolgozo) {
        city.append(dolgozo);
    }
}

Nézzük meg a hívás helyét:

        appendCity1(dolgozo);
        appendCity2(dolgozo);

A hívás helyén nem tudjuk eldönteni, hogy melyik fog történni. Meg kell nézzük a függvény hogyan lett megvalósítva.

Az objektum orientált programozás lehetővé teszik hogy kimeneti paraméter használata helyett így hívjunk:

dolgozo.appendCity(city);

Egy dolgozó objektumon hívjuk az appendCity() metódust.

A teljes kód:

Dolgozo.java
class Dolgozo {
    public StringBuffer line;
    public Dolgozo() {
        this.line = new StringBuffer("Pala Béla:");
    }
    public void appendCity(StringBuffer city) {
        this.line.append(city);
    }
}
Program01.java
class Program01 {
    public static void main(String[] args) {
        StringBuffer city = new StringBuffer("Szeged:");
        Dolgozo dolgozo = new Dolgozo();
 
        dolgozo.appendCity(city);
 
        System.out.println(dolgozo.line);
 
    }
}

Ismétlődések

Előfordul, hogy egy algoritmus kétszer, háromszor, négyszer vagy akár többször szerepel a programban. Ha javítani kell, mind a négy helyen meg kell tenni. Nem könnyű ezeket egyetlen algoritmusba összeállítani, mert általában kicsit eltérnek egymástól.

Keressük meg az ismétlődéseket, készítsünk belőlük függvényt.

App.java
public class App {
    public static void main(String[] args) throws Exception {
        Dice dice = new Dice();
        dice.fiveRoolsHuman();
        dice.fiveRoolsMachine();
    }
}
Dice.java
import java.util.Random;
 
public class Dice {
    Random random = new Random();
    private int diceRoll() {
        return random.nextInt(6)+1;
    }
    public void fiveRoolsHuman() {
        Integer[] dices1 = new Integer[5];
        dices1[0] = diceRoll();
        dices1[1] = diceRoll();
        dices1[2] = diceRoll();
        dices1[3] = diceRoll();
        dices1[4] = diceRoll();
        System.out.printf("%10s: ", "Ember");
        for(int dice : dices1) {
            System.out.print(dice);
            System.out.print(" ");
        }
        System.out.println();        
    }
    public void fiveRoolsMachine() {
        Integer[] dices1 = new Integer[5];
        dices1[0] = diceRoll();
        dices1[1] = diceRoll();
        dices1[2] = diceRoll();
        dices1[3] = diceRoll();
        dices1[4] = diceRoll();
        System.out.printf("%10s: ", "Gép");
        for(int dice : dices1) {
            System.out.print(dice);
            System.out.print(" ");
        }
        System.out.println();        
    }
}

Az algoritmus ismétlődik, mivel más dobásra van szükség a két szereplőnek és más nevet kell kiírni.

Javított verzió:

App.java
public class App {
    public static void main(String[] args) throws Exception {
        Dice dice = new Dice();
        dice.startGame();        
    }
}
Dice.java
import java.util.Random;
 
public class Dice {
    Random random = new Random();
 
    public void startGame() {
        Integer[] dices1 = fiveRools();
        Integer[] dices2 = fiveRools();
        printResult("Ember", dices1);
        printResult("Gép", dices2);
    }
 
    private Integer[] fiveRools() {
        Integer[] dices = new Integer[5];
        dices[0] = diceRoll();
        dices[1] = diceRoll();
        dices[2] = diceRoll();
        dices[3] = diceRoll();
        dices[4] = diceRoll();
        return dices;
    }
 
    private int diceRoll() {
        return random.nextInt(6)+1;
    }
 
    private void printResult(String role, Integer[] dices) {
        System.out.printf("%10s: ", role);
        for(int dice : dices) {
            System.out.print(dice);
            System.out.print(" ");
        }
        System.out.println();
    }
}

Strukturált programozás

Edsger Dijkstra strukturált programozás szabályait követve, arra törekszünk, hogy egyetlen függvénynek csak egyetlen kilépési pontja legyen. A gyakorlatban ez azt jelenti, hogy egyetlen return utasítás szerepelhet a kódban. Nem lehet benne break, continue vagy goto utasítás.

Példaként két függvény:

    public static boolean isGoodSize(String name) {
        boolean goodName = true;
        if (name.length()<1) goodName = false;
        return goodName;
    }
    public static boolean isGoodString(String name) {
        boolean goodName = true;
        if (!name.matches("[a-zA-záéíóöőúüűÁÉÍÓÖŐÚÜŰ]+"))
            goodName = false;
        return goodName;
    }

Ilyen kis függvények esetén eltekinthetünk ezektől a szabályoktól. Talán átláthatóbb kódot is kapunk:

    public static boolean isGoodSize(String name) {        
        if (name.length()<1) 
            return false;
        else
            return true;
    }
    public static boolean isGoodString(String name) {        
        if (!name.matches("[a-zA-záéíóöőúüűÁÉÍÓÖŐÚÜŰ]+"))
            return false;
        else
            return true;
    }

Megjegyzések

A legjobb megjegyzés, ami meg sem született.

A megjegyzések akkor kerülnek a kódba, ha nem tudtuk magunkat kifejezni megfelelően a kóddal.

Jó megjegyzés

  • jogi megjegyzések
  • szándék magyarázata - Néha ez is jó lehet
  • tisztázás - pl. homályos paraméterek leírása
  • következményekre figyelmeztetés - pl. futtassuk ha sok időnk van
  • TODO megjegyzés
  • figyelemfelhívás - valami fontos
  • javadoc a nyilvános API-hoz

Jogi megjegyzés:

/*
  Szerző: Nagy János, 2022-11-07
  Copyright (c) 2022, Nagy János
  Licenc: MIT
*/

Tisztázás:

assertTrue(a.comparetTo(b) == 1); // a > b

Rossz megjegyzések

A rossz megjegyzések rendre:

  • rizsázás - csak késztetést érzünk az írására
  • felesleges megjegyzés - a kód magában is érthető
  • félrevezető - bent maradt, de már nem érvényes
  • kötelező - nem kötelező mindenhol, pl. nem kell javadoc mindenhez
  • napló bejegyzések - a forráskód fejlődéséről - ma ott verziókövető
  • helyzetjelzők - /*********************auth*******************/
  • megjegyzés bezáró zárójelnél - hosszú blokknál esetleg indokolt
  • személyes megjegyzések
  • megjegyzésben kódrészlet
  • átalakítás után felesleges
  • HTML megjegyzések
  • elcsúszott bejegyzés
  • túl sok információ
  • homályos kapcsolat a kód és a megjegyzés között
  • kis függvény fejléccel

Rizsázás:

/*
Ez a modul eredetileg benne volt a főprogramban. 
Tegnap úgy tűnt, hogy jó lenne külön tenni. 
*/

Felesleges megjegyzés (zajok):

/* A név */
private String name;

Félrevezető megjegyzés:

/* Az a változó a háromszög a oldala */
double a = input.nextDouble();

Tegyük fel, hogy az „a” váltózónak már megváltozott a szerepe, már nem a háromszög „a” oldala. De megjegyzésben még mindig ez szerepel.

Kötelező:

/**
 *
 * @param base A háromszög alapja
 * @param height A háromszög magassága
 */
public double calcArea(double base, double height) {
    return base*height/2;
}

Naplóbejegyzések a forráskód fejlődéséről.

/*
2001-01-07 : A kód át lett írva C# nyelvből Java nyelvre.
2001-02-22 : Hozzá lett adva a getVehicles() függvény.
2001-05-07 : A hiba megoldva a getVehicles() függvényben.
2002-02-08 : A hiba megoldva a getRounded() függvényben.
2003-07-02 : Megvalósításra került egy szerializáló modul.
*/

Pozíció jelölése:

/*-------------- itt kezdődik az azonosítás -----------*/
public void authUser(String user, String pass) {
  //...
}

Megjegyzés bezáró zárójelnél:

  for(int i = 0; i<10; i++) {
      num += 3;
      num += i;
  }//for ciklus vége

Hosszú függvények, blokkok esetén van létjogosultsága olyan megjegyzéseknek, amik megmutatják, melyik blokkzáró mit zár le. De ajánlott helyette a blokkok, és függvények méretének csökkentése.

Szerzői bejegyzések:

/* János írta */

A verziókövető rendszereke megmondják ki írta a kódot. Nincs szükség ezekre.

Megjegyzésben kódrészlet:

//mode = modes.getMode();

Átalakítással a megjegyzés megszüntethető.

/* A module függ attól részrendszertől amiben benne vagyunk */
if(module.getDependSubsystems().contains(base.getSubsystem()))
ArrayList<Dependence> dependenceList = module.getDependSubsystems();
String dependenceName = base.getSubsystem();
if(dependenceList.contains(dependenceName))

HTML megjegyzés:

/*
<p>
Ez a függvény a Triangle osztály része.
</p>
*/

Elcsúszott bejegyzés (rossz helyen van):

/*
A setPremium() metódussal emelhető a dolgozó jutalma.
*/
public void setSalary(double baseSalary, double increase) {
   //...
}

A megjegyzésnek nem a setSalary() metódus felett van a helye.

Túl sok információ

/*
A AS 345-s szabvány használjuk a bevételezéshez.
Minden bevétel kódolva van base64 kódolással. 
Külön titkosító kódolót használunk AES algoritmussal.
128 bites kulcsot generálunk a megoldáshoz. A 
kódolást 45-ször újrafuttatjuk. Két darab 16 
bájtos sót használunk a szöveg elején és végén.
A sót RIF 145 szabvány szerint készül, és 8 bájtos
só van megadva. Ez egy kicsit át lett alakítva, így
lett 16 bájtos.
*/

Homályos kapcsolat a kód és a megjegyzés között:

/*
Maximum 26 méret adható meg. 
*/
 
Vigenere vigenere = createVigeere(26, 26, Vigenere.ALPHABET);

Kis függvény számára felesleges a fejléc:

/*
A függvény kiszámítja a területet.
*/
public double calcArea(double base, double height) {
    return base * height / 2;
}

Formázás

Függőleges méret

Egy fájl sorait tekintve: maximum 200 sor körül ideális, de maximum 500.

Függőleges elválasztás

Az összefüggő részeket válasszuk el más összefüggő részektől egy üres sorral.

Függőleges sűrűség

public class Employee {
    /**
     * A dolgozó neve
     */
    String name;
    /**
     * A dolgozó települése, ahol lakik
     */
    String city;
    public void setName(String name) {
        this.name = name;
    }
}

Az összetartozó name és city változókat, megtöri a megjegyzés.

Függőleges távolság

  • változók - a használathoz közel vezetjük be
  • példányváltozók - az osztály tetején megfelelnek
  • függvények - a hívó a hívott felett legyen (nem minden programozási nyelvben lehet)

Vízszintes formázás

  • maximum 80 oszlop karakterekből, de ne kelljen jobbra görgetni

Vízszintes térközök

  • értékadás - jobb és bal oldalból áll
    • base = 45;
  • fuggvenynev() - A név után nincs szóköz
  • műveleteknél az összetartozó részeket szóköz jelezheti( a+b - c*d )

Vízszintes igazítás

Ennek lejárt az ideje:

double    area;
int       base;
String    name;

Legyen csak így:

double area;
int base;
String name;

Behúzás

Megmutatják a kód felépítését, hierarchiáját.

Behúzások nélküli kód:

import java.util.ArrayList;
public class EmployeeFactory {public Employee
findEmployee(String name, ArrayList<Employee> 
employeeList) { Employee foundEmployee = new 
Employee(); for (Employee employee: employeeList)
{ if ( employee.name.equals(name)) { foundEmployee
 = employee; } } return foundEmployee; } }

Olvashatóbb változata:

import java.util.ArrayList;
 
public class EmployeeFactory {
    public Employee findEmployee(String name, 
            ArrayList<Employee> employeeList) {
        Employee foundEmployee = new Employee();
        for (Employee employee: employeeList) {
            if ( employee.name.equals(name)) {
                foundEmployee = employee;
            }
        }
        return foundEmployee;
    }
}

Objektumok és adatszerkezetek

Demeter törvénye

Ha van egy „A” objektumunk, az elérheti B objektum szolgáltatásait, de ne vegyük igénybe rajta keresztül „C” objektum szolgáltatásait.

D d = a.ker().ker().ker();

Vonatroncsot kaptunk.

Inkább legyen külön:

B b = a.ker();
C c = b.ker();
D d = c.ker();

Hibakezelés

Kivételek hibakód helyett

Használjunk hibakódok helyett kivételeket.

if(!szoveg.matches("[0-9]+")) {
   throw new InputTypeError("Input type error!");
}
App.java
import java.util.Scanner;
 
class InputTypeError extends Exception {
    private String value;
    InputTypeError(String value) {
        this.value = value;
    }
    @Override
    public String toString() {
        return "Hiba! A megadott érték hibás: " + this.value;
    }
}
public class App {
    public static void main(String[] args) throws InputTypeError {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Szám: ");
        String input = scanner.nextLine();
        scanner.close();
        if(!input.matches("^[0-9]+$")) {
            throw new InputTypeError(input);
        }
 
    }
}

Használjunk ellenőrizetlen kivételt

Az ellenőrzött kivételekre nincs feltétlenül szükség.

Az ellenőrizetlen kivételek, másként nevezve futási idejű kivételek a RuntimeException osztállyal készülnek.

App.java
import java.util.Scanner;
 
class InputTypeError extends RuntimeException {
    private String value;
    InputTypeError(String value) {
        this.value = value;
    }
    @Override
    public String toString() {
        return "Hiba! A megadott érték hibás: " + this.value;
    }
}
public class App {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("Szám: ");
        String input = scanner.nextLine();
        scanner.close();
        if(!input.matches("^[0-9]+$")) {
            throw new InputTypeError(input);
        }
 
    }
}

Hibakezelés leválasztása

Egy fájl tartalmát szeretnénk megfelelő adatszerkezetbe beolvasni. A függvény önmagában is összetett. Az alábbi példában a kivételt tovább dobjuk, nem kezeljük helyben. Az eredeti readFile() függvényt átneveztük tryReadFile() nevűre, amit egy readFile() függvény hív.

    public static ArrayList<Employee> readFile() {
        ArrayList<Employee> employeeList = new ArrayList<>();
        try {
            employeeList = tryReadFile();
        }catch(FileNotFoundException e) {
            System.err.println("Hiba! A fájl nem található!");
        }
        return employeeList;
    }
    private static ArrayList<Employee> tryReadFile() 
    throws FileNotFoundException {
        ArrayList<Employee> employeeList = new ArrayList<>();
        FileReader fileReader = new FileReader("adat.txt");
        Scanner scanner = new Scanner(fileReader);
 
        while(scanner.hasNext()) {
            String line = scanner.nextLine();
            String[] array = line.split(":");
            Employee employee = new Employee();
            employee.name = array[0];
            employee.city = array[1];
            employeeList.add(employee);
        }
        scanner.close();
        return employeeList;
    }

Ne adjunk vissza null értéket

Rossz példa:

App.java
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Scanner;
 
public class App {
    public static void main(String[] args) {
        ArrayList<Employee> employeeList = readFile();
        if (employeeList != null) {
            System.out.println(employeeList.get(0).name);
        }else {
            System.out.println("Nincs dolgozó");
        }
 
    }
    public static ArrayList<Employee> readFile() {
        ArrayList<Employee> employeeList = new ArrayList<>();
        try {
            employeeList = tryReadFile();
        }catch(FileNotFoundException e) {
            System.err.println("Hiba! A fájl nem található!");
        }
        return employeeList;
    }
    private static ArrayList<Employee> tryReadFile() 
    throws FileNotFoundException {
        ArrayList<Employee> employeeList = new ArrayList<>();
        FileReader fileReader = new FileReader("adat.txt");
        Scanner scanner = new Scanner(fileReader);
        while(scanner.hasNext()) {
            String line = scanner.nextLine();
            String[] array = line.split(":");
            Employee employee = new Employee();
            employee.name = array[0];
            employee.city = array[1];
            employeeList.add(employee);
        }
        scanner.close();
        if(employeeList.size()<1) {
            return null;
        }else {
            return employeeList;
        }
 
    }
}

Vizsgáljuk inkább a méretet:

App.java
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Scanner;
 
public class App {
    public static void main(String[] args) {
        ArrayList<Employee> employeeList = readFile();
        if (employeeList.size() > 0) {
            System.out.println(employeeList.get(0).name);
        }else {
            System.out.println("Nincs dolgozó");
        }
 
    }
    public static ArrayList<Employee> readFile() {
        ArrayList<Employee> employeeList = new ArrayList<>();
        try {
            employeeList = tryReadFile();
        }catch(FileNotFoundException e) {
            System.err.println("Hiba! A fájl nem található!");
        }
        return employeeList;
    }
    private static ArrayList<Employee> tryReadFile() 
    throws FileNotFoundException {
        ArrayList<Employee> employeeList = new ArrayList<>();
        FileReader fileReader = new FileReader("adat.txt");
        Scanner scanner = new Scanner(fileReader);
        while(scanner.hasNext()) {
            String line = scanner.nextLine();
            String[] array = line.split(":");
            Employee employee = new Employee();
            employee.name = array[0];
            employee.city = array[1];
            employeeList.add(employee);
        }
        scanner.close();
        return employeeList;
    }
}

Határok

Külső kód használata

Ha szeretnénk használni egy külső eszközt, ne építsük be rögtön a projektünkbe. Először írjunk tesztkódokat az új eszközzel.

Próbálkozáshoz:

Ez forrás formájában áll rendelkezésre:

Unit tesztek

Használjuk a TDD-t. A TDD a Test Driven Development.

Szabályok:

  • Előbb írjunk egy tesztet ami kudarcot vall.
  • Csak addig fejlesszük a tesztet, amíg kudarcot vall.
  • Az ipari kódból csak annyit írjunk, ami teljesíti a tesztet.
  • egy teszt - egy állítás
    • Egy tesztben egyetlen assert szerepeljen.
  • egy teszt - egy elem
    • egy teszt csak egyetlen elemet teszteljen.

Osztályok

  • Osztályok szervezése
    • Az osztály a változók listájával kezdődjön (adattagok).
      • Az első helyen nyilvános statikus állandók.
      • Ez után a privát statikus változók jönnek.
      • Végül a privát példányváltozók. (Publikus példányváltozókra ritkán van szükség.)
    • A változókat a nyilvános függvények kövessék.
    • A privát segédfüggvények kövessék a nyilvános függvényeket közvetlenül.
      • Az egészet fentről lefele lehessen olvasni.
  • Kis osztályok
    • legyenek az osztályok kicsik
    • és még kisebbek
  • Egyetlen felelősségi kör elve
    • Az osztálynak vagy modulnak egyetlen oka legyen a változásra.
  • Összetartás
    • Az osztály minden tagfüggvénye használjon egy vagy több példányváltozót.

S.O.L.I.D

  • Single Responsibility Principle
    • Egy felelősség elve.
    • Egy osztály csak egyetlen dologért legyen felelős.
    • Egy oka legyen a létezésének.
  • Open/Closed Principle
    • Nyílt/zárt elv.
    • Egy osztály legyen nyílt a kiterjesztésre, de zárt a módosításra.
  • Liskov substitution principle
    • Liskov helyettesítési elv.
    • Egy osztály helyettesíthető legyen a leszármazott osztályával.
    • A helyettesítés során, a helyes működés ne változzon.
  • Interface segregation principle
    • Interface elválasztási elv.
    • Több speciális interfész jobb, mint egy általános.
  • Dependency inversion principle
    • Függőség megfordítási elv.

Single Responsibility Principle

Egyetlen felelősség elve.

Egy osztálynak vagy modulnak, csak egyetlen oka legyen a létezésre.

App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek();
        jatek.kornyezetRajzolas();
        jatek.mozog();
    }
}
 
class Jatek {
    public void kornyezetRajzolas() {
        System.out.println("oooooooooooooooo");
    }
    public void mozog() {
        System.out.println("játékos mozog");
    }
    public void tamad() {
        System.out.println("játékos támad");
    }
    public void ved() {
        System.out.println("játékos védekezik");
    }
}

Esetünkben a Jatek osztály, nem csak a játékot írja, le de a játékost is.

Külön osztályban:

App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek();
        jatek.kornyezetRajzolas();
        jatek.elet();     
    }
}
 
class Jatek {
    public void kornyezetRajzolas() {
        System.out.println("oooooooooooooooo");
    }
    public void elet() {
        Jatekos jatekos = new Jatekos();
        jatekos.mozog();
    }
}
 
class Jatekos {
    public void mozog() {
        System.out.println("játékos mozog");
    }
    public void tamad() {
        System.out.println("játékos támad");
    }
    public void ved() {
        System.out.println("játékos védekezik");
    }
}

Open/Closed Principle

Egy osztály legyen nyílt a kiterjesztésre, de zárt a módosításra. Egy meglévő osztályt ne kelljen módosítani, egy új karakter létrehozása során.

Egy példa, amely sérti az elvet:

App.java
class App {
    public static void main(String[] args) {
        // ...
 
        // Ez sérti az Open/Closed elvet
        Karakter magus = new Karakter();
        karakter.mozog();
    }
}
 
class Karakter {
    public void mozog() {
        System.out.println("mozgás");
 
        // Ez sérti az Open/Closed elvet
        if (isRobot()) {
            System.out.println("robot mozgás");
        }
    }
 
    public void tamad() {
        System.out.println("támadás");
    }
 
    public void ved() {
        System.out.println("védelem");
    }
 
    // Ez sérti az Open/Closed elvet
    private boolean isRobot() {
        // A robot vizsgálata
        return false;
    }
}

A program sérti a nyitottság/zártság alapelvét. Ha például egy varázsló karaktert szeretnénk létrehozni, ahol más tevékenységekre van szükség, akkor bele kell nyúlni a Karakter osztályba.

Példa, ami nem sérti az elvet:

App.java
class App {
    public static void main(String[] args) {
        Magus magus = new Magus();
        magus.mozog();
    }
}
 
class Magus extends Karakter {
    public void mozog() {
        System.out.println("...mozgás....");
    }
}
 
class Karakter {
    public void mozog() {
        System.out.println("mozgás");
    }
    public void tamad() {
        System.out.println("támadás");
    }
    public void ved() {
        System.out.println("védelem");
    }
}

Létrehozhatok egy új karaktert, és nem kell módosítani a Karakter osztályt.

A következő példa, ugyanilyen jó példa:

App.java
class App {
    public static void main(String[] args) {
        KarakterMegvalositas magus = new KarakterMegvalositas();
        magus.mozog();
    }
}
 
class KarakterMegvalositas implements Karakter {
    public void mozog() {
        System.out.println("...mozgás....");
    }
    public void tamad() {}
    public void ved() {}
}
 
interface Karakter {
    public void mozog();
    public void tamad();
    public void ved();
}

Liskov substitution principle

Egy osztály helyettesíthető legyen a leszármazott osztályával. A Liskov Substitution Principle rövidítve: LSP.

Az LSP megsértése:

App.java
class Karakter {
    public void varazsol() {}
}
class Magus extends Karakter {}
class Harcos extends Karakter {}
 
class App {
    //nem helyettesíthető:
    Karakter harcos = new Karakter();
}

Nem minden karakter varázsol.

LSP ok

LSP-nek megfelelő változat:

Karakter.java
public interface Karakter {
    public void eszik();
}
Magus.java
public interface Magus extends Karakter {
    public void varazsol();
}
Harcos.java
public interface Harcos extends Karakter {
    public void harcol();
}
FeherMagus.java
public class FeherMagus implements Magus {
 
    @Override
    public void eszik() {
        System.out.println("eszik");
    }
 
    @Override
    public void varazsol() {
        System.out.println("varázsol");
    }
 
}
App.java
public class App {
    public static void main(String[] args) throws Exception {
        Magus imreMagus = new FeherMagus();
        imreMagus.varazsol();
    }
}

Forrás:

Interface segregation principle

Több speciális interfész jobb, mint egy általános.

App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek();
        jatek.mentes();    
    }
}
 
class Jatek implements Tarolas {
    public void mentes() {
        tarolTxt();
        tarolJson();
    }
    public void tarolTxt() {
        System.out.println("Tárolás .txt fájlban");   
    }
    public void tarolJson() {
        System.out.println("Tárolás .json fájlban");
    }
    public void tarolSqlite() {}
}
 
interface Tarolas {
    public void tarolTxt();
    public void tarolJson();
    public void tarolSqlite();
}
App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek();
        jatek.mentes();    
    }
}
 
class Jatek implements TxtTarolo, JsonTarolo {
    public void mentes() {
        tarolTxt();
        tarolJson();
    }
    public void tarolTxt() {
        System.out.println("Tárolás .txt fájlban");   
    }
    public void tarolJson() {
        System.out.println("Tárolás .json fájlban");
    }
}
 
interface TxtTarolo {
    public void tarolTxt();
}
 
interface JsonTarolo {
    public void tarolJson();
}
 
interface SqliteTarolo {
    public void tarolSqlite();
}

Dependency inversion principle

Függőség megfordítási elv. Az angol Dependency inversion principle szavakból rövidítve: DIP

App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek();
        jatek.mentes();    
    }
}
 
class Jatek {
    public void mentes() {
        Tarolas tarolas = new Tarolas();
        tarolas.tarolTxt();
    }
 
}
 
class Tarolas {
    public void tarolTxt() {
        System.out.println("tárolás .txt fájlban");
    }
}

A Jatek osztály példányosítja a Tarolas osztály, így teljes mértékben függ tőle.

A következő példában a Tarolas csak egy interface. Így a main() metódusban többféle tárolási mód hívható.

App.java
class App {
    public static void main(String[] args) {
        Jatek jatek = new Jatek(new TxtTarolo());
        jatek.mentes();    
    }
}
 
class Jatek {
    Tarolo tarolo;
    public Jatek(Tarolo tarolo) {
        this.tarolo = tarolo;
    }
    public void mentes() {         
        tarolo.tarol();
    }
 
}
 
class TxtTarolo implements Tarolo {
    public void tarol() {
        System.out.println("tárolás .txt fájlban");
    }
}
 
interface Tarolo {
    public void tarol();
}

Poszterek

Gyakorló programok

Gyakorlat 01

Keresse meg a setSpeed() függvényt, és írja át, a tiszta kód elvei alapján:

Feladat: Írja át az alábbi programot tiszta kód elvek alapján:

Gyakorlat 02

Feladat: Írja át az alábbi programokat tiszta kód elvek alapján:

Gyakorlat 03

Feladat: Írja át az alábbi programokat tiszta kód elvek alapján:

Gyakorlat 04

Feladat: Írja át az alábbi programot tiszta kód elvek alapján:

Forrás

oktatas/programozas/tiszta_kod.txt · Utolsó módosítás: 2023/12/24 21:18 szerkesztette: admin