שלום, אנגולר 2 (או 8+)

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

אנגולר 2 נכתבה מחדש (נרחיב מיד) – ועוררה מהומה גדולה. על גרסה 3 דילגו – וקפצו ישר לגרסה 4. כיום הגרסה הנוכחית היא גרסה 8, וכל חצי שנה צפויה לצאת גרסה חדשה.
את המהומה הגדולה שהייתה בעת הצגת אנגולר 2.0 – כבר פספסנו, העניינים נרגעו מאז. אתייחס אליהם בקצרה.

אנגולר, למרות שנכתבה מחדש – היא עדיין ספריה פופולרית למדי (קצת פחות מ React ו Vue – הרזות והפשוטות יותר ללמידה), היא עדיין מכוונת לפיתוח אפליקציות SPA (קרי: Single Page Application) מורכבות, היא עדיין דעתנית לגבי איך יש לכתוב אפליקציות ווב, והיא עדיין מספקת פתרון פיתוח שלם.

למה דווקא עכשיו?

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

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

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

פתרון מקיף – כבר אמרנו?

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

אפליקציית אנגולר היא סט של Components ו Services מאורגנים לתוך מודולים של אנגולר:

  • Components הם רכיבים ויזואליים, שרואים על המסך.
  • Services הם שירותים לוגיים המשרתים את ה Components. שירותים בסיסיים המשותפים לכל האפליקציה – יושיבם במודול הראשי שנקרא ה "App Module".

שימו לב שיש הבדל בין Angular Module לבין ES6 Module. בדומה ל ES6 modules גם Angular Modules מאגדים קוד בעל מאפיינים דומים – אך יש להם סמנטיקות נוספות. לאורך הפוסט כאשר אתייחס ל"מודולים" הכוונה היא ל Modules של אנגולר.

ה Components מאורגנים בהיררכיה, וכל אחד הוא View ("מסך") או חלק מהמסך:

בתוך ה App Module, נמצא ה component הראשי, שנקרא ה Root Component.

מכיוון שאנגולר מרכיב לרוב Single Page Applications (קרי SPA), ה Root Component יהיה לרוב המסגרת לדף הסטנדרטי, וכל "תוכן אפשרי של מסך" יהיה Component נוסף – שגם הוא יורכב מ Sub-Components נוספים.

  • הפרקטיקה היא ש Components מכילים כמות מוגבלת של קוד. אם Component הוא גדול ומורכב – הרי שיש לפרק אותו לכמה Components (בדומה ל microservices)
  • Components הם reusable, ורבים מהם יופיעו שוב ושוב – במסכים שונים של האפליקציה.
    • אפשר לחשוב על Component כתגית HTML חדשה עבור האפליקציה, למשל שאותה ניתן לשלב בכמה מסכים שונים. ייתכן וארצה ליצור רכיב בשם שמציג את הטבלה, וכמה שדות עם פרטים נוספים עבור הלקוח שנבחר. גם רכיב כזה יכול להיות שימושי בכמה מסכים שונים.
דוגמה פשוטה לחלוקת מסך ל Components. מתוך מדריך של Angual Augury.
עד כמה קטנים נכון שיהיו הרכיבים (קרי: Components)? לרוב ברמה של כמה רכיבים ויזואליים מאוגדים יחדיו, אך לעתים יש טעם להגדיר Component מסביב לרכיב ויזואלי יחיד, למשל: כאשר נלווה לרכיב הזה לוגיקה השווה re-use.

מודולים

את כלל הקוד באפליקציה מחלקים למודולים, בד"כ ע"פ הקריטריונים הבאים:

  • מסך מורכב או קבוצת מסכים המטפלים בנושא מסוים – הם מודול. לרוב נקרא: Feature Module. למשל: מסכי הגדרת לקוח – הם בסיס איתן למודול עצמאי.
  • פיצ'ר לוגי במערכת – יכול להיות מודול. למשל Data Export, לא כולל הרבה UI (אולי רכיב בודד) אך כולל הרבה לוגיקה או עבודה עם מערכות חיצוניות – מצדיק מודול. ייתכן וניתן לעשות Data Export לדברים שונים ממסכים שונים במערכת. זה גם Feature Module – אך מסוג מעט אחר.
  • Shared Modules הכוללים רכיבים ו/או שירותים שזקוקים להם בכמה חלקים של האפליקציה. לעתים Shared Modules הם משותפים בין כמה אפליקציות.
    • הרבה פעמים מקובל ליצור באפליקציה מודול משותף בשם core – על מנת שה AppModule יישאר קטן ככל האפשר. ה AppModule מייבא אותו ומחצין (export) אותו לכל שאר המודולים באפליקציה.
כל מודול יכול להשתמש ברכיבים ו Services שלו, באלו של ה AppModule (הציבוריים), ובאלו (הציבוריים) של מודול שהוא מייבא (import).
מודולים יכולים להיטען בצורה דינמית, וזה שיקול נוסף בחלוקה למודולים: מודולים הם יחידת הטעינה הדינאמית של אנגולר. למשל: אם יש לי אפליקציה עם 100 "מסכים", אף בשימוש טיפוסי משתמש ייגש רק לכ 10 "מסכים" – חבל מאוד לטעון את כל הקוד בהתחלה. כאשר יש אפליקציה SPA גדולות (שזה בעצם ה use-case העיקרי של Angular) – טעינה נבונה של מודולים עשויה לשפר מאוד את הביצועים.

TypeScript או ג'אווהסקריפט?

קוד Angular יכול להיכתב ב ES5 (ג'אווהסקריפט גרסת 2009 – כתבתי כמה פוסטים על חידוש שפת הג'אווהסקריפט חלק א', חלק ב', חלק ג') או גרסאות מתקדמות יותר, אך מקובל לכתוב Angular בעזרת TypeScript – זו ההמלצה הרשמית + Angular בעצמה נכתבת ב TypeScript.
TypeScript היא שפה מודרנית ומתוכננת-היטב, בניגוד גמור ל JavaScript המקורית.
  • שמה של השפה נובע ממערכת ה Types שלה, שמקל לבדוק את נכונות הקוד – אבל מאפשר גם ל IDEs לעבוד בצורה עמוקה יותר עם הקוד: מציאת שגיאות אפשריות, Refactoring ו Navigation ברמה ששפה דינמית וללא טיפוסים כמו JavaScript – לא מאפשרת.
  • TypeScript היא SuperSet של שפת ג'אווהסקריפט, כלומר: ג'אווהסקריפט היא מקרה פרטי של TypeScript (בערך). לדוגמה: אם ניקח קובץ js. ונשה את שמו ל ts. לרוב נצטרך להוסיף הגדרה של טיפוסים – אך משם הוא יעבוד.
  • הדפדפן לא מכיר TypeScript, ולכן הקוד מתורגם (Transpiled) לשפת ג'אווהסקריפט.
    • אם תראו את קוד הג'אווהסקריפט שנוצר מ Typescript – הוא יהיה דומה מאוד לקוד ה Typescript שכתבתם.
  • TypeScript מכילה את רוב היכולות של ES6 – אך לא את כולן. למשל: אין תמיכה ב Proxy (כלי דיי מתקדם, ולא לשימוש יומיומי). עם הזמן TypeScript מוסיפה עוד אלמנטים מ ES6, וגם אלמנטים ייחודיים משלה.

TypeScript, כפי שאמרנו, היא יישום של ES6 עם כמה תוספות, בעיקר:

  • Static Typing – למשתנים יש טיפוסים, והקומפיילר (ליתר דיוק: טרנספיילר) בודק את נכונות השימוש בהם עוד לפני שהקוד רץ.
  • Interfaces שלא קיימים בג'אווהסקריפט – הם תוספת משמעותית. למי שעבד בג'אווה – אין צורך להסביר את החשיבות.
  • Class Properties – קיימים ב ES6, אך בצורה לא מפורשת. ב TypeScript ניתן להגדיר properties על מחלקה בצורה מפורשת (או בתחביר מקוצר בתוך ה constructor) – מה שמאוד נוח ושימושי.
  • נראות – members במחלקה של TypeScript יכולים להיות גם private. ב ES6 נראות היא בעיקר ברמת המודולים.

לשימוש ב TypeScript יש כמה מחירים [א] (בעיקר: צורך בתהליך build, ניהול הטיפוסים עשוי להיות overhead מורגש) – אך סה"כ, מדובר בשפה מוצלחת למדי: גם לדעת "מומחים", וגם ע"פ מידת האימוץ שלה בתעשייה.

מה ההבדל בין Angular ל AngularJS?

האם זה לא אותו הדבר? (תשובה: לא.)

  • Angular החל כפרויקט פנימי בגוגל ב 2009, וב 2012 שוחרר לכלל התעשייה כפרויקט קוד פתוח.
  • ב 2014 הצוות של Angular החליט לבצע שינויים דרמטיים לספריה, ובעצם החל לכתוב אותה מחדש, תהליך שיארוך כשנתיים. היו הרבה דרמות וויכוחים מסביב למהלך, אך כיום אפשר לומר ש AngularJS היא Legacy – ובעצם רק Angular היא רלוונטית בראיה קדימה.
  • שם הספרייה החדשה היה "AngularJS 2.0" ובעצם מאז שמטו את ה JS מהשם (חשבו: TypeScript) והגרסה התקדמה. מאז השם הוא פשוט "Angular", למשל Angular 8.
בעוד AngularJS בנוי מסביב למבנה MVC (קרי: Model-View-Controller), מה שהיה מקובל באותם הימים, Angular היא ספריה שבנויה מסביב ל Component – ממש כמו ReactJS או Vue – וזה המודל הפופולארי כיום.

אפשר לציין ספריה בשם Aurelia שהיא, קונספטואלית, ממשיכת דרכה של AngularJS ומודל ה *MV. חלק ממאוכזבי אי-המשך הדרך של AngularJS – פנו לעבוד בה. עדיין, נתח השוק שלה קטן משמעותית משלושת הספריות המרכזיות (React, Vue, Angular).

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

יש הרבה דמיון בין הגרסאות שיכול לבלבל אותנו ולגרום לנו לחשוב שמה שאנו קוראים רלוונטי לאנגולר (החדש). למשל: ב AngularJS יש directive נפוץ בשם ng-if, אך ב Angular שינו לו את השם ל ngIf, ויש להשתמש ב directives עם סימן *. חבל לבזבז זמן ולדבג קוד של AngularJS בתוך אפליקציית Angular…

רכיבים (Components)

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

  • נתחיל ב Metadata, הנכתב כ decorator [ב] על המחלקה. זהו בעצם המידע שהופך מחלקת TypeScript רגילה – ל Component, ומחבר עליה את כל החלקים.
    • selector – ה HTML Tag שמייצג את הרכיב. כל הוספה שלו ל Markup – יוסיף את הרכיב (יש לדאוג ל imports מתאימים).
    • template (אלמנט שני חשוב ברכיב) – ה HTML Markup של הרכיב, הוא חלק משמעותי ברכיב. בד"כ ינוהל כקובץ נפרד, אם כי גם ניתן לכתוב אותו כ inline בתוך ה template.
      • ה Template הוא לא ה HTML הסופי שיוצג, והוא עשוי להכיל directives (אלמנט נוסף של אנגולר) ורכיבים אחרים (ע״י ציון ה Selector שלהם) המשפיעים על ה Markup הסופי. למשל ngIf directive יסיר את האלמנט שעליו הוא יושב, אם הביטוי המשויך אליו אינו חיובי.
        ייתכן ובמידה והחתול של שרדינגר יריץ את הקוד – הכותרת ״Hello World!״ לא תוצג.
    • style – ה S)CSS) של הרכיב. אפשר גם לכתוב inline, כאן אנו רואים קישור לקובץ.
      • הסוגריים המורבעים הם מכיוון שזה מערך. ניתן להחיל על הרכיב כמה קבצי Style (אמצעי ל reuse/שיתוף של CSS).
      • מי שמכיר את SMACSS – ה guidelines לכתיבת CSS, בוודאי ינטה לתת namespaces ל styles של הרכיב, בכדי שלא ישפיעו בטעות על רכיבים אחרים. אל דאגה! Angular עושה זאת בצורה אוטומטית עבורנו – ואפשר לשמור על ה markup מינימלי יותר.
  • מחלקה (אלמנט שלישי חשוב ברכיב) (בד"כ ב TypeScript) – הכוללת קוד, קרי: Presentation Logic.
    • בדוגמה הזו אין בכלל קוד במחלקה. אין Presentation Logic – אך עדיין יש רכיב שרץ ומציג HTML מסוים. לפעמים ה metadata הוא מספיק לרכיב – ולא צריך לכתוב קוד במחלקה.
Lifecycle Events
איזה סוג של קוד כותבים במחלקה של רכיב? כבר אמרנו: Presentation Logic, בעיקר.
מה מניע את הקוד הזה? מאיפה הוא מתחיל לרוץ? – מאירועים בחיי הרכיב, או מ Events (נדבר על Data-Binding בהמשך). הנה אותו הקוד עם מימוש של כמה מתודות נפוצות של ה Lifecycle של הרכיב:
  • הוספתי את ה Lifecycle events הנפוצים ביותר. את הרשימה המלאה של Lifecycle Hooks של רכיב אנגולר ניתן למצוא כאן.
  • שימו לב שלכל אירוע. המחלקה מממשת מנשק מתאים (OnInit וכו'). מכיוון שלממשקים אין ייצוג ב ES6 (הם קיימים רק ב TypeScript) אנגולר לא באמת מסתמכת עליהם בזמן ריצה. הסיבה להוסיף אותם היא לכדי לוודא שאין שגיאת כתיב בשם הפונקציה. הקומפיילר לא יתלונן אם אגדיר פונקציה בשם ngOnDestory – אבל היא לעולם לא תרוץ, ומציאת הבעיה עשויה לארוך זמן.
  • כמה דגשים ספציפיים לגבי האירועים:
    • ההבחנה החשובה הראשונה היא בין הבנאי ו ngOnInit. יש להרחיק מהבנאי פעולות ארכות / שעשויות להשתבש (כמו הבאת נתונים מהשרת) – ולשים אותם ב OnInit. שימוש-יתר בבנאי היא נפוצה בקרב מפתחים החדשים לאנגולר.
    • כשנעסוק בנושא ה Data Binding, נראה היכן ngOnChanges מקבלת תפקיד מרכזי.
    • כדי לקבל הודעות מה services של אנגולר – אנו צריכים להירשם (subscribe) אליהם. זה קוד שלרוב יקרה ב ngOnInit. כדי להימנע מ memory leak עלינו לעשות Unsubscribe ב ngOnDestory – פעולה חשובה שפעמים רבות שוכחים ממנה!
      • הדבר נכון אפילו יותר לשימוש ב NGRX (מימוש דפוס ה "Redux" באנגולר) ו RxJS (ספריה פופולרית בשימוש באנגולר לתכנות ראקטיבי) – שם רישום לאירועים הוא דרך העבודה הסטנדרטית.

רכיבים מקוננים

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

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

ורוד: התפקיד של כל קובץ ברכיב, סגול: הטווח של כל רכיב, תכלת: הטווח של כל מודול.

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

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

 $ ng generate component MyComponent

גישת ה generation (או Scaffolding, כפי ש Ruby On Rails הגדירה אותה) מונעת בעיות רבות של copy-paste ועוזרת לשמור על מבנה אחיד בפרויקט. עשו מאמץ קל – והתרגלו לעבוד ב cli להוספת רכיבים חדשים לפרויקט.

בואו נראה קצת קוד, של App Component המכיל בתוכו רכיב פשוט.

הנה הקוד של ה AppComponent:

הוא מכיל בתוכו תגית שמתאימה ל selector של MyComponent:

יכולנו, באותה המידה, לכלול את MyComponent מספר פעמים.

הנה התוצאה המרשימה של האפליקציה הפעוטה שכתבתי לצורך הפוסט, כאשר הקווים האופקיים (hr) מגיעים מתוך ה AppComponent, והתוכן (כותרת ורודה וטבלה) – מגיעים מתוך ה MyComponent:

ה AppComponent לרוב יכיל רק Layout עם רכיבים אחדים: רכיבי מסגרת (header, footer, side-panel, וכו') ורכיב אחד ל"מסך"/תוכן – שישתנה תוך כדי עבודה באפליקציה (נושא של Routing – שלא אכסה בפוסט).

גם בתוך הרכיבים המתארים "מסכים", נוהגים לבנות את הרכיב העליון כרכיב רזה מבחינת UI, המכיל מספר רכיבים שמשמשים לפרזנטציה. דפוס מקובל באנגולר הוא שהרכיב העוטף (להלן: Container Component) מכיל Layout ואת הגישה לנתונים – והוא מורכב מרכיבים קטנים הנקראים Presentation Components – שמייצרים את עיקר ה Markup. רכיב ה Container אחראי להביא את הנתונים (דרך ה services, מצד השרת) – ומעביר אותם, ע"פ הצורף, לרכיבי ה Presentation – שרק עוסקים בתצוגה.

היתרונות שב Container Components:

  • גישה לנתונים נעשית במקום אחד – ברכיב ה Container. קל מצוא את הקוד (LIFT). ייתכן וכך גם ניתן לגשת שרת המרוחק – פחות פעמים.
  • רכיבי ה Presentation פשוטים ורק עסוקים בהצגת ה State (להלן Presentation Logic).
    • קל לכתוב להם בדיקות-יחידה (מה שנקרא גם Component-Test: לבדוק את ה Markup שנוצר). אין צורך ב Mocks כי כל ה Integration Logic נמצא ברכיב ה Container.
יש חיסרון בגישה הזו, בכך שרכיבי ה Presentation הם לא עצמאיים ולא יכולים לרוץ בלי Container. למשל: הם לא Routable – כלומר: אי אפשר לשמור Bookmark למצבים ספציפיים שהם מייצגים, בלי עבודת נוספת ברמת ה routing. לא עניין גדול – לפי דעתי.

Data Binding

נושא אחרון שארצה לסקור, בזריזות, הוא נושא ה Data-Binding, היכולת לקשר בין תכונות (properties) של המחלקה ל Template בצורה ״אוטומטית״.

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

באנגולר, יש ארבע צורות של Data-Binding:

נסקור אותן (בקצרה) בזו אחר זו.

  • Interpolation היא הצורה הבסיסית ביותר: אנו כותבים ביטוי בתוך סוגריים מסולסלים כפולים (מה שנקרא Mustache Style) בתוך ה Template Markup, בזמן ריצה הביטוי מחושב ותוצאתו נוספת ל HTML הסופי.
    • מה שאולי קצת מיוחד, הוא שיש לנו גישה לשדות והפונקציות של המחלקה של הרכיב. במקרה הזה yourName ו ()addNumbers הם שדה ופונקציה במחלקה, בהתאמה.
  • Property Binding היא צורה מתקדמת יותר המאפשרת לקשור property של אלמנט HTML לשדה במחלקה של הרכיב. למשל, image URL.
    • תחביר: את ה property של ה HTML Element לו אנו רוצים לעשות binding אנו עוטפים בסוגריים מרובעים (להלן: box), והערך שמוצב הוא expression שיעבור evaluation בזמן ה binding. במקרה שלנו זהו שם של שדה על מחלקת הרכיב (יכול להיות גם פונקציה, או כל ביטוי ב ג'אווהסקריפט).
    • יכולנו לכתוב את אותו הביטוי כ interpolation:
    • התוצאה של חישוב ביטוי ב interpolation – תמיד תהיה מחרוזת. אם יש property מטיפוס אחר (למשל: disabled הוא property מטיפוס Boolean) – עלינו להשתמש ב Property Binding. מחרוזת שאינה ריקה ב js – תחשוב כ true. כלומר: גם "false" היא true. אופס!
    • ה Property Binding מאפשר לנו לגשת לתכונות מקוננות, מה שמאוד קשה לעשות עם interpolation. למשל, לקבוע רוחב של תמונה:
      • התכונה הזו נקראת "Style binding", אבל יש גם class binding או attribute binding ועוד…
  • Event Binding היא היכולת להגיב ל Browser Event בתוך המחלקה של הרכיב. במקום שנכתוב inline javaScript בתוך ה Template (פרקטיקה רעה לתחזוקה) – אנו נעטוף את שם האירוע שאנו רוצים לטפל בו בסוגריים עגולים, ואז נציב לתוכו ביטוי שיהווה את ה handler באירוע. בד"כ: הפעלה של פונקציה.
  • לעתים אנו רוצים גם להציג תכונה של המחלקה ב UI, וגם לתת ל UI לשנות את הערך – ולעדכן את המחלקה. היכולת הזו מתאפשרת בקלות בעזרת Two-Way-Data-Binding (בקיצור: 2WDB) יכולת שעוד זכורה לטובה מ AngularJS (אם כי היא גם זכורה לרעה, בשל שימוש יתר).
    • בקיצור: אפשר לקצר 2 ביטויי bindings שנראים כך:
[value]="view.value" (valueChange)="view.value = $event"
    • לביטוי המקוצר הבא: "value)]="view.value)].
      האופרטור נקרא "Banana in a box" בכדי להזכיר שהסוגריים העגולים הם פנימיים לסוגריים המרובעים.
    • השימוש הנפוץ של 2WDB הוא בטפסים (Forms), כאן בא לשימוש Angular Forms (מודול אופציונלי של Angular) שניתן להוסיף לאפליקציה, ואז להשתמש ב directive בשם ngModel המקל את העבודה עם 2WDB וטפסים. בשימוש ב ngModel יש לזכור להוסיף את FormsModule ל import של ה NgModule שלנו.
    • בדוגמה למעלה, חיברנו את 2 השדות name ו age של מחלקת הרכיב לשדות input.
      • כל שינוי ב UI – יעדכן את ערך השדה באובייקט (ויקפוץ את האירוע: ngOnChnages)
      • שינוי בערך השדה מתוך הקוד – יעדכן את ערך השדה ב UI.
    • בדוגמת הקוד למעלה גם שילבתי pipe, רכיב באנגולר שבצע formatting ל output של שדות.
      ה pipes הם יכולת משלימה ל data binding מכיוון שיש פער בין האופן שבו אנו רוצים להחזיק את הנתונים במחלקה – ובאופן שאנו רוצים להציג אותם ב UI. למשל: formatting של Date. ישנם כמה pipes מובנים באנגולר, ואתם יכולים לכתוב custom pipes משלכם – לשימוש חוזר ואחיד באפליקציה.

      • ה Titlecase pipe הופך את המחרוזת ל lowerCase מלבד אות ראשונה בכל מילה שתהיה Capital. מה שלעתים נקרא גם humanize.
כל דוגמאות הקוד שלי לגבי data binding נלקחו מהפוסט הזה שמכסה בצורה תמציתית (אך רחבה יותר ממה שאני כיסיתי) את נושא ה Data Binding.

סיכום

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

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

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

—-

לינקים רלוונטיים 

השוואה בין אנגולר ל React. מה ההבדל? מה הדמיון?

—-

[א] why you might NOT want to use TypeScript – חלק א', חלק ב'.

[ב] Decorators ב TypeScript הם כמו Attributes ב #C או Annotations בג׳אווה: דרך להגדיר metadata על רכיב בשפה. במידה ועובדים עם ES5 – יש workaround אחר.

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

מדי פעם אנשים פונים אלי עם שאלות דרך הבלוג. אני מניח שהשאלה הנפוצה ביותר היא זו:

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

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

התשובה שלי בקצרה (אמ;לק) היא זו:

  • מדובר בניהול סיכונים. אני לא אקח את הסיכון עבורכם – אנסה רק לתת שקיפות על ה 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? – חלק ג׳

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

פרדיגמת התכנות הפונקציונלי (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 – יתכן ומדובר בסט חשוב של רעיונות שעשוי להעלות מדרגה את יכולת ההתמודדות שלנו עם סיבוכיות – לפחות אם נשתמש בהם בצורה נכונה.

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

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" (שמות שהרגע המצאתי). אם ישאלו – אמרו שאולי זה מזכיר סקראם, ״אבל זה בכלל לא אותו הדבר״.

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

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

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

—-

עדכון 10/19 – היום ראיתי הרצאה של דייב תומאס (אחד מהחותמים על ה Manifesto for Agile Software Development) – שאומר דברים ממש דומים לפוסט!

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

—-

[א] ביקורת שהועלתה כמה פעמים לגבי סקראם, היא שסקראם רואה בצורה הפוכה ממש, שני עקרונות ליבה שעליהם חתמו ב Agile Manifesto:

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

על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 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) ואנו מכמיסים את מורכבות המבנה הפנימי בכדי לחשוף למשתמש מבנה-הנתונים הפשטה פשוטה יותר.