A magic numberek kiemelesenek szerepe a karbantarthato kodban

Mindenki ismeri a klasszikus kodreszletet:

--

String typeValue = (String) paramValue.get("type");

if ("beachball".equals(typeValue)) {
// do something
} else if ("volleyball".equals(typeValue)) {
// do something else
}

...

somewhereElseInTheCodebase();

if ("beachball".equals(typeValue)) {
// do something
} else if ("volleyball".equals(typeValue)) {
// do something else
}

--

Mi itt a baj? Hat, ha meg akarjuk talalni, hogy hol voltak kivancsiak az integer type-okra, akkor csak a text search marad, ami valljuk be, nem idealis. Typo-kat csak runtime lehet kiszurni, tesztelessel. Refaktoralaskor text search-replace kell, ami vagy csak erre matchel, vagy nem.

Nade mi van, ha ezeket kirakjuk valami kozos valtozoba (DRY)?

--

public static final String BEACHBALL = "beachball";
public static final String VOLLEYBALL = "volleyball";

...

String typeValue = (String) paramValue.get("type");

if (BEACHBALL.equals(typeValue)) {
// do something
} else if (VOLLEYBALL.equals(typeValue)) {
// do something else
}

...

somewhereElseInTheCodebase();

if (BEACHBALL.equals(typeValue)) {
// do something
} else if (VOLLEYBALL.equals(typeValue)) {
// do something else
}

--

Mit nyertunk most, azon kivul, hogy megtanultunk egy uj shortcutot az IDE-ben? Hat, hogy kihasznaljuk a forditot es az IDE-t a kod validalasaban! Nem nekunk (vagy az utodunknak) kell kezzel melozni azert, hogy a kod ne omoljon ossze, hanem megoldja ezt helyette a fordito, mi meg fokuszalhatunk a koddal valo birkozas helyett az erdemi munkara. Nincs veletlen typo, hiszen valtozot hasznalunk, nem stringet. Refaktoralas, atnevezes hipi-szupi konnyu, akar a valtozonev, akar az ertek elnevezese valtozik.

Na de lehet am ezt meg cifrazni azzal, ha az egeszet kitesszuk enum-ba, aminek leginkabb csoportositasi/olvashatosagi szerepe van, nem beszelve arrol, hogy igy a kulonbozo ertekek mar osztalyokon ativeloen is hasznalhatoak (DRY).

--

public enum ParameterType {
BEACHBALL, VOLLEYBALL
}

...

String typeValue = (String) paramValue.get("type");
ParameterType type = ParameterType.valueOf(typeValue);

if (ParameterType.BEACHBALL.equals(type)) {
// do something
} else if (ParameterType.VOLLEYBALL.equals(type)) {
// do something else
}

...

somewhereElseInTheCodebase();

if (type.equals(ParameterType.BEACHBALL))) {
// do something
} else if (type.equals(ParameterType.VOLLEYBALL)) {
// do something else
}

--

Ezzel elertuk, hogy mar a kod elso olvasasa alatt is teljesen vilagos, hogy mi az a INTEGER es STRING amirol szo van (parameter tipus), utana is tudunk nezni, hogy az meg milyen ertekeket vehet fel, nem kell a kodbazist vegigbogaraszni.

En alapvetoen ugy vagyok ezzel, hogy szerintem ha nem is a kod irojanak a kotelessege ezeket kirakni _legalabb_ egy konstansba, de akkor annak, aki legkozelebb piszkalja a kod ezen reszet. Konstansba kirakni meg azert is nagyon jo, hiszen mint megallapitottuk, a refaktoralas egyszeru, ha segit a fordito, es sikit, ha nem mindenhol irtuk at a kodot pl. enumosra.

Aki akar kuldeni egy ekezetes valtozatot, ne kimeljen, kicserelem :)

Szerk: es meg akar az egeszet at is rakhatjuk egy switch-case-be, amivel megintcsak sokat nyerunk olvashatosag teren!

--

public enum ParameterType {
BEACHBALL, VOLLEYBALL
}

...

String typeValue = (String) paramValue.get("type");
ParameterType type = ParameterType.valueOf(typeValue);

switch (type) {
case BEACHBALL:
// do something
break;
case VOLLEYBALL:
// do something else
break;
}

...

somewhereElseInTheCodebase();

switch (type) {
case BEACHBALL:
// do something
break;
case VOLLEYBALL:
// do something else
break;
}

--

Edit: megvaltoztattam a parameter type-okat integer es string-rol beachball es volleyballra, mert nem volt egyertelmu paraknak, hogy ez most reflection, vagy honnan van az a type (a peldat egy JSON object feldolgozasabol vettem).

Hozzászólások

Egy megjegyzés: ha meglátok egy INTEGER nevű konstansot, vagy kitalálom mi van benne, vagy nem. Talán jobb lenne az S_INTEGER név, ha String vagy N_INTEGER, ha szám.
Már csak azért is, hogy ne tűnjön úgy, hogy a konstans neve meg kell egyezzen a tartalmával: az utóbbi megváltozhat, amikor Tibeti nyelvre fordítjuk a programot, míg az előbbi remélhetőkeg megmarad.

Egy megjegyzés: ha meglátok egy INTEGER nevű konstansot, vagy kitalálom mi van benne, vagy nem. Talán jobb lenne az S_INTEGER név, ha String vagy N_INTEGER, ha szám.

Ez a hungarian notation, amirol azert jo leszokni. Ez a "vagy kitalalom, mi van benne, vagy nem" annyit jelent csak, hogy rosszul van elnevezve a konstans.

Ez a Systems Hungarian.

A rendes hungarian notation nem erről szól. Hanem arról, hogy mondjuk van két egész számod, az egyik oszlopok számát tárolja (cntCols), a másik meg mondjuk egy indexváltozó (iCol), akkor nem igazán jó összeszorozni őket, mert az eredmény kérdéses típusú. Mi van, ha egy countert összeszorzol egy indexváltozóval? Általában nincs értelme a műveletnek, ez gyanús. Míg annak van, hogy összehasonlítod őket (ez a legtermészetesebb felhasználás).
Simonyi ötlete volt ez, a fizikából vette: az egyik oldalán egy kifejezésnek ha négyzetméterek vannak, akkor a másik oldalon nem lehet kilojoule és másodperc, nem jön ki a dimenzió.

Amúgy vannak programozási nyelvek, meg libraryk, amelyek az egyes változókra akarnak mértékegységeket is bevezetni, hogy fordítási időben kiderüljön, hogy hülyeséget akarsz leírni (például egy sorszámot összeszorozni egy hosszal).

"if (INTEGER.equals(typeValue)) {
// do something
} else if (STRING.equals(typeValue)) {
// do something else
}"

Ilyeneknek pontosan egy helyen szabad előfordulnia a kódbázisban. Abban a factoryben, ami a StringBasedImplementationt és az IntegerBasedImplementationt példányosítja.
Aztán a többi helyen már nincs if(type == ez) else if(type = az).
Pont azért van típusrendszerünk, hogy ne kelljen ilyeneket többször leírni a kódban, hanem az egyes típusimplementációkra delegálni a feladatot. És így persze több típussal kiegészíteni a kódot is csak annyiból áll, hogy a factoryt bővíted.

Ha több helyen is van if (KONSTANS == type) típusú kód, az egy nagyon rossz code smell. Nem véletlenül van (haha, mily csodás név) típusrendszere az OOP nyelveknek.

Az Open/Closed nem erről szól szerintem, hanem igazából a mechanism vs policy elkülönítésről.
A mechanism-höz nem kell hozzányúlni, ha új policyt akarsz implementálni.
Persze ez az egyik felhasználása, ahol új típus valójában új policyt jelent.
https://en.wikipedia.org/wiki/Separation_of_mechanism_and_policy
Aki persze nem Open/Closed principle szerint kódol, az nem tudja megvalósítani a mechanizmus és a policy szétválasztását.

Edit a postban.

Masik tanulsag, a hup-os kozonseg szinvonalat alapul venni, mint baseline szakmai tapasztalatban es megertesben nem jo otlet, mert baromi sok kollega nem fogja erteni, hogy mirol is hablatyolsz :) Szoval erdemes kicsit kifejtosebben irni ilyeneket, hogy a tapasztalatlanabbaknak is atjojjon.

Illetve azon is gondolkozom, hogy lehet erdemes lesz majd 5-15 perces villambeszelgeteseket tartani ezekrol, megiscsak jobban atjohet szoban a dolog, kar hogy verbalisan (vagy ugy egyaltalan) nem tudok magyarazni :)

Magát a factoryt. Ugyanis olyan nincs, hogy a codebaseben más helyen is előfordul switch az enumra a factoryn kívül.

Hogy értsük:
van egy interface, mondjuk a példa szerint a Ball. Ennek két implementációja, a Volleyball és a Beachball.

És a két helyen, ahol használod a dolgokat, azt mondod:


String ballType = "myparam";
Ball ball = Balls.of(ballType);
ball.doThis();

// somewhere else
String ballType = "myparam2";
Ball ball = Balls.of(ballType);
ball.doThat();

Ennyi. A hívó kód (azaz a mechanizmus) nem is tudja, hogy hányféle Ball típus (azaz policy) van a rendszerben (nem is szabad tudnia), mégis tud mindegyikkel együttműködni, akárhány van.

És a Balls.of() factory methodban van egyedül switch(), sehol máshol.
Persze ehhez az kell, hogy a Ball interface szép nagy lesz, de hát a lényeg ez: MINDEN, a rendszerben előforduló típusspecifikus viselkedést tartalmazza.
Így megvalósítható az, hogy új típust úgy adj hozzá a rendszerhez, hogy biztosan tudd, mi a Ball összes viselkedése a renszerben. Lehet, hogy ez a viselkedés sokféle és a Ball interface-nek 50 metódusa lesz. Előfordul.

Szerk:
Esetleg meghivatkoznám még ezeket a dolgokat, ha már szóbakerült a polimorfizmus, és az O/C principle.
https://8thlight.com/blog/uncle-bob/2014/05/12/TheOpenClosedPrinciple.h…
https://www.youtube.com/watch?v=4F72VULWFvc
https://sourcemaking.com/refactoring/smells/switch-statements
https://sourcemaking.com/refactoring/replace-type-code-with-class
https://sourcemaking.com/refactoring/replace-type-code-with-subclasses
https://sourcemaking.com/refactoring/replace-conditional-with-polymorph…