ugrás a tartalomhoz

PHP Chemotox I.

gerzson · 2004. Szep. 14. (K), 21.00
PHP Chemotox I.
A 2004. évi PHP Konferencia és a Tiszaújvárosi Roadshow keretében már kétszer is megpróbáltam negyven percbe sűríteni a mondanivalómat erről a témáról - nem sok sikerrel. Most a Weblabor hasábjain arra vállalkozom, hogy immár időkorlátok nélkül adjak választ arra a kérdésre: "mit is jelent ez a PHP Chemotox?" Cikksorozatomat remélem azok is haszonnal fogják olvasni, akik már ízelítőt kaptak a tartalomból valamelyik rendezvényen.

A sorozatban megjelent

Annyi elméleti és szakmai dologról esik szó az elkövetkezendőkben, hogy mire a végére érünk, talán elfelejtjük még egyszer feltenni az előző kérdést. Nem akarok megválaszolatlan kérdéseket hagyni, ezért rögtön ezt magyarázom el. A Chemotox rovarirtószer, a rovarok pedig, vagyis inkább a bogarak - főképp a szoftverekben előforduló változataik - néha igen kártékonyak tudnak lenni, amiket a legjobb elkerülni vagy kiirtani. Ennek a cikksorozatnak az a célja tehát, hogy közelebbről megismerkedjünk olyan - a PHP nyújtotta vagy e nyelv köré szerveződő - megoldásokkal, amelyekkel könnyebb hibamentes programokat írni.

Ebben a részben főként a száraz, elméleti alapokat rakjuk le, hogy azután a leszűrt tanulságokat a gyakorlatban is alkalmazhassuk. És hogy miért éreztem erre késztetést? Nos, álljon itt erre egy idézet: "Mihelyst elkezdtünk programozni, meglepődve tapasztaltuk, hogy ez nem is olyan egyszerű, mint amilyennek először gondoltuk volna. Fel kellett fedeznünk a hibakeresést. Élesen él bennem az a pillanat, amikor rádöbbentem, hogy attól kezdve életem nagy részét azzal fogom tölteni, hogy hibákat keresek a saját programjaimban." - 1949, Maurice Vincent Wilkes

Mindegyikünk megtapasztalhatta már ezt az érzést. Maurice Wilkes szavai a mai napig aktuálisak, és könyörtelenül emlékeztetnek arra a tényre, hogy nincs program hiba nélkül. (Másképp megfogalmazva az egyetlen hibátlan program az, ami nincs.) Itt álljunk is meg egy pillanatra, és vegyük sorra, miféle hibákkal találkoztunk vagy találkozhatunk PHP programok írása közben, és ami fontosabb, mit tehetünk ellenük? Átfogóbb nézőpontból szemlélődve a hibákat hatásuk és megjelenésük helye szerint így (is) csoportosíthatjuk:
  • A futtató környezet (webszerver, PHP) leállását okozó hiba. Ezzel leginkább még fejlesztési stádiumban levő kiterjesztések (bővítmények) használatakor találkozhatunk. Ebben az esetben még az is valószínű, hogy nem a saját szkriptünkben van a hiba, hanem olyan extrém értékekkel végeztünk műveleteket, amire a fejlesztőknek "még nem volt ideje gondolni". Az ilyen esetek szülnek új bejegyzéseket a PHP bug adatbázisában, ezt leszámítva nemigen van beleszólásunk a dolgokba.
  • PHP értelmezési hiba (E_PARSE). Ez már sokkal ismerősebben hangzik. Ilyenkor nincs más dolgunk, mint megértetni az értelmezővel a kódunkat. Mivel az nem fog új nyelvi elemeket megtanulni, kénytelenek vagyunk saját magunk az elgépelések nyomába eredni, és valami mással próbálkozni. Ez nem igazi programozási hiba, és viszonylag hamar és egyszerű módon javítható.
  • PHP futási hibák. Számos egyéb mellett ide tartoznak azok is, amelyek a szoftverkörnyezet kiesése miatt lépnek fel, és szigorú értelemben a normális működés részének tekintendők. Ide értem azokat az eseteket, amikor az adatbázis-kiszolgáló nem elérhető, vagy a hibás lekérdezés miatt a programunk hibázik. Néha a szerver túlterheltsége is okozhat szolgáltatás-kiesést, és egy jól megírt, de erőforrás-igényes műveletsorozat így kudarcot vallhat. Szerencsére a hibaüzenet révén van mibe kapaszkodni, és lehetőség megkeresni a zavar okát. Egy későbbi részben több tippet ismertetek is, ami megkönnyíti az ilyen esetek feltárását és kezelését.
  • Végül, de nem utolsó sorban, amikor a futtató környezet nem észlel hibát, de valami egészen mást látunk végeredményként, mint amit kellene. Ez az igazi logikai hibák szintje, amelyeket a legnehezebben detektálni, a "hibakeresés művészetének bölcsője".

A hiba bennünk van

A hiba a program nem kívánt viselkedése, helytelen reakciója, a kívánttól eltérő kimenet produkálása valamilyen bemenet és belső állapot kombinációjára.

Sajnos, ennyi év tapasztalata után sem tudunk jobb leírást találni a programhibára. Ennek oka az, hogy a hibák, amint az őket "hordozó" algoritmusok megalkotása is, manapság csakis emberi tényezőkön múlik, azaz legtöbbször rajtunk. A fenti hibadefiníció elsőre nem sok hasznosat mutat magából és a témakörből, mint a matematikai definíciók általában, de ez a lexikonszagú mondat mégis tartogat néhány megfontolandó kitételt. Rágjuk át magunkat még ezen, hogy utána tényleg valami kézzelfogható következzen! (Mindeközben tegyük le lelki terhünket, hogy a programjainkban a hibák belőlünk fakadnak. Nekünk legyen mondva máséi is, még ha nem is olvassa ezeket a sorokat.)

Az első és már félig kitárgyalt szempont, hogy itt valamilyen viselkedésről van szó. Ez feltételezi a működést, még ha az rossz is, azaz nem a PHP szintaktikai hibák kiküszöbölésének mesterfogásairól fogok ezután szólni. Ha azonban témába vág, felidézünk majd néhány jó öreg, szakállas programszerkesztési fogást, amivel csökkenthető egy-egy hibatípus előfordulása.

A viselkedés hosszúra nyúló körülírásában szemet szúrnak a helytelen és nem kívánt jelzők is. Ezek azt jelzik, hogy általában van elképzelésünk - sőt! elvárásunk - arról, hogy mit kellene csinálnia a programunknak. Az elképzelések pedig igenis terveken alapulnak, amelyek vagy papíron elfeledve a fiókok mélyén, vagy a fejünkben kósza gondolatként azért valahol mégis csak megbújnak. A programjaink minősége annál jobb lesz, minél pontosabban tudjuk ezeket beléjük kódolni. Ezenkívül fontos, hogy tudjuk meghatározni, mi alapján tekintjük a viselkedést helyesnek vagy hibásnak, azaz milyen jelekből és eredmények alapján hozzuk meg ítéletünket. Ez most még elég ködös, nem is feszegetem tovább, ugyanúgy ahogy a mondat befejező részét sem. Legyen elég annyi, hogy nekünk tiszta képpel kell rendelkezni a program helyes működéséről.

A tervezés fontossága

A tervezés egyik legfontosabb célja, hogy olyan programrészeket - függvényeket, osztályokat, modulokat - alakítsunk ki, amelyek jól meghatározott felületeken (interfészeken) keresztül kapcsolódnak egymáshoz, ahol minden egyes résznek pontosan szabályozott a szerepe. A tervezés több okból is hasznos:
  • A kisebb részeket könnyebb átlátni, gyorsabban és biztosabban ellenőrizhető a helyességük.
  • Minél átgondoltabb egy rendszer, annál több pontját tesztelték már jóval azelőtt, hogy ténylegesen létrehozták volna, azaz a terveknek megfelelő programok utólagos tesztelésére és hibajavítására kevesebb időt, pénzt és energiát kell áldozni.
  • A működés során újabb igények lépnek fel, új funkciókat kell beépíteni, meglevőket módosítani és régi hibákat kell kijavítani a programokban, ezt nevezzük karbantartásnak. Ez megköveteli a karbantartó programozótól, hogy megértse a szerző forrásait és azok céljait. Minél átgondoltabban van valami felépítve, annál könnyebb elkapni a szerző gondolatmenetét.
  • Ha ezek a tervezés során dokumentációban testet is öltöttek, az kiváló, hiszen a dokumentáció "szabad nyelvi formalizmusa" tömörebb, és hatékonyabb megértést biztosít. (Valójában a dokumentáció megléte önmaga csodaszámba megy.)
Ha eddig nem értettünk egyet a tervezés fontosságával, ez a három nyúlfarknyi érv sem fog meggyőzni minket, ráadásul a dokumentáltságnak vannak árnyoldalai is. Ahogy a fejlesztés átlép a tényleges programírás fázisába (kódolás), a kódban menetközben fellépő változtatások nem lesznek összhangban a dokumentációval, aminek naprakészen tartása mindinkább háttérbe szorul.

Emellett nehézséget okoz, hogy karbantartás során általában fizikailag is külön helyen van a dokumentáció és a forráskód, ami még inkább megnehezíti e kettő összekapcsolását: egy-egy kódblokkhoz tartozó részletes magyarázat megtalálását. A forrásokon belül elhelyezett megjegyzések sokszor pedig nem adnak többlet információt a kódhoz képest, mert nem a követendő gondolatmenetet írják le, hanem más, egyébként hasznos, figyelmeztetéseket tartogatnak "utódaink" számára. Milyen jó lenne, ha ezeket a gondokat is le tudnánk küzdeni!

assert() - a futtatható dokumentáció?

Ebben van segítségünkre a C nyelvből átvett, de igen kevéssé kiaknázott assert() PHP függvény. Ezzel a függvénnyel bármilyen feltételezésünket logikai kifejezések (kijelentések) formájában elhelyezhetjük a kódunkban, azaz a dokumentációban rögzített kritériumok teljesülését ellenőrizhetjük velük automatikusan a programunk futásával egy időben. Az assert() függvényt az eval() és a trigger_error() keverékeként lehet elképzelni.

<?php
function my_assert ( $code ) {
  if ( eval("return $code;" ) == FALSE )
    trigger_error("assert(): Assertion \"$code\" failed in ... ", E_WARNING);
}
?>
A paraméterül átadott PHP kód - ami maga a logikai kifejezés - futtatásra kerül, mintha az eval()-t használnánk. Az egyik fontos szempont, hogy az átadott kódnak mindig egyszerű, logikai értékkel bíró kifejezésnek kell lennie, nem tartalmazhat vezérlőszerkezeteket és deklarációkat. Ha a kifejezés értéke igaz (TRUE), akkor az abban megfogalmazott tervezési feltétel teljesül, nem történik semmi, minden halad a terv szerint. Ha viszont a kiértékelt kód értéke hamis, akkor a rendszer megnyomja a vészjelzőt, és egy hibaüzenet küldésével riasztja a fejlesztőt.

Nézzük meg rögtön a gyakorlatban, milyen helyeken lehet használni assert()-et! Az alábbi példában a felhasználótól érkező változók elérését szeretnénk még rugalmasabbá tenni, ehhez írtunk egy get_input() segédfüggvényt, ami a $scope paraméterében kiválasztott szuperglobális tömbök egyikéből adja vissza a $name-ben megadott elemet. (A példa emlékeztethet minket a 4.2.0 verzió előtti időkből két php.ini beállításra, amelyeknek a nevét most oktató szándékkal nem említem.)

<?php
   function get_input ( $name, $scope ) {
  // $name-ben nem ures string-et varunk
  assert('is_string($name) && strlen($name) && eregi("[a-z0-9_]", $name)');

  // $scope csak CGPS karaktereket tartalmazhat
  $scope = strtoupper( $scope );
  assert('strspn($scope, "CPGS") == strlen($scope)');

  for ( $i = strlen($scope); $i--; ) {
    switch ( $scope{$i} ) {
      case 'P':
        if ( isset($_POST[$name]) ) return $_POST[$name];
        break;
      case 'G':
        if ( isset($_GET[$name]) )  return $_GET[$name];
        break;
      case 'C':
        if ( isset($_COOKIE[$name]) )  return $_COOKIE[$name];
        break;
      case 'S':
        if ( isset($_SESSION[$name]) ) return $_SESSION[$name];
        break;
     }
   }
   return FALSE;
}
?>
Érdemes kipróbálni, hogy mi történik akkor, ha a paramétereket aposztrófok nélkül vagy idézőjelek között ("") adjuk át a függvénynek. Vajon ez hogyan befolyásolja a végeredményt és a teljesítményt?
Miről is van itt szó? A függvénybe lépéskor két állítás helyessége kerül ellenőrzésre, mindkettő a paraméterek típusával és értékével van kapcsolatban. Látható, hogy vezérlési szerkezet nélkül is - pusztán PHP függvények segítségével -, milyen átfogó vizsgálatokat tudunk végezni. Ha valamelyik paraméter nem felel meg a függvénynek, mint az alábbi képfeltöltő szkriptben:

<?php
  // feltöltött kép adatbázisba mentése
  $picture_id = pg_lo_create($db);
  pg_exec($db, "INSERT INTO GALERIA VALUES " . 
               "(" . get_input('cim', 'GP') . ",$picture_id)");
  // ...
  // 'F' $_FILES[] tömbre nincs felkészítve a függvényünk!
  pg_lo_write($handle, get_input('kep', 'F'));
?>
abban az esetben hiba generálódik:

Warning: assert(): Assertion "strspn($scope, "CPGS") == strlen($scope)" failed in ... on line ...

A fejlesztő ezt látva - remélhetőleg - kibővíti az új funkcióval a függvényt. Ez a leggyakoribb felhasználási formája az assert()-nek: az interfészen átadott paraméterek típus-, és ritkábban értékellenőrzése. A kód bármely olyan pontján használhatjuk, ahol függvényhívás engedélyezett, akár egy függvényblokk belsejében, ahol a változók pillanatnyi értékéről alkotott feltételezésünket akarjuk tesztelni. Szigorú programozási szabályok kikényszerítéséhez - interfészek, keretrendszer-komponensek kialakításakor - vehetjük igénybe, sőt némi körültekintéssel felhasználható a még ki nem dolgozott kódterületek jelölésére.

A fenti rövid példából is látható, hogy ennek a függvénynek főleg a programfejlesztés és tesztelés során vehetjük hasznát. Óriási előnye a változóérték-kiíratásos (echo $valami;) technikákhoz képest, hogy csak akkor szól, ha valami baj van, és ez a PHP hibákhoz hasonló módon történik, ami egyszerűbbé teszi a kezelésüket.

Az assert() használatának további előnye, hogy egyetlen php.ini beállítás (assert.active) megváltoztatásával minden ilyen kiértékelés kikapcsolható, mintha bele sem írtuk volna a forrásba. Ezzel a program éles verziójának előállítása nem igényel semmilyen módosítást. Ekkor a forrásban egyrészről megmaradnak a feltételek, mint dokumentációs elemek, amelyek másodsorban bármikor aktiválhatók hibakeresés céljából, de nem futnak, amíg nincs rájuk szükség. Mivel a programnak mindkét esetben ugyanúgy kell viselkednie, ezért alapszabály, hogy az assert()-ben elhelyezett kódnak semmilyen mellékhatása nem lehet az őt körbevevő kódra, ha mégis lenne (pl. létrehoz egy átmeneti változót), azt tilos kihasználni.

Jelenleg öt kapcsoló befolyásolja az assert() függvények működését, ezek közül mindenképpen az assert.active az elsőrendű, de a többiről is érdemes szót ejteni. Az assert.warning letiltásával és az assert.callback-ben saját üzenőfüggvény beállításával például élesben működő szkriptekben is be tudjuk kapcsolni a kiértékelést úgy, hogy arról csak mi, fejlesztők értesüljünk, míg a látogatók ebből semmit ne vegyenek észre.

Zárásul azonban álljon itt a fenti kezdetleges "assert szimuláció" bővített változata, ami PHP nyelven mondja el, hogy melyik kapcsoló mire való, és bemutatja az assert_options() függvény egyik használati módját is:

<?php
// csak egy képzelt szimuláció!
function my_assert ( $code ) {
  // ha nem ACTIVE, akkor nem vizsgáljuk a feltételt
  if ( !assert_options(ASSERT_ACTIVE) ) 
    return;
  // a kódkiértékelés közbeni programhibák elnyomása
  if ( assert_options(ASSERT_QUIET_EVAL) )
    $result = @eval("return $code;" );
  else
    $result = eval("return $code;" );

  if ( $result ) 
    return TRUE; // igaz feltétel esetén nincs dolgunk

  if ( assert_options(ASSERT_WARNING) ) // hibaüzenet küldése
    trigger_error("Assertion \"$code\" failed in ... ", E_WARNING);
  if ( is_string($callback = assert_options(ASSERT_CALLBACK)))
     $callback($code); // callback függvény meghívása
  // szkript leállítása, ha a feltétel nem teljesült
  assert_options(ASSERT_BAIL) and die();

  return FALSE;
}
?>

Előretekintés

A következő részben még elidőzünk egy kicsit az assert() további lehetőségein egy gyakorlati szempontból is hasznos függvénygyűjtemény kifejlesztése kapcsán, valamint megismerkedünk egy, a programrészek tesztelésére alkalmas PHP csomaggal.
 
1

assert()

Anonymous · 2004. Szep. 21. (K), 16.14
Sziasztok!

Nem rossz ez a függvény, de sajnos nem sok mindent lehet találni róla, ami ténylegesen használható lenne.

Kiéhezett szemekkel fogom figyelni a cikksorozat folytatását.

Üdv mindenkinek.

Anonymous
2

remélem ...

gerzson · 2004. Dec. 22. (Sze), 11.04
kedves Anonyomous, a sorozat második része már jóllakatta szemeidet, még ha ilyen sokat is kellett rá várni.
3

Valasz anonym-nak

durexhop · 2005. Jan. 27. (Cs), 00.12
www.php.net => Online Document..., de csak angol attol fuggetlenul szinte minden fuggv-hez van hozza szolas es nem is keves elegge hasznosak es altalanos problemakat is felfedik...