Néha találkozok olyan alkalmazással, ami az adatbázban található boolean (logikai) típusú mezőnek a logikai viselkedésen túl, további szerepet próbál adni.
Egy boolean típusú mező igaz, vagy hamis értéket tárol, igaz, az "értéke" NULL is lehet.
A fejlesztés során azonban törekednünk kell arra, hogy az alkalmazást felépítő minden összetevő működése konzekvensen ugyanarra a logikára épüljön. (Ez segíti az alkalmazás folyamatainak könnyebb megértését.)
Ez a szemléket egy logikai mező esetében azt jelenti, hogy a mező csak igaz vagy csak hamis értéket vegyen fel. (Ha ezektől eltérő, akkor tekintsük hibásnak az algoritmusunk.)
Szerencsére az alapértelmezett érték megadása segíthet a problémában.
Egy meglévő alkalmazásnál talált NULL érték esetében, nem tudjuk, hogyan változik meg a rekord "viselkedése" azzal, ha igazra, vagy éppen hamisra változtatjuk azt.
Lekérdezések
Sajnos a lekérdezéseink is bonyolódhatnak a boolean mezők true/false/NULL értékeitől függően.
A lekérdezéseknél mindig azt fogalmazzuk meg, hogy milyen értékű rekordokat keresünk. (És ne azt, hogy milyen rekordokat NEM keresünk.)
Vegyünk egy könyvtári kölcsönzési alkalmazást, két példával
A könyveket a book nevű tábla tárolja, minden könyvhöz egy-egy rekordot. A táblában egy is_kolcsonozheto (direkt írtam magyarul; lásd: Elnevezési ajánlások) nevű mező jelöli, hogy a könyv kölcsönözhető-e (igaz értékkel), vagy sem (hamis értékkel).
1. Kigyűjtöm a kölcsönözhető könyveket:
SELECT * FROM book WHERE is_kolcsonozheto IS TRUE;
(Írhattam volna - hibásan - azt is, hogy "WHERE is_kolcsonozheto IS NOT FALSE AND is_kolcsonozheto IS NOT NULL", ami bár ugyanazokat a rekordokat gyűjti ki, de ezt sokkal nehezebb megérteni, ráadásul a fenti példa teljesen egyértelmű, mivel a kölcsönözhető, nem pedig a NEM nem kölcsönözhető és NEM nem NULL értékűeket keresem.)
2. Valamint az éppen nem elérhető könyvek listája:SELECT * FROM book WHERE is_kolcsonozheto IS FALSE;
Két hibát is vétettem!
1. Azt írtam: "nem elérhető". A mező nevét figyelembe véve ez hibás megfogalmazás, mivel az eredeti "köcsönzés" logikára épülő folyamat helyett egy eddig nem említett, "elérhető" tulajdonságot írtam.
2. A mező nevéből arra következtethetünk, hogy igaz értékkel jelöljük a kölcsönözhető könyveket. A logikát követve azokat a könyveket, amik nem kölcsönözhetőek "NOT TRUE"-val kellene lekérdezni.
A 2. példa újra - most már helyesen
2. Valmint az éppen nem kölcsönözhető könyvek listája:
SELECT * FROM book WHERE is_kolcsonozheto IS NOT TRUE;
Ha specifikációt írunk, figyeljünk, hogy fogalmazunk.
Továbbfejlesztés
Készen is vagyunk... legalábbis azt gondoltuk
Az idő múlásával bizonyos könyveket - mivel meg vannak rongálódva -, szeretnének leselejtezni (jobb esetben kisebb könyvtárak részére elajándékozni).
Lényeg: a selejtezett könyveket nem lehet kikölcsönözni, sőt, ezek nincsenek is a raktárban!
Eljárhatnánk úgy is, hogy töröljük ezeket a rekordokat [könyveket], de mivel meg akarjuk tartani, hogy korábban ki mit kölcsönzött, ezt nem tehetjük meg.
Vagyis: a könyveket meg kell tartani az adatbázisban, de nem szabad, hogy növeljék a raktárkészletet (mivel fizikailag nincsenek ott).
Ha követtük a fenti ajánlásokat, akkor a kölcsönözhetőséget igaz értékkel jelöljük, így minden "nem igaz" állapot (beleértve a FALSE, és a NULL-t is) nem kölcsönözhetőséget jelent, vagyis a könyvet nem lehet kikölcsönözni.
Összefoglalva
Az is_kolcsonozheto mező értékét "továbbgondolva":
- igaz, ha a könyv kölcsönözhető,
- hamis, ha a könyv nem kölcsönözhető,
- NULL, ha a könyvet leselejtezték.
Sajnos a NULL érték miatt az is_kolcsonozheto mezővel kapcsolatos logikánk a továbbiakban már nem helyes.
A kölcsönözhető állapotot tároló mezőt ugyanis így új tulajdonsággal "ruháztuk" fel.
A csak az adatbázist nézve, a mező nevéből, és a NULL értékből nem gondolunk - honnan is gondolnánk - a "selejtezettség" állapotára; ezt a "logikát" a továbbiakban már csak az algoritmusból tudnánk kideríteni. (Azt, hogy ez a specifikációban nem lesz rögzítve, biztosra vehetjük.)
Megoldást jelentene, ha átneveznénk a mezőt egy olyan kifejezésre, ami magában foglalja mind a kölcsönözhetőséget, mind pedig a selejtezettség állapotát - várom az ötleteket -, viszont nem ez a helyes út.
A megoldás
Hagyjuk érintetlenül a mező nevét - így a logikát is -, valamint értékének igaz/hamis jelentését.
A "selejtezett" tulajdonság tárolására vegyünk fel egy "is_selejtezett" mezőt, az alábbi tulajdonságokkal:
- típusa: logikai (boolean)
- alapértelmezett értéke: hamis
Ha követjük a boolean mezőkkel kapcsolatos ajánlást, akkor a book táblában igaz értékekkel találjuk meg a selejtezettek könyveket, és nem igaz értékkel - helyes logika esetén hamis-al - azokat, amik nem selejtezettek (vagyis elviekben kikölcsönözhetőek).
Végeredmény
A logika azt diktálja, hogy:
- mivel a selejtezett könyvet nem lehet kikölcsönözni - nincs fizikailag a raktárban -, az is_kolcsonozheto mező értéke egyértelműen hamis,
- ha egy könyv selejtezésre kerül, akkor az is_selejtezett mező szintén hamis.
1. kiegészítés: ha megírjuk a selejtezés funkciót, ne felejtsük el mindkét mezőt hamis-ra állítani, mivel nem lehetünk benne biztosak, hogy az is_kolcsonozheto mező bizonyosan hamis: előfordulhat ugyanis, hogy valakinél olyan régen van kint a könyv, hogy egy idő után "lemondanak" róla.
2. kiegészítés: ha tovább gondolkodunk, akkor ilyen módon hibásan tároljuk a vissza nem hozott könyvek állapotát, az eddig a selejtezett könyveket jelölő is_selejtezett mezőben. :)
Ezt a problémát viszont egyszerűen megoldhatjuk:
1. a mezőt átnevezhetjük (ez is refactoring) - pl. is_nem_elerheto-re. (Figyeljük meg, hogy a névben használtuk a "nem" kifejezést, ami tiltásra utal, vagyis, ha egy könyv nem elérhető, akkor igaz az értéke. Az új mező default értéke természetesen: hamis.)
2. felveszünk egy új mezőt - az is_nem_elerheto mellé -, is_nem_hoztak_vissza néven. (A vissza nem hozott könyvek esetében mindhárom boolean mező értéke hamis.)
A környezet ellenőrzése
Bár végül sikerült megoldást adnunk a selejtezett és a kvázi "elveszett" könyvek kezelésére, az alkalmazás egyéb, book táblához kapcsolódó algoritmusait ellenőriznünk, vagy kiegészítenünk kell:
- a kölcsönözhető könyvek listájában nem jelenhetnek meg a nem elérhető könyvek,
- ugyanez igaz a leltár-listával kapcsolatban is (ami elveszett, az nem lehet raktáron, sem kölcsölcsönözve; viszont lehet értelme a "honnan veszett el" kérdésnek: kölcsönzés során, vagy a raktárból?),
- biztosítani kell, hogy az érintett könyveket "nem elérhető" státuszba tudjunk helyezni,
- szükséges lehet még a "nem elérhető" könyvek listázása is,
- stb.
Számadás
Egy egyszerű problémából kiindulva, összetett feladat kerekedett, amit végül logikus lépések sorozataként sikerült megoldanunk. (Ebből is látszik, hogy a megvalósítási költség-becsléskor át kell gondolnunk a teljes folyamatot.)
Fontos szempont volt, hogy - lehetőség szerint -, az eredeti logikát ne módosítsuk, hanem igyekezzünk a rendelkezésre álló adatbázis-szerkezetet "fájdalommentesen" bővíteni, így kerülve el a felesleges többletköltségeket.