Artykuły na każdy temat

[PHP] Debugowanie czyli odnajdywanie błędów

Dodano 26.10.2009r. o 00:24

Spis treści:

  1. O debugowaniu słów kilka
  2. Fazy debugowania
  3. Metody szukania
  4. Izolowanie źródła błędu
  5. Identyfikacja przyczyny usterki
  6. Usuwanie błędu
  7. Przykłady

O debugowaniu słów kilka

Debugowanie jest procesem, który ma na celu doprowadzić do zredukowania błędów w oprogramowaniu. W językach wysokiego poziomu ma to miejsce przy użyciu debugera (czyli programu, który będzie kontrolował proces). Nadzorując aplikacje, która działać w czasie rzeczywistym możemy wykonywać kod linijka po linijce, obserwować kod od wybranego miejsca bądź zostawić pułapki, które uaktywnia się po wykonaniu konkretnego segmentu kodu. W PHP sprawa jest prostsza. Nie musimy kontrolować kodu linijka po linijce lecz odpowiednio reagować na komunikaty błędu. Znając znaczenie błędu w ciągu paru sekund możemy naprawić każdorazowo jeden błąd. Trzeba wspomnieć, że błędy nie musza ujawnić się odraz lecz mogą pojawić się po pewnym czasie (kwestia budowy skryptu). Może się zdarzyć tak, że programista zauważy błąd w czasie testów lecz może być też także, że jakiś użytkownik zauważy wadliwe działanie kodu. Wtedy sprawa się komplikuje nieco gdyż czas działa na nasza niekorzyść.

Fazy debugowania

  1. Pierwsza, najistotniejsza faza debugowania jest wykrywanie miejsca, w którym występuje defekt.
  2. Druga faza jest identyfikacja błędu i tego co go powoduje, jakie czynniki.
  3. Trzecia faza jest usuwanie usterki.
  4. W czwartej fazie jest weryfikacja czy naprawdę miało miejsce naprawienie błędu poprzez dokładne testy.

Metody szukania

Jak wyżej napisałem, błąd może zostać wykryty przez programistę bądź użytkownika. Jeżeli programista wykrywa bug'a to ma zadanie wykryć miejsce błędu czyli plik (linijki), nazwa funkcji bądź metody oraz poznanie wszystkich parametrów, które wpływają na nieporządne działanie danego fragmentu kodu. Dla użytkownika sprawa jest nieco prostsza. W przypadku gdzie nie ma zaimplementowanej metody informowania o błędach, użytkownik musi jedynie podać datę zdarzenia, okoliczności oraz lokalizację strony, ma której zauważył nieprawidłowość. Dodatkowo jeżeli strona wyrzuciła komunikat błędu to mile widziane będzie podanie go.

Izolowanie źródła błędu

Gdy posiadamy już informację o błędzie pierwsza czynnością jaka musimy zrobić jest wyeliminowanie wszystkich czynników, które nie wpływają bezpośrednio na powstawanie błędu. Bardzo ważne jest abyśmy już w tym momencie mieli pewność, które czynniki na pewno nie wpływają na nieprawidłowe działanie.

Identyfikacja przyczyny usterki

Odnajdywanie przyczyny usterki odbywa się poprzez obserwację danych wyjściowych i danych używanych wewnątrz danej metody. Następnie skrupulatnie krok po kroku dochodzi się do miejsca, w którym konkretna funkcja nieprawidłowo spełnia swoją rolę.

Usuwanie błędu

Gdy już wiemy gdzie leży problem musimy jedynie go usunąć np. poprzez manipuluję parametrów błędnie wykorzystanej funkcji, poprawienie łańcucha zgodności (REGEX) bądź dodanie walidacji nieprawidłowych informacji, etc.

Przykłady

Zanim przejdę do omawiania poszczególnych błędów wyprzedzę nieliczne przypadki, w których mogą nie występować komunikaty błędu. W takim przypadku trzeba sprawdzić w konfiguracji (php.ini) czy error_reporting oraz display_errors posiadają odpowiednie wartości. Więcej informacji tutaj. Chcę także wspomnieć o tym, ze bardzo istotna rzeczą jest znajomość angielskiego. To dzięki niemu szybko dotrzemy do wniosku co jest źle. Wystarczy zobaczyć na pierwszy przykład i wynieść wnioski ze słowa unexpected czyli niespodziewany. Ostatnią rzeczą jaka chce dodać tutaj jest fakt, że zanim zapytamy powinniśmy użyć wszystkowiedzącego wujka google!

Bill Gates napisał(a):

Jeżeli nie można czegoś znaleźć w Google, to to po prostu nie istnieje

unexpected T_ECHO badź T_STRING, deklarowanie zmiennych

Kod:
<?php
$zmienna 'text'

echo $zmienna;
?>
Oczywiście w przykładzie powyżej brakuję średnika po stworzeniu zmiennej. Aby pozbyć się tego błędu należy jedynie go dokleić.
Kod:
<?php
$zmienna 'text';

echo $zmienna;
?>
T_STRING wystąpi wtedy gdy użyjemy funkcji print_r() zamiast echo().

Nieprawidłowe łączenie ciągów

Dość często popełniany błąd przez nowicjuszy. Korzystając z okazji wyjaśnię różnicę miedzy cudzysłowem pojedynczym, a podwójnym. W pojedynczym cudzysłowu $zmienna pozostanie jedynie zestawem znaków, a w podwójnym zostanie zinterpretowana i do tworzonego stringu zostanie wrzucona wartość $zmienne. Drugą różnicą jest to, że w cudzysłowu podwójnym znaki cytowane ulegają interpretacji. Przykłady poniżej.
Kod:
<?php
$dodatkowa_zmienna 'php';
$ciag_znakow 'test \r\n test $dodatkowa_zmienna';

echo $ciag_znakow;
?>
Wynikiem tego kodu powinno być:
Kod:
test \r\n test $dodatkowa_zmienna
Teraz przykład z podwójnym cudzysłowem.
Kod:
<?php
$dodatkowa_zmienna 'php';
$ciag_znakow "test \r\n test $dodatkowa_zmienna";

echo $ciag_znakow;
?>
Wynikiem powyższego kodu powinno być:
Kod:
test
 test php
Znak podwójnego cudzysłowa wewnatrz " i "
Kod:
<?php
$ciag_znakow "test znak " i mamy problem";

echo $ciag_znakow;
?>
Jak widzimy powyżej składnia "nieprawidłowo" zaczęła nabierać kolorów. Oto solucja jak poprawić powyższa deklaracje.
Kod:
<?php
$ciag_znakow "test znak \i już nie ma problemu";

echo $ciag_znakow;
?>
Analogicznie ta sama sytuacja dotyczy '. Mieszanie ' i " jest dość częstym problemem spotykanym na forach. Szczególnie często występuję w zapytaniach SQL.

Kod:
<?php
$sql 'SELECT obiekt FROM tabela WHERE family = 'Niespodzianka'';

echo $sql;
?>
Jeżeli zbudowane przez Ciebie zapytanie wyrzuca błąd bądź nie zwraca wyników wyrzuć je za pomocą echo() podobnej funkcji bądź użyj funkcji mysql_errno() (w przypadku używania MySQL). W powyższym kodzie nieprawidłowo połączono ze sobą '. Oto optymalny sposób sformułowania zapytania:
Kod:
<?php
$sql "SELECT obiekt FROM tabela WHERE family = 'Niespodzianka'";

echo $sql;
?>
Jeżeli uprzemy sie przy stosowaniu pojedynczego cudzysłowu to tylko musimy odpowiednio używać znaku \ przed '
Kod:
<?php
$sql 'SELECT obiekt FROM tabela WHERE family = \'Niespodzianka\'';

echo $sql;
?>
Kolejnym błędem jest używanie jakiś niespotykanych wzorców przy łączeniu ciągów i zmiennych.
Kod:
<?php
$extra 'extra';
$zmienna 'test '$extra' test '$extra;

echo $zmienna;
?>
W powyższym przykładzie zapomniano oczywiście o kropce miedzy zmienną a ciągiem. Poniżej poprawny sposób:
Kod:
<?php
$extra 'extra';
$zmienna 'test '.$extra.' test '.$extra;

echo $zmienna;
?>
Powyższy kod można także przedstawić w nieco innej formie.
Kod:
 <?php
$extra 'extra';
$zmienna "test $extra test $extra";

echo $zmienna;
?> 
Czasami zdarza się, że musimy dodać cos do zmiennej. W takim przypadku robimy to w ten sposób:
Kod:
<?php
$zmienna 'test';
$zmienna .= ' extra';

echo $zmienna;
?>

Dyrektywa global w funkcjach

Czasami zdarza się, że musimy mieć dostęp do zmiennej z zewnątrz. Są dwie metody. Pierwsza to przekazywanie danych przez parametry funkcji. Druga metoda oznacza zastosowanie dyrektywy global. Oto prosty przykład jak to działa.
Kod:
<?php
$zmienna 'text';

function test()
{
 global $zmienna;

 return $zmienna.' cos extra';
}

echo test();
?>
Jeżeli usuniecie global wtedy wyświetli się tylko cos extra

failed to open stream

Najczęstsza wina takiego stanu rzeczy jest nieprawidłowa ścieżka, zła lokalizacja do zdalnego pliku bądź nie odpowiadający serwer. Na początku omówimy kwestie związana z otwieraniem nieistniejącego pliku za pomocą funkcji fopen().
Kod:
<?php
$uchwyt fopen('./plik.txt''r');
?>
Powyższy kod wygeneruję błąd No such file or directory jeżeli plik nie istnieję. Aby zapobiec takiemu zdarzeniu trzeba dodatkowo użyć funkcji file_exists().
Kod:
<?php
$plik './plik.txt';

if(!file_exists($plik))
{
 echo 'Plik nie istnieje';
}
else
{
 $uchwyt fopen($plik'r');
}
?>
Gdyby plik nie posiadał odpowiedniego chmodu to dodatkowo dostalibyśmy komunikat Permission denied. Dlatego też trzeba się dobrze zastanowić w jaki sposób będziemy operować na plikach.

fsocketopen() i unable to connect

Mamy sobie taki kod, który doprowadzi do błędu ze względu na to, że próbujemy połączyć się z nieistniejącym serwisem.
Kod:
<?php
$uchwyt fsockopen("www.example.com"88$errno$errstr2);

if(!$uchwyt)
{
 echo "$errstr ($errno)";
}
else
{
 echo 'dalsze operacje';
}
?>
Przypuśćmy iż host i port są prawidłowe to jednak przy przekroczeniu limitu czasowego funkcja także wywali błąd. W tym przypadku mamy dwie opcje. Jedna z nich to zmiana poziomu raportowania błędów w php.ini. Druga opcja to proste stłumienie błędów za pomocą @ przed nazwa funkcji.

headers already sent by

Mój ulubiony błąd. Najczęściej spotykany na wszelkiego rodzaju forach. Taki komunikat mówi nam o tym, że przed wysłaniem nagłówka (np. rozpoczęciem sesji, wysłaniem ciastka) zostało już coś wysłane do przeglądarki. Aby zapobiec temu zjawisku trzeba usunąć wszystkie znaki wędrujące do browsera przed operacjami, które wysyłają dodatkowe nagłówki do przeglądarki bądź zastosowanie buforu, patrz ob_start() i ob_end_flush(). Powyższy błąd dość szczegółowo opisałem w artykule pod tytułem [PHP] headers already sent by.

Błędy logiczne

Czasami zdarza nam się, że popełnimy błąd logiczny tj. taki kiedy podamy nieprawidłowe warunki dla instrukcji warunkowej. Przykład poniżej:
Kod:
<?php
$x 17;
$y 22;

if($x 17 && $y 22)
{
 /* ten warunek nigdy nie zostanie spełniony */
}
?>

Wyjątki w składni

Pamiętajmy aby nie zostawiać przez przypadek ; po pętli. Czasami zdarza się, że edytor sam dokleja. Poniższy przykład wyrzuci tylko 5.
Kod:
<?php
for($h 0$h 5; ++$h);
{
 echo $h.'<br />';
}
?>

Dozwolone znaki w nazwach zmiennych

Pamiętajmy, że w nazwach zmiennych i tablic nie możemy używać niedozwolonych znaków. Wolno używać a-z, A-Z, 0-9 (z wyjątkiem początku nazwy) oraz _ dodatkowo możemy użyć znaków z ASCII od 127 do 255.

Liczymy od 0 a nie od 1

Pamiętajmy, że w językach programowania indeksy tablic zaczynają się od 0 a nie od 1.

Brakujące klamerki w instrukcjach warunkowych

Czasami zdarzy się ze gdzieś brakuje klamerki. Gdy kod jest mocno rozbudowany to znalezienie takiego błędu doprowadzi do straty czasu. Dlatego tez warto stosować wcięcia w kodzie i starać się aby kod był jak najbardziej czytelny.
Kod:
<?php
$zmienna 'test';

if('test' != 'test')
/* ucinamy { */
 echo 'cos sie dzieje';
}

$zmienna2 'test';
?>
Powyższy kod wskaże nam 7 linię. W tym przypadku detekcja będzie prosta. Jednak co się stanie gdy utniemy klamerkę końcową?
Kod:
 <?php
$zmienna 'test';

if('test' != 'test')
{
 echo 'cos sie dzieje';
/* ucinamy } */

$zmienna2 'test';
?> 
Wskazało nam błąd w linii 11. Zabawne gdyż ten kod posiada tylko 10 linii. Właśnie w takich przypadkach mamy przysłowiowy pasztet szczególnie kiedy plik liczy parę tysięcy linijek. Dlatego ważne jest robienie częstych kopii zapasowych przez edytor. Potem unikniemy takich problemów jeżeli nagle okaże się ze gdzieś popełniliśmy błąd i nie potrafimy go zlokalizować. Możemy oczywiście próbować znaleźć błąd przez echo. Przed błędem dana treść zostanie wyświetlona lecz po błędzie już nie.

Perfekcja

Testy, testy i jeszcze raz testy. Bez nich szybko wejdziemy na minę. Dlatego też ważne jest abyśmy starali się na bieżąco testować kod w rożnych warunkach. Ważne jest aby samemu szukać rozwiązania, a nie czekać na to, że ktoś z forum znajdzie je za Ciebie!

Własna obsługa błędów

Aby tego dokonać tworzymy własna funkcje, a następnie używamy funkcji set_error_handler() aby nakierować na funkcje obsługująca własne błędy.
Kod:
<?php
function wlasny_blad($errno$errstr)
{
 echo '<b>Blad:</b> ['.$errno.'] '.$errstr.'<br />Blad zalogowano';
 exit;
 /* tutaj można dodać funkcje logująca takie przypadki */
}

set_error_handler('wlasny_blad');

$uchwyt fopen('cokolwiek''r');
?>

Wyjątki

Poniższy kod przedstawia metode zastosowania wyjątkow w PHP.
Kod:
<?php
function dzielenie($a)
{
 if($a !== 0)
 {
  return 1/$a;
 }
 else
 {
  throw new Exception('Dzielenie przez zero.');
 }
}

try
{
 echo dzielenie(1).'<br />';
 echo dzielenie(0).'<br />';
}
catch (Exception $e)
{
 echo 'Złapano wyjątek: '.$e -> getMessage();
}
?>

Komentarze

Publikowane komentarze są prywatnymi opiniami użytkowników serwisu. Serwis nie ponosi odpowiedzialności za treść opinii. W trosce o zachowanie poziomu dyskusji wszystkie komentarze podlegają akceptacji przed ich publikacją dlatego proszę cierpliwie czekać aż komentarz zostanie opublikowany.

CapaciousCore

Dodano 01.02.2011r. o 15:45
Dla potomnych:

Być może ta funkcja może się Wam także przydać debug_backtrace().

uki

Dodano 01.02.2011r. o 15:41
Warto zapoznać się z opisem funkcji set_error_handler() znajdującym się w manualu:
http://php.net/manual/en/function.set-error-handler.php
Co ważne - istnieje grupa błędów, których niestety nie skierujemy do obsługi przez naszą własną funkcję.

Z tego co widzę w manualu, ta funkcja ma głównie zastosowanie jako alternatywa wyjątków. Zamiast w środowisku try{}catch(){} użyć throw new Exception(); można wywołać trigger_error() i złapać taki błąd własną funkcją.

uki

Dodano 01.02.2011r. o 10:00
Własna obsługa błędów to niezła ściema... Nie da się wyłapać wszystkiego: http://php.net/manual/en/function.set-error-handler.php

Dodaj komentarz

Zostaw komentarz jeżeli możesz! Nie bądź przysłowiowym botem! Nie bądź obojętny! Ciebie to nic nie kosztuje, a mi sprawi uśmiech na twarzy.
Zezwolono używać: BBCode
Zabroniono używać:
znaczników HTML

(Wymagany)

(Wymagany, niepublikowany)

(Nie wymagana)

Token:

Obrazek dla bota

(Przepisz tylko cyfry!)

(Wymagana)