10 בינו׳ 2009

איך כותבים קוד כשאין debugger

אני זוכר איך זרחו עיני בפעם הראשונה שפתחתי את הדיבגר (יש לזה מלה בעברית?) של בורלנד turbo-c וחוויתי את מה שהיה נדמה לי בזמנו כחוויה מיסטית. לראשונה בחיי יכלתי לראות למשתנים המקומיים שלי ישר בלבן של העיניים, יכלתי לבדוק את הערכים שלהם, לשנות את הערכים שלהם, והייתי בעננים! ולא רק משתנים מקומיים, כי אם גם משתנים גלובליים, רחמנא ליצלן. ולא רק משתנים - יכלתי גם לעשות single-step ואפילו break-point. מדהים!!! לא עוד טבלאות עם משתנים והרצות של התכנית על יבש עם עדכון ערכים סזיפי, לא עוד מחקים ממוללים על שולחני, דור חדש של כלי פיתוח עמד על מפתני - דור הדיבגר!

מאז אותו רגע ועד היום מים רבים זרמו בקישון, turbo-c התחלף ב Borland C++ 3.0 אשר התחלף בבוא העת ב Visual Studio לגרסאותיו השונות, ומאוחר יותר לכלי יוניקס/לינוקס כדוגמת GDB.

ואז גם הגיעו ימי ה Java העליזים עם Net-beans, Idea, Eclipse ועוד ועוד וכל סביבה חדשה כזו באה עם עושר נוסף של יכולות להסתכל לקוד ישר בעיניים ולא לפחד.
אפשר לחזור אחורה בריצה, אפשר להסתכל על כל חוט (thread) בנפרד, אפשר לעקוב אחרי ערכים של משתנה אחד ספציפי, ליצור break-point עם תנאים שונים, לדבג תהליך מרוחק שנמצא אולי ביבשת אחרת, ועוד ועוד ועוד - ממש לונה-פארק למתכנתים.

וכמו בכל הסרטים עם מוסר ההשכל, מתישהו האידיליה היתה צריכה להגמר, כמאמר הפרסומאים "מה עשינו שמגיע לנו כל הטוב הזה?". היתה תחושה שבכלל לא צריך לכתוב קוד טוב, בכלל לא צריך לחשוב על הקוד, מספיק היה לשרבט איזה משהו שנראה שהוא נכון, שעובר קומפילציה, לפתוח דיבגר ואז ללמוד מה שהקוד שלי באמת הולך לעשות. ברצינות, זה נהיה כל כך קל לדבג, לתקן את הקוד תוך כדי הרצה, שפשוט כבר לא היה עוד טעם בלוודא שהקוד נכון לפני שמריצים אותו, לוודא שהאלגוריתם נכון לפני שמממשים אותו (שוב המילה הזו עם 3 הממים בתוכה). לא היו כרטיסיות נייר שהיה צריך לנקב, לא היו תורים לקומפיילר כמו בימי אבותינו, הכל היה כל כך קל, כל כך פשוט ליירט את הבאגים עד שנהייתי מפונק, נהייתי מושחת, לקחתי את כל הטוב הזה וסובבתי אותו נגדי. כי מה היה יוצא בסוף מתוכנית שלא חושבים עליה מספיק? אז נכון שאחרי כמה סשנים קצרים עם ידידי, משחיתי, הדיבגר, היא היתה מתחילה לעבוד, אבל תכנית כזו, השם ירחם, לא הייתי רוצה שתשלוט על הטייס האוטומטי במטוס שבו אני טס.

ואז באה הישועה ממקום בלתי צפוי. התחלתי לעבוד בשפה שאין לה דיבגר! התחלתי לכתוב ב actionscript2, שפה בעייתית מעוד הרבה סיבות אחרות, ובין השאר גם דיבגר אין לה. (ליתר דיוק יש, אבל א' הוא די פאתטי ו ב' אין מצב להשתמש בו בקונפיגורציה שבה אני עובד).
חצי שנה ראשונה קיללתי וקיטרתי - מי המציא את השפה המזעזעת הזו, למה הקומפיילר שלה כל כך מאעפן, ולמה לעזזל אין דיבגר?!
אחרי שלב הכעס בא שלב ההשלמה. הבנתי שאין דיבגר (וגם הקומפיילר לא משהו) אבל עם זה צריך להתמודד - ומאז יצאתי מחוזק.
אז איך כותבים קוד אם יודעים שאין דיבגר? איך חושבים עליו? איך משתכנעים שהוא רץ נכון אם לא רואים אותו צעד אחר צעד מתקדם בכיוון שייעדנו לו? שאלות אלה העסיקו אותי ולאט לאט גיליתי שאני משנה את הרגלי הקידוד שלי.
התחלתי לכתוב קוד כמו שצריך (טוב, אני מקווה...) להשקיע הרבה מאמץ במחשבה תחילה, לארגן את הקוד יפה יפה, לעשות תכנות דפנסיבי, כלומר אף פעם לא להניח כלום ותמיד לבדוק הכל ואולי החשוב מכל - לבדוק את האם-אמא של הקוד ע"י מימוש של עשרות או אולי מאות unit-tests. יצא שזמן כתיבת הבדיקות משתווה לזמן כתיבת הקוד האמיתי, ובמקרים מסויימים אני גם כותב את הבדיקות לפני המימוש (test driven development).

טוויסט נוסף בסיפור הגיע אחרי שלפני כחודשיים יצא לי לדבר עם אחד מעמיתי למשרד והסתבר לי שלמרבה הפתעתי שהוא מתנגד לשימוש בדיבגרים באופן עקרוני, ולמרות שהוא כותב בשפות שקיים להן דיבגר עוד משחר ההסטוריה הוא מתנגד באופן עקרוני לשימוש בהם. אני, שסבלתי כל כך מהמחסור בדיבגר ב as2 לא הצלחתי להבין איך הוא מוכן לוותר על כלי כל כך יעיל, ולא סתם מוכן - ממש מתנגד אליו באופן עקרוני וחושב שאף אחד לא צריך להשתמש בו. היתה לי תחושה שיש משהו בגישה הזו, אבל לא הצלחתי להבהיר לעצמי האם היא נכונה, או יותר נכון, להסביר לעמצי למה היא נכונה. אז קראתי לו לבירור ושאלתי אותו - מה זאת אומרת שאתה מתנגד לשימוש בדיבגר? והוא הסביר לי את כל התאוריה על רגל אחת. (תוספות ספרותיות - עלי - להגברת הדרמה)

הוא אמר לי - תראה - באגים יש לכולם, אין אחד שכותב בלי באגים, אבל איך ממשיכים משם זה כבר עניין של בחירה. כל דקה שאתה משקיע בלדבג את התכנית שלך היא דקה מבוזבזת, היא לא דקה מושקעת, או אולי במילים אחרות, היא השקעה בהווה, השקעה לטווח קצר, אבל לא השקעה לעתיד.
ופירש - יש באג? יופי! זה הזמן להפשיל שרוולים ולהתחיל לעבוד.

שלב א - קרא את הקוד שלך והבן אותו. רוב הבאגים נפתרים כך. אתה לא מבין את הקוד שלך? הקוד מסובך מידי? לא קריא? אבל אתה כתבת אותו (טוב, לא תמיד אתה, אבל לא חשוב, עכשיו אתה הבעלים שלו). אם ככה יש לך בעיות גדולות הרבה יותר מהבאג הבודד שנמצא זה עתה - יש לך קוד שאתה לא מבין. עבור לשלב ב.

שלב ב - חזור על הפעולת הבאות: עשה לקוד refactor לפונקציות קטנות יותר, למחלקות סגורות יותר ותעד את האם-אמא של מה שקורה. כעת חזור אל שלב א והבן את הקוד. עדיין לא מובן? חזור לשלב ב. זכור! אתה לא רק מתקן את הבאג הזה הנוכחי, אתה גם משקיע בעתיד טוב יותר, אתה משקיע בקוד יותר פשוט, קריא יותר, שבו יהיה קל ומהיר יותר לפתור באגים עתידיים. זו השקעה לטווח רחוק ואתה עוד תברך על הרגע הזה.

שלב ג - אם עוד לא עשית זאת קודם, זה הזמן לכתוב unit-tests. אין דבר טוב מזה. באמצעות unit-test אתה לא רק תמצא את הבאג הזה באופן אוטומטי, אתה גם תמצא באגים עתידיים באופן אוטומטי. זכור שעוד לא נולדה פיסת הקוד שנכתבה רק ע"י בן אדם אחד. גם אם היום אתה הוא היחידי שמתעסק בקוד שלך, אתה יכול להיות בטוח שבעתיד להקות של מתכנתים יעשו כמיטב יכולתם לשבור אותו על ימין ועל שמאל ואתה רוצה להגן עליו, להשאירו בחיים ולהגן על שמך הטוב. אתה לא רוצה להיות זה שאומרים עליו מאחורי הגב "הנה - הוא כתב את הקוד המצ'וקמק הזה שנשבר לנו על ימין ועל שמאל". בדיקות יחידה הן הדרך הטובה ביותר להשיג עמידות לאורך זמן.




אז זו התאוריה על רגל אחת - לקרוא, להבין, לפקטר ולבדוק. הגישה היא אולי סזיפית, אולי מעט מינימליסטית, אבל כמו הוראות הבטיחות הצהליות, היא נכתבה בהרבה דם ואין טעם לשפוך דם נוסף כדי להגיע אל אותן מסקנות (/יזע/דם/s). אם תסתמכו על דיבגר תמצאו את עצמכם מכבים שריפות כל היום. אם אתם רוצים לבנות תכנה יציבה ולאורך זמן כדאי להפסיק ולהתפנק, קצת משמעת עצמית ולשכוך מהשטן בלבוש המלאך.

ואמרו אמן.

תגובה 1:

אנונימי אמר/ה...

כמו בכל דבר בחיים קיצוניות לשום כיוון אינה מועילה.

ניתן להשתמש ביעילות בדיבגר ולאזן את זה עם בדיקות יחידה לחלקים הדורשים זאת באמת.