2019-09-14

האם תואר אקדמי במדעי-המחשב הוא עדיין רלוונטי?

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

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


התוכניות הנחשבות (בארה"ב) ללימודי מדעי-המחשב. המעוזים שיהיו האחרונים של התואר האקדמי?


הפתרון הטוב ביותר, לטעמי, בשלב זה הוא שהמדינה תשנה את תארי ה B.A או B.Sc להיקף של שנתיים לימוד במקצועות הנדרשים בתעשייה (ואפשר גם להשאיר "תואר B.A / B.Sc מורחב" בן 3 שנים - למען תאימות אפשרית לעולם, כל עוד זו לא תהיה ברירת המחדל).

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

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

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

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

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

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


אבל אולי בעצם "לימודים אקדמיים הם לעומק - ולימוד במקום העבודה הוא ספציפי לצורך המיידי, ללא עומק"?
אני רוצה להתייחס לטיעון הנפוץ, ולא-מדויק הזה:
  • ה"עומק" הוא הרבה פעמים שם קוד לתאוריה לא מעשית, או "לבסיס" כ"כ low level שהוא פשוט לא רלוונטי לרוב הגדול של העוסקים במקצוע. למדנו באוניברסיטה תקשורת והתמקדנו בפרוטוקולים הנמוכים (שכבה פיסית עד IP), אבל כמעט כל הנדסת התוכנה היא סביב HTTP כאשר רק מעטים מתעסקים באמת ב IP (שלא לדבר על שכבות נמוכות יותר). אין בעיה היום לפתוח את האינטרנט וללמוד מהו ARP או איך נעשה Routing ב Ethernet.
  • גם כשלמדתי באקדמיה נושאים מסוימים - את העומק האמיתי שלי השלמתי בתעשייה. לפעמים התאוריה האקדמית הייתה מנותקת מדי מהמציאות בכדי להיות שימושית. לקחתי קורס מורחב בבסיסי-נתונים, אבל את העקרונות החשובים שלהם - למדתי רק בתעשייה. כנ"ל לגבי מערכות הפעלה, ונושאים נוספים.
  • חוק תכנותיקה אחד קובע שחצי מהידע שלנו בתחום יהפוך ללא רלוונטי כל עשור. בכל מקרה, עלינו ללמוד כל הזמן - ויעיל יותר להתמקד בלמידה לאחר שבחרנו תחום ואנו יכולים לבחור את הידע הספציפי. יש הבדל גדול בין כתיבת מערכות (System/Infrastructure), לכתיבת מערכות נתונים (Enterprise Systems), כתיבה ב Frontend או ב Backend. שום "לימודי עומק" באקדמיה - לא "יצילו" אותנו מהצורך בלמידה מתמדת.
  • בני-אדם קולטים ידע טוב יותר מתוך צורך ממשי. כל הבניית התוכן בצורת "מבואות ויסודות" - הוא פשוט לא צורת הוראה יעילה. הדרך הנכונה היא להתחיל בלימוד משהו מעשי - ורק בהמשך להגיע ליסודות ולמבואות.
  • יותר ויותר מקומות עבודה היום יתנו לכם היום את החופש ללמוד ולהתפתח ע"פ בחירתכם, וינגישו לכם גם לימודי-עומק. תפקידם של הלימודים האקדמיים כ"מספקי העומק" הולך ומתפוגג גם מהבחינה הזו.

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


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



הזמנה שקיבלתי, לעשות תואר Master אונליין בשנה. שימו לב שזו אוניברסטית Salford ולא Stanford ...


מה הידע הנדרש?


מה באמת מהנדס תוכנה ממוצע צריך לדעת כיום?
  • שפת תכנות אחת לפחות (עדיף מאוד שתיים, בכדי לקבל פרספקטיבה!) - יש בתואר, למרות שלרוב לא מגיעים במהלך התואר לעומק משמעותי בשפה.
  • תרגול בכתיבת קוד - יש בתואר, אך לא מספיק. אין בעצם כמעט פידבק משמעותי על הקוד שנכתב - ולכן זו במעט למידה ב"ריק" [א].
  • מבני-נתונים וסיבוכיות - מכוסה היטב (עם מקום קל לשיפור). זהו נושא חשוב, אבל בשל הספריות העשירות היום - שימוש מעשי בידע הופך לפחות נפוץ עם השנים (בתקופת ה ++C - היה צורך כמעט-יומיומי בידע הזה).
  • בסיסי נתונים, תקשורת - מכוסה בצורה חלקית עד סבירה, תלוי במוסד. פעמים רבות אלו קורסי בחירה - וחבל, כי רוב המהנדסים יידרשו לידע בתחומים הללו.
  • בדיקות אוטומטיות - נראה שמכוסה הצד הטכני היבש, ופחות המתודולוגי. זה עדיין תחום שלומדים אותו במקום העבודה.
  • מקביליות - כנראה מכוסה, לרוב באופן תאורטי מדי. גם זה ידע חשוב, אם כי לא בשימוש יומיומי נפוץ.
  • דוגמאות למבנים מעניינים בתוכנה, ארכיטקטורות, דפוסי-עיצוב (Design Patterns) - מכוסה ברמות שונות. מהנדסים צעירים נוטים בצורה גורפת להשתמש בדפוסי-עיצוב באופן לא מושכל ומוגזם - ולסבך את המערכת.
  • עקרונות של הנדסת תוכנה - DRY, POLA, High Cohesion/Low-coupling, SOLID/GRASP, וכו׳ - לרוב מכוסים בצורה בינונית עד סבירה, עם דגש חסר פרופורציות ל DRY (העיקרון הקל ביותר להסביר).
  • קריאות ופשטות של תוכנה - העניין בהחלט מוזכר, אך גם בהחלט לא נלמד. רוב או כל הקוד שהסטודנטים כותבים לא דורש תחזוקה, ולא זוכה לפידבק משמעותי. שוב: עבודה ב"ריק".
  • התנהלות והתארגנות אישית - כיצד לארגן את העבודה בצורה הגיונית, ניהול פרויקטים בסיסי, התנהלות ארגונית, כיצד להשפיע על אנשים אחרים, וכו'. לתואר אקדמי אין נגיעה לעולם העשיר הזה (למרות שיש כאן לא מעט ידע תאורטי שניתן ללמוד).
  • נושאים טכניים מגוונים שונים כמו: סטטיסטיקה בסיסית, מערכות הפעלה, חומרת המחשב, וכו' - פעמים רבות נלמד במהלך התואר, אם כי מהסיכומים היה נראה לי שהמיקוד לא כ"כ מעשי. בהחלט אלו נושאים שאפשר להשלים מאוחר יותר. רוב הנושאים הללו ישמשו רק חלק מהמהנדסים - במהלך חייהם המקצועיים.
התנהלות והתארגנות אישית הוא כנראה הנושא הגדול והחסר ביותר למהנדסים צעירים. חלקם דווקא מגיעים עם רמת התנהלות לא רעה, ידע שספגו בבית או בחיים עוד קודם למקום העבודה הראשון. לכאורה אין ציפייה למיומנויות או ידע בתחום מתואר אקדמי, וכך הבוגרים - תמיד עומדים בציפיות או אפילו מפתיעים לטובה.

קריאות ופשטות של תוכנה היא ככל הנראה נקודת החסר הגדולה ביותר של מהנדסים צעירים בצד הטכני. בעצם הם עדיין לא מהנדסים - הם עדיין מתכנתים. אחרי 3-4 של לימודים, הם עדיין לא חוו הנדסת תוכנה. כלים תאורטיים שנתנו להם (DRY, Design Patterns) - משמשים בשנים הראשונות לפגוע בקריאות ופשטות התוכנה. הכל מכוונה טובה, כמובן.



מקור: מאקו. כן, הרגע קישרתי פוסט למאקו. גם אני מופתע.


אז מה עושים?


אני קורא לכל המוסדות האקדמיים בארץ, להעביר את כל הקורסים החשובים למקום העבודה - במהלך השנתיים הראשונות של התואר. כך שסטודנטים יוכלו להקדיש את השנה השלישית שלהם ללמידה בעבודה, אפילו במחיר הזנחת הציונים של השנה השלישית - ע"פ הצורך. למשל: היה טוב מאוד לתעשייה לו כל קורסי המתמטיקה בתואר - היו עוברים לשנה השלישית [ב].

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

====

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

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

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


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

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

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

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

"למדת שנה - בוא נראה כיצד אתה פותר תרגילים" - הוא עדיין כלי הרבה יותר מוצלח לבחירת עובדים מהתבססות על IQ, ציון פסיכומטרי, או ראיון אישי.


אז מה עושים, כמה סיכון כדאי לקחת? אולי הסיכון הזה בעצם לא כ"כ גדול?

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


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


---

[א] ריק הוא ריק גם אם אינו מוחלט.
[ב] לא. הם לא באמת נדרשים בכדי להבין את החומר. אם רק היו מתאמצים קצת - היו מוציאים אותם מהמשוואה.

2019-07-29

על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019? - חלק ג׳


פרדיגמת התכנות פונקציונלי


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

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

מה שהחזיר אותה לזירה לפני עשור בערך, היא הדאגה מריבוי ה cores במעבדים. רבים חזו שיידרש שינוי פרדיגמות משמעותי בעולם התוכנה - הרי עשרות שנים היו רגילים לחשוב בעיקר על Execution Thread יחיד, ופתאום אנו עוברים לעולם מרובה-cores (תכנות HPC, ו UI - כן חשבו על כמה threads, כבר לאורך שנים רבות).

אני זוכר את הדיבורים, לפני עשור או עשור וחצי, על כך שאוטוטו - לא נוכל לכתוב קוד יותר כפי שכתבתנו. שכל מתכנת יצטרך להתמחות ב Fork-Join או Patterns דומים של מקביליות, אחרת הקוד לא יהיה יעיל. אחד התוצרים של הדאגה הזו הייתה להחזיר את התכנות הפונקציונלי לשיח בתעשייה: כאשר עובדים עם מבני-נתונים שהם Immutable - קל הרבה יותר לכתוב קוד שירוץ בצורה מקבילית.

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

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




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

ניתן לראות ששפות OO פופולריות רבות - הולכות ומאמצות רעיונות FP. אפשר לציין את סקאלה, קוטלין, או ג׳אווה 8 - שם רעיונות ה FP זוכים לתהודה ותשומת-לב, אבל שימו לב שרעיונות דומים אומצו גם בשפות כמו רובי, פייטון, וג׳אווהסקריפט כבר לפני עשור או שניים - והשתלבו היטב עם רעיונות OO.

גם בשפות ״פונקציונליות טהורות״ (כלומר: שפות בהן ניתן דגש רב על רעיונות של FP) - יש אלמנטים של OO. למשל, ב Haskell יש הכמסה ברמת המודול (מודול לרוב הוא בגודל של קובץ בודד, והוא מקביל במידה מסוימת למחלקה בשפות OO) - וזו תוספת חשובה מאוד לשפה. ב Haskell יש גם ריבוי-צורות (Polymorphism), מן הסתם ברמת הפונקציה - שזה המבנה העיקרי בשפה.

הכמסה + ריבוי-צורות, ובלי הורשה (השנויה במחלוקת) - אפשר לרגע להתבלבל ולחשוב ש Haskell היא בכלל שפת OO מודרנית?

בקיצור: אין סיבה שלא נאמץ גם ספרות וגם שירה - הם לא מתחרים זה בזה, ולא צריך באמת לבחור. כנ"ל לגבי OO ו FP.



אז מה שפות FP חידשו לנו?



Functional Style

לפעמים אוהבים להתייחס ל FP כענף של התכנות הדקלרטיבי. האם זה נכון?

ובכן, קשה להשוות FP לשפות דקלרטיביות כמו SQL. ב SQL אנו מתארים כוונה עם מעט מאוד הַכוונה - ו"מישהו" דואג שהיא תתבצע. בשפות פונקציונלית מתארים כוונה עם הרבה מאוד הַכוונה - וברור מאוד כיצד הדברים עומדים להתבצע. אני חושב שיש אכן טעם לעשות הפרדה בין Imperative Style לבין Declarative Style לבין Functional Style - שהוא משהו באמצע.

אפשר לומר שמקום לומר למעבד מה לעשות צעד אחרי צעד (אימפרטיבי) - ב FP אנחנו אומרים לו מה לעשות שלב אחרי שלב 😀

ב Functional Style מחליפים (בגדול) משפטי if במשפטי filter, לולאות במשפטי map או רקורסיות, ובמקום להשתמש במשתנה result שיצבור את התשובה של הפונקציה - משתמשים ב fold.

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

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

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



שרבטתי דוגמה לפונקציה, שכנראה תהיה פשוטה יותר לכתיבה ב Imperative Style.
אפשר (כמובן) לכתוב אותה גם ב Functional Style - אבל בסיכוי גדול הקוד יהיה מסובך (״מתוחכם״) וקשה יותר להבנה:

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



Immutability

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

Immutability משמע שהערכים ומבני הנתונים שקבענו לא ישתנו מרגע שנוצרו. מנקודת מבט של שפות אימפרטיביות, זה אומר שכל הערכים / השדות בשפה הם final, const, או איך שזה נקרא בשפה הספציפית.

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

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

שפות פונקציונליות מספקות לרוב סט של מבני-נתונים שהם Immutable, והגדרת משתנים (ערכים / שדות שיכולים להשתנות) - הם היוצאים מהכלל.

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

Immutability גורר שכל שינוי state כולל העתקה - ויש לכך מחיר בבצועים, ולפעמים מחיר ב boilerplate code - תלוי בשפה, אך בעיקר כאשר עושים שינויים עמוקים במבני-נתונים מורכבים ומקוננים.

משחק מחשב שהוא Realtime, היכן שהרבה state משתנה כל הזמן ויש עניין של ביצועים ותגובתיות - הוא לא מקום טוב להחיל בו Immutability.
.
עוד וריאציה חשובה של Immutability היא הדפוס של Event Sourcing - שמירת רשימת השינויים באובייקט, מבלי לשנות את האובייקט עצמו, והרכבה מחדש שלהם בכל פעם שצריך לגשת לאובייקט. כיסיתי את הדפוס הזה בפוסט עבר.



No Side Effects

רעיון מרכזי ב FP הוא רעיון ה "Pure Functions", פונקציות טהורות - עם אפס השפעה על הסביבה.

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

הרבה פעמים, האמון הזה נשבר. אם הפונקציה לא בנתה את הציפיות הנכונות ואני מגלה שדברים הם אחרת - זו הפתעה, ולרוב לא טובה. אם הפונקציה הפתיעה בכך שהחזירה רשימה ממוינת בלי שזה צוין - אין פה ממש נזק, כי לא בניתי ציפיה לסדר. אם הפונקציה עשתה משהו משמעותי שלא ציפיתי, שינתה state חשוב במערכת - זה עלול להיות סוג של Ohh fuck moment - וזו בעיה. הפתעות מהקוד הן מקור ברור לבאגים, הרי באג = המחשב עושה מה שנאמר לו, ולא מה שהמתכנת התכוון.

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

חלק משמעותי מההפתעות הגדולות שקורות בהפעלת פונקציה נובעות משינוי state של אובייקט אחר (קריאה לגיטימית לכאורה, לממשק הציבורי של האובייקט), שליחת מייל או הפעלת תהליך אחר לא צפוי. אלו בעצם side effects, ו side effects משמעותיים.

בפרדיגמת ה OO כלי משמעותי למניעת הפתעות ממש מאותו הסוג הוא Encapsulation: את ה state הפנימי של האובייקט ניתן לשנות רק במסלולים ״המקובלים״, מה שמצמצם משמעותית הפתעות. זה חשוב מאוד - אבל לא תמיד מספיק. הפתרון של FP הוא לחתור לכך שכמעט כל הפונקציות במערכת יהיו pure functions - פונקציות שבשום אופן לא משנות משהו מחוץ ל scope הפנימי שלהן, ולא מסתמכות על שום דבר מעבר לארגומנטים שנשלחו להן - וכך אנו מגבירים מאוד את הסיכוי שהן יהיו צפויות. הפעלה של Pure Function, מספר פעמים ובזמנים שונים - תמיד תציג אותה תוצאה, כאשר נשלחים לה אותם הארגומנטים.

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

האם זה לא Functional Decomposition? אותו דפוס שלילי שדיברנו עליו בפוסט הקודם?
ישנם הבדלים: Functional Decomposition קלאסי פעל על State גלובלאי בו ביצעו כל הזמן שינויים - וזה מאוד לא צפוי.
כן יש פה עניין של חלוקת אחריות מפוזרת ולא ברורה בפוטנציה, ולכן תראו הרבה פעמים שמערכות המושפעות מ FP עדיין משתמשות בחלוקה למודולים (או מחלקות, בשפות OO+FP) והקפדה על סידור הגיוני של המערכת.
אם אני לא יכול למצוא בקלות קוד שעושה משהו, סביר שאכתוב אותו מחדש => כפילות קוד.

בקיצור: בהחלט יש מה להיזהר כאשר מאמצים למערכת "ארכיטקטורה מבוססת pure functions ומבני-נתנים", ולנסות לא להיגרר ל Functional Decomposition. את זה עושים בעיקר ע״י ארגון של הקוד ע״פ נושאים ותחומי אחריות - היכן שעקרונות ה OO יכולים בהחלט לעזור.

יש עוד כמה יתרונות חשובים לפונקציות טהורות:
  • מאוד טבעי לכתוב להן בדיקות יחידה. כלל חשוב שהתווסף ב TDD עם השנים הוא לחלץ ״לוגיקה עסקית טהורה״ לפונקציות / מחלקות נפרדות - כדי לכתוב בדיקות-יחידה סופר-יעילות, וללא Mocks. זה בדיוק מה שקורה ב Pure Functions.
  • פשטות במקביליות. כאשר כותבים קוד מקבילי, state משותף הוא נקודת כאב מרכזית: אנחנו צריכים לנהל אותו, לנעול אותו, וגם להימנע מהשלכות שליליות של locks כגון deadlocks ופגיעה במקביליות - וזה לא קל. דווקא קל מאוד למקבל פונקציות טהורות - אם כי לא תמיד זה מוביל לביצועים הטובים ביותר (בשל ההעתקות הרבות הנדרשות, ובשל אי-ניצול של memory locality).
  • Memoization - אם פונקציות הן טהורות, אזי ניתן בקלות ובבטחה לעשות caching לתוצאות החישוב. אם פונקציה היא טהורה, ומבני-הנתונים הם Immutable אז ניתן להחליף את הקריאה לפונקציה בתוצאת החישוב המוכנה מראש - מבלי לשנות בכלל את התוכנה. העיקרון הזה נקרא גם referential transparency. כמובן ש caching הוא לא תמיד יעיל, למשל - אם יש פרמוטציות אפשריות רבות להפעלת הפונקציה.



כמה שאלות ותשובות:
  • האם באמת אפשר לכתוב מערכת רק מפונקציות "טהורות"? לא. כל מערכת חייבת Input/Output בכדי שתהיה לה משמעות. כלומר: יש את הבעיה הקטנה שמה שלקוחות המערכת צריכים ממנה - הם side effects. אז אי אפשר בלי side effects.
    • ״במערכות על טהרת ה FP״ מנסים למקסם את אחוז הפונקציות הטהורות במערכת. כאשר מדובר ב batch processing (סוג בעיה שהרבה פעמים מפתחים עם FP) - אז פשוט אפשר להעביר את ה state בין הפונקציות מההתחלה עד הסוף - הוא לרוב מספיק קטן בכדי לא לאבד עליו שליטה / ששכפול שלו יהיה Overhead גדול מדי.
      • גישה אחת, היא ליצור Queues שיקבלו הוראות על שינוי state (למשל: שינוי בבסיס הנתונים) ואז באמת יהיו כמה פונקציות, שמאוד ברור מי הן - שרק מבצעות "side effects". שולפות הודעות מה Queue - ומבצעות את השינויים.
        • Actors הוא מודל מקביליות שתומך בגישה הזו.
      • גישה אחרת, היא לאגור את ה state המשתנה במעין ״טרנזקציה״: הפונקציות יתרמו לשינוי state, אבל הוא לא יחול על לנקודה מאוד ברורה בקוד ובזמן. הגישה הזו עוזרת להתמודד עם עניינים כמו מקביליות / racing conditions של עדכון ה state או state מורכב שקשה לעדכן ע״י הודעות.
    • שווה לציין שלא רבות המערכות שקמות ״על טהרת ה FP״. זה עובד נחמד ב Batch Processing (היכן שזה פשוט) - אבל זה יכול בקלות להסתבך. 
    • גישה שנראה לי שהולכת ותופסת תאוצה היא פשוט לסמן בצורה ברורה (coding conventions, annotations, וכו׳ - למשל IO Monand) אלו פונקציות אינן טהורות (impure) וכך לתאם ציפיות. אלו הפונקציות להיזהר מהן. כמובן שלא תמיד אפשר לכתוב מערכת שרוב הפונקציות בה הן impure ואולי יותר נפוץ, בגישה OO+FP מעורבת, לסמן פונקציות שהן pure - על מנת שיהיה אפשר לסמוך עליהן יותר.
  • באם באמת כל הפונקציות שלא משנות state / ניגשות לנתונים חיצוניים - הן טהורות? לא בדיוק. למשל: כתיבת הודעת לוג מתוך פונקציה - הופכת אותה "רשמית" ללא טהורה. גם כאן, הפרגמטיות היא במידתיות. מבחינתי, אפשר להחשיב גם פונקציות עם side effects זניחים כ "פונקציות טהורות". אם נתפלסף, אזי גם לפונקציה שמחברת שני משתנים יכול להיות side effect. למשל: הוא גורמת לעדכון caches בתוך ה CPU. בואו לא נגזים.
  • מדוע אומרים שפונקציות אסינכרוניות הן לא טהורות?  נראה לי שזה ענין של קורולציה. בד"כ מפעילים פונקציות א-סינכרוניות עבור פעולות I/O (שזה side effect ברור), ולכן בדרך כלל הן לא טהורות. לא נראה לי שיש משהו "לא טהור" בפונקציה מעצם כך שהיא מורצת בצורה א-סינכרונית.


Function Composition

בגישת ה FP מדברים על "higher order functions״ כלומר - פונקציות שמקבלות פונקציות בתור פרמטר - על מנת להרכיב פונקציות מורכבות יותר.

למשל: רוב הפונקציות המאפשרות Functional Style הן צורה פשוטה של Function Composition. הפונקציות map או filter מקבלות פונקציה (״למבדה״) שבעזרתן הן עושות את הפעולה.

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

Function Composition הוא גם מקור לצמיחת סיבוכיות, ופיזור קוד.

אין לי דוגמה לשלוף, אז שוב שרבטתי משהו מהיר (ושוב: בשפת קוטלין):


הנה דוגמה ל pure function שמצליחה להיות לא צפויה ולא ברורה.
כבר אמרנו שבכל כלי טוב - ניתן להשתמש בצורה גרועה?

קריאת קוד הפונקציה לא מלמד אותי מה היא עושה - הכל תלוי בפונקציות ששולחים לה כארגומנטים.
גם אם אגש מה call-site (הנקודה בקוד בה מפעילים את הפונקציה) ואראה מה שולחים לה -  לא תהיה ברורה התוצאה, כי הקשר בין customerFilter ו selectionLogic הוא לא ברור-מאליו, בטח לא עם ״התוספות״ ששתלנו, כמו בדיקת ה balance ואם הלקוח פעיל.

האם מישהו כותב פונקציות כאלו?!
כן. זה קורה. מתוך אהבה ל FP אנשים מזהים דפוסים חוזרים בקוד - ומוציאים אותם ל composer functions שאמנם חסכו כמה שורות קוד כפולות - אך הפכו את הקוד למאתגר להבנה ותחזוקה.

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


טיפוסים גמישים

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

השורה הזו מגדירה מבנה נתונים חדש של עץ בינארי בשפת Haskell.
הלוגיקה של הכנסה / הוצאה / חיפוש - דורשת עוד עבודת קוד ניכרת, אבל המבנה עצמו קיים.
חשבו כמה קוד צריך כדי לבצע את אותה ההגדרה בשפה verbose כמו ג׳אווה...

עוד טיפוס שימושי מאוד בשפות פונקציונליות הוא ה Enum. ה״פילוסופיה של FP״ היא לא להשאיר דברים ליד המקרה בזמן הריצה. לא להשתמש ב nulls, ולא לעבוד עם exceptions - הקוד שנכתב צריך לכסות את כל המקרים בזמן הקומפילציה.
על Haskell נאמר ש״אם הקוד מתקמפל - אזי התוכנה עובדת״ - בדיוק בשל אימוץ הגישה הזו.

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



סיכום


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

אומר זאת שוב: אין עניין של בחירה ב OO או FP. שמעתי דיונים כאלו כמה פעמים - והם פשוט מטופשים. זה כמעט כמו לדון מה יותר טוב: HashTable או Vector/ArrayList?
את שניכם אתם רוצים שיהיו בסט הכלים שלכם, וכל דיון צריך להיות למקרה הספציפי והבעיה שאתם מנסים לפתור. יתרה מכך, השילוב ביניהם - הוא בד״כ האופציה הטובה ביותר. דיון רלוונטי הוא מתי, ועד כמה להשתמש בכל אחד.

פרדיגמת ה FP מציבה כמה רעיונות רבי-עוצמה, ששימוש מושכל בהם - יכול מאוד לתרום להתמודדות עם מורכבות של מערכות. התמודדות שרובנו חווים - כל יום.

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

אם ציפיתם, שאולי עכשיו, שלושים וקצת שנה לאחר שהמונח הזה נטבע - נמצא Silver Bullet שיפתור את כל בעייתנו - צר לי לאכזב אתכם. בכל מקרה, FP מרגיש כמו אחד הרעיונות ששווה בהחלט ללמוד ולהפנים. גם מבלי להיות Silver Bullet - יתכן ומדובר בסט חשוב של רעיונות שעשוי להעלות מדרגה את יכולת ההתמודדות שלנו עם סיבוכיות - לפחות אם נשתמש בהם בצורה נכונה.


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


2019-07-20

NoScrum


הקלטתי עם רן לוי (״עושים היסטוריה״) שיחה קצרה מדוע ב Next-Insurance החלטנו לא לעבוד בסקראם, כ side track לפרק של ״עושים תוכנה״.
רן בחור חברותי, אינטליגנטי, ואנרגטי - ומאוד נחמד היה לפגוש אותו.

אקשר את הפרק ברגע שייצא.

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


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

זו בשבילי היתה נקודת זכות ונקודת חיבור - כי גם אני, אחרי שנים של עבודה בסקראם ועם סקראם -- חשתי שסקראם לא סיפק את הסחורה ש״הובטחה״.

זה כמובן לא היה הדבר היחידי שהתחברתי אליו. היו גם רעיונות כמו אחריות End-to-end למפתחים (באמת), אי הסתמכות על QA, השפעה גדולה של רעיונות Lean Startup - ואחרים שחיברו אותי.

בכל מקרה, נושא השיחה שלנו כאן הוא סקראם, או בעצם: NoScrum.





מה זה סקראם?


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



אפתח כמה נקודות חשובות שאני רוצה להדגיש ולהזכיר לצורך הדיון הנוכחי:


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

רוב רעיונות האג׳ייל הן בעצם פרשנויות וניסיונות התאמה של Lean Manufacturing (או המקור שלה: TPS של טויוטה) לעולם התוכנה. על התהליך הזה כתבתי בעבר.

לרעיונות האג׳ייל יש פרשנויות שונות, בדמות מתודולוגיות שונות: SCRUM, Crystal, KANBAN, XP, וכו׳
  • XP (כלומר: Extreme Programming) מתמקדת בפרקטיקות של קוד: איך משפרים כתיבת קוד - בעזרת רעיונות של Agile/Lean.
  • Lean Startup מתמקדת בהגדרת מוצר (או פיתוח לקוח).
  • סקראם וקנאבאן מתמקדות בניהול פרויקטים. אין בסקראם (או קאנבאן) שום פרקטיקות העוסקות ישירות בכתיבת קוד. 






לסקראם יש ״תעשיה״ מאחוריה - תעשיית ייעוץ והסמכות 

הנקודה הזו היא לא העיקר, אבל היא רלוונטית - ולעתים מצליחה להפתיע אנשים.

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

הסמכה של מאמן SCRUM (זה שמכשיר SCRUM Masters), למשל, עולה $5000 לשנה ו/או הפרשה של $50  ל ScrumAlliance עבור כל תלמיד שהוסמך.

לכאורה The Scrum Alliance מוגדר כארגון שלא למטרות רווח, אך בהחלט יש לו צד כלכלי.

אני בטוח שיש יועצי XP ו LeanStartup איפשהו בעולם - אך מעולם לא נתקלתי בהם. כן נתקלתי בכמות מכובדת של יועצי סקראם, מחברות שונות בארץ ובעולם. ליווי צמוד וממושך - הוא דבר מקובל באימוץ סקראם. אפילו פעם ניסו לשכנע אותי להפוך ליועץ שכזה.

בקיצור: סקראם זו לא רק סט-רעיונות. זה גם ביזנס.


סקראם הוא הגורילה בשוק

כ-70% מארגוני התוכנה מתארים את עצמם כעובדים בסקראם. רובם הגדול ב״סקראם טהור״, וחלקים קטנים יותר - בשילובים שונים עם מתודולוגיות א׳גייל אחרות.

בהינתן הניסיון בשטח - אני חושב ש SCRUM הוא במידה רבה סוג של "ברירת-מחדל". דווקא "לא לעשות SCRUM" - היא סוג של בחירה.



מודעה שצצה לי באתר "דה-מרקר" בימים בהם כתבתי את הפוסט.



מה לא טוב בסקראם?


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

מה המחלה הנפוצה ביותר בקרב אנשי-תוכנה? נכון - בזזת.
הנטיה לקבל החלטות על בסיס ״באזז״.

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

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

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

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

טעיתם? לא נורא. הארגון יתאושש בקלות מטעות בהחלטה פעוטה שכזו.


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

כאן יוריסטיקת הבאזז - היא לא טובה, ואף מסוכנת. כאן שווה להשקיע שעות של עבודה ומחשבה, בכדי להגיע להחלטה הטובה ביותר + לרענן את ההחלטות מדי פעם.

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

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

באמת?!

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

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

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

בפועל סקראם הופך להיות חלק מהזהות הארגונית: ״אנחנו חברה ישראלית״, ״אנחנו בתעשיית ה...״, וגם: ״אנחנו עושים סקראם״.

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

כמה פעמים נתקלתי בצוותים שמתלוננים ש ״Daily stand-up" לא יעיל עבורם: קוטע את רצף העבודה, נמשך זמן רב מהרצוי, או לא רלוונטי הרבה פעמים לחלק גדול מהצוות.
אבל מה? "הארגון עושה סקראם" ו Daily stand-up הוא חלק מרכזי בסקראם. אותם ארגונים מזיזים את שעת הפגישה, משנים קצת את אופן התנהלותה, או אולי אפילו מדללים ל"יום כן, יום לא". הם מצליחים לשחק בפרטים, אבל הם לא מצליחים לוותר על הפרקיטיקה - אפילו כאשר זה הדבר הנכון לארגון. כאילו אימוץ הסקראם קיבע תבנית חשיבה מסוימת - שמאוד קשה לפרוץ אותה.

הקביעה ש"הארגון עושה סקראם" - מקבעת אותנו מנטלית.





סקראם כ Whole


כאן נכנס עוד אלמנט בעייתי של סקראם. סקראם, ע״פ התפיסה הרווחת, והמקודמת היטב - הוא "חבילה שלמה".

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

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

יש פה משהו מאוד פרדוקסלי: מתודולוגית פיתוח רזה, שאינה גמישה לשינויים משמעותיים? קבועה ומקובעת?
עוד פרדוקס: אחד העקרונות העמוקים ביותר ב Agile הוא קבלת value בהדרגה. האם סקראם לא עומדת בעיקרון הזה, בעצמה?

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

אפשר להאמין בזה פעם, פעמיים - אבל אחרי שרואים שזה דפוס שחוזר על עצמו - זה כבר נהיה מטריד.

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

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

בעולם הסקראם אפילו צמח מונח כנגד יישומים חלקים של סקראם (שלא מצליחים): ScrumBut: ״מימשנו סקראם... אבל״.

יישמנו סקראם, אבל אולי לא יישמנו פגישות retrospective בכל ספרינט, או לא שינינו את תחליף ה Team Lead ל Scrum Master = "אתם עושים ScrumBut, לא מימשתם את ס-ק-ר-א-ם במלואו - אך איך אתם מצפים שהוא יעבוד?"

יש משהו מבאס בלשנות כ"כ הרבה בארגון, רק כדי להגיע בסוף למסקנה שזה לא מספיק - בכדי לקבל תוצאות משמעותיות, וארוכות טווח.

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

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


אם אני צריך לבחור בארגון כלשהו מה השיפור הבא שארצה לקדם - "חבילת הסקראם", כנראה לא תהיה באחד המקומות הראשונים. כמובן שהבחירה צריכה להתאים לארגון וצרכיו, אבל אחרי שחוויתי הצלחות ברורות בכמה ארגונים עם פרקטיקות אחרות, כמו Minimal Viable Products או Continuous Integration (אמיתי) - ברור שאעדיף לנסות שוב את מה שכבר הצליח, ולא מה שהתמהמה ולא הניב תוצאות חד-משמעיות.



ההתמקדות בסקראם ממסכת אפשרויות אחרות


צריך מאוד להיזהר מהקישור הלא מודע, שאנשים לפעמים עושים: סקראם זה Agile ו Agile זה סקראם. זה פשוט לא נכון.

רעיונות ה Agile מופיעים בצורות ובגוונים שונים. סקראם הוא חלק מהן - אבל יש עוד המון.

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

אני זוכר את Uncle Bob מספר שתנועת ה Software craftsmanship בעצם צמחה, כסוג של תגובה לשימוש הנרחב בסקראם בתור המתודולוגיה המובילה בארגוני-פיתוח.

כבר אמרנו שסקראם מגיעה כ״חבילה״. יישום של סקראם, בד״כ - כולל הרבה מאמץ, וזה אומר שאנחנו הולכים להשקיע הרבה מאוד מאמץ ארגוני ביישום, הטמעה, ושכלול הסקראם שלנו.
כל אותן פגישות Retrospectives, ו Scrum Masters - יזינו למערכת הרבה פידבק ושיפורים רצויים - לגבי הסקראם.

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

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

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

זו בעיה עיקרית שתנועת ה Software Craftsmanship movement ניסתה לפתור - להחזיר כובד משקל מספיק לפרקטיקה הסופר חשובה - של הנדסת תוכנה.


?Why we switched from Scrum to Kanban
זו לא שיטה זו או אחרת, זה החופש לעשות את מה שבאמת צריך....


אז מה עושים?


כמו שאמרתי בראיון: הדבר הכי טוב (עבור תעשיית-התוכנה) שיכול לקרות לסקראם כרגע הוא להתפרק מחבילה - ולהטמיע בתעשייה כפרקטיקות שימושיות on-demand…. ממש כמו Extreme Programming (בקיצור: XP) או Lean Startup

מכירים היום ארגונים שמצהירים על עצמם שהם "עושים XP"? - כמעט ואין כאלו. אבל הפרקטיקות של XP - הן בכל מקום:
  • Continuous Integration
  • Unit Tests
  • Pair Programming
  • Refactoring
  • Coding Standards
  • Small releases
  • 40 שעות עבודה בשבוע
  • ועוד ועוד...
אלו פרקטיקות נפוצות מאוד בתעשייה. הניסיון לקחת את XP כחוק מחייב, לכל פינה בארגון, עבור כל האנשים, כל הזמן - פשוט לא צלח. Pair Programming ב 100% מהזמן - הוא מאוד לא יעיל. Pair Programming מדי פעם (וב context הנכון) - זה דבר נהדר.

מרגע שהתחילו להשתמש בפרקטיקות של XP במידתיות וע״פ הצורך הספציפי - הגיע הערך המשמעותי.

כנ״ל בסקראם: Story Points, Retrospectives, time-boxing, ועוד הן פרקטיקות טובות ושימושיות - במצבים המתאימים.

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

בגלל הגישה הכוללנית של סקראם, בגלל שיש הטפה שזו ״חבילה שלמה״ - החופש לבחור מה מתאים לי ומתי - הוא לא הפרקטיקה המקובלת התעשיה. הקיבעון הזה - גורם בפועל ל"הרים של waste".

ScrumBut צריך להפסיק להיות עלבון - כלי לנזוף במישהו שהוא לא עושה מספיק סקראם. במקום זאת, ScrumBut צריך להיות Best Practice: ״אני משתמש רק במה שטוב לי, ואם לא מוכח זה עוזר לי - אני לא אעשה את זה״

יותר מדי פעמים ראיתי יישומים של סקראם שמודדים את ההצלחה במידת האימוץ של הסקראם (״האם כל הצוותים מקפידים לעשות Daily Stand-up לפי הכללים, כל יום״) יותר ממה שהם מודדים את השיפורים שנעשים בארגון (״האם ה lead time שלנו באמת משתפר בקביעות?״). האם אנחנו תמיד זוכרים מה המטרה, שלשמה אימצנו סקראם?!


גם אם אתם מחליטים שסקראם הוא בדיוק המסגרת בשביל הקבוצה או הפרויקט שלכם, בדיוק בשלב הנוכחי - הייתי ממליץ להימנע מלהכריז על ״SCRUM״.

ספרו לאנשים שאתם עושים "SprintFlow" או "AgileXP" (שמות שהרגע המצאתי). אם ישאלו - אמרו שאולי זה מזכיר סקראם, ״אבל זה בכלל לא אותו הדבר״.

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

העדיפו את ההיגיון הבריא - על פני החוקים [א], את האנשים - על פני התהליך, ואת היכולת להגיב לשינוי - על פני תכניות קבועות מראש.


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




----


[א] ביקורת שהועלתה כמה פעמים לגבי סקראם, היא שסקראם רואה בצורה הפוכה ממש, שני עקרונות ליבה שעליהם חתמו ב Agile Manifesto:
  • Responding to change over following a plan - בסקראם אסור להפריע במהלך הספרינט. הנה סרטון שמלמד זאת בצורה משעשעת (ובמבט לאחור: מעט מבהילה).
  • Individuals and interactions over processes and tools - בסקראם התהליך הוא במרכז, ואפילו מלמדים ״לא להסתמך על סופר-מנים, אלא על תהליך טוב יותר״.
אני יכול להתחבר בנקודות האלו ל-2 נקודות המבט: גם של סקראם, וגם של ה Agile Manifesto. בשתיהן יש היגיון בעיני, אם כי אני נוטה מעט לכיוון הפרשנות של ה Agile Manifesto.



2019-07-07

על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019? - חלק ב׳


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





התכנות הפרוצדורלי - מה הוא נתן לנו?


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

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



Functional Decomposition

הרעיון החזק ביותר שהציג התכנות הפרוצדורלי [א] נקרא Functional Decomposition. הוא הציל את עולם התוכנה מכבלי הקדמוניות - אבל מאז הפך גם ל Anti-Pattern מסוכן ונפוץ.

זוהי טכניקה / תהליך / Pattern שמשפר מערכות בגודל בינוני, אך מסוכן עד מזיק - כאשר מנסים להתאים אותו למערכות גדולות ומורכבות.


הרעיון, הולך בערך כך:

איך מתמודדים עם בעיה גדולה ומורכבת? - נכון: Divide & Conquer. מחלקים לבעיות קטנות יותר, שקל יותר לפתור אותן אחת אחת.

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

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

יתרה מכך - הפרוצדורות / פונקציות הללו - הן בסיס טוב ל code reuse. טרנספורמציה על מחרוזת כגון ()trim או ()replaceAllCaptialsWithNonCapitalLetters - יכולה לשמש אותי במקרים רבים נוספים!


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

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


מה הבעיות של Functional Decomposition שהפכו אותו ל Anti-Pattern בעולם ה OO?
  • אי התייחסות / מחסור ב Information Hiding ו Encapsulation:
    • האם ה State הראשוני חשוף לכלל המערכת? האם הוא גלובאלי, למשל? - כך היה בהתחלה.
    • האם כל פונקציה שאפעיל רשאית לעשות כל שינוי ב state?
      אם ה state הוא רשימה של מחרוזות - אז אין בעיה. ככל שה state גדול ומורכב יותר (קיצונות אחרת - לב הנתונים של המערכת) - קל יהיה יותר לאבד שליטה ובקרה: איזה פונקציה עושה מה - על הנתונים הללו.
  • סיכויים גוברים לארגון נחות של המערכת:
    • כאשר הפונקציות מפוזרות במערכת ללא סדר וללא היגיון - קל להגיע לבלאגן.
    • כאשר אין סדר במערכת, סביר יותר שלא אמצא פונקציה קיימת ומספקת - ואכתוב אותה מחדש = קוד כפול.
  • סיכויים גדולים למידול נחות של המערכת:
    • חשיבה על המערכת בצורה של ״נתונים״ ו״טרנספורמציות״ אינו טובות למערכת גדולה ומורכבת. במערכת מורכבת, חשוב מאוד למצוא הפשטות (abstractions) טובות לפרטי המערכת, כך שיתאפשר לנו לחשוב ברמה גבוהה יותר של הפשטה. ה Functional Decomposition לא מוביל אותנו לשם, ואולי אפילו מפריע.
    • ביטוי של הסיכון הזה עשוי להגיע בדמות מודל אנמי - דפוס עיצוב שלילי שכתבתי עליו בהרחבה.

למרות ש Functional Decomposition נשמע כמו נאיביות של העבר, השימוש בו צץ שוב ושוב גם היום -  גם בשנת 2019. בעשור האחרון נתקלתי בעשרות מקרים של ״נסיגה״ מ OO ל Functional Decomposition שלא הועילו למערכת. שווה ללמוד את ה Anti-Pattern הזה ולהבין אם אתם מיישימים אותו במערכת. אולי ההבנה הזו - תעזור לכם לפשט את המערכת, ולהפוך אותה לקלה יותר לשינויים ותחזוקה.

עוד מסר חשוב: Functional Decomposition עשוי להישמע כמו Functional Programming - אבל הוא לא
. בתכנות פונקציונלי, למשל, פונקציות לא משנות state, בטח לא state חיצוני. ניתן, כמובן, ליישם Functional Decomposition (גם) בשפות של תכנות פונקציונלי - אבל שפות של תכונת פונקציונלי מספקות כלים נוספים, בכדי לא להגיע לשם.

Functional Decomposition היא יוריסטיקה טובה לחיפוש פתרון Design לבעיה במערכת: לפרק בעיה גדולה לגורמים ולהבין מהיכן לתקוף אותה. חשוב מאוד לא לסיים שם, ולהפוך את הפתרון הנאיבי הנדסית, שאליו הגענו בעזרת Functional Composition - לפתרון בר-קיימא.



Modules


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

יש דרכים שונות לארגן קוד לאזורים שונים במערכת. בשפות תכנות שונות, יש וריאציות מעט שונות או לפחות שמות שונים לאותם "מודולים": Packages, Namespaces או Modules - הם שלוש צורות / שמות - לאותו הרעיון:
  • כל פיסת קוד שנכתבת תהיה ״מקוטלגת״ או ״מיוחסת״ לאזור מסוים בקוד - להלן ״מודול״.
  • הקרבה הזו היא קרבה רעיונית, ולא בהכרח פיסות קוד שקוראות אחת לשנייה.
  • אין ברירה, קוד ממודול אחד - יקראו לקוד במודול שני. אבל, אנחנו רוצים לחלק את הקוד למודולים כך, שנצמצם את הקריאות בין המודולים, ונמקסם את הקריאות / ההיכרות בתוך אותו המודול.
    • הרעיון הזה נקרא גם: ״High Cohesion / Low Coupling״. בעברית: אחידות גבוה (בתוך המודל) / תלות נמוכה (בין המודולים).
למרות שרעיון החלוקה למודלים הוא לא מדויק (לא מדויק בהגדרה, ולא מדויק במימוש) - הוא רעיון רב עוצמה שיש עליו קונצנזוס גדול: רבים ממובילי דעת הקהל בעולם התוכנה מאמינים שאם נחלק את המערכת לחלקים ע״פ סדר משמעותי - יהיה יותר קל להשתלט עליה ולנהל אותה. גם אם אין לכך הוכחה מתמטית ברורה.

בהמשך הדרך, האזורים של הקוד, "המודולים" - צמחו גם להיות יחידות נפרדות של קומפילציה, או linking/loading ואולי גם deployment. הנה, למשל, רעיון ה Micro-Services - על קצה המזלג.
כל אלו הם רק שיפורים - על הרעיון הבסיסי.



הפרדיגמה מונחית-העצמים (OO)


מי שחי בתקופה בה צמחה הפרדיגמה מונחית העצמים, בעיקר בשנות ה-80 וה-90 - חווה דיון נוקב בהבדלים בין תכנות מונחה-עצמים (OOP), ותכנון מונחה-עצמים, Object-Oriented Design, או בקיצור: OOD.
אפילו לא פעם נשמעה הטענה ש"OOD הצליח - אך OOP נכשל". טענה שנויה במחלוקת, הייתי מוסיף.

אני לא רוצה להשקיע זמן על שחזור הוויכוח, ועל הדקויות הנלוות - ולכן אתייחס בעיקר ל OO כ OOD+OOP.

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

על פייטון ניתן לומר שהיא יותר שפת OOD מאשר שפת OOP... 


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


Everything is an Object

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

לרעיון יש שני פנים:
  • לארוז קוד ביחידות קטנות (יותר) של מודולריזציה - להלן מחלקות. 
  • למדל את המערכת ליחידות המתארות את העולם האמיתי. זה עיקרון ליבה של ה OOD, שלעתים לא מודגש מספיק: זה לא מספיק לחלק את הקוד ל״מחלקות״. חשוב מאוד שהחלוקה הזו תתאר את עולם הבעיה (העסקית) - בצורה נאמנה והגיונית. המיפוי / מידול הזה - מקל על הבנת המערכת, ועל התקשורת לגביה - מה שמקל על לקבוצה גדולה של אנשים להיות שותפים אליה ולעבוד בצורה קוהרנטית.
    • אם בעולם שאותו המערכת משרתת יש מונחים שלא מתבטאים במערכת כאובייקטים - אז כנראה עשינו מידול לא מוצלח.
    • אם הקשר בין האובייקטים לא תואם להיגיון מקובל - אזי כנראה שהמידול שלנו לא מוצלח.
    • אחד מכלי המחשבה למידול לאובייקטים הוא המחשבה על ״תחום אחריות״ (להלן: SRP). אם האובייקט היה בן-אדם, מה הוא היה דורש שרק הוא יעשה? שיהיה רק באחריותו?
      • טכניקה מעניינת של מידול שצמחה, למשל, היא CRC cards.
האובייקטים תוארו בעזרת המטאפורה של כ״תא ביולוגי עצמאי״. לתא יש מעטפת קשיחה המגנה עליו מהעולם (על כך - בהמשך) והוא מכיל בפנים את מה שהוא זקוק לו לפעולה עצמאית. כלומר: אנו מצמדים את הנתונים (state) לפעולות (functions). זה עוזר לנו גם בכדי להגן על ה state - אבל גם בכדי ליצור סדר הגיוני וצפוי במערכת.

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

אם המערכת שלנו בנויה מ"מחלקות של נתונים", ומנגד, מ"מחלקות של פעולות" - אז פספסנו את הרעיון המרכזי הזה של Everything is an Object. זה לא משנה בכלל אם אנחנו עובדים ב Enterprise Java או Shell Script. יותר מהכל, OO הוא לא כלי - אלא רעיון.

אל תבטלו את העניין הזה סתם: אנחנו עדיין כותבים הרבה קוד שלא מציית לרעיון של Everything is an object. הרעיון הזה קל אולי להבנה - אבל לא תמיד קל ליישום.




Information Hiding

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

OO חידשה בכך ששמה את ה Information Hiding כעקרון ליבה מחייב - ולא עוד כהמלצה ״לפעמים כדאי לצמצם את הידיעה של חלק אחד בקוד על חלקים אחרים״. ב OO מדגישים: ״בכל מקום במערכת אנחנו נסתיר כל מה שלא נחוץ - לחלקים האחרים״.
  • Encapsulation הוא השילוב של הרעיון הזה, בשילוב הרעיון של Everything in an Object. ההכמסה היא ״קרום התא הביולוגי״ המגן כל פנים התא הרגיש (ה state הפנימי) - מהשפעות חיצוניות.
    בשפות OO לרוב יש לנו directives הנותנים לנו לשלוט על הנראות: private, package, protected - וכו'.
    • אובייקטים הרוצה משהו מאובייקט אחר, שולח לו ״הודעות״. לא עוד אלך ואפשפש לי (ואולי גם אשנה) State של חלק אחר במערכת. יש מעתה Gate Keeper, שלו אני שולח רק את המידע הנדרש ("ההודעה") - ואקבל בחזרה - תשובה.
      • אם אתם יוצרים הודעות בין אובייקטים ושולחים את כל ה State של האובייקט, אז אתם מממשים משהו שקרוב יותר לתכנות פרוצדורלי - מאשר ל OO.
  • יש צורות נוספות של Information Hiding מלבד הכמסה של אובייקטים:
    • הפרדה של בסיס הנתונים לסכמות, תוך הגבלת הגישה לסכמות השונות - הוא information hiding
    • תכנון המערכת כך שיהיה צורך של פחות אובייקטים להכיר אחד את השני - הוא information hiding מעבר להכמסה של האובייקט הבודד.
    • העברה ל Front-End רק את פיסות המידע הנדרשות (לפעמים נדרש עיבוד נוסף לצורך כך) - הוא information hiding. 
  • המוטיבציה העיקרית ל Information Hiding היא היכולת לבצע שינויים בחלקים מסוימים במערכת, בלי שהדבר יגרור צורך לשינויים באזורים נוספים. להגביל את גודל השינוי.
    • אם אני לא יודע על חלקים אחרים - שינוי בהם לא אמור לדרוש ממני להתעדכן.
    • זה לא תמיד נכון. לפעמים יש השפעות עקיפות - שכן ידרשו ממני להתעדכן. למשל: סדר שונה של פעולות שמרחש במערכת. לא נחשף לי מידע חדש / שונה, אבל עדיין אני צריך להתאים את עצמי.

לרעיון של Information Hiding (או הכמסה: אנו נוטים היום להשתמש במונחים לחליפין) - יש מחיר: הסתרה של נתונים דורשת עבודה נוספת - עוד כתיבת קוד, וחשיבה ותשומת לב - שבעקבותיה עוד כתיבת / שינוי קוד.

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

לאורך חיי ראיתי מערכות רבות שחסר בהן Information Hiding, וראיתי גם מערכות שיש בהם עודף של Information Hiding - והתקורה לכל שינוי, הייתה רבה מדי. 






הורשה

דיברתי כבר המון על הורשה: הנה ב 2012, הנה הרצאה מוקלטת מכנס רברסים 2016, הנה הרצאה מוקלטת אחרונה בנושא כמפגש של JavaIL.

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

לפחות הייתי ממליץ להגביל את השימוש בהורשה לאובייקטים קטנים ופשוטים. כן יש משהו קל להבנה בהורשה רדודה ולא-מתוחכמת.

שלא תגידו שלא ידעתם.


non-Objects

מהה?? אם "Everything is an Object" - אז איך יש דברים שאינם אובייקטים?

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

כש OO אומץ בצורה יותר ויותר רחבה, עלו עוד ועוד קולות שרצו להבחין בין מחלקות (classes) לבין מבני-נתונים (structs). בניגוד למחלקה, מבני-נתונים רק "מחזיקים" כמה נתונים ביחד - ואין עליהן פעולות (כך טענו בתחילה). יותר מאוחר הסכימו שיכולות להיות גם על מבני-נתונים פעולות (פונקציות) - אבל רק כאלו שעוזרות לגשת לנתונים במבנה, ובוודאי לא Business Logic. ההבחנה הזו לא השתנה - ולא נראה שיש קולות לשנות אותה.

ג׳אווה, שפה מאוד פופולארית ומשפיעה - דילגה על ההבחנה בין מחלקות למבני-נתונים. אם אנחנו רוצים להגדיר בה מבנה-נתונים - אנחנו משתמשים ב classes. בעצם כך, שפת ג'אווה "מנעה" במידת מה, מההבחנה בין מחלקות למבני-נתונים לחלחל בתעשייה.
  • האם Map או List הם מחלקות? לא - אלו הם מבני-נתונים. כל מטרתם היא להחזיק ולחשוף נתונים. הכמסה? זה לא הקטע שלהן [ב].
  • האם DTOs או Enum הם מחלקות? לא - אלו מבני-נתונים. בשפת ג׳אווה נממש DTOs כ Class, אך מבחינה רעיונית הן לא מחלקות: אין להן הכמסה [ב], ואין להם אחריות לתאר פעולות בעולם.
    • בג'אווה הרבה פעמים כאשר ממשמשים DTO (לרוב מצוין ע"פ Suffix בשם) - מאפשרים לכל השדות של ה DTO להיות public - ומדלגים על השימוש ב getter/setters. הגיוני.
חוסר ההבחנה בין מחלקות ומבני-נתונים לא מאפיין את כל השפות: ב #C קיימים structs, בקוטלין יש Data Classes, ובפייטון היו named tuples כסוג של ייצוג, ולאחרונה הוסיפו גם Data classes.
אפשר להסתכל על המבנים הללו רק ככלי לאופטימיזציה של קוד/ביצועים בשפה - אבל שימושי הרבה יותר להשתמש בהם ככלי להבחנה סמנטית בין אלמנטים שונים בתוכנה: מחלקות, ומבני-נתונים.

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

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



סיכום


המשכנו לאורך המסלול ההיסטורי, מתכנות פרוצדורלי עד לתכנות מונחה-עצמים.

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


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



-----

[א] יש כאלו שמסווגים את הרעיון הזה כחלק מהתכנות המובנה. אין פה אמת אחת, ויש חפיפה רעיונית ותקופתית בין שתי הפרדיגמות.

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

[ב] זה כמובן, חצי מדויק: לפעמים מבנה הנתונים הוא מורכב (למשל HashMap) ואנו מכמיסים את מורכבות המבנה הפנימי בכדי לחשוף למשתמש מבנה-הנתונים הפשטה פשוטה יותר.


2019-06-30

על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019?


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

בתכנות ישנן כמה פרדיגמות תכנות מקובלות. הפרדיגמות המשפיעות ביותר כיום הן פרדיגמת ה Objection-Orientation (בקיצור: OO) ו Functional Programming (תכנות פונקציונלי) - אך גם יש אחרות מוכרות יותר ופחות.


פורסם מעט אחרי שסיימתי לכתוב את הסדרה



באיזה פרדיגמה אתם מאמינים? עם איזו פרדיגמה אתם עובדים?

עבור הרוב המוחץ של אנשי-התוכנה, התשובה היא זו:

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

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



קושי מובנה בדיון בפרדיגמות היא שתומכים של כל פרדיגמה נוטים לפרש ולתאר את הפרדיגמות האחרות - לצורכיהם.


מה עדיף?



איזו פרדיגמה היא הטובה ביותר?

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


אם אנחנו הולכים לכתוב קוד קצר בן כמה עשרות שורות קוד - שימוש ב OO, או תכנות פונקציונלי, או Reactive Programming - יהיה סיבוך ובזבוז זמן!

קוד קצר מאוד כנראה לא ירוויח דבר מהשימוש בפרדיגמות הללו. אם אנו כותבים תוכנות קצרצרות בכלים הללו - ההצדקה היחידה לכך היא ההרגל.

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

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


בעצם, משם הכל התחיל [א]: מפרדיגמות מאוד פשוטות, עבור תוכנות פשוטות, שרצו על חומרה - שלא יכלה לתמוך בתוכנות מורכבות יותר:



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

הסיבה להשתמש בפרדיגמה אימפרטיבית - הוא שאין פרדיגמה דקלרטיבית שתספק את הצרכים שלנו, וזה המצב ברוב הפעמים. המערכות הן שונות ומורכבות, מכדי שמישהו יוכל להגדיר אלגוריתם כללי שיספק אותן. הדברים הללו עובדים ב SQL או HTML - אבל לא במערכות ווב מורכבות, שהדרישות מהן משתנות על בסיס שבועי.


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

למשל: בתיאורים המקודמים של Alan Key ל Object-Oriented - יש דגש גדול על העיקרון ש"אובייקטים מתקשרים ע"י שליחת וקבלת הודעות". אפשר להסביר למה ההדגשות הללו היו חשובות בזמנו, ומה משמעותן, אבל היום ההגדרות הללו נכנסות תחת הגדרת ההכמסה (Encapsulation). כלומר: אובייקט שולח הודעות - כי הוא לא יכול לגשת לנתונים הפנימיים של האובייקט האחר בעצמו. בכלל, המונח "שליחת הודעות" מתקשר היום חזק יותר לפרדיגמה של Event-Driven - וזה כבר נושא אחר.



הדברים מסתבכים


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

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

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

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

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

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

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


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

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

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

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

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

הדיון הספציפי זה נעלם עם השנים: כיום, אם הקוד יהיה אפילו טיפה קריא יותר כאשר מוציאים כמה שורות לפונקציה - אנו לא חושבים פעמים ומבצעים פעולת "extract function". הנה - יש לזה אפילו שם, וקיצור-מקשים ב IDE.


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

הדיונים על נכונות השימוש בפרדיגמה הם כמובן חשובים: לא כל פרדיגמה מורכבת יותר - היא אכן טובה יותר. לאימוץ פרדיגמות יש מחיר ניכר, ולא כל פרדיגמה ש"תפסה" בארגון X - תצליח בהכרח גם בארגון Y. למשל: ב C עדיין משתמשים במשפטי goto - בעיקר בשם היעילות הגבוהה (קרבה למעבד).
בקרנל של לינוקס, למשל, יש כ 13K משפטי goto (בדקתי הרגע).

יש גם פרספקטיבה של זמן: פרדיגמות שונות שנראו מבטיחות בתחילה (למשל: שימוש עמוק ב XML) - לא עמדו במבחן הזמן.

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

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

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

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

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

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

הורשה מרובה, למשל - היא לא בהכרח דבר רע, וב 90% מהפעמים - אפשר להשתמש בה בצורה נשלטת. אני זוכר שבעבודה עם ++C היו שטענו שכל הפחד מהורשה-מרובה - הוא מוגזם.
10% מהפעמים שבהם היא יצאה משליטה - היו מספיק קשות -- שהתעשייה (!) באופן גורף החליטה לזנוח את האפשרות הזו.

אם כלי כלשהו כושל ויוצר בעיה מהותית, נאמר, ב 10% מהשימושים - זה עשויה להיות סיבה מספיקה בכדי להחליט ולהכחיד אותו מהמערכת. השיקול הכללי - גובר על השיקול המקומי.

גם היום, עדיין יש בשפות, וב Frameworks יכולות בעייתיות: שמדי פעם מקצרות את הקוד - אבל בפעמים אחרות הן זרז לבעיה. מדוע חשיש יצא מחוץ לחוק ואלכוהול לא? - קשה לומר.

חשוב לזהות אלמנטים במערכת שגורמים לבעיות רבות מדי - ולחסום אותם. אני בטוח שיהיו מתנגדים, אבל זכרו שמערכות לא הופכות עם הזמן לפשוטות יותר, או קלות יותר לתחזוקה. הסתמכות על משמעת ודיוק של כל וכל מהנדס במערכת - היא מתכון לכישלון.
על זה נאמר: "It must be simple - or it simply won't be"




?Monty Python: What have the Romans ever done for us
(זכורה לי טענה שזה במקור דיון שהופיע בתלמוד -- אבל לא מצאתי מקורות המאשרים זאת)


אז מה הפרדיגמות המוקדמות תרמו לנו?



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



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

למשל: המצאת ה"בלוק". בתחילה - לא היה דבר כזה.

למשל: משפטי if היו יכולים באותה התקופה להפעיל רק ביטוי (expression) יחיד. לו היו 4 פעולות שלא היינו רוצים שיפעלו אם x הוא שלילי - היה עלינו לכתוב 4 משפטי if שכל אחד חוזר על הבדיקה "האם x שלילי" ואז מבצע עוד פעולה.

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

דוגמה אחרת היא משפטי ה GOTO המפורסמים. תוכנה היא לא תמיד רצף סדרתי של פעולות: לפעמים רוצים לקחת דרך אחרת. למעבר יש יכולת לעשות מעבר ולהריץ קוד ממקום אחר בזיכרון, התרגום הישיר של היכולת הזו היא פקודת GOTO (או JMP - אם אתם חושבים באסמבלי). יישום ראשוני של GOTO היה מעין תחליף פרימיטיבי לפונקציות: לך לשורה 300, ואז שורה 306 מחזירה אותנו לשורה 28 ממנה באנו. שימושים נוספים היו טיפול בשגיאות וניקוי זיכרון - מה שבשפות מודרניות כבר זוכה לטיפול ראוי יותר.

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

סיזיפי? סיזיפי אך אפשרי? - כך נראתה החלוציות בתחום התוכנה...

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

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


ארצה להדגיש שני עקרונות שצצו מהגישה הזו ושווים דיון, גם ממרום שנת 2019:


Scoping [ב]

הרעיון אומר כך: כאשר אנו נותנים שם (name binding) למשתנה, פונקציה, קבוע, וכו' - נאפשר להגביל את מרחב הקוד שיהיה חשוף לשם הזה.

בתחילה היה קיים רק המרחב הגלובלי, אך בשל התנגשויות / טעויות שנבעו מכך - החלו להגדיר scopes מצומצמים יותר.

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

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

היום, בעולם ה IDE המתקדם - המשמעות המידית של scoping היא מה יציע לנו ה IDE ב Auto-complete. אם נגדיר הכל ב Scope הגלובלי - כל הקלדה תציע לנו את כל מה שיש.
אם נקפיד על הגדרת ישויות ב scopes מצומצמים ככל הניתן - הצעות ה Auto-complete יהפכו לממוקדות, והרבה יותר רלוונטיות.

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

מתלבטים מה עדיף?
- אני לא.

שווה לציין ש Scoping הוא צעד ראשון בכיוון של Information hiding ו Encapsulation. שוב ושוב עלו, לאורך ההיסטוריה - רעיונות בכיוון הזה.






Single Exit Principle

בכדי להגדיר מהו מבנה צפוי ומנוהל של רצף הרצת התוכנה, ולהפסיק "לקפוץ לשורה אחרת בקוד", הגדירו בפרדיגמת התכנות המובנה את "עיקרון היציאה האחידה" (מפונקציה). שם נוסף:"Single Entry, Single Exit". לכל בלוק קוד (=פונקציה) צריך להיות רק מקום אחד להיכנס דרכו - ורק מקום אחד שניתן לצאת ממנו.


העיקרון הזה הוא מובנה בשפות שאנו עובדים איתן - עם כמה חריגות. קשה מאוד להסתדר ב 100% מהמקרים עם Single Exit יחיד.

נסו לרגע לחשוב: האם אתם מצליחים לחשוב היכן בשפה שלכם מפרים את העיקרון?

הנה דוגמאות עיקריות:
  • Exceptions שנזרקים - הם לא Single Exit.
  • continue או break בלולאה ל Label (בג'אווה, למשל) - הם לא Single Exit.
  • אפשר גם לטעון ש break ו continue באופן כללי - הם לא Single Exit, לא ברמת הבלוק.
    • בשפות פונקציונליות - לעתים אין להם מקבילה.
  • coroutines (על עוד לא ממתינים באותו הבלוק לסיומם) - הם גם לא Single Exit. הפעלנו קוד - שרק בונה-עולם יודע מתי בדיוק הוא יסתיים.
  • אחרון וחביב: יש גם מי שרואים בקריאת return מתוך גוף הפונקציה - הפרה של עקרון ה Single Exit. אמנם נקודת היציאה נתונה וקבועה - אך דילגנו על קוד - להגיע אליה. הטענה העיקרית - היא שזה לא צפוי. אני מצפה שהפונקציה תגיע תמיד לשורתה האחרונה.

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




סיכום



שוב לא הצלחתי לסיים את הפוסט ב 400 מילים.

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

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

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



----

[א] דילגתי עם קוד מכונה, אסמבלי, ועל הדיון של המכונה של טיורינג לזו של פון-ניומן. זו היסטוריה low level מדי, ולא מעניינת לדיון.

[ב] הרעיון הזה מרגיש מתקדם יותר, ולכן אני מניח שהוא נוסף בשלב מאוחר יותר.