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
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?
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.
public class Employee {
//magyar-jelölés:
String sName;
String sCity;
double dSalary;
}
public class Employee {
//tagváltozó-jelölése:
String m_Name;
String m_City;
double m_Salary;
}
Helyette:
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()
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:
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.
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:
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:
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:
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.
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.
class DatabaseConn {
public Connection connect(DatabaseType.PostgreSQL) {
...
}
}
Helyette:
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:
class Sides {
double a, b, c;
public Sides(double a, double b, double c) {
this.a = a;
this.b = b;
this.c = c;
}
}
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.
class Mariadb {
boolean connected = false;
public Connection connect() {
...
connected = true;
}
}
Matematikai értelemben ((https://softwareengineering.stackexchange.com/questions/40297/what-is-a-side-effect)), 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.
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:
class Dolgozo {
public StringBuffer line;
public Dolgozo() {
this.line = new StringBuffer("Pala Béla:");
}
public void appendCity(StringBuffer city) {
this.line.append(city);
}
}
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.
public class App {
public static void main(String[] args) throws Exception {
Dice dice = new Dice();
dice.fiveRoolsHuman();
dice.fiveRoolsMachine();
}
}
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ó:
public class App {
public static void main(String[] args) throws Exception {
Dice dice = new Dice();
dice.startGame();
}
}
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 -
/*
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 dependenceList = module.getDependSubsystems();
String dependenceName = base.getSubsystem();
if(dependenceList.contains(dependenceName))
HTML megjegyzés:
/*
Ez a függvény a Triangle osztály része.
*/
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
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 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!");
}
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 **[[oktatas:programozas:java:java_kivetelek|futási idejű kivételek]]** a **RuntimeException**
osztállyal készülnek.
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 readFile() {
ArrayList employeeList = new ArrayList<>();
try {
employeeList = tryReadFile();
}catch(FileNotFoundException e) {
System.err.println("Hiba! A fájl nem található!");
}
return employeeList;
}
private static ArrayList tryReadFile()
throws FileNotFoundException {
ArrayList 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:
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 employeeList = readFile();
if (employeeList != null) {
System.out.println(employeeList.get(0).name);
}else {
System.out.println("Nincs dolgozó");
}
}
public static ArrayList readFile() {
ArrayList employeeList = new ArrayList<>();
try {
employeeList = tryReadFile();
}catch(FileNotFoundException e) {
System.err.println("Hiba! A fájl nem található!");
}
return employeeList;
}
private static ArrayList tryReadFile()
throws FileNotFoundException {
ArrayList 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:
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 employeeList = readFile();
if (employeeList.size() > 0) {
System.out.println(employeeList.get(0).name);
}else {
System.out.println("Nincs dolgozó");
}
}
public static ArrayList readFile() {
ArrayList employeeList = new ArrayList<>();
try {
employeeList = tryReadFile();
}catch(FileNotFoundException e) {
System.err.println("Hiba! A fájl nem található!");
}
return employeeList;
}
private static ArrayList tryReadFile()
throws FileNotFoundException {
ArrayList 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:
* https://logging.apache.org/log4j/2.x/
* https://commons.apache.org/proper/commons-codec/
* https://itextpdf.com/
* https://pdfbox.apache.org/
* http://poi.apache.org/
Ez forrás formájában áll rendelkezésre:
* https://github.com/rockaport/alice
===== Unit tesztek =====
Használjuk a [[oktatas:programozas:tdd|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.
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:
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:
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:
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:
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:
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:
public interface Karakter {
public void eszik();
}
public interface Magus extends Karakter {
public void varazsol();
}
public interface Harcos extends Karakter {
public void harcol();
}
public class FeherMagus implements Magus {
@Override
public void eszik() {
System.out.println("eszik");
}
@Override
public void varazsol() {
System.out.println("varázsol");
}
}
public class App {
public static void main(String[] args) throws Exception {
Magus imreMagus = new FeherMagus();
imreMagus.varazsol();
}
}
Forrás:
* https://tusharghosh09006.medium.com/liskov-substitution-principle-lsp-744eceb29e8 (2023)
==== Interface segregation principle ====
Több speciális interfész jobb, mint egy általános.
Az interfészeket úgy kell megtervezni, hogy az azt implementáló
osztályok csak számunkra szükséges metódusokat érjék el.
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();
}
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
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ó.
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 ====
A S.O.L.I.D. elvek poszterekkel:
* https://www.globalnerdy.com/2009/07/15/the-solid-principles-explained-with-motivational-posters/ (2022)
===== Gyakorló programok =====
==== Gyakorlat 01 ====
Keresse meg a setSpeed() függvényt, és írja át, a tiszta kód elvei alapján:
* https://github.com/andteki/carRace
Feladat: Írja át az alábbi programot tiszta kód elvek alapján:
* https://github.com/andteki/reLitedict
==== Gyakorlat 02 ====
Feladat: Írja át az alábbi programokat tiszta kód elvek alapján:
* https://github.com/andteki/repass
==== Gyakorlat 03 ====
Feladat: Írja át az alábbi programokat tiszta kód elvek alapján:
* https://github.com/andteki/repoker
* https://github.com/andteki/retriangle
==== Gyakorlat 04 ====
Feladat: Írja át az alábbi programot tiszta kód elvek alapján:
* https://github.com/andteki/recollector
===== Feleletválasztós feladatok =====
==== Feladat 01 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* emailInfo()
* getEmail()
* setEmail()
* sendEmail()
==== Feladat 02 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* getAddress()
* addressDetails()
* updateAddress()
* validateAddress()
==== Feladat 03 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* getAge()
* calculateAge()
* setAge()
* ageCalculation()
==== Feladat 04 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* getUserNameInfo()
* setUserNameInfo()
* userNameInfo()
* updateUserNameInfo()
==== Feladat 05 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* getCustomerAddressDetails()
* updateCustomerAddressDetails()
* validateCustomerAddressDetails()
* customerAddressDetails()
==== Feladat 06 ====
A következő metódusnevek közül egy nem felel meg a tisztakód alapelveinek. Melyik az?
* ageCalculation()
* calculateOrderAge()
* getOrderAge()
* updateOrderAge()
==== Feladat 07 ====
Melyik tiszta kód alapelvhez kapcsolható a következő leírás?
Az osztályoknak csak egyetlen okból kellene változniuk, és ez az egy ok általában a felelősségi körük bővítésével vagy változtatásával függ össze.
* A) Single Responsibility Principle (SRP)
* B) Interface Segregation Principle (ISP)
* C) Dependency Inversion Principle (DIP)
* D) Open/Closed Principle (OCP)
--> Sol #
Megoldás: A
<--
==== Feladat 08 ====
Melyik tiszta kód alapelvhez kapcsolható a következő leírás?
A meglévő kódot nem szabad módosítani, de új funkciókat adhatunk hozzá. Az új funkciók érdekében használhatsz új osztályokat, amelyek kiterjesztik a meglévőket.
* A) Dependency Inversion Principle (DIP)
* B) Open/Closed Principle (OCP)
* C) Interface Segregation Principle (ISP)
* D) Liskov helyettesítési elv
--> Sol #
Megoldás: B
<--
==== Feladat 09 ====
Melyik tiszta kód alapelvhez kapcsolható a következő leírás?
Az interfészeket úgy kell megtervezni, hogy az azt implementáló
osztályok csak számunkra szükséges metódusokat érjék el.
* A) Interface Segregation Principle (ISP)
* B) Fermski helyettesítési elv (Fermski Substitution Principle)
* C) DRY elv (Don't Repeat Yourself Principle)
* D) YAGNI elv (You Aren't Gonna Need It Principle)
--> Sol #
Megoldás: A
<--
==== Feladat 10 ====
Melyik tiszta kód alapelvhez kapcsolható a következő leírás?
Minden osztály legyen helyettesíthető a leszármazott osztályával anélkül, hogy a program helyes működése megváltozna.
* A) Liskov helyettesítési elv
* B) Polimorfizmus elve
* C) Kiterjesztési elv
* D) Absztrakció elve
--> Sol #
Megoldás: A
<--
===== Forrás =====
* Robert C. Martin: Tiszta kód (2010, Budapest, KISKAPU)
* [[https://www.digitalocean.com/community/conceptual-articles/s-o-l-i-d-the-first-five-principles-of-object-oriented-design|https://www.digitalocean.com]] (2023)