Varázsszámok (antiminta)
A varázsszám, más néven mágikus szám az egyik legrégebben megfogalmazott antiminta a számítógép-programozásban. Az elnevezés arra utal, hogy a kódban tisztázatlan jelentésű számok szerepelnek, amikről nem lehet tudni, hogy miért éppen annyi. Fő problémája, hogy megnehezíti a kód megértését, ezzel rontja annak karbantarthatóságát.[1] A varázsszámok elkerülésére már az 1960-as években felhívták a figyelmet a COBOL, FORTRAN és PL/1 kézikönyvek.[2]
A varázsszámok gyakran (de nem kizárólag) olyan konstansok, amelyek nem a program belső logikájából fakadnak, hanem külső ismeretet (például üzleti logikát) közvetítenek.
A varázsszámok elhomályosítják az eredeti jelentést, nem lehet tudni, hogy mire használták,[3] így mindenütt el kell gondolkodni, hogy az adott számnak mi a célja, mit fejez ki. Megnöveli a hibázás valószínűségét is, még annál is, aki tudja, hogy mit jelentenek ezek a konstansok, mivel az elírásokat nem jelzi a fordító, így nehezebb megtalálni a hibát. Nehezíti a módosítást is, mert a programbeli összes előfordulásról egyenként el kell dönteni, hogy az adott mágikus szám értéke áll-e ott, vagy véletlen egyezés van. Az antiminta megoldása, hogy értelmesen elnevezett konstansokat vezetnek be, így könnyebb a programot olvasni, megérteni, karbantartani.[4]
A programok elemeit úgy kell elnevezni, hogy értelmesen illeszkedjenek a program kontextusába. A nem intuitívan elnevezett konstansra példa az int SIXTEEN = 16
deklaráció, ezzel szemben a int NUMBER_OF_BITS = 16
értelmezhetőbb.
A varázsszámokkal kapcsolatos problémák nem kötődnek kizárólag számokhoz, hanem bármely típussal kapcsolatban felmerülhetnek. Így például a const string testUserName = "John"
deklaráció jobb, mint a tesztelő programban előforduló "John"
mágikus érték.
Példa
[szerkesztés]A célkitűzés megkeverni egy pakli kártyát. Ehhez az alábbi pszeudokód a Fisher–Yates keverési algoritmust használja:
for i from 1 to 52 j := i + randomInt(53 – i) – 1 a.swapEntries(i, j)
ahol a
a keverendő tömb, randomInt(x)
véletlen egészt ad vissza 1 és x között, beleértve a határokat, és a swapEntries(i, j)
felcseréli az i és j-edik elemeket a tömbben. Ebben a példában az 52
mágikus szám. Jobb programozási stílus a következő megvalósítás:
constant int deckSize := 52 for i from 1 to deckSize j := i + randomInt(deckSize + 1 – i) – 1 a.swapEntries(i, j)
Magyarázat
[szerkesztés]A második kódnak és általában az értelmesen elnevezett értékeknek több előnyük is van:
- Könnyebben olvashatók és értelmezhetők. Az első példában az olvasó programozó, aki értelmezni próbálja a kódot, elgondolkodik azon, hogy miért éppen 52. Ezután jobban, többször is el kell olvasnia a kódot, hogy kiderítse, melyik szám éppen mit jelent, mert akármelyik számot képezhették akármelyikből.
- Az elnevezett értékeket könnyebb megváltoztatni, különben minden számról el kell dönteni, hogy mi célt szolgál, és miért éppen annyi. Egy számot több helyen is felhasználhatnak, különböző jelentéssel. Az 52 jelentheti a pakli méretét, de a hetek számát is a Gergely-naptárban. A nyers "mindent cserél" csak még jobban összezavarna mindent, mivel olyan számokat is cserél, amelyeknek egészen más a jelentésük. Még nagyobb baj, hogy a származtatott mennyiségeket, mint a példában az 53, nem cseréli. Így a 32 (magyar kártya) és a 78 (tarot kártya) keverése is hibát eredményez. Ezzel szemben az elnevezett értéket csak egy helyen kell megváltoztatni, ami lényegesen egyszerűbb, és a származtatott mennyiségek is automatikusan megváltoznak vele.
- Segít felismerni az elírásokat. Ha valahol az 52 helyett 62 szerepel, akkor a fordító nem hívja fel a figyelmet a hibára, amihez így át kell fésülni a kódot. Ezzel szemben a "dekSize" elírást a fordító felismeri, mert nincs deklarálva.
- Fejlesztőkörnyezetek további támogatást nyújtanak, hogy ne kelljen a hosszú neveket másolgatni, mivel az első néhány betű után kiegészítik a nevet.
- Használható csoportos deklaráció is, amikor a függvény vagy a fájl elején, az osztálydeklarációban, … definiálják a konstansokat és változókat.
Hátrányok
[szerkesztés]- A csoportos deklarációval az a probléma, hogy a deklaráció túl távol lehet a felhasználási helytől, így az érték megtudásához fel kell görgetni a fájl vagy a függvény elejére. Ezzel szemben javasolják azt is, hogy mindent közvetlenül azelőtt deklaráljunk, hogy használjuk is.
- Terjengősebbé válik a kód, de ez igazolható azzal, hogy az érték változhat, vagy különben nem lenne egyértelmű. Például a keverési rutint más játékok is felhasználhatják.
- Korábbi programozási nyelvekben futásidőben ment végbe a kifejezések kiértékelése. Mostanra ezt áttették fordítási időbe, így a lefordított kódban már a kiszámított érték szerepel. Emiatt nem lesz lassabb a program.
- Növeli a sorok hosszát, így a sortörések számát is, különösen, ha egy sorban több konstanst is használnak.
- Ha a debugger nem mutatja a konstansok, változók értékeit, akkor a debuggolás is nehezebb.
Megengedett használat
[szerkesztés]Van, hogy nem állnak fenn értelmezési nehézségek, ekkor bizonyos konstansokat nem kell elnevezni, és nem is tekintik őket mágikusnak.
- Ciklusokban a 0 és az 1, mint kezdő és inkrementáló érték, mint például
for (int i = 0; i < max; i += 1)
. - A 2 használata annak eldöntésére, hogy egy szám páros vagy páratlan, pélodául
isEven = (x % 2 == 0)
, ahol%
a modulo operátor. - Százalékszámítás esetén a 100.
- Metrikus mértékegységek, időegységek átváltásakor a 10 hatványai és a 60 mint váltószámok.
- Egyszerű konstansok, például a kör kerületének számításában,
circumference = 2 * Math.PI * radius
[2] vagy a másodfokú egyenlet diszkriminánsának számításáband = b^2 − 4*a*c
.
A C nyelvben régen nem volt külön logikai típus, a 0 és az 1, illetve minden nullától különböző érték felelt meg a logikai hamisnak és igaznak. Ma már van bool típus, így a legalsó szinttől eltekintve a 0 és 1 logikai értékként való használata ellenjavallt. A legtöbb nyelv boolean
vagy bool
néven tartalmaz logikai típust.
C-ben és C++-ban a 0 a null pointert is jelenti. A C szabványos könyvtára tartalmazza a NULL
makrót, inkább ennek használatát ajánlják. Más nyelvekben hasonló célokra a null
, nil
értékek valók. A C++11 bevezette a típusozott nullptr
-t.
Jegyzetek
[szerkesztés]- ↑ Datamation.com, "Bjarne Stroustrup on Educating Software Developers" http://www.datamation.com/columns/article.php/3789981/Bjarne-Stroustrup-on-Educating-Software-Developers.htm
- ↑ a b Martin, Robert C,. Chapter 17: Smells and Heuristics - G25 Replace Magic Numbers with Named Constants, Clean Code - A handbook of agile software craftsmanship. Boston: Prentice Hall, 300. o. (2009). ISBN 0-13-235088-2
- ↑ Martin, Robert C,. Chapter 17: Smells and Heuristics - G16 Obscured Intent, Clean Code - A handbook of agile software craftsmanship. Boston: Prentice Hall, 295. o. (2009). ISBN 0-13-235088-2
- ↑ IBM Developer, "Six ways to write more comprehensible code" http://www.ibm.com/developerworks/linux/library/l-clear-code/?ca=dgr-FClnxw01linuxcodetips
Fordítás
[szerkesztés]Ez a szócikk részben vagy egészben a Magic number (programming) című angol Wikipédia-szócikk fordításán alapul. Az eredeti cikk szerkesztőit annak laptörténete sorolja fel. Ez a jelzés csupán a megfogalmazás eredetét és a szerzői jogokat jelzi, nem szolgál a cikkben szereplő információk forrásmegjelöléseként.
A cikk az Unnamed numerical constants szakasz fordítása.