ניפוי שגיאות באמצעות print, warn, ומשפחת Carp#
פרק זה מכסה אבחונים לא־אינטראקטיביים: prints מוטמעים, dumps של משתנים, stack traces מתוך התוכנית. טכניקות אלה נשארות המסלול המהיר ביותר להבנה של ״מה הקוד שלי באמת רואה כאן?״ עבור באגים שזולים לשחזור.
קוראים שכבר מכירים את print ואת warn ימצאו כאן את הווריאנטים המובנים השווים את המאמץ: משפחת Carp לדיווח מצד הקורא, Data::Dumper והחלופות המודרניות שלו, ושורות־פקודה אחת ל־stack-trace בכל מקום.
warn מול print - בחירת הזרם הנכון#
warn כותבת ל־STDERR; print משתמשת ב־STDOUT כברירת מחדל. השתמשו ב־warn עבור אבחונים: היא שומרת על פלט תוכנית נקי, שורדת הפניית STDOUT מחדש, והיא הזרם שלוגרים כבר לוכדים.
warn "*** foo=[$foo] bar=[@bar]\n";
עטפו ערכים בסוגריים מילוליים כדי שרווחים גוררים ושורות חדשות יהיו גלויים בפלט. "foo=[$foo]" חושף foo=[ ] שבו "foo=$foo" מציג רק foo= .
ללא \n גורר, warn (ו־die) מצרפים at FILE line N. מנקודת המבט של הקורא. עם \n, הם לא. כלל אצבע: צרפו \n עבור הודעות הפונות למשתמש; השמיטו אותו עבור אבחונים למפתחים שעבורם נחוץ המיקום.
תגיות מיקום זולות מהקומפיילר:
warn "reached $0 " . __FILE__ . ':' . __LINE__ . "\n";
warn "in @{[__SUB__->name]}\n"; # 5.16+
משפחת Carp - דיווח מצד הקורא#
כאשר פונקציית ספרייה נכשלת, הצבעה על המקור של הספרייה עצמה כמעט אף פעם לא שימושית. משפחת Carp מדווחת מהמסגרת של הקורא:
פונקציה | מדווחת מ | fatal? | stack trace? |
|---|---|---|---|
| caller | לא | לא |
| caller | כן | לא |
| caller | לא | מלא |
| caller | כן | מלא |
use Carp;
sub open_config {
my $path = shift // croak "open_config: missing path";
open my $fh, '<', $path or croak "cannot read $path: $!";
return $fh;
}
קוד ספרייה צריך להשתמש ב־croak/carp, לא ב־die/warn - הודעת השגיאה מצביעה על שורת הקורא, ששם נכנס התיקון.
Stack traces מבלי לגעת בקוד#
Carp::Always מתקין את $SIG{__DIE__} ואת $SIG{__WARN__} כדי לנתב דרך Carp::confess / Carp::cluck. הריצו סקריפט מתנהג בצורה שגויה עם:
perl -MCarp::Always script.pl
כל die ו־warn מפיקים מחסנית מלאה. ללא שינויים במקור. זה הדבר הראשון לפנות אליו כשקריסה מספקת הודעת שורה־אחת חסרת שימוש.
Devel::Confess הוא מקבילה drop-in עם פחות מקרי קצה סביב עצמים בעלי overloading:
perl -d:Confess script.pl
שני המודולים מוסיפים עלות לכל warn ו־die; השתמשו בהם במהלך חקירה, לא בייצור יציב.
להתקנה קבועה בתוך הקוד:
use Carp;
$SIG{__DIE__} = sub { return if $^S; Carp::confess(@_) };
$SIG{__WARN__} = sub { Carp::cluck(@_) };
return if $^S; - ״לחזור אם אנו בתוך eval״. ללא שמירה זו, כל חריגה נתפסת משלמת את עלות העקבה. ראו exceptions עבור התבנית המלאה של $SIG{__DIE__}.
Dumping של מבני נתונים#
העבירו הפניה לכל dumper; ביצוע dump ל־hash או למערך חשופים מדפיס את הצורה השטוחה־כרשימה שלהם ומאבד את המבנה.
Data::Dumper - אוניברסלי, מודול core, ניתן ל־roundtrip דרך eval#
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
$Data::Dumper::Terse = 1;
$Data::Dumper::Deepcopy = 1;
warn Dumper(\%config);
Sortkeysהופך את הפלט ליציב ב־diff.Indent=1היא ההגדרה הקריאה (0 = שטוח, 2 = ברירת מחדל, 3 = עם אינדקסי מערך).Terse=1מדכא את קידומות$VAR1 =כשאינכם זקוקים ליכולת round-trip.Deepcopy=1משבית את המעקב ״ראיתי את ההפניה הזו קודם״ של$VAR1->{...}- מדפיס את הנתונים, לא שחזור של גרף.
Data::Dumper::Concise הוא drop-in עם ברירות מחדל הגיוניות מיושמות מראש; הסטנדרט בקהילת DBIx::Class / Moose.
Data::Printer - צבעוני, מודרני, קריא#
use DDP;
p %config; # not a reference - DDP handles both
Data::Printer מדפיס פלט נראה־טוב יותר מ־Data::Dumper, צובע לפי טיפוס, מטפל בעצמים באופן מיוחד (מחלקה, מאפיינים, overloads), ואינו מנסה לבצע round-trip. העדיפו אותו כאשר המטרה היא קריאת ה־dump, לא הזנתו מחדש למערכת.
Data::Dump - שורה־אחת קומפקטית#
use Data::Dump qw(dd);
dd \%config; # prints to STDERR in compact form
ה־Data::Dump של Gisle Aas. שימושי כאשר פלט ברירת המחדל של Data::Dumper מילולי מדי עבור שורת warn יחידה.
מתי לפנות אל מה#
Data::Dumperכאשר ייתכן שתזינו את הפלט בחזרה ל־evalאו תשוו שני dumps.Data::Printer(DDP) כאשר אדם קורא את הפלט.Data::Dumpכאשר רוצים סיכום בשורה אחת.Data::Dumper::Conciseבפרויקטים שכבר משתמשים בו.
debug prints מותנים#
קבוע של זמן־הידור עובר אופטימיזציה מלאה והוסר כאשר הוא שקרי:
use constant DEBUG => 0;
warn "state=$state\n" if DEBUG;
עם DEBUG => 0, הבדיקה if DEBUG מתקפלת בזמן הידור וה־warn אינה מופיעה מעולם ב־op tree - עלות אפס בזמן ריצה.
חברו לסביבה כדי שניתן יהיה להפוך את הדגל ללא עריכה:
use constant DEBUG => $ENV{APP_DEBUG} // 0;
ערוצים מרובים באמצעות bitmask:
use constant { WEB => 1, SQL => 2, REGEX => 4 };
my $DEBUG = $ENV{APP_DEBUG} // 0;
sub dbg { my $c = shift; warn "[DBG] @_\n" if $DEBUG & $c }
dbg WEB, "request $req_id start\n";
dbg SQL, "query: $sql\n";
dbg REGEX, "matched: $&\n"; # (unless you care about perf - see below)
חלופת source-filter: Smart::Comments#
use Smart::Comments;
my @stuff = compute();
### @stuff # prints: @stuff: [...]
### Checking key: $key # print, plus asserts $key is truthy
### Processing |=| ... for @stuff
שורות המתחילות ב־### נכתבות מחדש כ־prints (ועם תחביר מיוחד, פסי התקדמות ו־assertions). המודול הוא source filter - הוא משכתב את טקסט התוכנית לפני ההידור - ולכן מספרי שורות מדווחים יכולים לסטות בשורה אחת. כאשר אינו מיובא, הערות ה־### מתהדרות לכלום. הסטייה היא המחיר של אי־עומס הקוד בשורות warn מפורשות.
Taint ו־$& - עלויות לוואי שכדאי להכיר#
הפניה אל $&, $`, או $' בכל מקום בתוכנית מאלצת את perl לתחזק את הגלובלים האלה של הקשר התאמה עבור כל התאמת regex בתוכנית כולה, כולל התאמות בתוך מודולים טעונים. היסטורית, הדבר הטיל האטה ניכרת; ב־perls מודרניים העלות קטנה יותר אך אינה אפס, והכלל עדיין בתוקף: אל תגעו בהם אם ניתן להימנע מכך.
השתמשו ב־@- / @+ (מערכי מיקום של התאמה), לכידות בעלות שם, או במתאם /p עם ${^MATCH}, ${^PREMATCH}, ${^POSTMATCH} במקום. הווריאנטים של /p מטילים את העלות רק על התאמות שמצטרפות במפורש.
לוגינג עם חותמת זמן#
warn שנכתב ידנית עם localtime מספיק לחקירות חד־פעמיות:
use Time::HiRes qw(gettimeofday);
sub t { my ($s, $us) = gettimeofday; sprintf "%d.%06d", $s, $us }
warn sprintf "[%s] state=%s\n", t(), $state;
לכל דבר מובנה יותר, השתמשו ב־framework לוגינג במקום לפתח משלכם:
Log::Any- בטוח לספריות; לפלוט לוגים מבלי לבחור backend. שלבו עם מתאם (Log::Any::Adapter::Log4perl,::Stdout,::Syslog) ביישום.Log::Log4perl- קטגוריה / רמה / appender / layout; מונע על־ידי קובץ הגדרות.Log::Dispatch- נתב ברמה נמוכה יותר; משמש לעיתים קרובות תחתLog::Any.
רמות סטנדרטיות, מהנמוכה לגבוהה: TRACE, DEBUG, INFO, WARN, ERROR, FATAL. הגנו על TRACE / DEBUG מאחורי דגל סביבה בייצור.
למידע נוסף#
exceptions -
die,$@,$SIG{__DIE__}, מחלקות חריגה מטופסות, ה־try/catchהמודרני.interactive-debugger - כאשר הוספת prints עולה יותר מהפעלת
perl -d.tracing - עקבה של תוכנית שלמה שורה־אחר־שורה ללא עריכת המקור.