2012-06-30

על יזמות ואמונה

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

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

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

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

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


סאנסה: מאמינה. מקור: wallpapers.com

הטיית השרידות

באנגלית: Survivorship Bias. דוגמה טובה להטיית השרידות היא ביצועים של קרנות נאמנות. אם תשבו עם יועץ השקעות, קרוב לודאי שהוא יציג בפניכם מספר של קרנות בעלות ביצועים יפים ועוד כמה "חדשות". התחושה היא טובה - "הנה זו הרווחיה 5% וזו 7% - בממוצע של ארבע שנים. קרן נאמנות היא רווח בטוח!" - אתם עלולים להסיק.
מה שלא מספרים לכם היא שכל קרן שביצועיה לא היו טובים (נאמר 3% ומטה) - נסגרת ונפתחת בשם חדש, כך שההיסטוריה הכושלת שלה "נמחקת". אתם רואים מדגם לא מייצג של "הקרנות השורדות" ויכולים להסיק, בצורה שגויה, שאתם יכולים להניח על ממוצע ביצועים של 6% בשנה, מכל קרן כלשהי. כשמציגים בפניכם נתונים, נסו לשאול את היועץ כיצד יש כ"כ הרבה קרנות "חדשות", ללא נתונים היסטוריים משמעותיים...
אין שום סיבה להניח שקרנות שהצליחו במשך 4 שנים - ימשיכו להצליח בהמשך. סביר יותר שהן מהמרות עם סיכון בינוני+, וביום שהם יתחילו להפסיד - הן ימחקו ויופיעו בשם חדש.

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

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


סינדרום "סאנסה"

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

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

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

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


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

מקור: https://hbr.org/2009/04/are-great-companies-just-lucky


דברי סיכום

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

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

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

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


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

אם אתם רוצים לדעת איך עושים את זה - כדאי שתכירו את ה "Lean Start-up", אחד הדברים המעניינים שמתרחשים פה בשנים האחרונות.

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

כל טוב.

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


----

[א] בעברית תקנית, "להמר" היא המילה הנכונה לתיאור מצב זה.

[ב] פוסטים קודמים מסביב לנושא:

[ג] ע"פ הספר היא קצת פחות תמימה.



2012-06-23

כיצד ייתכן?!

שייך לסדרה: אג'ייל - מתודולוגיות פיתוח רזות


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

כיצד ייתכן, אם כן, שהאייפוד הראשון תמך רק ב MacOS עם חיבור Firewire?
ושהוא היה כ"כ לא אמין עד שכ 20% המכשירים התקלקלו בטווח שימוש סביר?

כיצד ייתכן שרק ב iOS 4 (אמצע 2010) האייפון קיבל תיקיות לארגון האפליקציות? האם אתם מדמיינים אתכם מפתחים מערכת הפעלה ללא תיקיות?

“If Apple can launch a smartphone without Find or Cut-and-Paste, what can you cut out of your product requirements?” – Sramana Mitra

כך נראה Youtube בשנת 2005. לא, זו לא תקלה - כך באמת הוא נראה.
מקור: http://www.telegraph.co.uk/technology/6125914/How-20-popular-websites-looked-when-they-launched.html


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

כיצד ייתכן שרק בסוף 2010 Gmail שחררה את הפ'יצר הבא: Context Sensitive Help. עד אותו רגע ה Help תמיד נפתח בדף הראשי, ללא קשר. האם אתם מדמיינים את עצמכם משחררים תיעוד של מוצר שלא מקושר לתוכן?


העיצוב של וויקיפדיה בשנת 2001.
מקור: http://www.telegraph.co.uk/technology/6125914/How-20-popular-websites-looked-when-they-launched.html 


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

כיצד ייתכן שהארכיטקטורה של גרופון הייתה דף בבלוג Wordpress ציבורי, עם Widget של ThePoint שכולל מקטע AppleScript ששולח קופונים ב PDF? איזה מתכנת היה מוכן לשחרר דבר שכזה? שלא לדבר על איזה ארכיטקט היה מאשר את זה...


כך נראה טוויטר בשנת 2006. האם הייתם משחררים אתר שנראה כך?
מקור: http://www.telegraph.co.uk/technology/6125914/How-20-popular-websites-looked-when-they-launched.html


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

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

מנכ"ל דרופבוס מדבר על הקמת החברה.
מקור (שווה קריאה): http://www.slideshare.net/gueste94e4c/dropbox-startup-lessons-learned-3836587?from=ss_embed

The lesson of the MVP is that any additional work beyond what was required to start learning is waste, no matter how important it might have seemed at the time.


2012-06-20

קללת התאימות לאחור של Internet Explorer


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

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

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

חלק גדול ממה שאכתוב פה על Quirks Mode נכון גם ל Firefox או אופרה - אבל עד היום לא נתקלתי באף אחד שהתעסק בנושאים אלו בדפדפן שאינו IE - ולכן אדבר רק על IE.

אם אתם לא כותבים קוד שנדרש לרוץ ב IE8 או גרסאות ישנות יותר, או שאתם יכולים פשוט להגדיר את Chrome Frame [א] שיפתור לכם את הבעיות - רוב הבעיות שיוצגו פה הן כנראה לא בעיות שלכם. אלו נושאים שגרמו סבל ללא מעט אנשים.
המניע לעיסוק המחודש בנושא זה הוא השחרור הקרוב של IE10 (במסגרת Windows 8).
בסוף הפוסט כללתי פרטים גם על גרסה זו.

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


הערכה של ג'ון רזיג על היחס עלות / תועלת בתמיכה בדפדפנים. פילוח השוק של הדפדפנים שונה ממה שאני מכיר. מקור: http://www.manning.com/resig/resig_meapch1.pdf 


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

Microsoft will offer a minimum of 10 years of support for Business and Developer products. Mainstream Support for Business and Developer products will be provided for 5 years or for 2 years after the successor product (N+1) is released, whichever is longer. Microsoft will also provide Extended Support for the 5 years following Mainstream support or for 2 years after the second successor product (N+2) is released, whichever is longer. Finally, most Business and Developer products will receive at least 10 years of online self-help support.
מקור

הנה כמה דוגמאות:
  • Silverlight 5, טכנולוגיה שנראה שמייקרוסופט רוצה לזנוח - תיתמך עד 2021.
  • Windows Vista הבעייתית תיתמך עד 2017. 5 שנים נוספות מעכשיו.
  • IE8, שהמהדורה אחרונה שלו (עם Bing כ Default) שוחררה ב 2010 - ייתמך עד 2020. 
  • כיצד ניתן להסביר שמייקרוסופט הפסיקה לתמוך בIE6 המפורסם כבר לפני שנה?? האא... הוא שוחרר בשנת 2001.
שלא תבינו לא נכון: כמובן שיש ערך עסקי משמעותי בתמיכה לאחור. עבור רוב המשתמשים מוצרים ששוחררו לפני עשור (נאמר Office 2000) הם מספיק טובים ושדרוג הם כאב-ראש ועלויות מיותרות. העניין הוא שיש ערך כספי, לעתים רב, בשחרור גרסאות חדשות (למשל, אופיס) ואז נוצרת קשת של גרסאות שונות שזמינות בשוק - ומי שמפתח מוצר צד שלישי נאלץ להתאים את עצמו למספר גרסאות.

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

בשנת 2020 מייקרוסופט עתידה לתמוך ב 11 גרסאות שונות של אינטרנט אקספלורר: IE8, IE9, IE10, IE11, IE12, IE13, IE14, IE15, IE16, IE17 ו IE18 [ד]. בסוף פוסט זה תראו שכל גרסה של IE היא סיפור בפני עצמו.

כך עובדים ערכים של חברה: הם מחלחלים לכל פינה. לטוב ולרע.


ה"תאימות לאחור" מתחילה להסתבך
עד כמה שידוע לי הגרסה הראשונה של IE שידעה לרנדר (render) דף HTML ביותר מצורה אחת היא IE6. מייקרוסופט קוראת למודים אלו Document Modes, אני אשתמש בפוסט זה בשם Rendering Mode שנראה לי פחות מבלבל. החלק בדפדפן שאחראי על ציור הדף נקרא Rendering Engine או Layout Engine. הוא מפענח את ה HTML וה CSS, בונה בזיכרון את ה DOM מה-HTML ו Style Tree מה-CSS, מרכיב אותם אחד על השני ומצייר את התוצאה על המסך. המנוע בו IE ל Windows משתמש נקרא Trident (= קלשון, בעברית).



מנוע הרינדור של IE6 הוסיף תמיכה תקנית ב Box Model (הוסבר בפוסט הקודם) - שינוי די משמעותי ש"שובר" דפים שעבדו ב Box Model הישן. על מנת לתמוך "גם וגם" הוא הגדיר 2 מודים:
  • IE6 Standards Mode - שהציג את ה Box Model בצורה התקנית.
  • Quirks Mode - שהציג את הדפים כפי שעבדו בגרסה הקודמת (IE 5.5) - ע"פ ה Box Model של IE.


וכיצד הדפדפן יודע אם מדובר בדף שנכתב עם Box Model "ישן" או "חדש"?
הבעיה הייתה לא רק של IE, אלא גם של FF ואופרה, ו W3C הגדיר פתרון: שימוש בתוית בשם DOCTYPE.
הפתרון, בהפשטה מסוימת, הוא כזה: אם הדף הוא מסוג "חדש", כלומר פועל ע"פ הסטנדרטים של W3C בכל הנוגע ל Box Model ובכלל - הוא יסומך בעזרת תוית DOCTYPE, שממוקמת בראש הדף, מעל תג ה HTML.

אם לדף לא הייתה תגית DOCTYPE - מניחים שמדובר בדף ישן מאוד ולכן IE יריץ אותו ב Quirks Mode. גישה זו שמרה על תאימות טובה לאחור, מכיוון שלא היה צריך לבצע שינויים בדפים ישנים: פשוט לא הייתה בהם תגית DOCTYPE [ה].

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


IE7 שיפר את מנוע הרינדור. הוא תיקן סטיות רבות מתקן ה CSS שהמפורסמת בהן היא תמיכה בשקיפות של קובצי PNG.
מוד הרינדור של IE6 פשוט שופר והוחלף והוא נקרא IE7 Standards Mode.









IE8 כבר עבר את מבחן ה ACID2 (לבדיקת תאימות של דפדפנים לסטנדרטים), אחרי שנים ש IE נכשל במבחן בנחרצות. הוא הוסיף תמיכה, כמעט מלאה, ב CSS 2.1.

CSS 2.1 הוסיף מורכבות חדשה. הוא הגדיר את הדרך בה נקבע הגובה של תאים בטבלה, דרך ששונה ממה ש IE נהג לחשב. שימוש בטבלאות "בלתי נראות" לצורך עימוד הדף היה מאוד נפוץ באותה תקופה - ודפים שנכתבו עבור IE6 או IE7 והוצגו ע"פ הצורה הסטנדרטית של CSS 2.1 - שובשו קשות.


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


וכיצד הדפדפן יודע "עבור איזה דפדפן נכתב דף ה HTML שמורץ כרגע"?
- W3C הוסיפו הגדרת DTD (אימות מבנה הדף) על תג ה DOCTYPE. סימון Strict Mode מעיד על תמיכה בתקן החדש ו Loose או Transitional הם מודים "רכים" שעובדים ע"פ ההגדרות הישנות.


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


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

הצורך לחזור ולשנות את ה Markup (או קוד, אם מדובר בדף דינאמי) של דפים קיימים על מנת שיוצגו בצורה טובה היה בלתי נסבל עבור מייקרוסופט. מייקרוסופט התמודדה עם הבעיה בעזרת הצגה של תווית META חדשה (שניתן להגדיר גם ברמת פרוטוקול ה HTTP) שדורסת את ההחלטה של ה DOCTYPE לגבי המוד בו יש לרנדר את הדף. Application Servers היו יכולים בדרך זו לשתול את התגית החדשה ברמת ה HTML HEAD או ה HTTP וכך לגרום לדפים שתוכננו עבור IE7 להתרנדר ב Almost Standards Mode - כך שייראו היטב. התגית ה META נראית כך:
<meta http-equiv="X-UA-Compatible" content="IE=7" />

ל IE8 כבר יש אלגוריתם לא פשוט לבחירה ב Rendering Mode (מתוך ה 4 הקיימים). תוכלו למצוא תרשים זרימה וועוד מקור המתארים את האלגוריתם.


עוד השפעות של התאימות לאחור ב IE8 (המשפיעות גם על IE9 ו IE10)

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

החלטה הזו תקפה לא רק על ה Rendering Engine אלא גם על ה JavaScript Engine: באגים וחוסרי תאימות שהיו קיימים ב IE7 נשמרו במנוע הג'אווהסקריפט של IE8 (והלאה). אם הדף מרונדר ב IE7 Standards Mode, מנוע הג'אווהסקריפט החדש יפעיל את משפטי ה IF [ו] המתאימים ויתנהג בדיוק כמו שהתנהג ב IE7.


הערה: מפתחים שרוצים לדעת איזו גרסה אמיתית של IE רצה מולם נתקלים בקושי: ה BC גורם לדפדפן להתנהג בדיוק כמו דפדפן ישן יותר וזה כולל User Agent, יכולות נתמכות ואפילו מנוע ה JavaScript ש"משחזר" באגים ישנים. הדרך האמינה לדעת מול איזה גרסה של IE אתם עובדים היא לתשאל את גרסת ה JavaScript Engine. שלא תזדקקו לזה.

בנוסף, מייקורוסופט לא סומכת רק על ה DOCTYPE ותוית ה X-UA-Compatible שיידעו כיצד על הדף להתרנדר. מייקרוסופט מנהלת רשימה של Domains של אתרים פופולריים וה Rendering Modes של IE בהם הם רצים בצורה הטובה ביותר. רשימה שנוצרה ומתוחזקת בצורה ידנית, כנראה. IE קורא את הרשימה ומגיב אליה.
מייקרוסופט מאפשרת לארגונים לנהל רשימה נוספת משלהם שתשפיע על כל דפדפני ה IE בארגון שלהם.
תוכלו למצוא כיצד רשימות אלו משתלבות באלגוריתם הבחירה בתרשים הזרימה שקישרתי למעלה.


התאימות לאחור ממשיכה להסתבך: IE9
בעת פיתוח IE9, ניצב בפני מייקרוסופט אתגר חדש: התמיכה בשרשרת תקנים שאנו מכירים אותם כיום כ HTML5 ו CSS3. תקנים אלו הם עליית מדרגה מול היכולות שהיו בדפדפנים עד כה. נראה שהמפתחים של IE החליטו שעל מנת לתמוך בתקנים אלו, בצורה Preformant, יש עליהם לכתוב את מנוע הרינדור מחדש.
מה עושים עם התאימות לאחור? אין ברירה. משלבים בדפדפן החדש, IE9, שני מנועי רינדור שונים: מנוע ישן (של IE8) ומנוע חדש שיתמוך בתקנים החדשים:



כל טאב ב IE9 מתרנדר או במנוע החדש או במנוע הישן - אך אף פעם לא בשניהם ביחד.
אם יש דף עם iFrames ייתכן והדף החיצוני (מה שנקרא topFrame) מרונדר במוד אחד (נאמר IE9 Standards) בעוד ה iFrame הפנימי מרונדר במוד אחר (נאמר QME). אולם, בניגוד ל IE6 עד IE8 בהם יכול היה הדפדפן לבחור עבור כל iFrame כל מוד מהרשימה הנתמכת, ב IE9 יש בחירה של מנוע הרינדור ומאותו הרגע הבחירה למוד עבור על Frame מוגבלת למודים הזמינים עבור אותו מנוע רינדור.

הבחירה במנוע הרינדור היא דיי פשוטה: בפעם הראשונה שהדפדפן מזהה סימן שמעיד טיפוס הדף (יהיה זה X-UA-Compatible ברמת ה HTTP או תג Doctype) - הוא בחור את המוד המתאים מכל הרשימה. מנוע הרינדור שיודע לצייר מוד זה הוא מנוע הרינדור ש IE ישתמש בו לשאר הדף.

ריבוי ה Rendering Engines, אם כן, משפיע רק על דפים הכוללים Frames או iFrames. במקרים אלו ה Top Frame יכתיב את מנוע הרינדור עבור ה iFrames שהוא מארח.

הנה הדגמה של התנהגות זו בפועל:
כתבתי דף פשוט בשם page.html שידמה אפליקציה עסקית (הוספתי Source Code בתחתית הפוסט [ג]). הדף מכיל מספר אלמנטים שבהם יש בעיות של תאימות לאחור:
  • עיגול המצוייר ב SVG (תכונה של HTML5)
  • תיבה עם צבע שהוגדר פעמיים: פעם בצורה תקנית (כתום) ופעם נוספת בצורה לא תקרנית (טורקיז).
  • אזור אפור (border עבה במיוחד) שגובהו יהיה חצי מהתיבה ע"פ ה IE Box Model ושליש מהתיבה ע"פ ה W3C Box Model.
אם אני שם בראש דף זה תגית DOCTYPE הוא ייראה שונה למדי מאשר אם לא תהיה תגית כזו. אם יש תגית Doctype אזי IE ייבחר מוד רינדור שונה מזה בו אין Doctype.

ייתרה מכך, אם אשים 2 עותקים של הדף page.html בתוך דף מארח (topframe) - ה Doctype של הדף המארח ייבחר עבורי מנוע רינדור כך שהצורה בה ירונדר הדף תושפע פעם נוספת. 
במקרה 1 למטה - הדף המארח מפעיל את מנוע הרינדור החדש (ומריץ את האפליקציה פעם במוד IE9 Standars ופעם ב QME).
במקרה 2 למטה - הדף המארח מפעיל את מנוע הרינדור הישן (ומריץ את האפליקציה פעם במוד IE8 Standards ופעם במוד Quirks Mode)

ההבדל היחיד בקוד בין מקרה 1 למקרה 2 הוא ה Doctype של ה topframe. התוצאות לפניכם:


חתיכת הבדל בשביל Doctype - לא?

אתם יכולים להבחין ב Scrollbars שונים בשני המקרים שנובעים מ Defaults שונים בין המודים השונים. הבדלים אלו אמורים להתאפס בעקבות CSS Reset.

השלכה משמעותית של ההפרדה ל 2 מנועי רינדור שונים אי חוסר היכולת (בעזרת ifלהציג HTML5 באותו הדף עם תאימות מלאה לדפים ישנים מאוד (Old Quirks Mode) - שיש רבים כאלו במערכות עסקיות.

כיצד מתמודדים עם זה? מייקרוסופט הוסיפה מוד בשם (QME (Quirks Mode Emulation שתואם, במידה רבה, לתקן של W3C כיצד יש לרנדר Quirks Mode - כלומר דפים ישנים מאוד.

ה QME לא מתועד בצורה ברורה כמוד של IE9. לקח לי כמעט חודש של התעסקות בנושא עד שהבנתי שהוא קיים. מייקרוסופט לעיתים קוראת לו "Quirks Layout" (נשמע כמו מימד אחר של פונקציונליות) או Quirks within IE9 Engine - שיכול בקלות לבלבל עם ה Quirks Mode שקיים במנוע הישן. ב Dev Tools של IE10 הוא נקרא פשוט "Quirks Mode" בנוסף ל "Explorer 5 Quirks" שקיים שם. בקיצור: מבלבל. רק לאחרונה אני נתקל במונח QME בצורה יותר מפורשת וברורה.

הנה תיעוד שמציין את קיום ה QME - הייתי זקוק לו בכדי להיות בטוח שאני לא מדמיין. הסיבה שהמוד לא מפורט ברשימת ה modes של הדפדפן קשורה כנראה לכך שIE9 לא מאפשר להשתמש במוד זה ב topmost Frame - כלומר הוא מותיר להשתמש בו רק בתוך iFrames. 
הנה תיאור של ההבדלים בהתנהגות של QME. גם לדפדפנים אחרים יש QME . הנה הגדרת ההתנהגות של QME ב FF.


כשמפעילים את כלי הפיתוח של IE (לחיצה על F12), יש אפשרות לדרוס בכוח את המוד בו ירוץ הדפדפן. אין פה QME או Almost Standards Mode. אליהם ניתן להגיע רק בעזרת אלגוריתם ההחלטה של ה Rendering Modes. האלגוריתם שונה בין הגרסאות של הדפדפן ולכן בכלי הפיתוח ניתן לבחור Browser Mode שהוא קובע באיזו גרסה של האלגוריתם להשתמש. נסו להבין את זה לבד : )




הנה תיאור האלגוריתם בעזרתו IE9 מחשב באיזה Rendering Mode להריץ את הדף. דיי דומה ל IE8 - אך השפעותיו  משמעותיות יותר. 


התאימות לאחור עושה קאמבק (קטן): IE10



בניגוד ל IE9 בו התמיכה ב HTML5 ו CSS3 היא דיי בסיסית, IE10 תומך בתקנים אלו בצורה דיי יפה. ניתן לומר שהוא כמעט in line ביחד עם שאר הדפדפנים בתחום זה.


ב IE10 בוצעו כמה שינויים שמשפיעים על ה BC:

ה Default Rendering Mode עבור מסמך ללא Doctype הוא QME ולא IE5.5 Quirks Mode - מקור. התנהגות זו רצויה, אך היא תגרום לדפי Quirks Mode שרצו יפה ב IE9 - לרוץ פחות יפה ב IE10.
פתרון פשוט: להוסיף תג X-UA-COMPATIBLE, ie=7 בכדי להכריח את מנוע ה rendering הישן לרוץ.
פתרון נכון: לתקן את הדפים לעבוד ב QME, לא לדחות את ההתנתקות מ IE5.5 לנצח! ההבדלים העיקריים בין Quirks Mode ל QME הם מסביב לטבלאות, Box-Model (ניתן לתקן בקלות בעזרת CSS) והדבר הכי קשה: באגים שהיו ב IE5.5, תאימותם נשמרה עד ל IE8 - אך הם תוקנו ב QME.

QME נתמך גם ב topframe, בשונה מה IE9 - אך בדומה לFF או כרום. נראה גם שהיישום של QME קרוב יותר לסטנדרט מאשר IE9 - אך קשה לומר בוודאות לפני ש IE10 ישוחרר ויהיה יותר נסיון לקהילה איתו.

ביטול התמיכה ב Conditional Comments (כגון  <!--[if IE]>).
ביטויים אלו נמצאים בשימוש בכמה ספריות מודרניות כגון ie7.js ו html5shim - אך אלו ספריות של תאימות לאחור שלא יזדקקו, כנראה, ליכולת זו ב IE10. אם הקוד שלכם עושה כאלו בדיקות (נו, נו, נו!) - הזהרו.
בראייה ארוכת טווח, זו כמובן החלטה טובה של מייקרוסופט.

Windows 8 מציגה שני סוגים של "סביבות עבודה": Metro ו Desktop.
Metro הוא ברירת המחדל והעתיד. אופיס 2013 יקבל ממשק מטרו, למשל. מטרו הוא Touch Enabled ויהיה הסביבה היחידה על Windows 8 Tablet.
Desktop היא הסביבה שאנו מכירים מאז Windows 95 ועד Windows 7. סרגל משימות, תפריט "התחל" וכו'.

חווית השימוש ב IE10 בסביבת המטרו היא שונה מסביבת העבודה של ה Desktop. למשל, Plug-Ins ו ActiveX לא ירוצו בסביבת המטרו. פלאש דווקא כן. אם יש לכם דף שמכיל Plug-Ins ואתם רצים על בסביבת המטרו - הדפדפן יפנה אתכם לסביבת ה Desktop. לא בהכרח התנהגות BC מלאה, אך אני מוצא אותה סבירה. ב Tablet כנראה תהיה סתם הערת שגיאה או פשוט התעלמות.


תעשו חיים!

מקורות נוספים בנושא:



-----

[א] Chrome Frame[ב], למי שלא מכיר, הוא Plug-In של גוגל ל IE שכולל את מנוע ה Rendering של Chrome ויכול, ע"פ תג Meta במסמך ה HTML - להפעיל את הדף, מעשית, בכרום[א]. התקנתו לא דורשת הרשאות Administrator והוא יכול להריץ את הגרסה האחרונה של Chrome (כלומר מנוע HTML5 מעולה) בתוך IE6 המיושן. טקטיקה מקובלת היא "לתמוך ב IE6 ו IE7 בעזרת Chrome Frame", כלומר: להפעיל עבור משתמשי IE ישנים את כרום מאחורי הקלעים. יש לשיטה זו כמה מגבלות בכל הנוגע לתקשורת בין iFrames.

[ב] יש לבטא "קרום" ולא "ח-רום" - כפי שישראלים רבים נוהגים.

[ג] 

[ד] נשמע לי קצת מופרך לתאר משהו שיקרה 8 שנים מעכשיו: עידן ועידנים. אני מניח שמשהו יקרה וזו לא תהיה התוצאה בפועל.

[ה] התקן ביקש מבעלי דפים ישנים, כגון HTML 3.2 לסמן את הדפים שלהם ב DOCTYPE מסוים (ש IE ידע להתעלם ממנו) - אך לא הייתה לבעלי הדפים מוטיבציה אמיתית לבצע סימון שכזה.

[ו] אני מניח ומקווה שהשתמשו ב Strategy Design Pattern ולא באמת במשפטי IF.



2012-06-12

הרצאה בנושא פיתוח ווב למובייל בפורום אנשים ומחשבים

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



הנה הלינק למצגת בה השתמשתי.

עברתי עליה והוספתי עליה notes בעברית על מנת לעשות אותה קריאה גם למי שלא היה בהרצאה.
יש להוריד (file->download) את קובץ ה PPT בכדי לקרוא את ה notes. אמרו לי אם אתם נתקלים בקשיים.

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

2012-06-02

מבוא מואץ ל JavaScript עבור מפתחי Java / #C מנוסים - חלק 2


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


שייך לסדרה מבוא מואץ ל JavaScript ו jQuery


מבוא 
האם JavaScript היא שפת Object Oriented? - תלוי את מי שואלים.
פורמלית, ג'אווהסקריפט מוגדרת כ Object-Oriented Language מול Class Oriented-Languages שהן ג'אווה ו#C. יש שוני שהדגש הוא על אובייקטים - לא מחלקות. בואו נשים את הפורמליסטיקה בשלב זה בצד.

התשובה שלי היא כזו: ג'אווהסקריפט היא יותר סט של כלים (toolkit) בו ניתן להשתמש בכדי לכתוב קוד OO. ניתן גם לכתוב בה קוד פונקציונלי ואולי אפילו גם קוד לוגי. השפה לא תחייב אתכם ל OO וכמעט לא תסייע או תנחה. זוהי גישה שונה למדי משפות כמו #C או Java שהן שפות OO מוצהרות ומכוונות את המפתח להשתמש בהן ככאלו.

אם תרצו לכתוב OO איכותי בג'אווהסקריפט - תדרשו להפעיל לא מעט משמעת עצמית. הקלות בה ניתן לבצע "תרגיל" (hack) בקוד ג'אווהסקריפט היא מדהימה, אנו נזדקק למנגנונים חברתיים (למשל Code Review) או טריגרים אוטומטיים (JSLint / JSHint) על מנת לשמור על קוד OO נקי ומסודר.

הכמסה
מהו העיקרון החשוב ביותר של Object Oriented? נכון: הכמסה (אם אתם חושבים אחרת - אז זהו פוסט בשבילכם).
הדרך הבסיסית להשיג הכמסה בג'אווהסקריפט היא בעזרת Closure:
(function() {
  var n = 2;
  var addBy = function(num, x) { return num + x; }
  var multiplyBy = function(num, x) { return num * x; }

  n = addBy(n, 2);
  n = multiplyBy(n, 3);

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

שימו לב לחשיבות להגדיר את המשתנים ואת הפונקציות הפנימיות בעזרת var. ללא var, הפונקציה / המשתנה יוגדרו ב global scope.
באפליקציות ג'אווהסקריפט, רוב הקוד שנכתוב יהיה private - אף אחד לא אמור לקרוא לו. אנו ניגש ל DOM, נייצר UI, נרשום אירועים ונגיב אליהם - את כל זה אפשר לעשות מבלי "ללכלך" את ה global scope. אין סיבה שלא תעטפו את כל הקוד האפליקטיבי שלכם, שאינו מספק שירותים לקוד אחר - בצורה זו.
עבור ספריות שבהן נרצה לחשוף פונקציות לשימוש חיצוני - הסיפור הוא אחר.

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

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


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

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

בואו נתבונן כיצד המילים השמורות prototype ו new בשפה מאפשרת לנו לנהל "אובייקטים":
var Calculator = function () { // constructor
  this.value = 0;
};

Calculator.prototype = { // prototype = Object Literal
  addBy: function (x) {
    this.value += x;
    console.log('value = ' + this.value);
  }
};

// alternate way to define method, less recommended
Calculator.prototype.multiplyBy = function (x) {
  this.value *= x;
  console.log('value = ' + this.value);
};
ה Constructor הוא בעצם פונקציה רגילה לכל דבר ועניין. השימוש ב C גדולה הוא קונבנציה שמתארת שאני מייעד פונקציה זו להיות Constructor.

הורשה בג'אווהסקריפט מבוטאת ע"י שרשרת של קשרי prototype - לכל אובייקט יש אב שמסומן ב property בשם __proto__. ב default זה יהיה האובייקט Object - האב המשותף לכל האובייקטים בשפה. המילה השמורה prototype מייצגת את המשתנה __proto__ (שלא אמורים להשתמש בו ישירות, אך הוא שימושי ל debug).

בשלב הבא אנו מחליפים את ה prototype של הפונקציה Calculator (שהיה עד כה האובייקט Object) לאובייקט שאנו מגדירים (בעזרת תחביר של object literal). ההחלפה תשפיע על כל instance חדש שייווצר מ Calculator בעזרת המילה השמורה new.

לבסוף, אנו מוסיפים מתודה נוספת לאובייקט שנוצר.


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

לאחר הגדרה זו, אנו יכולים לייצר instances ולהפעילם בצורה הבאה:
var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); 
> 24
בואו נסכם מה קיבלנו:
  • יצרנו מעין "מחלקה"* שניתן לייצר instances שונים שמתנהגים אותו הדבר. השימוש במילה "מחלקה" היא הרגל מג'אווה, הוא לא מדויק כאשר אנחנו מדברים על ג'אווהסקריפט. בעצם יצרנו אובייקט Prototype (אב-טיפוס) שבקריאה ל new ייווצר instance חדש שלו. סלחו לי אם אני ממשיך להשתמש ב"מחלקה" - הרי זה פוסט למפתחי ג'אווה / #C.
  • אנו יכולים להוסיף דינמית ל"מחלקה" זו מתודות או משתנים ע"י שימוש במילת ה prototype. האמת, כל אחד יכול. גם מפתח אחר שכותב קוד בקובץ אחר משלכם ומודע למחלקה בדרך-לא-דרך.
  • חשוב!: השימוש ב this בתוך המתודה, כמו addBy, הוא חיוני על מנת לגשת ל instance / אובייקט. 
  • הקוד של פונקציות (addBy) נטענות לזיכרון פעם אחת בלבד. זה עשוי להישמע מוזר, אך במבנים אחרים, שנגיע אליהם עוד מעט, הגדרת הפונקציה תוכפל בזיכרון עבור כל אובייקט שנייצר. ההשלכה היא זמן יצירה ארוך יותר של אובייקטים ותפוסת זיכרון גדולה יותר - בעיה משמעותית אם אנו עומדים לייצר מאות או אלפי אובייקטים מאותו הסוג.
  • בעזרת השימוש ב Object Literal ה"מחלקה" מוגדרת בשני חלקים. ניתן גם להגדיר על פונקציה כהשמה חדשה ל Prototype וכך ליצור אובייקט שהגדרתו אינה בהכרח רציפה בקוד. רציפות זו כמובן רצוייה - ועל כן צורת ה Object Literal נראית לי עדיפה.
היכולת לשנות בזמן ריצה, בעזרת המילה השמורה prototype, מחלקות אינה מוגבלת למחלקות שכתב המשתמש. אדרבא, ניתן לבצע שינויים בכל זמן ועל כל מחלקה, גם מחלקות של השפה עצמה כמו String או Object. ניתן לשנות את Function - האב של כל הפונקציות, כפי שנעשה בדוגמה שהצגתי בתחילת הפוסט הקודם.

למרות שכמה ספריות נפוצות (למשל Prototype.js) עושות תרגילים שכאלו - זו נחשבת התנהגות לא רצויה ולא מומלצת בעליל. כדאי להימנע ממנה לגמרי.
רק להזכיר סיבוך אחד אפשרי: בג'אווהסקריפט אובייקטים הם "שקים" של properties (מעין HashTable או Map) - ולעתים קרובות אנו מתייחסים אליהם ככאלו. כל אובייקט יציג את ה properties שהוגדרו עליו ישירות וגם על כל שרשרת ה prototypes שהוא קשור בה. שינוי (הוספה / מחיקה) של property לאחד ה prototypes - יכול לשבור קוד קיים במספר תסריטים. ההמלצה היא לנקוט באחת משתי גישות:
  • בכל גישה ל property של אובייקט - לבדוק שה property באמת שלו (קריאת hasOwnProperty)
  • להימנע לחלוטין משינויים ב prototypes מלבד הגדרת ה"מחלקה".
אתם בוודאי מנחשים איזו גישה קלה יותר ליישום.


הרצון באובייקטים+הכמסה = Module
עד עכשיו השגנו או הכמסה, או אובייקטים - אך לא את שניהם ביחד. בואו נבחן מבנה שיאפשר לנו לקבל את שניהם:
var Calculator = function () {
  // private members
  var value = 0;

  return {
    // public members
    addBy : function (x) {

      value += x;
      console.log('value = ' + value);

    },

    multiplyBy : function (x) {

      value *= x;
      console.log('value = ' + value);

    }
  };
};


var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24
הבסיס כאן הוא דיי פשוט: נשתמש ב Closure על מנת לבצע הכמסה ונשתמש בערך החזרה מסוג Object Literal על מנת לאפיין את המחלקה.
הפונקציה Calculator (=קונסטרקטור) מגדירה משתנים ופונקציות בתוך עצמה, בתוך ה Closure. בנוסף היא מייצרת אובייקט (בעזרת Object Literal) שחוזר למי שקורא לה - במקום this. לא ציינתי קודם, אך קריאה ל new תחזיר את this רק במידה ולא הוגדר return value מפורש. אם הוגדר return - אזי הוא מה שיחזור.

שימו לב שבעזרת החזרה זו נוצר ההבדל מהדוגמה הקודמת: הערך החוזר מהקונסטרקטור הוא לא instance של הפונקציה Calculator עצמה, אלא אובייקט שאנו יצרנו ואנו שולטים בו. אובייקט זה מייצג את החלקים ה public של האובייקט שלנו. כמובן שבג'אווהסקריפט אין type safety ואין שום בעיה שקונסטרקטור יחזיר אובייקט מ"טיפוס" אחר.
כיוון שיש לנו פונקציה בתוך פונקציה, ויש reference מהפונקציה addBy למשתנה value - מובטח לנו שהמשתנה value ימשיך לחיות גם לאחר שהפונקציה Calculator הסתיימה. אם אתם לא זוכרים מדוע - חזרו להסבר על Closure בפוסט הקודם.

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

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

מבנה זה הוא דיי מקובל ונקרא "Module".

הנה וריאציה קצת שונה של Module:
var myNS = myNS || {};

myNS.Calculator = function () {
  // private members
  var value = 0;

  var addBy = function (x) {
    value += x;
    console.log('value = ' + value);
  };

  var multiplyBy = function (x) {
    value *= x;
    console.log('value = ' + value);
  };

  return { // public parts (aka interface)
    addBy : addBy,
    multiplyBy : multiplyBy
  };
};

var calc = new myNS.Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24
היתרון העיקרי בווריאציה זו בא לידי ביטוי כאשר הקוד ארוך יותר. במקום שהמקום הפיסי בקובץ בו מוגדרת הפונקציה הוא שיכתיב את היותה ציבורית או פרטית (התבוננו בדוגמה הקודמת), כאן ההחלטה לגבי "הציבוריות" נלקחת מבלי לחייב את גוף הפונקציה להיות כתוב במקום מסוים. סגנון זה יותר נקי ובדרך זו הרבה יותר קל לנו להבחין מה ציבורי ומה פרטי.

בנוסף עשיתי עוד שדרוג קטן והגדרתי את המודול בתוך namespace. באופן זה אני מצמצם משמעותית את היכולת של ה Constructor להידרס ע"י מפתח אחר שגם במקרה בחר בשם Calculator. ייתרון נוסף ב namespace הוא ב Debugging, כאשר אוכל למצוא בקלות את המשתנים שלי בתוך ה namespace - ולא בערמה אחת עם כל משתני המערכת. ייתרון זה בא לידי ביטוי במיוחד כאשר יש כמה מפתחים על אותו הקוד. עדיין במודול יש Closure ומשתנים פרטיים של Closure לא נראים ברוב ה debuggers ללא breakpoint בתוך הקוד של יצירת ה Closure. לא נורא.

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

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


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

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

בואו נתבונן על הווריאנט הבא:
var calculator = function () {
  // private members
  var value = 0;

  var addBy = function (x) {
    value += x;
    console.log('value = ' + value);
  };

  var multiplyBy = function (x) {
    value *= x;
    console.log('value = ' + value);
  };

  return { // public parts (aka interface)
    addBy : addBy,
    multiplyBy : multiplyBy
  };
}();

שמתם לב להבדל? הוא קטן למדי ולכן הדגשתי אותו ב Bold.
הסוגריים בסוף הביטוי גורמים לכך שהמשתנה calculator מכיל את תוצאת הרצת הפונקציה ולא מצביע לפונקציה, כפי שהיה קודם. לכן, ע"פ קונבנציה, יש לקרוא לו calculator ב c קטנה.

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

ובכן, Singleton הוא דיי נפוץ בפיתוח אפליקציות Client Side. השינוי שביצענו בעצם הוא דרך לבטא Singleton. בניגוד לצד השרת בו דיי נדיר למצוא Singleton ברמת שפה - אנו לרוב מגדירים singleton ברמת הDependency Injection Framework או Service Layer. בג'אווהסקריפט לא נראה לי שיש דDI או Service Layer ואנו מגדירים Singleton ברמת השפה.

הקריאה לקוד, אם כן, תראה משהו כזה:
calculator.addBy(4);
calculator.multiplyBy(6); // 24

ניתן גם לממש Singleton בצורה יותר "קלאסית" (דומה לג'אווה): לאכסון את המצביע ל instance היחיד על ה prototype ולהחזיר אותו בפונקציית getInstance. אני חושב שהדרך שהצגתי פה היא משמעותית יותר נפוצה בעולם הג'אווהסקריפט. שימו לב לדקויות כמו אם שם הפונקציה מתחילה ב Capital Letter או האם יש סוגריים של הפעלה בסוף הגדרת הפונקציה. פעמים רבות הסוגריים לא יהיה ריקים אלא יגדירו פרמטר שמועבר לקונסטרקטור - אולי ערך שמחושב בזמן יצירתה.

הנה מבנה קצת מורכב שמשלב אלמנטים שונים מהדוגמאות השונות:
var Calculator = function () { // constructor
  // private fields
  this._value = 0;
};

Calculator.prototype = function () {
  // private functions

  var _addBy = function (x) {
    this._value += x;
    console.log('value = ' + this._value);
  };

  var _multiplyBy = function (x) {
    this._value *= x;
    console.log('value = ' + this._value);
  };

  return { // interface
    addBy : _addBy,
    multiplyBy : _multiplyBy
  };
}();


var calc = new Calculator();
calc.addBy(4);
calc.multiplyBy(6); // 24
במה מדובר פה?
האם אתם יכולים לעצור לדקה, לקרוא את הקוד, ולחשוב מה המשמעות שלו?


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

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

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

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


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

גם בשפת JavaScript עצמה יש עוד לא מעט ללמוד. אם שרדתם את המדריך הזה הייתי ממליץ להמשיך לאחד או יותר מהמקורות הבאים:
  • JavaScript Garden - ריכוז מצוין של נושאים מבלבלים / בעייתיים בג'אווהסקריפט. אתם תזהו כמה דוגמאות שלקחתי משם וקצת פישטתי.
  • Learning Advanced JavaScript - מדריך מאת ג'ון רזיג לקידום ספרו "סודות הג'אווהסקריפט נינג'ה". אם אתם זוכרים את קוד ה C-Syntax הלא ברור בעליל בתחילת הפוסט הראשון - מדריך זה הולך צעד אחר צעד להסביר אותו - ואתם אמורים להיות מוכנים "לרוץ" עליו.
  • Learning JavaScript Design Patterns - מדריך קצת יותר ארוך שעוסק במבנים בג'אווהסקריפט, גם הוא כאמצעי לקידום ספר שיצא בקרוב. כל עניין ונושא בשפה (למשל namespace) מוגדר במדריך זה כ "pattern" - אבל נו טוב, אני מניח שככה מוכרים הרבה עותקים של ספר תכנות.

אם אתם רוצים לבדוק את הקוד ולבצע debug בסביבה קצת יותר רצינית מהדפדפן, אז שווה לנסות את jsFddle או ישר לקפוץ ל IDE מלא כמו Netbeans, Aptana או WebStorm.


הערות / השגות / מחשבות, כרגיל, יתקבלו בשמחה.

שיהיה לכם המון בהצלחה!