Varázsszámok (antiminta)

A Wikipédiából, a szabad enciklopédiából
Ugrás a navigációhoz Ugrás a kereséshez

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á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 feltelt 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]

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

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