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.Datamation.com, "Bjarne Stroustrup on Educating Software Developers" http://www.datamation.com/columns/article.php/3789981/Bjarne-Stroustrup-on-Educating-Software-Developers.htm 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.

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, í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.IBM Developer, "Six ways to write more comprehensible code" http://www.ibm.com/developerworks/linux/library/l-clear-code/?ca=dgr-FClnxw01linuxcodetips

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 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 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 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 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éldá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 vagy a másodfokú egyenlet diszkriminánsának számításában d = 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

Fordítás

A cikk az Unnamed numerical constants szakasz fordítása.

Kategória:Programozási antiminták