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.