4 חטאים של פיתוח תוכנה בן-זמננו [דעה]

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

כלל אצבע אומר שכל עשור שחולף – גורם לכחצי מהידע שלנו להפוך לנחות או לא-רלוונטי.
כלומר: יש להשקיע כמות מסוימת של למידה רק בכדי להישאר, יחסית, באותו המקום.

מכאן נוצרת נכונות רבה ללמוד, אבל הרבה פעמים קשה למצוא תוכן משמעותי ללמידה:

  • בספרי תכנות, למשל, הפרקים המאוחרים (לעתים מאוגדים כ \"Advanced Topics\") הם לרוב נושאים פחות שימושיים ביום-יום. ביום פקודה – אפשר להשלים את הידע נקודתית. זו גישה מאוד הגיונית.
  • היתרון מלהכיר עוד Frameworks הולך ופחות ככל שאתם מכירים יותר Frameworks. אם אני מכיר כבר שני Web Frameworks בצורה טובה – איזה יתרון באמת יהיה לי מללמוד את השלישי?!
  • אפשר ללמוד אינספור כלים וספריות, אבל אם לא עובדים בהם בצורה משמעותית – זה יידע שלא יעשה בו שימוש ו/או יישכח במהרה.
  • ישנם נושאים קצת יותר רחוקים מכתיבת הקוד עצמו, אך מספיק שונים בכדי לספק לנו \"קרקע בתולית ללמידה\". הרבה פעמים יש להשקיע בהם השקעה משמעותית מאוד – עד שנראה תמורה אמיתית ביום-יום שלנו. למשל: Machine Learning, מערכות מבוזרות, או Big Data. לא בטוח שזה אפיק משתלם עבור רוב אנשי-התוכנה.

—-

אני רוצה להציג תוכן משמעותי ללמידה, בדמות 4 מיומנויות שנמצאות בחסר בתעשייה.

כנראה שלפחות 3 מהמיומנויות הללו נמצאות ברמת \"השורה בקורות החיים\" אצל רוב המפתחים, אבל לרוע המזל הן לא מצליחות לממש את הפוטנציאל שלהן בפועל: אנחנו עושים אותן – עם הרבה חוסרים משמעותיים.

מיומנות איננה \"ידע\", ולא ניתן ללמוד אותה מתוך ספר או קורס אונליין.
הדרך היחידה לפתח אותה – היא תוך כדי עשייה. הדרכה ולמידת התאוריה בצורה מעמיקה – הם תומכים ראויים לפיתוח המיומנות.

אני מלא אמונה שיש פה ״ארביטראז\'״ – פער שניתן לנצל לטובה!
מיומנויות שחלקן לא קשה כ״כ לפיתוח, הנמצאות במצב חסר ברור בתעשייה – ובעלות פוטנציאל נהדר לתשואה גבוהה.

במילים פשוטות: אם אתם משקיעים את הזמן שלכם בלמידה / פיתוח עצמי – כנראה שפיתוח המיומנויות הללו יועיל לכם הרבה יותר מכל מיני נושאים אחרים.
הן לא \"נוצצות\" – כי לכאורה לכולן יש אותן, אבל מי שמפתח אותן ברמה טובה יוכל לכתוב תוכנה טובה, מה שבד\"כ הסביבה יודעת לזהות – ולהעריך (אפילו אם לא בדיוק לאפיין למה זה יותר טוב).

אז מה יש לנו?

TDD – איך כותבים בדיקות מוצלחות, ואיך כותבים קוד שקל לכתוב לו בדיקות מוצלחות.

\"נו, ליאור – הגזמת! כ-ו-ל-ם, אבל כולם בתעשייה כבר יודעים לעשות TDD, ולא רע. לפעמים זה עובד טוב יותר, לפעמים טוב פחות – אבל זה נושא שכבר מוצה!\"

אני חולק על כך, ובגדול. אני חושב שגם אחוז גדול מאלו שמרגישים שכתיבת Programmer Tests עובדת אצלם טוב – עושים את זה בצורה מאוד לא יעילה.

אני משתמש במונח ״TDD״, על מנת להדגיש שמדובר בכתיבת קוד – ולא בבדיקות ידניות או ״כלי אוטומציה״ (נוסח Selenium). הטכניקה של כתיבת בדיקות לפני הקוד – היא לא העיקר מבחינתי. העניין הוא לכתוב בדיקות טובות.

מה הן בדיקות טובות? יותר קל לי להציג אלמנטים נפוצים של בדיקות לא טובות:
  • אנשים מגזימים בכמות הבדיקות המערכתיות (System Test, Integration Tests) על חשבון בדיקות ממוקדות. גלידה ולא פירמידה. זה כ״כ נדוש ושחוק, אך עדיין – טעות שממשיכה ונעשית.
  • אנשים לא מבודדים Pure Business Logic משאר הקוד לצורך unit tests – ואז באמת קשה מאוד מאוד לכתוב ולקבל את היתרונות של unit tests.
    • נתקלתי הרבה פעמים במצב הזה, וזו בעיה שיחסית קל לתקן, ברגע ש״נופל האסימון״ – ומשנים גישה.
  • אנשים כותבים יותר מדי קוד בדיקות – מה שמאט את העבודה שלהם, ומקשה על ביצוע שינויים במערכת:
    • גם בדיקות שהן overfit למימוש ארעי (situational), כלומר תנאי שמתקיים – אך אינו חשוב ועקרוני לפעולת המערכת / הביזנס. בהמשך הוא ישתנה, לא תהיה בעיה עסקית – אך הבדיקות יפלו וידרשו עדכון.
    • גם בדיקות שהן יתירות (בודקים את אותו הדבר שוב ושוב באופנים שונים). כל שינוי של מימוש קוד – ידרוש סדרה של שינויים בקוד הבדיקות – מה שיגרום לנו לרצות לעשות פחות שינויים.
      • יעילות מגיעה מניהול סיכונים נכון: האומץ לצמצם את כמות הבדיקות (לא לכתוב בדיקות מסוימות), מתוך הבנה אלו בדיקות חשובות ומשמעותיות יותר.
  • אולי הכי גרוע: בדיקות ועוד בדיקות שנכתבות (ומתוחזקות!) מבלי שהן מגרדות את פני השטח. הן בקלות יכולות לעבור – בזמן שמשהו עקרוני ולא טוב קורה ב flow. בקיצור: בדיקות לא-משמעותיות.
    • זכרו: אם הבדיקות שלכם אף פעם לא נשברות – זו לא סיבה לגאווה. זה אומר שבזבזתם את הזמן בכתיבת בדיקות שלא אומרות כלום.
  • אנשים שהתייאשו מבדיקות ו״למדו״ (אבוי!!) – שבדיקות הן נושא overrated ומיותר.
    • זהו מצב שמאוד קשה להתאושש ממנו.
לסיכום: Programmer Testing הוא כלי כ\"כ חשוב, כל כך יישומי, ושמביא תוצאות כ\"כ טובות ומהירות (כשעושים אותו נכון), ועדיין – רק אחוז נמוך בצורה מבהילה של אנשי-תוכנה באמת שולט בפרקטיקה הזו.

המחשבה שאם אתם מכירים את הספרייה שאיתה עושים בדיקות (JUnit5, Jasmine, RSpec, Sinon), אזי אתם יודעים \"לכתוב בדיקות טובות\" – היא שגויה מיסודה. חפשו את העלות/תועלת: כמה השקעה יש בכתיבת ותחזוקת הבדיקות – מול כמה זמן הן מקצרות בתהליכי ה debug ותיקון שגיאות.

כשאני עושה Code Review – אני מחפש קודם את הבדיקות.
אם הבדיקות טובות, אני לא צריך ״לחפור״ ולהבין עד הסוף את האלגוריתם – הבחינה שהבדיקות מכסות את המקרים החשובים בהצלחה – היא היא ההבטחה שהאלגוריתם עובד.
להחליט בוודאות שאלגוריתם עובד מתוך קריאת הקוד בלבד – הוא משהו שאני עדיין לא יכול לעשות, גם אחרי 15 שנות קוד. לא נראה לי שזה דבר אפשרי בכלל. הקוד נראה הגיוני – אך תמיד ייתכן מקרה קצה שבו הוא נכשל…

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

בהזדמנות אחרת, בחור קולני שמאוד חיבבתי ניסה להסביר לי ש״אני מבזבז את הזמן שלי בכתיבת הבדיקות – וחבל. יותר מהר זה לכתוב ולתקן ע\"פ הצורך״. הוא דאג לי. אז עשינו ״תחרות״: בחרנו אלגוריתם שהיה צריך לכתוב, בכל מקרה ושנינו כתבנו אותו במקביל: אני עם בדיקות – הוא בלי. סיימתי את כתיבת הבדיקות והקוד – בזמן שהוא עוד היה עסוק ב debugging של הקוד שלו, שלא עבד בכל מיני מקרי קצה.

זה לא שהייתי מקודד טוב יותר – זו הטכניקה שעבדה.

Refactoring אקטיביסטי

הנה עוד דבר שעשוי להישמע מעליב: ״אני לא יודע לעשות Refactoring טוב מספיק? יש לך מושג כמה פעמים כבר עשיתי Refactoring? מה הבעיה בלעשות Refactoring?״

אני רוצה להדגיש מימד שקצת שנשכח לגבי Refactoring: האקטיביזם.

כמה פעמים לקחתם קוד וביצעתם בו שינוי, ולאחר השינוי – הקוד היה פחות יעיל, היו בו חלקים מיותרים, או הוא היה פחות קריא?

המון! מן הסתם.

ברגע הזה, שקורה לרובנו על בסיס יומי, עומדות בפנינו שתי ברירות:

  • להמשיך הלאה.
    • יש לי מנהל עם ״סטופר״ שיבוא בתלונות אם לא אדלוור פיצ\'רים מהר.
    • יותר גרוע: שינוי בקוד הוא סיכון ליצירת באג. אם יש באגים שנוצרו על ידי – אני יוצא לא-טוב. (איפה ה Unit tests שלכם עכשיו, הא?)
  • לבצע Refactoring ולהחזיר את הקוד לרמה אופטימלית X (כלומר: רמה טובה, אבל לא מושלמת. שלמות היא בזבוז).
    • Refactoring אינו צריך, ועדיף שלא יהיה \"פרויקט ענק\". הוא יכול להיות בכל commit שלישי.
    • אם שומרים על רמת קוד טובה לאורך הזמן – יהיה הרבה פחות צורך בפרויקטי ענק.
אז מה אתם בוחרים?
לרוע המזל רוב אנשי-התוכנה בוחרים ב\"דרך הבטוחה\". זה עובד? – אז לא לגעת! 
אשמה גדולה היא בקרב המנהלים, שהם קצרי רוח לזמני ביצוע של פיצ\'רים והופעות של באגים – אך יש להם מספיק סבלנות ל\"פרויקטי תחזוקה\", ופיצ׳רים פשוטים / חקירות באגים שמתארכות לאורך ימים.
החטא של המפתחים הוא שהם תורמים את חלקם למעגל המזיק הזה – ובעצם פוגעים באינטרסים שלהם.
התמריץ לשמר את הקוד ברמה \"אופטימלית X\"  הוא לא רק עניין של ערכים \"אני בעד קוד יפה\", חלילה!
יש פה אינטרסים מעשיים:
  • קוד שמתוחזק ברמה גבוהה – יאפשר להוסיף פ\'יצרים נוספים בצורה קלה ומהירה יותר, ועם פחות תקלות.
    לאורך הזמן השאלה צריכה להיות: האם אתם רוצים לעבוד בקוד מתוחזק, או בקוד \"עולם שלישי\"? באיזו סביבה אתם חושבים שתתפתחו, אישית – בצורה טובה יותר?
  • כאשר בוחשים בקוד – רמת העומק וההבנה האישית שלנו את הקוד, ומה שקורה בו – צומחת בקצב אחר לגמרי.
    • אני לא יכול להדגיש זאת מספיק: מי ששובר את הקוד (או לפחות מסתכן בשבירה) – הוא מי שמבין אותו לעומק. \"לשבת על הברזלים\" זו אסטרטגיה נוחה לטווח הקצר – אך נחותה לטווח הארוך.
עוד אלמנט חשוב הוא היכולת שלנו לראות כיצד הקוד יכול ללבוש צורות שונות – והיכולת להעביר את הקוד בקלות מצורה לצורה: אולי functional? אולי לולאת foreach? אולי break ואולי exceptions.
  • בעיות שונות בקוד יפתרו באלגנטיות רבה יותר בעזרת צורות שונות של קוד. 
    • כאשר אנשים מקובעים לתבנית אחידה / סגנון אחיד – זה מגביל!
    • לאנשים רבים, גם כאלו עם ניסיון של שנים – חסרה ממש הגמישות הזו: קשה להם לקרוא ולהבין קוד בסגנון שונה, והם חוזרים וכותבים קוד בצורה \"שהם רגילה אליה\" – גם במקרים בהם היא מסורבלת וקשה לקריאה.
  • Refactoring תכוף – הוא דרך נהדרת ללמוד ולהתנסות בצורות קוד שנות. זה האינטרס האישי שלכם!
    • שווה לציין גם טכניקה בשם \"Coding Dojo״ שאמורה לפתח מנעד רחב יותר של סגנונות קוד:
      • מתכנסים כמה אנשים בחדר ופותרים תרגיל קוד קטן כאשר מחליפים ידיים כל פרק זמן נתון (מעבירים את המקלדת מאדם לאדם). עוד נוהג הוא לעשות את אותו התרגיל – מספר פעמים. בכל פעם – תהיה תוצאה קצת אחרת.
      • נ.ב. אני נוטה להאמין שיעילות המפגש שכזה היא ביחס ישיר לאדם המוכשר ביותר בסגנונות קוד שנוכח בו.
    בקיצור: האם יש ״סגנון שנוח לכם איתו״ ורק בו אתם כותבים?
    האם אתם עסוקים ב\"לשמור על הקונבנציות\" יותר מאשר לחשוב ולהבין איזו צורה של קוד היא הטובה ביותר לבעיה?
    האם אתם \"חכמים מספיק לא להתעסק עם קוד שעובד\". מעתיקים מדוגמאות קוד אחרות במערכת – מבלי לצלול לעומק מדוע הקוד הזה עובד?

    Refactoring הוא לא אמור להיות שם-קוד לפרויקט מזוויע של תשלום חוב טכנולוגי (Technical Debt) ענק.
    Refactoring בצורה המוצלחת שלו – הוא דרך חיים: להזרים \"חמצן\" לקוד כל הזמן, ולהתפתח באופן אישי – כל הזמן.
    כמו דברים רבים אחרים: לעשות בצורה תכופה מעט Refactoring תפתח אתכם באופן אישי הרבה יותר – מביצוע פרויקט ענק אחת לפרק זמן ארוך.

    האם אתם משקיעים 10% מהזמן שלכם, בשיפור קוד קיים? – אני ממליץ לחתור לשיפור קטן כל יום.

    Design to Go

    \"ביצוע Design הוא תהליך מתמשך, ולא עוד שלב במחזור פיתוח התוכנה (כמו שפעם חשבו ב Waterfall)\".
    כולנו יודעים לדקלם זאת – אבל רבים מאתנו לא עושים זאת.
    אנחנו מפספסים:

    • עבודה ב Small Batches.
    • יצירה של Short and Effective feedback cycles.
    • בחינת אלטרנטיבות – מתוך ההבנה שיש יותר מדרך משמעותית אחת לסדר קוד ו/או לפתור בעיה.
      • ״קו האפס״ הוא פתרון יחיד שעובד – ומשם משפרים. 
      • אחרת: אנחנו עובדים על עצמנו. לא משנה כמה מלבנים ציירנו בדרך.
    • כאשר ״תקיעה״ בתהליך הדזיין, מובילה אותנו לוותר עליו – במקום לעבור ל Exploration.
    כבר דיברתי הרבה בנושא בהרצאה שלי ברברסים. אין טעם לחזור.
    מקור: Integrating and Applying Science\" (pg. 136) – http://ian.umces.edu/press/publications/259/

    Modeling

    Modeling היא לא פרקטיקה נפוצה בקורות החיים של אנשים. 

    המונח ״Medling״ כנראה מובן לרוב האנשים, אך הוא לא נתפס כנושא בעל חשיבות עליונה – שכדאי לפתח.

    • הזכרנו כבר שנקודת מפתח ב Design היא בחינת אלטרנטיבות.
      • החלק המשמעותי באלטרנטיבות הללו הוא לא ״אובייקט גדול״ מול ״שניים קטנים״ – אלא מידול שונה של האובייקטים העסקיים. למשל: ״תשלום, הכולל ניסיונות תשלום״, מול ״נסיונות תשלום הכוללים תוצאה״.
      • ״גמישות לדרישות עתידיות״, ו״פשטות״ הם BuzzWords – אך הם גם סופר-משמעותיים במבחן התוצאה. 
        • מודל פשוט וטבעי לביזנס – יכול בהחלט להכפיל את התפוקה של הצוות.
          מיומנות מעטות בעולם התוכנה עשויות לגרום להשפעה (impact) רבה שכזו!
      • היכולת לעשות modeling נכון נובעת מניסיון תמידי להבין את הביזנס והצרכים + הפעלה של חשיבה ביקורתית.
        • קל לצייר בראש מודל – שלא ממש מתאים לביזנס. חשוב לתקשר ולאמת אותו.
        • לא תמיד אנשי הביזנס יתחברו למודל – וחשוב גם לנסות ולאתגר אותם.
      • Modeling לא נעשה רק בשלב דזיין – אלא גם כתהליך refacotring, שינויים קטנים כל הזמן.
      • Modeling מתקשר בד״כ למידול של אובייקטים עסקיים, אך הפרקטיקה נכונה גם למודל טכני (מודל concurrency, מודל eventual consistency, מודל sevurity):
        • שואלים ומאתגרים כל הזמן מה הם הצרכים
        • מנסים למצוא מודל פשוט ואלגנטי ככל האפשר, פשוט ע״י איטרציות של שיפורים במודל.
        • מתקשרים את המודל – כך שיהיה רעיון משותף, ולא ״מחשבה פרטית״.
      • איך לומדים לעשות מודלינג?
        • ע״י צפיה בדוגמאות של מודלים. למשל הספר PEAA (דוגמה יפה: המודל של Money), או הספר המעולה (אך קצת מיושן): Analysis Patterns – של אותו המחבר.
        • ע״י בניית מודלים והפקת לקחים אישיים.

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

      הקושי היחידי הוא באמת בצבירת מיומנות. זו מיומנות שדורשת עשרות או מאות ניסיונות – על מנת להתמחות בה. כמה שתתחילו יותר מוקדם – כך מוטב.

        ה Killer instinct

        ה Killer Instinct הוא משהו שקשה לאפיין בדיוק, אבל יש את אותם מפתחים מעטים שמצליחים להבין ולקלוע אילו השקעות / פיצ׳רים / שיפורים יהיו משמעותיים לביזנס: אם מדובר בפיצ׳ר עסקי או פיצ׳ר טכני שחשוב ל״ביזנס״ של קבוצת הפיתוח.
        רוב האנשים מפספסים את זה שוב ושוב ונוטים להשקיע במקומות הלא נכונים ו / או מתחילים שינוי שנראה מבורך – אך לא מסוגלים להניע אותו בצורה מהירה.

        כשאתם נתקלים ב״Killer Instinct״ – קשה להתעלם ממנו.

        • זה השילוב של הבנת ביזנס, חשיבה ביקורתית, קריאה נכונה של הארגון (מי מדבר שטויות, מי יודע), קצת חוצפה (ממי להתעלם, למי להתייחס), והאומץ לבצע שינויים / לכתוב קוד שיש לו חסרונות ברורים – לצד יתרונות ברורים, כמובן.
          • תמיד נתקלתי ב Killer Instinct בצמידות לנטייה לגעת בקוד ולשנות אותו. חוסר פחד, ביחד עם סקרנות ורצון לחולל שינויים.
            אני נוטה להאמין שיש פה גם אלמנט של סיבתיות: הניסיונות הקטנים לשפר את הקוד -> יוצרים הבנה עמוקה של הקוד (עם הזמן). הבנה עמוקה של הקוד -> מאפשרת את ה״מאסה הקריטית״ של העומק – הדרושה בכדי לבצע שינויים משמעותיים במערכת בזמן קצר.
        • ״להתעסק״ עם הקוד בלי שיש בדיקות טובות – לא כדאי. הקוד ישבר, וההתעסקות תהפוך לעניין כואב ומתסכל.
        • הבנה עמוקה של הקוד, ללא הבנה של הביזנס – עשויה לפספס את האימפקט:
          אתם עושים שינוי עמוק במערכת, שאף אחד לא האמין שאפשרי – אבל אז גם לאף אחד לא אכפת הוא נעשה, כי הוא פשוט לא מעניין.
        • בכדי ליצור אימפקט, חשוב להבין את הביזנס. הבנה של הביזנס נבנית מתוך Modeling.
        • בכדי שהתוצר יהיה טוב יותר, ומשמעותי גם לאורך זמן – חשוב גם לדעת איך לעשות Effective Design.
        האם זה מספיק? האם זה המתכון הסודי והמובטח לשחרור ה״Killer Instinct״?
        לא. מן הסתם זה גם עניין של אופי: חוצפה/תעוזה, הרבה אכפתיות ורצון עז להשפיע.

        בוודאי יש גם דרכים נוספות להגיע לאימפקט גבוה.

        שיהיה בהצלחה!