שלום, אנגולר 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 אחר.

ניסוי בטכנולוגיות צד-לקוח (React.js)

פוסט זה נכתב בשיתוף פעולה של ניר בנימין, איתמר שגב ושלי.
הוא מסכם ניסיון של 3 ימים בכתיבה בראקט, שהתרחשה במסגרת “האקתון” במעבדות SAP.
הפוסט פורסם לראשונה ב geektime, אך ללא הפיסקה “כמה נושאים מעט יותר מתקדמים” (בעצתו של ניר – בכדי להקל מעט על הקוראים). אני רוצה להאמין שקוראי בלוג זה הם בעלי קיבולת גבוהה יותר מ 1600 מלים / 7 דקות קריאה – המומלצות לבלוגים.אמרו לי אם לא. אני לא מזלזל בטענה “זה קצת ארוך מדי בשבילי”. אנחנו בני-אדם.

—-

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

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

React.js (להלן “ראקט”), לא תתחרה באנגולר ראש-בראש בעתיד הנראה לעין: אנגולר היא מקיפה הרבה יותר (הן פונקציונלית והן תהליכית), ומתאימה הרבה יותר ל mainstream.
ראקט פותרת רק חלק קטן ממה שאנגולר פותרת – ועושה זו בצורה לא שגרתית, מה שעלול להרחיק ממנה מפתחים רבים מה mainstream. היא אנדרדוג. כמה מהרעיונות שלה מקוממים במבט ראשון. אולי גם במבט שני :).

יש כבר כמה דוגמאות לשימושים ב React.js במוצרים מרכזיים[א]: פייסבוק (חלקים), אינסטגרם (עם ה router של backbone) ו Khan Academy (עם Backbone). ניתן למצוא עליה דיונים ב StackOverflow ואפילו יש 4 גרסאות שונות שלה לשימוש ב jsFiddle.

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

יצאנו לבדוק במה מדובר…

מה ראקט מציעה?

הדבר הראשון הניכר משימוש בראקט הוא מ Client Side Rendering מהיר ביותר! ראקט יכולה לשמש לכתיבת אפליקציות שמרגישות מאוד “חלקות” ולא נתקעות. היא מתאימה לאפליקציות בעלות רינדור אינטנסיבי.

עד כמה שזה מרשים – זה עדיין לא פיצ’ר שמצדיק שימוש בספרייה עבור רוב המשתמשים. היכולות הללו בעיקר קוסמות למי שנמצא ב extreme optimization של אפליקציות ווב (כמו חברי “מועדון ה sub-second”) ומחפש דרך לאופטימיזציה גבוהה מאוד, מבלי לחרב את הקוד.

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

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

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

רעיון שלישי ואחרון של ראקט הוא חלוקה מחדש של ה concerns בצד הלקוח: במקום הפרדה בין UI, לוגיקה, נתונים ואולי שליטה (וריאציות שונות של MVC) – ראקט מציעה הפרדה לרכיבים המחולקים ע”פ “יכולות עסקיות”, כאשר כל רכיב אחראי על הנתונים, הלוגיקה וה UI שלו בעצמו.

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

רבים ממשתמשי ראקט לא מאמצים / מאמצים חלקית בלבד את הרעיון השלישי, ומשתמשים בראקט בתור שכבת UI (קרי View) לארכיטקטורת MVC סטנדרטית. MVC היא אולי לא מושלמת – אך היא מוכרת ומובנת (כלומר: כל קבוצה מכירה אותה בדרך משלה).
שימוש בראקט עם Angular או Ember אפשרי, אך הוא לא נפוץ. השימוש בראקט מייתר חלק מיכולות Angular/Ember ומחליש את הסדר המובנה שהן מציעות. שימוש בראקט עם Backbone.js, הוא דווקא טבעי בהרבה – כאשר ראקט מחליפה את ספריית ה Template ליצירת Views.

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

סיכום ביניים:

  • ראקט לא מציעה MVC, או תחליף ישיר ל MVC.
  • ראקט מהווה סוג של תחליף ל Templates (וקצת יותר מכך). במקום שפת template “מנוונת” – ראקט מאפשרת להשתמש (פחות או יותר) בג’אווהסקריפט לבניית ה markup.
  • ראקט מערבבת בין ג’אווהסקריפט לבין “HTML) Markup)”, אך מפרידה בין רכיבים על הדף – כל אחד אחראי על הפונקציונליות שלו.
  • ראקט יעילה מאוד ברינדור, ומתאימה לאפליקציות אינטנסיביות ברינדור (בעיקר כאלו שעובדות על ה DOM).
    אציין שגם Ember יעילה למדי בתחום זה (בעזרת Handlebars) – אולם נראה שהקוד יהיה פחות קריא.

כיצד ראקט עובדת

React.js היא ספרייה פשוטה, יחסית, בעלת אלמנטים ספורים.

הגדרת רכיבים (Components) נעשית ע”י המבנה הבא:

var = React.createClass({
  method1: function(…){..};
  method2: function(…){..};
});
לא שונה בהרבה מ Backbone, Angular, או ספריות נפוצות אחרות.

בראקט ישנה הבחנה בין 2 סוגי רכיבים:
  • רכיבים שהם Immutable
  • רכיבים בעלי state
שני הרכיבים נוצרים ע”י createClass, אך הם שונים בשימוש שלהם ב State: האם כותבים משהו על ה state של הרכיב או לא. ראקט מעודדת לנסות ולכתוב כמה שיותר רכיבים Immutable – עבור יתרון בביצועים. כדי לחסוך ב Garbage Collections ראקט מבצעת pooling של אובייקטים (כגון Events) ועושה בהם שימוש-חוזר.הרכיבים מושפעים בעת הרינדור שלהם מ2 מקורות: ה properties – שנקבעים בעת יצירת המופע של הרכיב (ולא ניתן לשנותם מרגע זה) וה state – שנשמר על הרכיב, וניתן לשנותו בכל רגע. בדכ ה properties ישמשו גורם חיצוני לקבוע את התנהגות הרכיב, בעוד ה state הוא פנימי לשימושו של הרכיב.

אפליקציית ראקט טיפוסית מתחילה בקריאת:

 React.RenderComponent();
היוצרת את רכיב האב וקוראת ל ()render שלו. הוא מצידו יקרא ייצור מופעים של רכיבים בנים (משולבים ב HTML markup) – ויקרא ל render שלהם וכך חוזר חלילה לעומק עץ של רכיבים ו markup.

כאשר יש שינוי ב state של אחד הרכיבים – פונקציית ה ()render של אותו רכיב תקרא ותגרום לעדכון של תת העץ שלו.
הרכיבים בראקט מגדירים את המראה שלהם בעזרת Markup ורכיבים בנים, כאשר הם לא כותבים ישירות ל DOM של הדפדפן לא רק ל React.DOM – ה DOM הוירטואלי שראקט מנהלת.

כפי שציינו, כתיבה ל DOM היא פעולה יקרה. יתרה מכך: הדפדפן מחזיק Buffer קטן של כתיבות ל DOM אותן הוא מנסה לאגד לאצווה, אולם כל קריאה מה DOM עשויה להכריח אותו לכתוב ל DOM בנקודת זמן לא רצויה (מדוע? הסבר בפוסט זה, “ההשפעה של פעולות DOM מתוך קוד ג’אווהסקריפט”).
מכיוון שראקט מנהלת עותק שלה של ה DOM, היא יכולה לקרוא ולכתוב ל DOM הווירטואלי שלה מבלי לעדכן את ה DOM של הדפדפן. כמו כן היא יכולה לסדר פעולות ל DOM בצורה אופטימלית מבחינת ביצועים (סדרות של קריאות וסדרות של כתיבות).
כאשר היא רוצה לעדכן את ה DOM של הדפדפן, היא עושה diff יעיל (להלן הסבר האלגוריתם) של ה DOM הווירטואלי וה DOM האמיתי ומעדכנת ב DOM האמיתי רק אלמנטים שבאמת השתנו. כמובן, שכדי שזה יעבוד, אין לערבב על אותו אזור ב DOM את ראקט עם ספריה אחרת שכותבת ל DOM ישירות.

ראקט מחלקת את הטקסט לרכיבים אטומיים קטנים, כך שיהיה אפשר לעדכן רק את ה nodes של הערכים (בירוק), מבלי לעדכן את הערכים של ה Labels (בצהוב). שימו לב למספור שראקט מייצרת על ה DOM (תכונת data-reactid) – בעזרתו היא מבצעת השאוות יעילות של ה DOM.
הרעיון של Virtual DOM אינו חדש, אגב: Backbase הציגה מין Virtual DOM משלה לפני שנים (היא כנראה לא היחידה), וטכניקה דומה נמצאת בשימוש במנועים גרפיים של משחקי מחשב.
הניתוק מה DOM מאפשר לראקט גם לרוץ בצד השרת (עבור מכשירים חלשים / sub-second load time) – ממש כמו rendr עבור Backbone.js.
השוואה לא מדוייקת בין האתר Hacker News (שורה עליונה) ו clone שלו שכתבנו בראקט (שורה תחתונה). אדום – טעינה ראשונית. בכחול – פעולת דפדפוף בין הכתבות (האתר שלנו היה הרבה-הרבה יותר מהיר). פרופיל ה timeline שקבלנו מקוד הראקט מאוד לא אופייני לאתר / אפליקציה שלא עברה עדיין שום אופטימיזציית ביצועים.

דוגמת קוד

אין כמו לראות קצת קוד.
הנה אפליקציה פשוטה למדי עם 2 כפתורים (++X ו ++Y) המעלים את הערכים של ה counters המתאימים במרכז המסך.

התוצאה הסופית של האפליקציה

קוד ב react ניתן לכתוב באחד מ2 סגננונות: JSX ו”ג’אווהסקריפט נקי”.
JSX הוא הרחבה לג’אווהסקריפט עם XML (מבוסס E4X?), המאפשרת לכתוב snippets של XML בתוך הג’אווהסקריפט ע”פ חוקים מסויימים.

JSX אמור להיות פשוט יותר ונקי יותר לכתיבה – ועל כן מועדף ע”י החלק הגדול של מפתחי ראקט. ה JSX מקומפל לג’אווהסקריפט רגיל לפני שהוא מורץ על הדפדפן. ניתן לקמפל אותו בתוך הדפדפן ע”י הוספת קובץ ג’אווהסקריפט בשם JSXTransformer (לפיתוח מהיר יותר) או בצד השרת ע”י רכיב מתאים של Node (לביצועים טובים יותר).

אנו עבדנו ב IDE בשם WebStorm (מומלץ) שקיבל יפה מאוד את הרחבות ה XML בתוך קובצי ה javaScript, והמשיך לתפקד היטב עם קוד הג’אווהסקריפט (hinting, ניווט, וכו’).

עוד וריאציה נפוצה יחסית לשימוש בראקט היא כתיבה ב CoffeeScript על מנת לכתוב קוד מינימליסטי של ראקט ללא JSX.

בדוגמה זו נראה כתיבה בצורת JSX ולאחר מכן, את הגרסה המקומפלת שלה ל”ג’אווהסקריפט נקי”.

  1. כאשר משתמשים ב JSX Transformer בכדי לקמפל JSX “תוך כדי שימוש” (“on the fly”) – חייבים לכלות הערה זו בתחילת הקובץ.
  2. אנו מגדירים רכיב (component) פשוט של ראקט, בשם Simple.
  3. את הרצת התוכנה בראקט מבצעים בעזרת פקודת renderCompoent על הרכיב הראשי. שימו לב לתגים ה Simple – שאיננה סטנדרטית ב HTML, אלא מבטאת את הרכיב שלנו. אנו שולחים לרכיב ה Simple שלנו Property (בלתי ניתן לשינוי immutable) – בשם message ואומרים לראקט להוסיף את הרכיב בתוך document.body (קרי InnerHTML).
  4. אחד השלבים הראשונים במחזור החיים של רכיב react הוא קריאה למתודה ()getInitialState המאתחלת את ה state של הרכיב. בשלב זה, ניתן להבין שזהו stateful component.
  5. שלב מעט מאוחר יותר במחזור החיים של הרכיב, הוא הקריאה למתודה ()render המייצרת את ה markup של הרכיב – ומחזירה אותו לרכיב האב (במקרה זה – זהו רכיב האב).
  6. יצירת ה markup נעשית על ידי כתיבה inline של markup ב JSX. עטפנו הכל בסוגריים כדי שנוכל למקם את ה div הראשון בשורה חדשה (אחרת JavaScript יחשוב שרצינו לכתוב ;return, ואח”כ אולי עוד משהו לא רלוונטי).
  7. סוגריים מסולסלות הוא ה escaping לשילוב ביטויי ג’אווהסקריפט בתוך ה XML (חייב לחזור ערך). במקרה זה אנו קוראים לאובייקט ה properties של הרכיב (שהוא immutable  – בלתי ניתן לשינוי). את הערך של התכונה message קבענו בשלב 3.
  8. באופן דומה אנו רושמים מטפלים לאירועים (incX, incY).
  9. כאשר האירועים נקראים (בעקבות לחיצה של העכבר על אחד הכפתורים), מספיק שנשנה את ה state של הרכיב – כדי ש react ידאג לקרוא ל render, ובצורה יעילה (למשל: לא יותר מפעם אחת ב Animation Frame של הדפדפן).
  10. לתחביר ה XML יש כמה תכונות מיוחדות – למשל הטיפול ב inline style attribute.
ניתן למצוא online JSX compiler בקישור הבא
כפי שאפשר להבחין, ה markup שאנו כותבים ב JSX הוא איננו HTML. זהו XML שרק דומה ל HTML. הסיבה לשימוש ב XML הוא הצורך ב markup קל לפענוח – HTML הוא מאוד סלחן ובעייתי לפענוח.
הדרך הכי בולטת לשים לב להבדל בין ה JSX XML ו HTML היא לקחת דוגמת HTML מהאינטרנט (שעובדת!) ולהדביק אותה לתוך האפליקציה. כמה הבדלים מרכזיים הם:
  1. אלמנטים חייבים להסגר. לא עוד
    אלא רק
    .
  2. מכיוון שה markup מקומפל לג’אווהסקריפט, לא ניתן להשתמש במלים שמורות של שפת ג’אווהסקריפט. למשל, במקום תכונת class משתמשים ב className.
  3. inline style attribute לא נכתב כמו ב HTML, אלא כאובייקט – כמו בדוגמת הקוד למעלה. אפשר גם לכתוב אותה inline בעזרת סוגריים מסולסלים כפולים {{…}}=style.

בסה”כ, בניגוד לציפיותנו, התרגלנו לכתיבת ה XML דיי מהר – והיא לא היוותה מטרד מיוחד.
מעניין לציין ש react עושה escaping ל markup בכדי להגן בפני חולשות XSS.

הנה הקוד לאחר שקומפל ל JavaScript בעזרת ה JSX Transformer:

הודאה קטנה: זה לא בדיוק הקוד. הקוד שנוצר הוא מעט “עמוס” בגלל השימוש ב “React.DOM” בכל מקום – ולכן עשינו לביטוי זה extract למשתנה שפחות תופס את העין: קו תחתון (בירוק).
הכנסנו את הביטוי במקום שורת רווח כדי לא “לקלקל” את מספרי השורות. ה JSX Transformer דואג לשמר את מספרי השורות בין ה source JSX לתוצר ה javaScript שנוצר.

כמה נושאים מעט יותר מתקדמים

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

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

אפשר לנהל את מודל כזה בצורה הקלאסי (חיבור למודל backbone, למשל) או לתת לרכיב להביא את המידע לו הוא זקוק ישירות מהשרת בעזרת קריאת Ajax (גישה של micro-services – כל רכיב דואג לעצמו מא’ ועד ת’).

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

  1. List רכיב שמיצג את הרשימה כולה – רכיב זה ייצור ListItem עבור כל אחת מהרשומות במודל שלנו, וגם ינהל את הרשימה במידה ויש צורך.
  2. ListItem רכיב שמייצג כל רשומה – רכיב זה יצטרך להכיר את המאפיינים של כל רשומה וידע להציג אותם כראוי.

כזכור, בראקט כל אחד מהרכיבים מחזיר בפונקציות ה () renderאת מבנה ה DOM שעליו הוא אחראי.
בדוגמה שלפנינו, כל רשומה היא בעצם DIV שמכיל את מבנה ה DOM של הרשומה. רכיב ה ListItem יחזיר אך ורק את ה DOM שנדרש בשביל להציג את הרשומה הבודדת.
רכיב ה List שאמור להציג את כלל הרשומות, יחזיר רק את העטיפה הנדרשת לכלל הרשומות, במקרה שלנו רק DIV עוטף אחד.

  1. זוהי הגדרת רכיב האב, ה List. בעת יצירתו הרכיב מקבל startIndex – מאיזה מספר רשומה להציג את ה listItems ו data – הנתונים עצמם המגיעים מהשרת מבנה json.
  2. דרך יעילה לרנדר רשימה של אברים היא היא לעבור על נתוני המקור (this.props.data) בעזרת פונקציית map (של מערך בג’אווהסקריפט) וליצור מכל פריט נתונים – מופע של הרכיב (ListItem).
    לא הייתה מניעה, אגב ש ListItem יהיה מורכב גם הוא מתתי-רכיבים. במקרה שלנו הפרדנו רק אלמנט אחד לרכיב-בן: Comment (האלמנט האחרון).
  3. הרכיב ListItem הוא עצמאי ואינו מודע ל List. אחד הרעיונות בראקט הוא לייצר רכיבים הניתנים לשימוש חוזר.
  4. ניתן לראות כיצד ListItem משתמש בטבעיות כ properties שהושמו לו ביצירה (שלב 2) – אלו בעצם הנתונים כפי שקיבלנו אותם מהשרת.
  5. שראקט יודע לטפל ב markup (למשל: אלמנט div), רכיבים (למשל ) או במערך של רכיבים / אלמנטי markup – כמו במקרה זה.
בעקבות שלב 5 בקוד, ראקט יוציא לנו הערה ב console של הדפדפן בזמן ריצה:
לא כדאי להתעלם מהערות אלו (ואחרות שהוא מוציא): כאשר יש רשימה, ראקט מבקש מאתנו מפתח ייחודי שבעזרתו יזהה שינוי state באובייקטי הרשימה. אחרת, האופטימיזציות של ראקט עלולות לגרום לו לדלג על שינויים שנעשו באובייקט – מבלי לעדכן אותם על המסך.
הנה דוגמה ל key כזה שהוספנו לאובייקט הרשימה בפרויקט שלנו:



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

יש עוד כמה נושאים ששווים ציון, אך היריעה קצרה להכיל. הנה מספר נקודות התייחסות אליהם:
מחזור החיים של הרכיב
כדאי להכיר: Component Lifecycle Event
כשתכתבו רכיבים מורכבים יותר – כנראה שתוכלו להיעזר בשלבים שונים ב lifecycle.
ממשק חיצוני של רכיב
כפי שאמרנו, רכיבים נבנו לשימוש חוזר / תחוף. ראקט מספקת מספר כלים כדי לכתוב רכיבים עמידים (robust):
  • default props – ערכי default במידה והמשתמש לא הגדיר את Property נדרש.
  • propTypes – מנגנון אימות על properties: האם יש property שחייבים להגדיר (isRequired), האם יש Property שסט הערכים התקניים שלו הוא סגור ( ([…])isRequired, ) וכו’.
שימוש חוזר בקוד בין רכיבים
במקום להסתבך עם prototype chains, ראקט מציעה מנגנון נוח יותר של mixins, לשיתוף התנהגות בין כמה components שונים.
שינויים נקודתיים
על רכיב ניתן לשמור קישורים (תכונת refs) לאובייקטים מסוימים ב DOM בכדי לבדוק / לעדכן אותם בצורה נקודתית.

סיכום ורשמים אישיים

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

התיעוד שלה – לא טוב. את רוב התשובות מצאנו ב stackoverflow וכמעט לא בתיעוד הרשמי. נציין שכל נושא שלא עניין אותנו, דווקא היה מתועד בצורה מופתית! (“חוק מרפי של התיעוד”).נתקענו כמה פעמים על בעיות, לזמן לא קצר – אך בסוף הצלחנו לפתור את הבעיות ולהבין את מקורן. למשל גילינו, בדרך הקשה, שקביעת ערך לאובייקט ה state (כלומר (…)setState) לא מתרחשת מיידית, אלא היא מנוהלת בתור ומבוצעת רק לפני ציור ה Activation Frame (כלומר האירוע בו הדפדפן מעדכן את תצוגת המסך, שאמור להתרחש 60 פעמים בשנייה). קביעת ערך בפונציה א’ לא מבטיח שזה הערך שיקרא מאובייקט ה state בפונקציה ב’ שמתרחשת מיד לאחר מכן. עדיף להעביר את הערך כפרטמר לפונקציה ולא להסתמך על ה state שיהיה מעודכן.

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

היינו מנסים את react.js בעתיד באפליקציות לא-גדולות ועתירות רינדור (הייתה לנו אחת כזו לפני מספר חודשים).
מכיוון שרקאט היא לא כ”כ דומה ל frameworks אחרים, כדאי כנראה להשתמש בה רק בצוות שירגיש נוח איתה – לרוב מפתחי client-side hardcore הרגילים לכתוב קרוב ל DOM.
שאר המפתחים – יוכלו להמתין שראקט תתבגר עוד קצת ותגיע לגרסה…נאמר, 0.5.

כותבים שותפים:




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



איתמר שגב הוא מפתח Client-Side ב SAP מאוד אוהב טכנולוגיות חדשות ובעיקר את כל מה שגוגל עושה.

קישורים

[א] בהשוואה לאנגולר. ניתן להזכיר שגוגל לא משתמשת באנגולר במוצרים המרכזיים שלה.

Backbone.js (ספריית MVC) – אוספים

בפוסט הקודם, הצצה ל Backbone.js (בקיצור BB, ובלי לדבר על פוליטיקה) ראינו את שני הטיפוסים המרכזיים ביותר של ספריית BB. בפוסט זה ארצה להרחיב מעט את היריעה לטיפוס מרכזי נוסף שנקרא Collection שמסייע לארגן את המודלים במערכת.
להזכיר: מודל (model) מייצג אובייקט המתאר “יישות בעולם בו עוסקת המערכת”, קרי Domain Object. לעתים קרובות נרצה להציג / לטפל בסדרה של מודלים. BB מאפשר ותומך בצורך זה בעזרת Collection שהוא אוסף של מודלים, המספק יכולות מתקדמות יותר מרשימה פשוטה.שייך לסדרה: MVC בצד הלקוח, ובכלל.

דוגמה בסיסית

בואו נפתח בדוגמה פשוטה:

אתם יכולים לזהות מודל מינימלי בשם Person ואת האוסף (Collection) בשם People – אוסף של Person.

1 – ההגדרה של model בבנאי של האוסף אינה נדרשת בשלב זה. היא משמשת רק כאשר משתמשים ביכולות ה REST המובנות ב Backbone.Model ו Backbone.Collection על מנת לסנכרן שינויים לשרת. איננו עוסקים ביכולת זו עדיין, אך אני מעדיף להגדיר את המודל לצורך הקריאות. הקונבנציה המקובלת ב BB היא לקרוא לאוסף בשם הרבים (s בסוף) של המודל, למשל Invoice ו Invoices.

2 – אנו יוצרים מודל (בצורה נאיבית, אציג דרכים יעילות יותר בהמשך) של מג’יק ג’ונסון ומוסיפים אותו לאוסף.

3 – לכל מודל (בעצם לכל אובייקט של BB) שנוצר ניתן מזהה גלובלי ייחודי, בפורמט “c”. במקרה זה אני יודע שאובייקט של מג’יק ג’ונסון הוא השלישי שנוצר במערכת ועל כן אני יכול לאמת את ה cid שלו. זו הנחה שלא הייתי רוצה להסתמך עליה בקוד אמיתי.
cid הוא קיצור של Client Identifier, שנועד לסייע לזהות מופע של מודל בצורה ייחודית. ב BB מניחים שצד-השרת, בעקבות השימוש בבסיס נתונים כלשהו, יהיה לכל אובייקט זיהוי ייחודי. בטווח הזמן בו האובייקט נוצר בצד-הלקוח ועדיין לא נשמר בבסיס הנתונים וקיבל Id ייחודי מבסיס הנתונים, ה cid אמור להיות כלי העזר על מנת לזהות ייחודית את המודלים.

4 – הוספנו עוד מודל אחד לאוסף.

5 – אוסף של BB, באופן מובנה, יכול להשתמש utilities של underscore.js – שהם דיי שימושיים. הנה דוגמה של פונקציה בשם plunk שעוברת על כל אחד מהמודלים באוסף, ומכניסה לתוך מערך התשובה ערך של שדה שהוגדר, במקרה זה – כל שמות המשפחה. כמה נוח.
כן, underscore.js “מתעסק לנו” באובייקטים בסיסיים של השפה כמו “Object” או “Array”‘, במקרה זה הוא הוסיף את plunk ומתודות אחרות למערך. נכון, זה משהו שממליצים לא לעשות. לא לעשות באפליקציה – אך זה “מותר” לספרייה. תתרגלו.

Collection ו Views

הנה דוגמה מורכבת מעט יותר המציגה חיבור של Collection ל View:
View אחד עבור כל Person, ועוד View עוטף של האוסף People.

התוצאה, עבור אוסף מינימלי של 2 אנשים, תראה משהו כזה:

הכותרת “שני אנשים” שייכת לPeopleView, והשאר נוצר ע”י שני מופעים של PersonView. בלי שום Styling כמובן – בכדי לצמצם את גודל הדוגמה.

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

ב1 – אנו יוצרים אוסף בשם people ומזינים אותו בערכים. המשמעות של reset היא “החלף את כל הערכים הנוכחיים ברשימת הערכים הבאה” והיא האופן בו נרצה להזין ערכים כאשר אנו טוענים מצד-השרת רשימה מלאה של ערכים. אנו מזינים את people במערך של אובייקטים שכל אחד מתאר את המודל של הרשימה – Person.

ב2 – אנו יוצרים את ה PeopleView, ה View שיודע להציג את הרשימה וליצור את ה HTML הנ”ל. נראה את הקוד שלו מייד.

ב3 – כך מבצעים בדיקת-יחידה יציבה (לא-שבירה) עבור תוצאת HTML. טעות נפוצה היא להשוות את כל תוצאת ה HTML כ String לערך שהיה נכון בסוף רגע הפיתוח ובחנו שהוא תקין. הבעיה: כל שינוי ב markup – אפילו ריווח או תוספת תג לא משמעותית (למשל label) תשבור את הבדיקה – גם כאשר התוצאה עדיין תקינה. הדרך הנכונה לבדוק היא לאתר “עוגנים חשובים” ב HTML ולבדוק רק אותם. האומנות היא למצוא את רמת הפירוט האופטימלית.
אנו מאמתים את טקסט הכותרת
אנו מאמתים את מס’ האלמנטים מסוג h2
אנו מאמתים שהטקסט של הראשון הוא “ג’ונסון, מגיק” – כלומר את הפורמט. eq היא המקבילה של jQuery ל index.
אנו מאמתים שאחרי (“~” ב css selector) הכותרת h2 הראשונה ישנו אלמנט p שמכיל את כתובת האימייל של זיגי. איננו רוצים “להפיל” את הבדיקה אם מחליפים את התחילית “email” באייקון קטן, למשל. אנו מתמקדים בנתונים ההכרחיים.

ב4 – אנו מבצעים reset לנתונים – עם רשימה באורך 3, רק כדי לוודא שהכותרת הראשית (h1) אכן תלויה באורך הרשימה.

הנה הקוד עצמו:

ההתחלה אמורה להיות מוכרת. פרקטיקה טובה היא להוסיף ערכי defaults למודל עבור כל שדה שאנו הולכים להשתמש בו ב View – אחרת אנו עשויים להיתקל ב Runtime Errors ללא fallback (למשל כשיש מודל עם age בלבד).

1 – לא התאפקתי והשתמשתי הפעם ב template engine – שהיא הדרך המומלצת ב BB כדי לייצר את ה markup עצמו. השתמשתי בצורה בסיסית ביותר בספרייה המובנה בתוך underscore.js (אתם יכולים לזהות ע”פ תחביר ה ERB/JSP), אבל אין שום מניעה לעבוד עם ספרייה אחרת כמו handlebars.js, mustache.js או dust.js.
אתם יכולים לשים לב שב template.people יש קו על התגית . הסיבה: התגית היא deprecated ב HTML5. הדרך ה”נכונה” ע”פ HTML5 לייצר קו תחתון הוא לעטוף ב span עם class שלי ואז להחיל עליו קו-תחתון בעזרת CSS. לצורך דוגמה זו אתעלם מזוטות שכאלה.

עוד נקודה מעניינת היא השימוש ב class (נקודה) ולא id (סולמית) על מנת לזהות את החלק שבו “יוזרקו” חתיכות ה markup של ה Person הבודד. על Id ב HTML להיות ייחודי בכל הדף ואיני יודע בוודאות אם ה PeopleView יוצג יותר מפעם אחת על הדף או לא. סיכוי אחר: אולי מישהו אחר ישתמש בid בשם people-list במקום אחר. על מנת להישאר סטנדרטיים יש להימנע משימוש ב id אלא אם אפשר לוודא שהוא אכן ייחודי – מה שלרוב קשה לדעת. הפתרון: שימוש ב  css class לזיהוי.

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

גישה פופולרית היום היא להקליד את ה templates עצמם בתוך ה HTML עצמו עטופים בתג ועם id ידוע מראש ואז להשתמש ב jQuery על מנת לטעון את ה template בזמן ריצה. ה template כמובן אינו משפיע על ה HTML – הוא רק “יושב שם מבלי להפריע”. מדוע לעשות זאת?

  • כדי ליהנות בעת כתיבת ה template מה HTML Editor עם Syntax Highlighting ובדיקת שגיאות.
  • הצמדות לכלל שאומר: makrup נמצא בקובץ ה HTML – לוגיקה ב javaScript. כל ספריות ה template engine שציינתי למעלה הן “logic-less” ואינן מאפשרות כתיבת javaScript בתוך ה template, אלא רק לוגיקה רק בשפה הפנימית שלהן. היה נכון יותר לקרוא להן “javaScript-less”, בעצם.

מדוע, אם כן, אני מעדיף בכל זאת לשמור את ה templates בתוך ה javaScript code?

  1. אני משתמש בIDE בשם WebStorm שמספק לי את היתרונות של HTML Editor בתוך קובצי הג’אווהסקריפט. החיסרון היחיד: חוסר החופש לייצר שורה חדשה ללא +.
  2. כתיבת ה templates בתוך ה javaScript מאפשרים לי להריץ את בדיקות-היחידה ללא קובצי HTML – מה שמאפשר להם לרוץ הרבה יותר מהר. התוצאה: אני מריץ בדיקות בצורה תכופה הרבה יותר – כי כולן רצות בשתי שניות בתוך ה IDE.

2 – ביצירה של ה PersonView אני מייצר את ה”אוטומט” של ה template. על מנת להשיג יעילות מרבית, ה template engine נוהגים לפרק את ה string של ה template לצורה מהירה יותר לשימוש. לרוב מקובל לקרוא למשתנה שמחזיק את התבנית פשוט “template”, השתמשתי ב myTemplate על מנת לנסות ולהקל על ההבנה במקרה זה.

3 – כאן מבצועת ההפעלה של ה”אוטומט” של ה template engine על נתונים מתאימים, במקרה זה – המודל המדובר. ה”אוטומט” myTemplate הוא בעצם פונקציה שמרנדרת html markup ברגע שמזינים לה נתונים. אנו טוענים את התוצאה לתוך this.el שהוא, כאמור, העטיפה ב DOM ל View שלנו.

בואו נעבור עכשיו ל PeopleView המייצג את התמונה הכוללת:
4 – לאחר שרינדרתי את החלק של People ב HTML, הרי היא ה”מסגרת” (במקרה שלנו – רק כותרת), אני רוצה להזין את הנתונים של המודלים הבודדים. אני קורא ל $.this על מנת לבצע חיפוש בתוך ה markup של this.el בלבד, אחר המקום בו “אזריק” את ה Views של המודלים הבודדים. זוהי גרסה מקוצרת (ומומלצת) לכתיבת “(this.el).find(‘.people-list’)$” (אני מניח שאתם מכירים jQuery).

5 – each של collection מאפשרת לי להריץ פונקציה עבור כל מודל באוסף – משהו כמו foreach.

6 – או יוצרים מופע של PersonView עם המודל ה person הנוכחי. אולי זה מרגיש קצת “כבד” לייצר View כל פעם ולא לייצר אחד ולעשות בו שימוש חוזר – אך זה בסדר.

7 – אנו משתמשים ב view על מנת לרנדר את ה markup ומוסיפים את ה “glueing markup”, שבמקרה זה הוא רק תגית br. אנו רוצים לשמור את ה template של Person “נקייה” כך שנוכל להשתמש בה גם ללא ה PeopleView.

סיכום

בפוסט זה צללנו לדוגמה ריאלית קצת-יותר (מהפוסט הקודם) של קוד ב backbone – על מנת להבין כיצד קוד ב BB “מרגיש”. אני מקווה שהצלחתי להעביר את התחושה, אם כי היא עדיין מינימלית למדי. כפי שראינו, BB לא עושה “קסמים” (כמו Ember או Angular) – אנו עדיין כותבים את כל הקוד, אך BB נותן לנו מסגרת, guidance ו utilities על מנת להגיע לשם.
על הדרך השלמנו את הפער אודות השימוש ב template engine, שהוא היבט מרכזי בספריות “javaScript MVC”, והדגמתי כיצד לבצע בדיקות-יחידה ל View וראינו Views מקוננים. כל זה דחוס בפוסט לא כל-כך ארוך. אני מקווה ששמרתי על איזון נכון של “לא משמעמם” ו “ניתן להבנה”.

הערות יתקבלו בשמחה!

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

הצצה מהירה על Backbone.js (ספריית MVC)

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

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

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

שייך לסדרה: MVC בצד הלקוח, ובכלל.

סמל, שאם תכתבו ב Backbone.js – תיתקלו בו עוד הרבה…

רקע

לא ניתן להתעלם מתרומתה הרבה של ספריית jQuery ודומותיה[ב] בעיצוב מקומה החדש של שפת ג׳אווהסקריפט. במעגל השני של ההשפעה (לפחות בצד-הלקוח) נמצאות ספריות שעוזרות בכתיבת אפליקציות צד-לקוח גדולות. ספריות אלו נקראות ״MVC Frameworks” או ״ספריות ארכיטקטוניות״. השם MVC עשוי לבלבל את מי שכבר מכיר ועבד עם ספריות MVC צד-שרת כגון Struts או ASP.NET MVC.

בעוד ספריות “MVC קלאסיות” מטפלות באפליקציות רבות דפים והניווט בניהם, ספריות ה “JavaScript MVC” לרוב מטפלות באפליקציות דף-יחיד (כגון GMail) בהן יש מסך עקרי אחד שחלקים בו מתחלפים / משתנים – אך אין ניווט משמעותי. בכלל, עצם הפעולה בצד-הלקוח, ב Ajax, בו אין refresh לדף בעת פעולה – משנה את כללי המשחק: מציב בעיות חדשות ופתרונות אחרים. עוד הבדל בולט הוא שברוב ספריות ה “JavaScript MVC” אין Controller. השם MVC בא לבטא את הרזולוציה ואת הבעיה שהן באות לפתור: ארגון קוד באפליקציית UI גדולה ומורכבת.

קיימות כיום מספר רב של ספריות “JavaScript MVC” וצפוי בהן עוד תהליך של קונסולידציה. שלושת הגדולות הן:

  • Backbone.js כנראה הנפוצה והבוגרת מכולן. מספקת מסגרת כללית בלבד ומשאירה חופש / חללים למפתח להחליט ולמלא בעצמו.
  • Knockout.js ששמה במרכז את ה Data Binding ומאפשרת לכתוב בקלות אפליקציות “מונחות-נתונים” (מערכות מידע וכיוצ”ב) נפוצה במיוחד בעולם ה NET.
  • Ember.js (לשעבר Amber.js), ספרייה מקיפה שכוללת גם Class System[ג] ושואבת רעיונות רבים מ Rails ״כתוב מעט קוד, אך עשה זאת בדרך שלנו” צעירה יחסית – אך זוכה למומנטום משמעותי. נפוצה במיוחד בקרב מתכנתי רובי.

ספריות משמעותיות אחרות הן Spine.js, JavaScriptMVC, Batman.js ו AngularJS (מבית גוגל). האתר המצוין todoMVC מציע השוואה מעמיקה בין האפשרויות בכך שמימש אפליקציית דוגמה בכל אחת מספריות הנ”ל (ועוד כמה ספריות נוספות).

רגע של התפלספות

כפי שציינתי, ספריות “JavaScript MVC” הן לא בדיוק MVC קלאסי. בעצם, גם ל”MVC קלאסי״ יש כמה פרשנויות שונות (לדוגמה, האם ה Controller מתמקד בעיקר בטיפול ב input או אחריות על ניהול הflow והתלויות?[ד]).

המדקדקים מגדירים את Knockout.js כ MVVM) Model, View, View-Model) – תבנית עיצוב שמייקרוסופט מקדמת בעולם הNET. זה דיי נכון.
את Backbone.js ו Ember.js נוהגים להגדיר כ Model, View, Presenter) MVP). האמת, ספריות כמו ASP.NET או GWT מתאימות יותר להגדרה זו. הגדרה כגון Model, View-Controller, Router (בקיצור MVCR) תהיה יותר מדויקת.

ההגדרה המועדפת עלי כרגע היא !Model, View, Whatever (בקיצור MVW או *MV).
התחלתי את הפוסט הזה בניסיון לעקוב אחר ההיסטוריה של תבנית העיצוב MVC, הוריאציות והשינויים שהיא עברה במשך השנים, בעיקר MVP ו MVVM, והניסיון להסביר כיצד ספריות ה “JavaScript MVC” מתאימות לתבניות אלו.

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

בסופו של דבר – MVC הוא ניסיון לארגן אפליקציית UI מורכבת בצורה שתהיה קלה לתחזוקה ושינויים. כל דור של UI (Web, Fat Client, Fully Client Side, Command Line) – והאתגרים שלו.
כפי שמרטין פאולר ציין פעם: “אנשים נוטים להסתבך עם MVC. החלק הכי חשוב הוא לבצע הפרדה אמיתית בין Business Logic ל UI. כל השאר – משני”. אני מסכים ב 100%.

לכל ספרייה יש את הדרך שלה לארגן את הקוד – ואולי יותר מעשי ומעניין פשוט להתבונן בישומים קונקרטיים.
בפוסט זה אתן הצצה בסיסית ביותר ל MVW בג׳אווהסקריפט בדמות Backbone.js – הספרייה הנפוצה והמינימליסטית שיכולה להיות בסיס לימודי מצוין להבנת MVC בג׳אווהסקריפט.

אז מה יש ב Backbone.js?

אקצר מעתה ואקרא ל Backbone.js פשוט BB.
  • BB היא קטנה למדי. 700 שורות קוד בערך. 1400 שורות עם הערות.
  • BB משתמשת ב underscore.js (ספרייה טובה ל javaScript Utils מאותו היוצר) ו jQuery. אם אתם עובדים עם BB – יהיה לכם מאוד נוח להשתמש גם כן בספריות אלו, וקצת פחות נוח (אך אפשרי לחלוטין) להשתמש בספריות אחרות. BB מעודדת שימוש בספריית Templating – ואתם יכולים לבחור את זו של underscore (תחביר JSP-like, שלי אישית, עושה צמרמורת) או כל אחת אחרת.
  • ל BB יש תיעוד טוב (reference) ו Tutorials סבירים. יש קהילה גדולה ופעילה.
  • בגלל ש BB פשוטה – מייחסים אותה כמתאימה למערכות פשוטות יחסית – אך בפועל יש גם מערכות גדולות מאוד ומורכבות שנכתבו בעזרתה.
  • מאוד קל לעבוד עם BB מול שרת שמספק REST APIs – במיוחד אם תבנית-הנתונים היא JSON.
  • ל BB יש ארכיטקטורת Plug-Ins וניתן למצוא Plug-Ins רבים שמרחיבים את יכולות-הבסיס שלה.
  • BB מספקת מסגרת לא-מחייבת. אם אתם רוצים לפעול קצת אחרת, יש לכם את החופש לעשות זאת. כאשר מדובר במבנה של האפליקציה שלי – אני אוהב את זה.
  • כמו ספריות “JS MVC” אחרות, BB מספקת:
    1. מערכת-מחלקות (Class System) עבור Model ו Views (וגם Routers ו Collections – שהם רכיבים מרכזיים אחרים בBB)
    2. ניהול של המודלים (ה instances), כולל הקשרים בניהם.
    3. כלים לאימות ערכים (Validation Logic) במודל.

הנה דוגמה למודל מינימלי שניתן לייצר ב BB:

הערה: משפטי ה expect…toBe הן בדיקות-יחידה המציגות את התנהגות הקוד. כל הבדיקות עוברות בהצלחה.

המודל שלנו, Person בדוגמה זו, הוא פשוט למדי. הייתי יכול ליצור אותו גם ללא defaults – רק עלי ידי הורשה ריקה מהמחלקה Backbone.Model. תכונות המודל (כמו שם פרטי/משפחה) הן דינמיות ומוגדרות בזמן יצירת האובייקט הספציפי. ה default היא היכולת לחייב שתכונות מסוימות יהיו בכל מקרה.

ב1 – אנו יוצרים מודל של אדם בשם אוזי כהן.
ב2 – בכדי להדגיש את השוני בינו ובין אמן אחר בשם ״ליאונרד״ ובעל שם משפחה זהה, אוזי משנה את שם המשפחה שלו.
ב3 – אנו מקבלים את המודל המלא בפורמט JSON.

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

השתמשתי במודל הכי פשוט שאפשר – כי ברצוני להתמקד ב View.

PersonView היא המחלקה שאחראית לצייר מודל של Person. יכולים להיות, כמובן, כמה Views שונים לאותו המודל. את ה markup שה View יוצר, ארצה לשלב היכנשהו ב DOM. בדוגמה זו – זה יהיה container$, שמשתמש ב span בכדי לא לבלבל עם ה div ש Backbone יוצר באופן אוטומטי לכל מופע של המודל.

el הוא בעצם אלמנט ה DOM שיצר לי BB כחלק מה View. הוא עושה זאת על מנת לאפשר ל markup בפנים להשתנות, בלי שאצטרך לחבר את ה markup שוב ל container. הבחירה ב div היא כמובן רק default, ואם אני זקוק ל list element (תג

  • ) – אפשר לקבוע זאת.ב1- אני בודק מה יש בתוך ה container – ניתן לראות את ה markup שנוצר בתוך הפונקציה render. זו דוגמה דיי מכוערת – לרוב נשתמש בספריית templating על מנת שהקוד שיוצר את ה html snippet יהיה אלגנטי. לא רציתי להעמיס על דוגמה זו ולכן הקוד ה”פרימיטיבי”.

    ב2- אנחנו בוחנים את ה markup כולל ה container. רק לוודא שהתמונה הכללית ברורה.

    ב3- אנחנו מבצעים שינוי למודל ורואים שה markup ב container השתנה באופן פלאי. “פלאי” – כמובן שלא. אנחנו כותבים את הקוד שעושה זאת – backbone רק מכווין אותנו למבנה מסוים ועוזר לנו.

    בואו נבחן את הפונקציה initialize שהיא לצורך העניין הבנאי (constructor) של מחלקת ה PersonView שלנו, וכוללת 2 משפטים מוזרים:
    ב5- אנחנו מבצעים פעולת JavaScripts’s bind לכל המתודות במחלקה שאנו הולכים ל”ייצא” כ callbacks לאובייקטים אחרים. במקרה זה – יש רק את ״render״, אך יכולתי באותה מידה לשרשר גם שמות של מתודות נוספות. למה צריך לעשות JavaScript bind? – כדי ש this ימשיך להצביע לאובייקט שלנו. מבולבלים? קראו את הפוסט להבין את JavaScript this.
    הסימן “_” (קו תחתון) הוא הקיצור לגשת לספרייה underscore.js, ממש כמו שניגשים עם $ ל jQuery. אלו המקומות ש underscore מסייעת מאוד לכתוב קוד ב BB. אתם לא חייבים – אבל כדאי.

    ב6- אנחנו “מחייטים” את ה View שלנו לבצע פעולת render כל פעם שהמודל משתנה (change’ event’). מאוד דומה לאירועים ב jQuery.
    הבהרה קלה: אם אתם מכירים את תבנית העיצוב MVC אתם יכולים להבחין שה View ב Backbone מבצע פעולות (“חיוט”) של Controller. זה נכון: אפשר לומר שה View ב BB הוא בעצם View-Controller: הוא עושה הרבה פעולות שבמקור יוחסו ל Controller.
    אם אתם מכירים את תבנית העיצוב MVP נראה שה View הוא בעצם יותר Presenter. הוא אחראי ל Presentation Logic. זה נכון: בעצם מה שנקרא “View” ב BB הוא דומה ל MVP’s Presenter בעוד ספריית ה Templating (בעצם ה Template עצמו) – דומה מאוד ל MVP’s View.

    סיכום

    הצצנו ל Backbone וטעמנו כיצד נראה קוד שמשתמש ב Backbone.js. טעימה קטנה.
    דיברנו גם על החשיבות של ספריות “JavaScript MVC” – כאשר קוד הג’אווסקריפט שלכם מתחיל לגדול ולהסתבך. וגם על החשיבות לא לחפור בתבנית-העיצוב MVC וההיסטוריה שלה יותר מדי 🙂

    ייתכן ואמשיך לכתוב קצת על Backbone על מנת להתעמק בו קצת יותר, בניסיון לבחון JavaScript MVC “מהשטח”.

    בהצלחה!

    ——

    [א] נקראת Harmony, ג׳אווהסקריפט 2.0 או ECMAScript 6.0. כוללות, ככל הנראה, מחלקות, מודולים (חבילות של מחלקות עם ניהול תלויות), תיקונים לכמה כשלים של שפת ג׳אווהסקריפט ועוד. ממש מהפיכה!

    [ב] Prototype ו MooTools היו גם הן מועמדות ראויות, אך קהל המפתחים בחר לבסוף בjQuery. ניתן לקרוא על העיקרון מאחורי jQuery בפוסט מבוא מואץ ל jQuery.

    [ג] כלים / Framework להגדרת מחלקות וכל מה שקשור בהן. בג’אווהסקריפט, כפי מתואר בפוסט מחלקות בג’אווהסקריפט. בשפת ג’אווהסקריפט לא קיימות מחלקות – ועל המתכנת לייצר אותן לבד. בעוד ספריות “JavaScript MVC” רבות מספקות סביבה אחידה / מוגנת ליצירת מחלקות עבור האלמנטים הבסיסיים בספריה (כמו Model או View).- ספריית Ember מספקת כלים שתוכלו להשתמש בהם עבור כל המחלקות בפרויקט שלכם.

    [ד] המקור של MVC הוא בשפת Smalltalk בשנות ה-70. כשהגיעו ה Fat Clients (ב ++C), ולאחר מכן הווב – נוצרו פרשנויות ווריאציות שונות ל MVC על מנת להתמודד עם האתגרים החדשים שהציבו טכנולוגיות אלו.

על בדיקות-יחידה (Unit Testing)

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

מי שניסה Unit Tests מוצלח עלול פשוט להתאהב! כנראה שמעבר לתמורה המעשית – זוהי חוויה רגשית. אחרת, אינני יודע להסביר מדוע אנשי מפתח בעולם התוכנה מתאמצים להוכיח שכתיבת Unit Tests בעצם חוסכת בעלויות הפיתוח[א], מעלה את האיכות, משמחת את הלקוח ומרצה את בעלי המניות… . נראה שהם מאמינים מושבעים המנסים רק להוכיח את אמונתם. מי שחווה את הנירוונה של הרצה של 100+ בדיקות שמסתיימות כולן בהצלחה אחרי ביצוע שינוי מהותי בקוד – מבין את ההרגשה הממכרת. הצבע הירוק זורם ומפיח חיים בעץ הבדיקות שלכם. החרדה מהלא-ברור מתחלפת בשלווה מרגיעה שזורמת בעורקיכם ומפיגה כל זכר לקושי או מתח…

שנייה… להתאהב?! שלווה? נירוונה? – “על מה אתה מדבר?!”
אני יכול להעלות בזיכרוני תמונות של עשרות פרצופים סובלים וממורמרים בעקבות “איזו החלטת הנהלה לכתוב Unit Tests”. אז תנו לי להסביר: פעמים רבות ראיתי ניסיונות לאימוץ Unit Tests שגרמו סבל רב למפתחים ו/או לא החזיקו מעמד זמן רב. הייתי אומר שנתקלתי ב 2-3 ניסיונות כושלים לאמץ Unit Tests על כל ניסיון מוצלח.

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

מה הן בעצם בדיקות-יחידה (Unit Tests)?

[מינוח: “קוד פעיל” = productive code. זה שעושה את העבודה.]

בדיקות יחידה הן:

  • מקבילות לקוד הפעיל. לכל חתיכת קוד פעיל יש חתיכה “חברה” של קוד בדיקה שבודק אותו.
  • נכתבת ע”י המפתח שכתב גם את הקוד הפעיל.
  • רצות כל הזמן, תוך כדי פיתוח, כמו “קומפיילר שני”.
  • בהערכה גסה, ניתן לצפות לשורת קוד של בדיקות לכל שורת קוד של קוד פעיל.
  • דורשות תחזוקה. ימצאו בהן באגים.
  • דורשות שהקוד הפעיל ייכתב בגישה קצת אחרת: “Testable Code”.
  • רצות מהר. ממש מהר[ב].
  • דורשות מאסה קריטית (של כיסוי הקוד הפעיל, לפחות באזור מסוים) – על מנת להיות יעילות.
  • “בונות” אצל המפתחים ביטחון אמיתי בנכונותו של הקוד הפעיל.
  • משפרות איכות פנימית.
  • מובילות אותנו לכתוב קוד מודולרי וברור יותר. בזמן כתיבת הבדיקה, אנו הופכים לרגע מ”יצרני הקוד הפעיל” ל”צרכני הקוד הפעיל” – מה שעוזר מאוד להבחין בקלות השימוש בממשקים / API.
  • משרתות אותנו כתיעוד מעודכן ורב-עצמה.
ההבדל בין בדיקות יחידה שמחזיקות את ההשקעה לבין אלו שלא. שימו לב: לעתים נרצה בדיקות יחידה גם אם הן עולות לנו יותר. מקור:  http://xunitpatterns.com/Goals%20of%20Test%20Automation.html
כתיבה ותחזוקה של בדיקות-יחידה אכן גוזלות זמן, אך מצד שני הן חוסכות ומקצרות תהליכי פיתוח אחרים.
קוד לוגי מורכב יכול להיבדק ישר ב IDE. המתכנת יודע תוך שנייה אם הקוד עובד או לא. האלטרנטיבה, ללא קיומן של בדיקות-יחידה, היא לבצע Deploy של הקוד, להגיע למצב הרצוי במערכת ורק אז לבדוק אם התוצאה היא הרצויה – תהליך ארוך בהרבה.
היכולת לבצע refactoring בביטחון ובמהירות עם סיכוי נמוך לתקלות – גם הוא זרז משמעותי בתהליך הפיתוח. במיוחד בפרוייקטים גדולים בהם אינני יכול להכיר את כל הקוד.ראיתי שני אנשים שישבו ביחד וכתבו אלגוריתם אך הסתבכו: כל רגע מקרה אחר לא עבד. הנטייה הייתה לא לכתוב קוד בדיקות עד שהאלגוריתם לא עובד, “למה לכתוב פעמיים?”. דווקא בכך שהתחילו לכתוב בדיקות תהליך הכתיבה הסתיים מהר יותר: אדם אחד שכתב בדיקות הצליח לסיים את המשימה מהר יותר מאלו שלא. הכל בזכות faster feedback cycle.

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

בדיקות יחידה הן לא:
  • נכתבות ע”י אנשי QA, מפתחים צעירים או סטודנטים.
  • כלי יעיל לשיפור איכות חיצונית.
  • נזרקות לאחר שהקוד הפעיל “משוחרר” (shipped).
  • קוד שקל במיוחד לכתיבה.
  • מיוצרות ע”י כלים אוטומטיים (generated). יש להפעיל לא-מעט חשיבה אנושית בריאה על מנת לכתוב בדיקות יחידה טובות.
  • בדיקות אינטגרציה / מערכת / רכיב  component test / פונציונליות functional test / או API test.

בדיקות אינטגרציה / מערכת / פונציונליות / … (מסומנות בתרשים למטעה כ”F”) בודקות את המערכת כמקשה אחת. הן לרוב מתבצעות מול השכבות העליונות של המערכת (כדי לחסוך עבודה) ומפעילות פונקציות שקריאה להן מחלחלת לשאר המערכת.
כאשר יש באג באחד הרכיבים במערכת – בדיקות רבות יכשלו. כל הבדיקות שעשו שימוש באותו הרכיב. אם נרצה לפתור את הבאג יהיה עלינו להתחיל ולחקור מהיכן הוא נובע. רשימת הבדיקות שנכשלו ייתנו לנו רק כיוון כללי לחקירה.

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

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

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

הערה: איני מנסה לטעון ש”בדיקות-יחידה הן טובות” או “בדיקות פונקציונליות הן פחות טובות”. פתרון אוטומציה מאוזן כולל לרוב כ 70% בדיקות יחידה, כ 20% בדיקות פונקציונליות וכ 10% בדיקות ל UI. הבדיקות השונות משלימות זו את זו.

מה בודקים בבדיקות יחידה?

אפשר לחלק את הקוד הפעיל ל3 אזורים:

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

  1. קבע את X להיות 4.
  2. השם את X.
  3. קרא את X והשווה שהוא 4.

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

קוד אינטגרציה
קוד זה הוא קוד Gluing שמחבר בין רכיבים / מערכות:

  1. עשה א’
  2. עשה ב’
  3. עשה ג’
במקרים אלו קוד הבדיקה יכלול כנראה mock על מנת להקשיב להתנהגות הקוד והבדיקה תבדוק שא’, ב’ וג’ אכן נקראו. אולי אפילו שהם נקראו לפי הסדר. קוד הבדיקה עלול להיות ארוך משמעותית מהקוד הפעיל. אם הפעולות המדוברות שייכות לאובייקטים אחרים – אדרבא. לעתים קרובות קוד האינטגרציה יכלול אפילו קריאות למערכות אחרות / קוד שלא באחריותנו. עלינו לכתוב Mock Object ועוד Mock Object… בשביל מה כל זה? בשביל לבדוק לוגיקה דיי פשוטה של קריאה לסט פעולות ע”פ הסדר?
למרות שקוד זה רגיש יותר לשבירה במהלך חיי המערכת (תכונה שגורמת לנו לרצות ולבדוק אותו), כל שינוי שלו ידרוש מיד שינוי של הבדיקה. קוד הבדיקה הוא השתקפות (שמנה) של הקוד הפעיל.
מסקנה: ההשקעה בבדיקת קוד שכזה לא משתלמת מבחינת ההשקעה. אולי במערכת שבהן האיכות מאוד חשובה – משתלם להשקיע בבדיקות של קוד אינטגרציה. באופן כללי: התועלת להשקעה – נמוכה.

קוד לוגי (Business Logic)
קוד זה מתאפיין באזורי קוד שהם מופיעות פקודות if ו for (או המקבילות בשפה בהם אתם עובדים) – conditional logic. הקוד לוגי הוא הקוד הרגיש ביותר ל”שבירה”. בעצם: לעתים רבות הוא לא כתוב כשורה מלכתחילה!

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

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

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

function FormValidator($form) {

  var username = $form.find('#username'),
      email    = $form.find('#email'),
      error    = $form.find('.error');



  $form.bind('submit', function() {


    if (username.val() === 'Wizard') {
      error.html('Your argument is invalid');
      return false; // prevents the submission of the form
    }
    else if (username.val() !== 'Harry') {
      error.html('Your name is invalid');
      return false;
    }
    else if (!/@/.test(email.val())) {
      error.html('Your email is invalid');
      return false;
    }


  });
};

בפונקציה ניתן לזהות מספר רב של משפטי if, מה שמעיד על כך שיש כאן קוד לוגי – קוד שאנו רוצים לבדוק. גם regular expression (שהתחביר שלו בג’אווהסקריפט הוא טקסט בין סוגרי “/” כמו בביטוי האחרון) הוא קוד לוגי. ביטוי ה regex מבטא תיאור conditional logic כללית שנוכל להזין לה כמה דוגמאות ולוודא שהתוצאה המתקבלת היא הרצויה.

כדי להפעיל את הפונקציה אנו זקוקים להעביר ארגומנט בשם form$ (אובייקט jQuery) שנוצר ממציאת אלמנט form ב HTML שמכיל תגיות (כנראה מסוג input) עם מזהים בשם username וemail. את התוצאה של הרצת הפונקציה נוכל לקרוא מתוך שדה ה error (כמה נוח) בתוך אלמנט ה form.

יש לנו כמה בעיות:

  • הפונקציה החיצונית תרוץ, אך הפונקציות הפנימיות לא יפעלו ללא לחיצה של המשתמש על כפתור “Submit”. אולי אפשר “לזייף” לחיצה ב javaScript?
  • ה errors הם text string למשתמש שיכולים להשתנות בקלות. כל שינוי ישבור את הבדיקה => בדיקה רגישה לשינויים.
  • טעינת ה html markup (דף HTML עם javaScript נלווים) עלולה לקחת זמן רב. כלל שהבדיקה אטית יותר – יריצו אותה פחות. בבדיקות JavaScript מקובל להפריד את הבדיקות לקובצי html נפרדים ולהריץ רק אותם – אך עדיין יש פה עבודה לכתוב ולתחזק את ה makrup.
בכתיבת קוד בדיקות בג’אווהסקריפט מקובל לכתוב את בדיקת-היחידה בקובץ html נפרד. ניתן לכתוב קוד שיפעיל פעולת submit ויתפוס את ה error שנזרק. התוצאה: אנו כותבים ומשקיעים יותר שורות קוד וזמן בבדיקה מאשר בקוד הפעיל. לא סימן טוב.
במקרים אחרים, פחות ידידותיים (למשל אסינכרוניות, הסתמכות על סביבה חיצונית כמו טעינת image), כתיבת הבדיקות עלולה להיות פרויקט של ממש. אנו רוצים להימנע מכך.
הקוד למעלה הוא בעצם קוד מעורבב: קוד אינטגרציה וקוד לוגי. אם בבדיקות יחידה עסקנינו, עלינו ללמוד להפריד חלב (אינטגרציה) ואוויר (קוד נטול-לוגיקה) מבשר (קוד לוגי) – ולהתמקד בבדיקות בבשר בלבד. במערכת קיימת ההפרדה היא מאמץ – דבר שמקשה מאוד על ההוספה של בדיקות-יחידה ומפחית את יחס העלות-תועלת. לאחר שביצענו את התרגיל הזה כמה פעמים, יהיה לנו טבעי לכתוב את הקוד מופרד מלכתחילה – כך שכתיבת קוד חדש ובדיק (testable) לא תגזול זמן רב יותר. קוד בדיק הוא גם קוד מודולרי ונקי יותר – כך בעצם אנו מרוויחם פעמיים!

הנה הקוד לאחר שעשינו לו refactoring ובודדנו את הקוד הלוגי – ללא תלות ב DOM:

FormValidator.validate = function(username, email) {

  var errors = [];

  if (username === 'Wizard')
    errors.push('Your argument is invalid');

  else if (username !== 'Harry')
    errors.push('Your name is invalid');

  else if (!/@/.test(email))
    errors.push('Your email is invalid');

  return errors;

}

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

  • החזרת רק קוד ה error והפיכתו לטקסט מאוחר יותר.
  • קבלת כל אלמנטי ה input בטופס כמערך בתור פרמטר לפונקציה – כך שלא יהיה צריך לשנות את החתימה שלה (ושל הבדיקות) בכל פעם שנוסף לטופס פרמטר.

כלי עבודה

  • xUnit – ספרית בדיקה לשפה שלכם כגון nUnit ל #C או jUnit לג’אווה.
  • (CI (continuous integration על מנת להריץ את הבדיקות כל הזמן ולדעת מוקדם ככל האפשר – כשמשהו נשבר.
  • Code Coverage Tool – על מנת לדעת כמה מהקוד שלכם עובר בדיקה. למשל Emma ל Java או NCover ל #C.

אם אתם עובדים עם Java ו Jenkins (כלי CI) יש תוספת מאוד נחמדה בשם Sonar שעושה גם Code Coverage בעזרת Emma ומאפשרת ביצוע Drill Down שימושי למדי.

על מדד ה Code Coverage
אחד הדברים שמנהלים אוהבים לעשות הוא להציב לצוות יעדים מספריים. (Code Coverage (CC הוא מדד מספרי ברור וגלוי (אם הוא משתקף באיזה דו”ח שקל להגיע אליו) המתאר את כמות הקוד הפעיל שמכוסה ע”י בדיקות-היחידה. החישוב הוא % שורות הקוד הפעיל שהופעלו בעת הרצת כל בדיקות היחידה הקיימות אצלנו.

בעבר פיתחתי רגשות שליליים עזים למדד ה Code Coverage. הייתי חבר בפרוייקט בו נדרשנו ל 100% כיסוי – לא פחות. המערכת הייתה מערכת P2P Video Streaming שהייתה כתובה חציה ב Java/AspectJ וחצייה ב ++C. בקוד היו חלקים גדולים שהתנהגו בצורה סינכרונית, והיו פנייות רבות לפעולות I/O (לרשת). על מנת לבדוק חלקים מסויימים בקוד נאלצנו לכתוב את הבדיקות עצמן בצורה אסינכרונית: השתמשנו בספריית ה Concurrency של Doug Lea בעיקר עבור קוד הבדיקות – ורק מעט עבור הקוד הפעיל. זה היה בשנת 2003 ומקורות הידע בבדיקות-היחידה היה מצומצם.

הקזנו נהרות של דם בכדי להעלות למעל 90% CC. הכי גבוה שהגענו היה 93%-לרגע, כך נדמה לי.
תכונה בולטת של CC הוא המאמץ השולי הגובר: להשיג 50% זה יחסית קל. כל אחוז מעל 80% הוא עבודה מתישה וקשה – ובכל רגע נתון מישהו יכול לבצע submit של קוד לא-בדוק ו”לזרוק” את הצוות אחוז או שניים אחורה.
על האחוזים מעל 90% אין לי מה לדבר. זה נראה כמו טעות חישוב – כי לא נראה היה שאפשר באמת להגיע לשם. הוספנו לקוד הפעיל getters[ג] רק כדי “לסחוט” עוד קוד קל-לבדיקה ולשפר אחוזים. הערכנו שאנו משקיעים פי 3 זמן על קוד בדיקות מאשר על קוד פעיל (מצב לא בריא בעליל, אני יודע כיום לומר). מאז ראיתי פרוייקט שבו השקיעו כשליש מהזמן על כתיבת בדיקות, והחזיקו ב CC של כ 80% באופן שוטף.

כמה מסקנות:

  • התמודדות לא מוצלחת בכתיבת בדיקות-יחידה עלולה לגרום למפח נפש רציני.
  • היעד הסביר, לדעתי כיום, ל CC תלוי מאוד בסוג המערכת:
    בקוד שרובו אינטגרציה / UI / IO אני חושב שבריא לשאוף ל 60-70% CC.
    בקוד שרובו parsing או לוגיקה טהורה (לדוגמה XML Parser) אפשר בהחלט לשאוף ל 80-90%.
    במערכת שהיא באמצע (כמו רוב המערכות) אני חושב שיעד בריא הוא 70-80% CC. אני מציין טווח ולא מספר מדויק כי CC הוא מדד “חי”. בכל Submit של קוד הוא ישתנה. להישאר כל הזמן בטווח של 10% – הוא משהו שאפשר לדרוש מצוות. במיוחד במערכת גדולה.
  • CC הוא תנאי מחייב אך לא מספק. אם יש לכם 20% CC – בטוח שהקוד שלכם לא בדוק טוב. אם יש 80% CC – אזי זה סימן טוב אבל יש לעבור על הבדיקות ולוודא שהן משמעותיות. ניתן בקלות להשיג CC גבוה עם בדיקות חלקיות ולא יעילות.
  • למרות שבתאוריה ב TDD אמור להיות 100% CC, תאוריה זו תיאוריה: כמעט תמיד יהיו חלקים של חוק אינטגרציה, Adapters למשל, שלא נכון או כדאי לבדוק אותם.

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

(TDD (Test Driven Development
TDD הוא סוג של “השלב הבא” בבדיקות-יחידה. הרעיון הוצג ע”י קנט בק – גורו וחדשן אמיתי בעולם התוכנה.

ראיתי מספר פרשנויות מוזרות שמתהדרות בשם “TDD”. למשל: צוות שכל תחילת ספרינט השקיע 2-3 ימים בכתיבת מסמכי בדיקה מפורטים בוורד, כתב את הקוד ואז בסוף הספרינט מימש את הבדיקות בקוד, שבכלל היו בדיקות פונקציונליות / מערכת.
אם אתם רוצים להתהדר ב TDD או אפילו ATTD (נשמע טוב!) – לכו על זה. שום חוק מדינה לא יאסור על מאלף-סוסים, נאמר, להתהדר בכך שהוא “עובד TDD”, אז בטח לא על מישהו שעוסק בתוכנה…

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

  • איך אתם יודעים שתצליחו לכתוב קוד בדיק (testable) ולא תאלצו לעשות בו שינויים אח”כ? – פשוט כתבו כל בדיקה בסמיכות רבה לקוד שהיא בודקת. אולי אפילו כיתבו את הבדיקה לפני.
  • איך תוודאו שהכיסוי שלכם של בדיקות הוא אופטימלי? – החליטו שקוד פעיל נכתב רק על מנת לגרום לבדיקה לרוץ בהצלחה. “מה שלא בדוק – לא קיים“.
  • איך תפחיתו באגים בבדיקות שלכם – ופרט את המקרים של בדיקות שעוברות בהצלחה גם כאשר הקוד לא עובד כשורה? כתבו והריצו את הבדיקות קודם לכתיבת הקוד וודאו שהן נכשלות.
כלומר: קודם כותבים את קוד הבדיקה, ורק לאחר מכן – את הקוד הפעיל.
TDD הוא הפעלה חוזרת של סדר הפעולות הבא:
  1. הבינו מה הפונקציונליות (קטנה) שאתם רוצים להוסיף למערכת.
  2. כתבו בדיקת-יחידה שבודקת את הפונקציונליות הזו, קצה-אל-קצה.
  3. הריצו את הבדיקה: עליה בהכרח להיכשל, מכיוון שהפונקציונליות לא כתובה עדיין![ד]
  4. כתבו פיסת קוד הפשוטה ביותר שאפשר שתעביר בדיקה בודדת[ה]. אם הריצה הצליחה המשיכו לחתיכה הבאה וכו’.
  5. לאחר שכל הבדיקות ירוקות (עברו בהצלחה) – המשיכו לפונקציונליות הבאה.
הסבבים ב TDD מאוד קצרים: כתיבה – הרצת בדיקות, כתיבה – הרצת בדיקות.
המתכנת מחליף תדיר 2 כובעים: כותב הקוד הפעיל וכותב הבדיקות, והוא אמור לנסות בכל כובע “להערים” על הטיפוס השני ולספק לו את המינימום האפשרי. כלומר: להערים על עצמו.
אני לא בטוח שאפשר להסביר בקלות את סגנון העבודה מבלי לבצע תרגילים בפועל. יש בו אלמנט של משחק – שבהחלט עשוי לגרום להנאה.
בפועל TDD מסייע:
  • להתמקד במטרה ולכתוב רק קוד שנדרש (“eliminate waste”)
  • להשיג בדרך הקצרה והפשוטה קוד בדוק עם Code Coverage גבוה.
  • לכתוב את ה API / interface של כל מחלקה בראייה של הלקוח (= הבדיקה) – דבר שמסייע ליצירת API טובים וברורים.
  • מכריח אתכם לכתוב קוד מודולרי ובדיק (testable).
הסיבה העיקרית, לדעתי, ש TDD הוא לא כל-כך נפוץ היא שחלק גדול מאלו ש”עובדים עם בדיקות-יחידה” בעצם עובדים על עצמם בעיניים – הם כותבים בדיקות פונקציונליות או בדיקות בכיסוי מזערי. ברגע שיש לכם תהליך של כתיבת בדיקות-יחידה שעובד, המעבר ל TDD הוא השלב הטבעי הבא.

סיכום 

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

  • הבדיקות נכשלות תדיר – גם כשהקוד הפעיל עובד בסדר => בדיקות רגישות לשינויים (fragile tests).
  • הבדיקות שלכם בודקות קוד שלא סביר שיישבר (למשל קוד מה JDK)
  • הבדיקות שלכם נכשלות בהמוניהן – כמו בדיקות פונקציונליות.
  • אתם עושים שימוש תדיר ורב ב Mock Objects (שהם בעצם סוג של patch לקוד קשה-לבדיקה).
  • יש לכם בדיקות שמשאירות “עקבות” לאחר שרצו. זו יכולה להיות בעיה או בבדיקות או בקוד הפעיל.
  • הבדיקות שלכם רצות לאט.
כמה מדדים לשימוש בריא בבדיקות-יחידה:
  • הבדיקות רצות מהר.
  • אתם מריצים את הבדיקות (“עץ ירוק”) גם כשלא נדרש. זה פשוט מרגיע אתכם.
  • כאשר אתם נכנסים לקוד חדש, הדרך הטבעית ביותר היא לעבור ולקרוא את קוד הבדיקות.
  • בדיקות שנכשלות אכן תופסות באגים (רגרסיה או בכלל) לפני שאתם עושים submit.
  • כולם מתמכרים לרעיון של הבדיקות.

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

—-

[א] ע”פ התאוריה האקדמית, השקעה באיכות לרוב מעלה את עלויות הייצור – לא מוזילה אותן.

[ב] כאשר בדיקות יחידה מתחילות לרוץ לאט (נאמר, כפול מהקומפיילר או יותר) מחלקים אותן ל 2 חבילות: “בדיקות מהירות” שרצות כל הזמן, ו”בדיקות אטיות” שמריצים מדי פעם כמו ב CI או לפני פעולת submit של קוד.

[ג] שלפנים – בעברית. כמובן.

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

[ה] בלימוד TDD מלמדים שאם אתם בודקים את הפונקציה (add(a,b והבדיקה הראשונה בודקת ש 2+2=4 – יהיה נכון לכתוב את גוף הפונקציה בצורה הכי מוכוונת-מטרה שאפשר, כלומר return 4. זאת על מנת לוודא שאינכם כותבים קוד מיותר (“מה שלא בדוק לא קיים”) אך גם להרגיל אתכם לכתוב בדיקות מספיק מקיפות שבודקות את הפונקציה במספר מספיק של מקרים.