על העוצמה הטמונה ב"טכנולוגיות משעממות" [דעה]

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

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

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

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

בעיה של חלוקת-קשב

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

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

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

הקשב שלנו הוא משאב מוגבל. אנו יכולים יכולים להתמקד בנושא אחד לעומק, או בשלשה נושאים במקביל, ולהתעמק רק רבע(~) מכך. למה רבע? כי יש לנו Context Switch יקר [א].

אני חושש, שעבור מהנדסים צעירים בימים אלו, המקבילה של מה שקורס ה"נושאים מתקדמים במערכות היפר-מבוזרות, מתייצבות עצמית, מונחות בינה מלאכותית" היה עבורי – היא בד"כ טכנולוגיות חדשות: Docker, AngularJS, React, Scala, או בקיצור: Just Name It!

הדרך להתבלט, ולהרגיש מומחה, עוברת בשימוש בטכנולוגיה ה"מגניבה הנוכחית": "אני כותב ב Rust", "Go", או "Swift" – הוא סמל סטטוס, נחשק לא פחות מהעבודה היומיומית בשפות עצמן.

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

לאחר שעברתי כמה וכמה פעמים בחיי ל"טכנולוגיות מגניבות" (כן, ניסיתי את AngularJS, Docker ו React – ועוד מגוון טכנולוגיות שהיו "מגניבות" בזמנן…), אני מוכן להצהיר: בכל הטכנולוגיות החדשות הללו יש גם אזורים לא נעימים, במיוחד כשהן ממש חדשות.

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

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

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

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

איזון הוא לב העניין

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

מבחינה מסוימת, החתירה של מתכנת או צוות בודד בארגון לאמץ טכנולוגיה חדשה משיקולים אישיים הוא Local Optimization שאולי נחמד להם – אך נוגד את האינטרסים של כלל הארגון (ה Global Optimization).

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

מצד שני, לריבוי שפות-תכנות (או ספריות) שבשימוש יש גם בעיות:

  • קוד נוטה לא-להעלם: אם שוכחים ממנו – הוא לא באמת נעלם, הוא רק הופך לקוד Legacy בו צריך לבצע תיקונים מדי פעם. בכדי לבצע תיקונים – יש להכיר את שפת התכנות / הספריות שבשימוש ברמה סבירה כלשהי. "טריפ" של החלפת טכנולוגיות תדירה תשאיר אותנו עם גן-חיות של Legacies שיש לתחזק. זה הזמן לעבור מקום עבודה…
  • אף אחד מאיתנו הוא לא ליאונרדו דה-וינצ'י (איש אשכולות [ב]) – כלומר: לא ניתן להגיע לרמת מומחיות גבוהה במספר טכנולוגיות בו זמנית. זה או להגיע לשליטה X ב-3 טכנולוגיות, או X/2 ב-5 או 6 טכנולוגיות. יש כאן trade-off ברור.
  • חיבור בין טכנולוגיות שונות מציב, פעמים רבות, תקורה.

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

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

תובנה מעניינת ששמעתי בכנס GOTO; Berlin הגיעה מניתוח השימוש בחנויות ספרים מקוונות: לא רק בעולם הטכנולוגיה יש נטייה ללכת אחרי "החדש". הם הראו שגם רוב ספרי הניהול שנמכרים (ניהול – דיסיפלינה שמשתנה לאט) הם מהשנה האחרונה, או השנה שלפניה.

שנייה! האם גם הרופא שלי עשוי לנסות "את הטכניקה החדשה ביותר" כי ככה הוא ירגיש "מגניב יותר"? פעם עברתי טיפול שהרגיש לי ממש כך…

לוח פתקיות. לא "מגניב" כמו Mingle או ScrumWise – אבל עובד מצויין, וללא Learning Curve.

אבל…

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

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

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

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

כנ"ל לגבי Frameworks, בסיסי-נתונים, וכו'.

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

מה עושים?

ניתן לעשות חישוב עלות/תמורה פשוט בכדי לבחון האם אימוץ טכנולוגיה משתלם – וכך לאמץ טכנולוגיות במינון האופטימלי.

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

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

אם הבעיה היא פסיכולוגית / חברתית, אולי ראוי שגם הפתרון יהיה פסיכולוגי / חברתי?

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

במשך תקופה השתעשעתי ברעיון שאולי, רק אולי, גוגל מאפשרת למהנדסים לעבוד על "מה שהם רוצים" כ20% מהזמן בכדי שישחררו שם אנרגיות – ושאר הזמן יתמקדו בעבודה? מסתבר שגם ה 20% הם לא ממש…

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

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

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

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

אין לי פתרון. אני לא יודע מה אפשר לעשות.
אני החלטתי לכתוב פוסט בנושא – אולי הוא יעזור במשהו 🙂

—-

[א] מונח מעולם מערכות ההפעלה. כאשר נראה לנו שכמה יישומים רצים על אותו CPU core במקביל, הם בעצם רצים עליו סדרתית, כאשר מערכת ההפעלה מחליפה ביניהם כמה פעמים בשנייה ויוצרת את אשליית המקביליות.
ההחלפה הזו, דורשת כמה פעולות ממערכת ההפעלה ומייתרת חלק מזיכרון המטמון (בעיקר במעבד ובזיכרון) – מה שגורם לכל החלפה לגזול זמן לא מבוטל = תקורה.

באופן דומה, בני אדם שעוברים בין נושא לנושא יהיו פחות מרוכזים בנושא אליו עברו במשך כמה דקות. נוטים לייחס למוח האנושי context switch של 15 דקות, כלומר: כאשר אנו מחליפים נושא לוקח לנו כרבע שעה להגיע לאותה רמת ריכוז ויעילות שבה היינו בנושא הקודם שבו עסקנו. בממוצע גס – ניתן לומר ש 7 וחצי דקות מזמננו (ממוצע גס בין 0 ל 100% ריכוז) מתבזבזות בכל החלפת נושא. אם אנו עושים 20 דילוגים ביום בין נושאים, הלוך ושוב, בזבזנו שעתיים וחצי של פעילות המוח שלנו ביום הזה.

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

דפוסי ארכיטקטורה (Architectural Patterns): דפוסי עיצוב, בגדול

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

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

אתן לכם מטאפורה לדפוסי ארכיטקטורה:

דפוסי ארכיטקטורה הם כמו מתכון למרק

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

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

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

הדפוסים עליהם נעבור בפוסט

  • כדור גדול של בוץ (Big Ball of Mud)
  • "הדר המסודר" (Hadar Amesudar)
  • "שכבות עוגה" (Layered Architecture)
  • MVC (קיצור של Model-View-Controller)
  • "דודות מרכלות" (Event-Driven Architecture)
  •  Command-Query Separation (בקיצור: CQRS או CQS) 
  • "גרעין קטן" (MicroKernel) ודרכו נזכיר גם Plug-in Architecture
  • "מיקרו-שירותים" (Micro-Services Architecture, בקיצור: MSA)

דפוסים נפוצים אחרים שלא נעבור עליהם הם, אך שווה להזכיר:

  • צינורות ומסננים (Pipes & Filters) – אותו כיסיתי בפירוט בפוסט משלו.
  • BlackBoard (רלוונטי בעולם של בינה מלאכותית)
  • MVP, MVVM או Presentation-Abstraction-Control (דומים למדי ל MVC)
  • Broker (רלוונטי במערכות מבוזרות)
  • Model-Driven Architecture (מן "רעיון גדול" שלא ממש "תפס") או Domain-Specific Languages (בקיצור DSL – רעיון שהצליח חלקית).
  • וכו'

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

בגדול אזכיר ש:

  • לדפוסים יש ערך רב כשפה משותפת וכמקור השראה.
  • הצמדות לדפוסים "כפי שהוגדרו" – יכולה להיות הרסנית.
  • כדאי ליצור וריאציות שלכם של הדפוסים שיתאימו לכם יותר, או לשלב רעיונות (Mix and Match) מדפוסים שונים.

בואו נצא לדרך!

כדור גדול של בוץ (Big Ball of Mud)

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

מדוע כדאי לנו לחלק את המערכת לחלקים? בגלל:

  • בעיית ההתמצאות (Orientation)
  • בעיית ה Development Scalability
  • יצירת מסגרת מנטלית של סדר וחוקיות במערכת. זו לא בדיחה: כמה כללים פשוטים מה "נכון" ומה "לא נכון" עוזרים לאנשים בפרויקט להרגיש בטוחים יותר במה שהם עושים. אי קיום הכללים הפשוטים הללו / חוסר סדר בסיסי כלשהו בפרויקט – משפיע על אנשים רבים (לא כולם) בצורה שלילית וברורה
    קשה להבין את ההשפעה עד שלא רואים את המעבר של פרויקט ממצב של "אי-סדר", לאפילו מצב של "סדר בסיסי".
את 2 הבעיות הראשונות תיארתי בפירוט בפוסט מדוע אנו זקוקים ל Software Design?

"הדר המסודר"

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

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

  • מחלקות עד 1000 שורות קוד
  • מחלקות מתחת ל 1000 שורות קוד
  • קבועים, enums, או data objects (שהם בד"כ מאוד קטנים)

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

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

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

  • דואר נכנס
  • דואר יוצא
  • אנשי קשר
  • קונפיגורציה
  • אחר

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

מדוע אנו מעוניינים בסדר וניהול תלויות?

נמשיל את העניין לבעיה פשוטה מאוד: הפרדה של קוד בין 2 מחלקות:

את קוד ה Parser אנו מחלקים ל2 חלקים. מדוע?

  • כי יותר קל לנהל קוד בחתיכות קטנות ופשוטות, מחתיכה גדולה ומורכבת יותר.
  • כי אז ניתן לבצע שינוי באזור אחד, עם סבירות גבוהה* שלא יהיה צורך לשנות אזור אחר. פחות בדיקות רגרסיה, פחות "פחד" לשבור דברים, והיכולת של 2 מפתחים לעבוד במקביל בצורה יותר יעילה.
* כל עוד החלוקה היא אפקטיבית
לא מספיק שחילקנו את ה Parser ל 2 מחלקות, אנו רוצים להטיל מגבלות על התלויות ביניהן:
  • הכמסה (encapsulation) – מגבלה מאוד מקובלת בעולם ה OO, שמפרידה בין חלקים ציבוריים (בהם ניתן ליצור תלות) ופרטיים (בהם לא ניתן ליצור תלות). עצם כך שצמצמנו את התלויות – הגברנו את הסיכוי ששינוי בקוד (באזור הפרטי) לא ידרוש שינויים או ישפיע על מחלקות שתלויות במחלקה זו – שוב: Developer Scalability, תחזוקה קל היותר, ויכולת לבצע שינויים בצורה פשוטה יחסית (באזורים הפרטיים).
  • ניהול תלויות (dependency management) – מגבלה שאומרת שמחלקת ה Document Parser רשאית לקרוא ל Paragraph Parse – אך לא להיפך. צמצמנו כך את התלויות עוד יותר.

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

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

הכללים המגבילים, מציבים בפנינו שקלול-תמורות מסוים:

  • פחות מדי מגבלות: כולם מדברים עם כולם במערכת – ונפגעת מאוד תכונת ה Development Scalability.
  • יותר מדי מגבלות: מקשות על ה productivity של המפתחים.
ככל אצבע, ארכיטקטורה טובה לא כוללת כמה שיותר מגבלות: היא מתאימה את כמות המגבלות לגודל הפרויקט, לשלב ההתפתחות שלו (יותר מגבלות ככל שמתקדמים + כמה עולה לפיתוח להוסיף מגבלות בשלב זה?) ולארגון הספציפי (יש מתכנתים שיסתדרו טוב יותר עם מעט מגבלות, יש כאלו שלא).

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

Layered Architecture

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

כללי החלוקה של דפוס השכבות הוא כדלהלן:

  • נפריד את מאגר המחלקות במערכת שלנו לכמה שכבות: שכבות עליונות לטיפול בעניינים ברמת הפשטה גבוהה, כאשר רמת ההפשטה יורדת ככל שיורדים בשכבות.
  • לוגיקה בשכבה גבוהה (n) תהיה מורכבת מהפעלה של לוגיקות (או פונקציות) בשכבה שמתחתיה (n-1). כמו כן, לוגיקה בשכבה גבוהה (n) תעזור להרכיב את הפונקציות בשכבה הגבוהה ממנה (n+1).
  • בד"כ השכבה הגבוהה ביותר היא זו שמתקשרת עם העולם החיצון: משתמש או מערכות אחרות.
  • החלוקה של המחלקות לשכבות היא שלב אחד, ובד"כ יש חלוקות נוספות בתוך השכבה למודולים / תתי-מערכות (sub-systems).
  • בד"כ בכל שכבה יש תת-שכבת API (או "Facade") מנוהלת – בה, ורק בה השכבה העליונה יכולה להשתמש. לא נרצה שכל ה APIs שזמינים בתוך השכבה יהיו זמינים לשכבה מעל – אחרת, איזו משמעות יש להפרדה שהגדרנו?!

בהצצה לתוך השכבה הבודדת, המבנה עשוי להראות דומה למבנה הבא:

פרשנות נפוצה של מודל השכבות במערכות ווב, מערכות מידע, או Enterprise Systems יש מן מתכון מקובל של לחלוקה המערכת לשכבות.
אין לכם כח לקבוע רמות הפשטה שונות ושימושיות של המערכת? – השתמשו במתכון מוכן (מתכון של מתכון):

  • UI – ממשק המשתמש
  • Business Logic – הפונקציה העיקרית שהמערכת ממלאה (ניהול ספקים, רכש, מלאי, וכו')
  • Data Access – בשכבה זו פעם היה הרבה קוד מתוחכם, אך היא הוחלפה חלקית ע"י מערכות ORM – ומה שנשאר בה הם האובייקטים של המערכת שמתארים את הנתונים (Entities, Collections, וכו').
  • Persistency – שכבה זו מתייחסת לבסיס הנתונים (בהנחה שיש כזה, בד"כ רלציוני) – סקריפטים ליצירת הטבלאות (DDL) ולעתים גם קוד שרץ בתוך בסיס הנתונים (stored procedures) [ב].

לחלוקה זו יש וריאציות שונות (הוספת שכבת Service מעל ה Business Logic, או שכבת Presentation Logic מתחת ל UI, וכו') – אבל סה"כ היא מאוד מקובלת.

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

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

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

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

אציין, שבמערכות Enterprise כדוגמת ERP של סאפ – יש ערך ממשי בחלוקה הנ"ך לשכבות: מערכות אלו מבוססות במידה רבה על מידול (modeling) של נתונים, ואז באמת יש פיתוחים ב Business Layer שלא צריכים לעדכן את ה UI (כי הוא נבנה מתוך מודל הנתונים שלא השתנה), ולא צריכים לעדכן כמעט את הטבלאות בבסיס הנתונים. שכבת ה Business Layer היא משמעותית ומשתנה תדיר – ויש ערך רב בהפרדה זו. כמו כן ניתן לשנות את ה UI (כיצד מרנדרים את המודל) – מבלי לשנות את שכבת ה Business Logic בכלל, וכו'.

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

"הב לי עלים ירוקים…"

Model-View-Controller

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

דפוס MVC בא לחלק אפליקציית UI לחלקים מוגדרים היטב – ולהגדיר תלויות ביניהן.
המקור של MVC הוא ב framework של שפת Smalltalk-80 משנת 1980 בשם "Model-View-Controller Editor".

ה Framework הזה היה מיועד לאפליקציות command line (שיא ה UI באותה תקופה), והחלוקה הייתה בין:

  • Model – הנתונים של האפליקציה / מבני נתונים
  • View – תצוגת הפלט, מה לרשום על המסך
  • Control – קבל ה Input מהמשתמש וטיפול בקלט הזה – בעצם ה Business Logic.
MVC "קלאסי" / "המקורי"

כל מחלקה (או פונקציה) נופלת באחד מהאזורים – וחייבת לציית לכליל הנראות. בת'כלס ה Controller רק יוצר את ה View ולא משתמש בו אח"כ, ושניהם מכירים את המודל. בין מחלקה של View למחלקה של Controller יש בד"כ קשר של 1:1 – והם מתארים "מסך" או "תפריט" באפליקציה.

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

אם כולם היו מכירים את MVC ומשתמשים בו באותו האופן – לדפוס זה היה ערך רב יותר. אולם, יש 2 בעיות:

  • MVC המקורי לא כ"כ מתאים לאפליקציות Desktop, Web או כל מה שאינו command line.
  • יש המון וריאציות של MVC: חלקן קיבלו שמות נבדלים (יחסית) כמו MVP, MVVM או PAC – אבל רוב הווריאציות פשוט משתמש בשם "MVC", תוך כדי ביצוע שינויים במודל "המקורי".
התוצאה המידית היא בלבול וחוסר תקשורת –  FUD. אם דיברנו על כך שדפוסים משמשים כשפה משותפת, אזי MVC משמש לרוב כאנטי-שפה-משותפת: מתכנת אחד אומר "Controller" והשני שומע "Kotroller" (כלומר: מה שהוא מכיר כ Controller). שני המתכנתים רבים כמה ימים, אולי שבועות – עד שהם מצליחים להסכים שבעצם השני לא אידיוט/מפגר/לא מבין – שניהם פשוט מכירים שתי וריאציות שונות של MVC.
בפועל, אוותר על הניסיון לתאר ולאפיין את כל הווריאציות התאורטיות של MVC (להזכיר: יש כאלו שהם Client-Side, כאלו שהם MVC צד שרת, כאלו שהם מערבים צד-לקוח וצד-שרת, וכאלו שמתאימים לאפליקציות Desktop או Native Mobile).
אני ממליץ לוותר על ההגדרות, ופשוט להיצמד לספרייה הרלוונטית שבה אתם משתמשים – והכללים שהיא מכתיבה: בסוף, התועלת העיקרית היא מכללים ברורים שכולם מצייתים להם – קצת פחות אם כלל מסוים מנוסח בווריאציה 2א' או בווריאציה 3ג'.
יש ספריות יש רבות הממשות (וריאציה של) דפוס זה: Struts, Play!, Grails, Ruby on Rails, Django, ASP.NET MVC , Angular.js, Backbone.js ועוד….
מבנה של MVC צד-לקוח מודרני טיפוסי אפשרי
העיקרון המשמעותי ביותר, והמוסכם ביותר בקרב ספריות "MVC" מודרניות הוא ההפרדה בין UI (כיצד משהו נראה על המסך) ל Presentation Logic (כיצד להחליט מה יש להציג על המסך). למשל (דוגמה סופר-פשוטה):
במקום לקבוע כלל ש:
  • אדם עם +500 חברים מקבל icon מיוחד.
מחלקים את המימוש ל2 אזורים:
  • Presentation Logic – אדם עם 500+ מתויג כ "אדם פופולרי במיוחד" (What)
  • UI – אדם המתויג כ "אדם פופולרי במיוחד" מקבל icon מיוחד.
הפרדה זו יוצרת מעט overhead על הפיתוח, אך היא מאפשרת להחליף את ה presentation (כלומר: אלמנטים ב UI / כל ה UI) בצורה קלה יחסית, מבלי "לאבד" או לשנות את ה Presentation Logic שהוא יותר יציב, או לחלופין להוסיף presentation נוסף (נאמר: mobile version), מבלי לשכפל את הקוד לגמרי.
החלפת / רענון שכבת ה UI – היא פעולה תדירה מאוד במערכות ווב. ארגונים גדולים עושים זאת כל 2-3 שנים, ואתרים קטנים מבצעים שינויים לפעמים על בסיס חודשי.

סיכום

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

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

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

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

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

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

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

לינקים מעניינים

הדוד בוב בביקורת עוקצנית על MVC (וידאו): "MVC is not an Architecture"

דפוסי עיצוב (Design Patterns): ימים יפים, ימים קשים

[הפוסט נכתב בשיתוף עם רחלי אבנר, Area Architect בחברת SAP]

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

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

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

כיצד ומדוע עלו ה Patterns לגדולה?
מדוע אם כן יש עליהן ביקורת, והאם יש לכך הצדקה?

על כך בפוסט שלפניכם.

כריסטופר אלכסנדר

שורשים

את הרעיון של Design Patterns (להלן: דפוסי עיצוב) התחיל ארכיטקט בשם כריסטופר אלכסנדר אי שם בשנות השבעים.
כריסטופר אלכסנדר לא היה ארכיטקט תוכנה, כי אם ארכיטקט בנין ("אדריכל"). וגם דפוסי העיצוב שהגדיר – היו דפוסי עיצוב של אדריכלות. כריסטופר אלכסנדר לא היה אדריכל שגרתי: השכלתו לפני ארכיטקטורה כללה פיסיקה, כימיה ומתמטיקה (יש לו תואר שני במתמטיקה), ובנוסף לעיסוקו באדריכלות הוא היה גם contractor ("קבלן") – מה שנותן לנו להבין שמצד אחד ראשו בעולמות התאורטיים אבל מצד שני רגליו נטועות עמוק בעולם המעשה.

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

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

מ-ש-ע-מ-ם

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

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

הנה מבנה מוצלח יותר:

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

את שרשרת ההבחנות סיכם בשלושה ספרים, שהמשמעותי ביניהם נקרא: A Pattern Language: Town, Building, Construction.

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

ספרי דפוסי העיצוב של כריסטופר אלכסנדר

דפוסי-עיצוב בתוכנה

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

את הרעש הגדול עשו ארבעה: אריך גאמה[ב] ניהל שיחה עם ריצארד הלם בה עלו הרעיונות לספר. אח"כ הם צרפו את ראלף ג'ונסון וג'ון וילידס. ארבעתם ידועים כ "Gang Of Four" (או בקיצור GOF). רובם חוקרים / בעלי רקע משמעותי של מחקר במדעי המחשב – אבל הם כתבו את הספר, כנראה הנמכר ביותר בתחום, אחד המשפיעים ביותר ושנחשב למאוד מעשי ושהקדים את "האקדמיה" בשנים.

"הספר"

בספר הם תיארו סט של בעיות תכנות – ופתרונות Object-Oriented אלגנטיים ("דפוסי עיצוב"). דגשים עקרוניים היו הפשטה וגמישות להרחבה עתידית – עקרונות החרוטים על דגלה של פרדיגמת ה Object-Oriented.

Facade, Proxy, Adapter, Singleton ועוד – הם מושגים שהטביע בעולם התוכנה ספר זה.
"דפוסי העיצוב" היו כבר קיימים בעולם: לכל דפוס עיצוב הם סיפקו מספר דוגמאות של יישומים במערכות קיימות. בספר הם אספו אותם, תיעדו אותם ויצרו שפה חדשה להנדסת תוכנה Object Oriented.

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

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

גם היום, 20 שנה אחרי, ספרים חדשים משתמשים במותג "Patterns" בכדי לקסום לנו, ולהימכר טוב יותר:

סדקים ראשונים

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

הזמן גם עבר… והאופנות קצת השתנו – ויצרו סדקים נוספים:

  • תנועת האג'ייל: את התכנונים (Design) ה Top Down, החליפו ב Evolutionary Design ו Bottom Up. היכן ש Design Patterns כבר פחות מתאימים.
  • בזמן הוצאת ספר דפוסי-העיצוב, תכנות OO (או תכנות מונחה היבטים – AOP) היה "הדבר הגדול הבא". היום תכנות OO הוא נפוץ מאוד – אבל "הדבר הגדול הבא" הוא משהו בין תכנות דינאמי לפונקציונלי – קצת שונה מהעקרונות של OO. לשפות אלו, דפוסי העיצוב המוכרים – פחות מתאימות.
  • "אמיתות של הנדסת תוכנה" גם הן השתנו. אם פעם שימוש חוזר בקוד היה העיסוק המרכזי, היום אלו יותר פשטות של הקוד ו time-to-market. העיקרון הפתוח-סגור (Open-Closed Principle) פעם היה מאוד פופולרי (ורואים את חותמו בספר של GOF) – אך כיום הוא שנוי במחלוקת. דפוסי העיצוב מהספר של GOF מקדמים הרבה שימוש חוזר והפשטה – וקצת פחות פשטות.

את הסדק הגדול ביותר ניתן לתאר בעזרת הסיפור (לא מצאתי את המקור) על מרצה שהרצה על Design Patterns רק כדי לשים לב לאחר שנים לשאלות המוגזמות-מיסודן שמעלים אנשים: "האם עדיף להשתמש ב Flyweight או במשפט If"?
Flyweight או משפט If? האם Simplicity היא לא ערך – האם הגיוני להחליף שורת קוד אחת במבנה שלם (Flyweight)?!

דפוסי העיצוב באו להלחם בעולם עם Under-Engineering, אך יצרו עולם של Over-Engineering.

לזרוק ולשרוף?!

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

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

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

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

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

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

לו רק הייתה יותר הכוונה…

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

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

כיצד להשתמש בדפוסי עיצוב בצורה נבונה?

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

צדדים טובים:

  • פתרונות מוכנים ומעולים לבעיות תכנון. נכון חלקית: אלו פתרונות טובים לבעיות מאוד מסוימות, וקיים סיכון סביר (ומוכח) – שאלו לא הפתרונות הכי טובים למערכת שלכם. זכרו: גם בספר "Design Patterns" – לא תמיד באמת מדברים עליכם.
  • שפה משותפת: ברגע שגם אני וגם בין-שיחי מכירים את דפוסי העיצוב (למשל) "Strategy" – יהיה לנו הרבה יותר קל לתקשר על בעיה / פתרון טכני מסוים. מילה אחת יכולה להחליף הסבר של כמה דקות. חשוב ללמוד דפוסי-עיצוב בכדי לקבל מושגים משותף ולהפוך את התקשורת בזמן העבודה ליעילה ומדויקת יותר. כמו כן, היחסים המוכרים בין תבניות שונות מאפשרות בסיס לשיחה על מבנים מורכבים יותר (למשל: MVC). שפה משותפת היא התועלות המוסכמות ביותר בימנו המיוחסות לדפוסי-עיצוב.
  • מקור השראה: לא חייבים ליישם את דפוס העיצוב כלשונו. ניתן לעשות וריאציה, ניתן לקחת רק רעיון אחד מדפוס העיצוב – ולעשות משהו אחר לגמרי. לגישה זו יש הרבה פוטנציאל, ולשם כך טוב (באופן כללי בכלל) להכיר תכנונים לא-טריוואילים של מערכות אחרות.
צדדים פחות טובים:
  • הנדסת-יתר (over-engineering) – עסקנו בבעיה זו די והותר.
  • פוטנציאל לקיבעון: מצב בו למישהו קשה ליישם מבנה דומה-חלקית לדפוס העיצוב, כי הוא מרגיש ש"דפוס העיצוב" חכם בוודאי ממנו – ואז הוא נגרר ליישום פחות מוצלח (אם כי שמתועד בספר אגדי).
  • פוטנציאל לקיבעון כרוני: אנשים שלא מוכנים להאמין שיש פתרון טוב יותר (בהקשר המסוים) מזה המתואר בספר האגדי של "Design Patterns". לכו תשכנעו אותם.

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

הדרכים המומלצת היום להשתמש בדפוסי עיצוב הן:

  • ככלל: אל תתחילו לכתוב קוד ולהשתמש בדפוסי-עיצוב. אל תעשו הכנה ל Adapters – כאשר יש רק יישום אחד מוכר. "בטח בעתיד יהיו עוד – בואו נכין את הקרקע מעכשיו" – זו לא גישה אג'ילית.
  • Refactoring to Patterns – במידה והגעתם לקוד שיש בו ריחות (code smells) המצביעים על בעיה בקוד (שכפול קוד ברור, תלויות לא רצויות וכו')….
  • שקלו את דפוס העיצוב המתאים. זכרו שדפוסי עיצוב מגבילים לפעמים את גמישות הקוד בכיוון מסוים. לא על כל 5 שורות קוד כפולות – כדאי להשתמש ב State, למשל.
  • עשו Refactoring ושנו את הקוד כך שיישם את דפוס העיצוב שהחלטתם שהוא מוצדק – או גרסה דומה שלו. חשוב להבין "מה הקטע" של כל דפוס עיצוב – ולא סתם ליישם אותו "ע"פ הספר".
הספר הזה הוא כבר בן עשור. לו היה נכתב כיום – אני מניח שהמסרים שלו היו נראים קצת אחרת (יותר החלטיים).
עדיין – זה ספר שיכול לעזור

אפרופו: בהינתן ההבחנה ש"שפה משותפת" היא ערך עיקרי של דפוסי עיצוב ו"שימוש מופרז" היא הסכנה המרכזית – ניתן להצביע על Anti-Patterns ככלי שיכול להיות מצוין: Anti-Patterns הם תיאורים של דפוסים שליליים ולא-מומלצים מהם יש להימנע או לפחות להיזהר. אפשר לציין Anti-Patterns כגון "Gold Plating" (השקעה מופרזת באלמנט לא-חשוב), "Project Chicken" (אף אחד בפרויקט לא יכול לעמוד במשימה / לוחות הזמנים – אך כולם פוחדים להיות האדם הראשון שמודה בזה. לאחר שאחד הודה – כל השאר מצטרפים "אם הוא לא יעמוד ביעד – זה יתקע גם אותי"), "Death March" (עבודה על פרויקט ללא סיכוי הצלחה – אך מישהו "למעלה" מסרב להכיר במציאות הזו והפרויקט ממשיך) ועוד.

היופי ב Anti-Patterns הוא שאף אחד לא "ירוץ" ליישם אותם – זה לא מקור לגאווה. אין over-engineering.
החיסרון: שום מקור לא הצליח לייצר מומנטום מספיק גדול בכדי להפוך שפה כלשהי של Anti-Patterns לנפוצה מספיק בכדי שתהיה ידועה ומוסכמת בקרב קהל משמעותי [ד].

דפוסים כתהליך של התחדשות

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

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

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

Proxy ו Observer אולי הפכו לחלק משפת JavaScript (בצורת bind ו object.observe, בהתאמה) אבל עם התפתחות השפה והאתגרים שלה נוצרו גם דפוסים המתאימים להתמודדות עם האתגרים החדשים (לדוגמה: Module).

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

סיכום

  • היללנו את דפוסי העיצוב.
  • השמצנו וביקרנו את דפוסי העיצוב.
  • לקראת הסוף: ניסינו לספק תמונה רחבה ומאוזנת.

כדאי ללמוד על דפוסי-עיצוב כשפה, תוך כדי שימוש בדוגמאות עדכניות ורלוונטיות. למשל:
במקום ללמד על Proxy על ידי ניתוח ה UML שהובא בספר של GOF ועיקרו היה מימוש פרוקסי ב ++C, אפשר להתעכב על הרעיונות שפרוקסי מייצג, ואז לראות אותם ממומשים בשפות השונות (proxy$ בספריית jQuery או bind בג'אווהסקרפיט. Dynamic Proxy בג'אווה וכו').

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

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

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

—-

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

דפוסי עיצוב של חווית משתמש (בעברית) וגם פרק בפודקאסט

—-

[א] איש מדעי המחשב שעסק רבות ב concurrency והספריה שכתב כהשלמה לג'אווה – נכנסה מאוחר יותר לסט הספריות הסטנדרטיות.

[ב] מאוחר יותר היה שותף לכתיבת הספרייה JUnit והוביל את התכנון של סביבת הפיתוח Eclipse.

[ג] "All problems in computer science can be solved by another level of indirection", שפעם נחשב לאמת מקובלת. רפרנס: http://www.dmst.aueb.gr/dds/pubs/inbook/beautiful_code/html/Spi07g.html

[ד] והיו כמה ניסיונות, למשל הספר AntiPatterns או הספר Adrenaline Junkies and Template Zombies.

[ה] על עבודתו של אלכסנדר בתחום הארכיטקטורה אפשר לספר הרבה. הנה שני מקורות מעניינים מאד בעברית:
פרוייקט תרגום שיתופי לעברית של הדפוסים של אלכסנדר, והבלוג של יודן רופא, שהיה תלמידו של אלכסנדר
מי שקורא את מה שנכתב על עבודתו מגלה תאוריה עמוקה הרבה יותר מ"רשימת מכולת של דפוסים" וכוללת אלמנטים פילוסופיים כמו גם שיטות מעשיות להוצאה לפועל של התאוריות האלה. אגב, שיתוף משתמשים ועבודה איטרטיבית – שמוכרים היום כחלק מעקרונות האג'יליים בהנדסת תוכנה, היו גם חלק מהותי בשיטת העבודה של אלכסנדר, וניתן לקרוא עליהם בשני ספריו האחרים The Oregon experiment ו-  A Timeless way of building.

"תואר החלומות" – הסערה

וואהו. אני חייב להודות שהופתעתי!

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

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

מה לקחתי מהתגובות? (לטווח המיידי – הכנס)

אני מודה לכולכם על התגובות – אני חושב שהפקתי מהן כמה המלצות משמעותיות.
אני כותב את הפוסט בתחושת מחויבות לספק כמה הבהרות: כמה רעיונות שניסיתי להעביר בצורה מסוימת פורשו ע"י רבים בצורה שונה + הצלחתי לשפר כמה רעיונות אחרים.
הנה תקציר:
  • "תואר החלומות" הוא איננו תוכנית מבושלת – הוא היה אמצעי טכני להציג רעיונות (שדווקא בהם התמקדתי) בצורה מוחשית יותר. רעיונות כגון:
    • התמקדות בצרכים של רוב הסטודנטים – ולא של מיעוט (לו ניתן להקדיש תואר מיוחד, נקרא לו "מדעי-המחשב")
    • הכרה בכך שידע הפך זמין יותר מבעבר, וכנראה שניתן וכדאי להסתגל בהתאם ולקצר את מחזור ההשכלה (כמה זמן לוקח עד שמתחילים לעבוד).
    • בחינה מחדש של הערך (value) של כל נושא שנלמד, ללא הגנות מיוחדות, וצמצום ה waste.
    • מהנדסי תוכנה (שאני חוויתי) מתמודדים הרבה יותר עם בעיות ארגוניות / מערכתיות / אנושיות – מאשר עם אלגוריתמים. למה להשקיע בשני פי X יותר מבראשון? (יצירת השכלה שעונה על הצרכים המעשיים)
    • כמה רעיונות טכניים שנראים לי לא אופטימליים באוניברסיטה (לימוד ה stack הטכנולוגי מהברזלים למתכנת – ולא להיפך, שמהניסיון שלי הוא כיוון יותר יעיל; התמקדות בשכבת הפשטה אחת מתחת לעבודה השוטפת – ולא שלושה, כפי שמקובל לעתים רבות וכו\'…).
  • לא הייתה שום מחשבה / רמיזה על הפחתת הרמה האקדמית של החומר הנלמד. אמנם זרקתי כמה באזזים על שמות הקורסים – המטרה הייתה לחדד את משמעות הקורס.
  • אכן נתתי משקל רב יותר לפיתוח ווב / מערכות מידע ממה שיש צורך (תודה לכל המגיבים). אני מניח שזו הייתה הטיה אישית שלי לנושאים שהתעסקתי בהם בתקופה האחרונה.
  • אכן חתכתי את המתמטיקה לגמרי – ובהחלט אפשר לתת לה מקום של כבוד כנושא לבחירה (בכלל, תואר אקדמי הוא מנגנון עם מעט התאמה אישית לסטודנט – וזה נראה לי פספוס).

לצורך התרשמות בלבד

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

זכרו:

  • זה לא מלוטש
  • עכשיו אחת בלילה 🙂
  • זו דעתי – ואני לא טוען שזו אמת מוחלטת.

"אז מה אתה מציע?"

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

הנה מייל מעניין ורלוונטי שקיבלתי (פרטי השולח שמורים במערכת) – והתשובה שנתתי. ניסיתי.

שלום ליאור,

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

תגובתי:

היי מיסטר-X,
 
זו שאלה לא פשוטה. לא בגלל החומר הנלמד בתואר, אלא בגלל היוקרה של התארים האקדמיים.
תואר אקדמי יוקרתי (אוניברסיטה טובה) + ציונים גבוהים (בעיקר במקצועות המחשב) – פותחים דלתות בחברות רבות.
 
החלטה ללכת בלי תואר היא פחות סטנדרטית ולמרות שהיא יכולה לחסוך כמה שנים – היא יותר קשה באופן אישי (לפחות במצב הקיים).
עובד איתי בחור שלא עשה תואר, הגיע ל SAP (חברה מכובדת) על בסיס הכשרה בצבא. למרות שהוא מצוין דווקא בנושאים "אקדמיים" (תכנון, יעילות וכו\') – הוא הרגיש צורך להשלים תואר בגלל "מה שהוא מפסיד שלא עשה תואר". הוא התחיל תואר השנה – ובנתיים מאוד מתבאס ממנו.
 
אני חושב שבמצב היום יש עדיפות לבוגרי אוניברסיטה.
אני לא רואה כמעט שום תועלת לבוגרי "הנדסת תוכנה" בקבלה לעבודה על פני "מדעי המחשב" – זה עניין אישי אם אתה מוכן להשקיע הרבה זמן בלימודי מתמטיקה לא-קלים (יש כאלו שפשוט נהנים מעיסוק במתמטיקה).
 
מה אני יכול להמליץ?
להישאר באוניברסיטה, לא "להתאבד" על קורסי מתמטיקה או בקורסים תאורטיים למדי (למרות שזה יכול לפגוע בממוצע, עדיין אכפת יותר מציונים בקורסי מחשבים) ולא להסתפק במה שאתה מקבל מהתואר מבחינת ההשכלה.
למצוא מסגרת העשרה עכשווית ש"מדליקה" אותך, כגון:
  • meetups כלשהם (ניתן למצוא ב http://www.meetup.com/ או http://www.geektime.co.il/eventsboard/) – אל תחשוש להגיע כי "אתה עדיין סטודנט".
  • למצוא בעל עסק קטן שישמח להשתמש בתוכנה כלשהי – ולכתוב לו אפילו במחיר זעום (הניסיון של כתיבה ללקוח אמיתי – היא מעשירה ומתגמלת יותר מכל כתיבת תוכנה "למגירה"). יכול גם מאוד לעזור בקבלה לעבודה.
  • לקרוא כמה ספרי מופת בהנדסת תוכנה (גם אם יעברו על הרעיונות שהספר מבטא בתואר, הם לרוב יכוסו בצורה רדודה יחסית למקור).
  • משהו אחר…
 
מקווה שעזרתי,
ליאור

סיכום

הנה עוד פוסט חפוז שהתחלתי לכתוב ב 12 וחצי בלילה… ואין לי מושג איך הוא יסתיים 🙂

להבין גיט (Git)

Git הוא כלי לניהול גרסאות (Version Control System) הפופולרי למדי בימים אלו.
בניגוד ל (Subversion (SVN או Perforce שהם כלים בעלי ניהול מרכזי, ל Git יש ניהול מבוזר.משמעות אחת היא שבמקום שיהיה איש Operations מאחורי הקלעים שינהל את שרת ה SVN ויחסוך חשיבה למתכנתים ברוב הסוגיות הקשורות ל Source Control – כעת על המפתחים לדאוג לנושא זה בעצמם (לפחות במידה מסוימת).

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

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

בסה"כ Git (וכלי ניהול גרסאות מבוזרים בכלל) נחשב לתומך בפיתוח Agile המאפשר גמישות לנהל releases שתוכנם נקבע בשלב מאוחר, לעבוד בשיתוף עם מפתחים אחרים – מבלי להפריע לשאר המפתחים, לבצע שינויים משמעותיים בבסיס הקוד, ועוד. במקרים רבים ארגונים מאמצים Git בעיקר בכדי "לזרז את ה Agile".

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

גיט הוא פשוט להיט!
מקור: סקר המפתחים של אקליפס 2013

קצת הגדרות

כשלומדים גרמנית (ניסיתי), מהר מאוד מזהים את הדמיון שלה לאנגלית:
Ich bin Lior, משמעו: I am Lior.
Das ist gut, משמעו:  This is good.

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

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

ה Commit Graph בגיט, מורכב מה commits השונים (ריבועים בציור למעלה) – כל אחד כולל snapshot של כל הפרויקט ברגע מסוים, וחצים – המייצגים קשר בין commits. למשל: B הוא בן של A: מישהו עבד על A, שינה אותו ואז ביצע commit ל B.

Branch בגיט מוגדר כשרשרת כל parent commits של ה tip (קצה) של ה Branch. כלומר:

  • ה release Branch מכיל את A, B, C, E, G, H.
  • ה master Branch מכיל את A, B, C, D, F, I, J, K. כן, commits יכולים להיות חברים ביותר מ Branch אחד.
  • ה topic Branch מכיל את A, B, F, I, L, M.

בד"כ נעבוד על ה Tip של ה Branch ולא על commits שנמצאים בהיסטוריה שלו.
ה Branch הראשון שנוצר ב Git Repository נקרא master, אולם הוא לא שונה במאומה מכל מכל branch אחר שתיצרו מאוחר יותר – זה רק שם ברירת-מחדל.

גיט מקומי וגיט מרוחק

יש טעם לעבוד על גיט מקומי-בלבד, נאמר: כאשר שאר הצוות שלכם עובד על SVN ואתם רוצים Repository שלכם שישמור את השינויים שעשיתם כל 5 עד 30 דקות של עבודה.
אם אתם לא רגילים לגיט, לבצע commit כל כמה דקות נשמע מוזר – אבל זה עניין שמתרגלים אליו :).

כמובן, שהייעוד העיקרי של גיט הוא עבודה בצוות. גיט מאפשר לשתף עבודה בין Repositories:

פעולת ה "clone" תשכפל את ה Repository המרוחק ותייצר Repository מקומי זהה על המחשב שלכם. זהה = מכיל את כל הקבצים, כל ה Branches וכל ההיסטוריה מרגע יצירת הפרויקט.

פעולת Push דוחפת (commit(s מה Repository שלכם למרוחק ו Pull מביאה (commit(s מה Repository המרוחק אליכם.

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

לגיט יש עקומת למידה לא-קלה

שכבת ה Persistence של גיט

שכבת אכסון הנתונים של גיט ידועה בשם The Object Store או Object Database. את התוכן שלה ניתן למצוא בפועל תחת הספרייה git/objects.(בעקבות פעולת git clone נוצרת תיקייה נסתרת בשם "git." ב path בו התבצעה הפעולה).
ה Object Store מאכסן 4 סוגים של אובייקטים. הנה הקשרים הלוגיים ביניהם:

האובייקט הבולט ביותר הוא כמובן אובייקט ה Commit, המשמש כ Aggregator לתת העץ של כל הספריות והקבצים באותו ה Commit.
ה Commit מצביע לעץ (Tree) אחד, שהוא ה Root Directory של תתי-הספריות (עצים) וקבצים (BLOBs) שהם חלק מה Commit.

הערת צד: בוודאי מטרידה אתכם השאלה כיצד ענף (Branch) יכול להכיל משהו (Commit) שמצדו מכיל הרבה עצים (Trees) – הרי "ענף" הוא חלק מה"עץ". יש לכך 2 תשובות:

  1. Branch הוא ענף של ה "Commit Graph"', בעוד עץ (Tree) הוא ספרייה במערכת הקבצים של ה Commit הנוכחי.
  2. זו אכן טרמינולוגיה מבלבלת – היה עדיף לקרוא ל Tree פשוט Directory או Folder, על אף הקונוטציה הלא-רצויה למערכת ההפעלה חלונות (רחמנא ליצלן).

כל BLOB ב Object Store מייצג קובץ, יהיה זה קובץ קוד (למשל java.) או קובץ נתונים אחר (למשל תמונה בפורמט png.). תוכן הקובץ עובר פונקציית Hash בשם SHA-1 המייצרת מספר בן 160bit המתאר את תוכן הקובץ. פונקציית SHA-1 היא פונקציית hash בעלת פיזור אחיד למדי (מקורה בעולם האבטחה) – ואנו מניחים שהיא מזהה תוכן של קובץ באופן ייחודי. ב UI של גיט נראה את ה hash בייצוג הקסדצימלי, למשל:

12cfb1862b23356523d88127fa5d5aeb333950

לעתים נראה קיצור של ה hash בצורת 7 הספרות הראשונות שלו – לרוב זהו ייצוג ייחודי דיו בכדי לזהות אובייקט בצורה אמינה (וגם: קל הרבה יותר להקלדה).
גיט מזהה כל קובץ ע"פ התוכן שלו (לא ע"פ שם הקובץ, סיומת או תאריך) – כלומר, ע"פ ה hash שלו. זאת אומרת ש Tree בעצם מכיל רשימת hashes של קבצים, המתארת את הקבצים בספרייה, ומייצא בעצמו hash של ערכים אלו. ה Commit מקבל ה hash המחושב מה hash של תיקיית ה root של הפרויקט + כמה שדות.

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

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

באופן דומה, העצים הם hash של כל ה hashes של העצים / קבצים שהם מכילים. אם כל הקבצים בתת-עץ לא השתנו, אזי אובייקט ה Tree שמייצג אותם יישאר עם אותו ה hash וכן הלאה.
זיהוי אובייקטים ע"פ hash מאפשר לגיט לבצע השוואות מהירות בין אובייקטים ובין תתי-עצים של אובייקטים: פשוט יש להשוות בין שני hashes.

כמה properties חשובים על אובייקט ה Commit:


commit message

הערת טקסט המסבירה מה כולל ה commit.
parents
מצביעים ל commits אחרים, קשר המגדיר את ה Commit Graph.
  • אם ל commit יש יותר מהורה אחד (ניתן 3 או יותר) – אזי זהו merge בין ענפים (branches).
  • אם ל commit אין parents, אזי ה commit הזה הוא ה Root Commit (ראשית ה Repository) או orphan commit – מין אופציה מתקדמת בגיט לייצר branch חדש עם קוד שלא קשור לענפים הקיימים ב Repository.


committer vs. author

לרוב ה committer וה author יהיה אותו אדם, מלבד כמה מקרים בהם מישהו מבצע commit מחודש לקוד שמישהו אחר עשה לו commit בעבר.
דוגמה אחת לכך היא cherry-pick, היכולת לקחת שינויים של commit ולהעתיק רק אותם (כלומר: את השינויים) ל branch אחר. יכולת זו היא רבת-עוצמה כאשר רוצים להעביר בין ענפים תיקוני-באגים. יכולות אחרות הן filter-branch ("שכתוב היסטוריה") ו rebase (חיתוך ענף, והדבקה שלו במקום אחר). במקרים אלו יהיה ניתן להבחין בין האדם שביצע את הפעולה (committer) לבין ה committer של הקוד המקורי (author).

ה Index

תהליך העדכון קבצים ל Git Repository מתבצע בשני שלבים:

  1. עדכון ה Index (נקרא גם "Staging Area" או "Directory Cache") בעדכון.
  2. ביצוע העדכונים מתוך ה Index ל Repository.
תהליך דו-שלבי זה נוצר בכדי לאפשר למתכנת לבצע Review על השינויים שלו ולשלוט מה בדיוק נכנס ל Repository.

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

האינדקס הוא מבנה נתונים נוסף של גיט, שמנותק ממצב הקבצים במערכת הקבצים (נקרא בעולם הגיט "Working tree)" ו/או מה Commit האחרון. הוא פשוט מילון של pathnames ו hashes של אובייקטים ב Object Store, כלומר .

כאשר אנו מבצעים פעולת git status (או git status –short), או רואים השוואה בין התיקייה המקומית והאינדקס.
בשלב הראשון גיט יציג לנו קבצים שהשתנו במערכת הקבצים, אבל לא מנוהלים ("tracking") ע"י האינדקס:

הערה: git stat הוא alias שיצרתי לפקודה git status –short. לא נראה לי הגיוני שהגרסה הקצרה של הפקודה, ארוכה יותר להקלדה מהגרסה הארוכה של הפקודה. לכן יצרתי ה alias באופן הבא:

git config –global alias.stat "status –short"

ברגע שנבצע פעולת git add לקבצים – הם ייווספו לאינדקס:

  • ה pathname יזכור איזה קובץ במעקב.
  • תוכן הקובץ, כפי שהוא באותו הרגע, ישמר כ BLOB ב Object Store וה hash של אותו BLOB יישמר באינדקס.
הנה:

כעת אני רואה שהקובץ התווסף לאינדקס (עמודה ירוקה, A הוא קיצור של Add).
כדי לראות אלו קבצים נמצאים באינדקס – פשוט הקלידו git ls-files.

כמה דקות לאחר מכן אני מבצע git stat ומקבל את המצב הבא:

מה זה המצב הדו-פרצופי הזה – מישהו יודע?
הקובץ השתנה או התווסף? מדוע אדום וירוק יחדיו?

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

אם נבצע git commit בשלב זה, העותק שהוספנו קודם לכן לאינדקס (ונשמר ב Object Store) יכנס ל Repository, בעוד השינויים שביצעו לאחר מכן – יישארו במערכת הקבצים.
לאחר ה commit, פעולת Git Stat תראה רק את השינוי שבדיסק (הקובץ יוותר במעקב):

עוד טיפ קטן:

  • פעולת <git rm <filename מסירה קובץ מהאינדקס (כלומר – הוא לא יהיה במעקב) ומהדיסק: זו הדרך להעיף קובץ שאינכם מעוניינים בו מהפרויקט. במילים אחרות: הוסף קובץ להסרה – לאינדקס. בכלל ש rm נשמע ההיפך מ add – זו דרך מצוינת להתבלבל.
  • לצורך הגנה, אם ניסיתם לבצע git rm על קובץ שנעשו בו שינויים – גיט יסרב ויציע לכם להוסיף cached– .לא פעם הקשבתי לעצתו בשמחה – ומחקתי לעצמי כמה שינויים.
  • פעולת <git reset <filename רק מסירה את הקובץ מהאינדקס – זו בעצם הפעולה ההופכית ל git add.
פתרון אפשרי לבלבול, יכול להיות להגדיר alias בשם remove, שיהיה ההופכי ל add:
git config –global alias.remove "reset"

לי זה עזר.

אם אתם רוצים לבצע היפוך ("revert" בשפת perforce) לשינוי בקובץ, עליכם פשוט לבצע <git checkout <filename. כפי שהזהרתי בהקדמה על השפה הגרמנית – פעולת git revert עושה משהו לגמרי אחר (והרסני).

ענפים

הפוסט מתארך, ולכן אסיים בנושא אחרון חשוב לפוסט זה: Branches.

מהו Branch?
כבר אמרנו ש Branch מוגדר כאוסף ה commits שניתן להגיע אליהם מה tip (הקצה) של ה Branch.
הגדרה נוספת ומשלימה הוא ש Branch הוא מצביע ל commit, ומנקודה זו מוגדר ה branch.

גיט מחזיק במבנה נתונים נוסף: refs (קיצור של References, נקרא גם Pointer לפעמים). יש בגיט 2 סוגי מצביעים:

  • מצביע ישיר, המצביע לאובייקט ב Object Store (לרוב זה יהיה commit או tag).
  • מצביע סימבולי (symbolic ref) המצביע למצביע אחר. משהו כמו symbolic link במערכת ההפעלה.
גיט משתמש במצביעים כדי לתת שמות / לסמן דברים, ובמקרה שלנו – branches. כל אחד מה branches הוא פשוט מצביע שכזה.
לעתים יווצר מצב בו ה Repository המרוחק ("origin") יכיל branches שלא קיימים אצלנו. פקודת git pull מייבאת את ה commits – אך לא את המצביעים. פקודת git fetch מייבאת את המצביעים מה Repository המרוחק, מה שיראה אצלנו כ branches ו tags.
מצביע מיוחד הוא מצביע ה HEAD המסמן את המיקום הנוכחי שלנו ב Commit Graph. כאשר אנו מחליפים branch לעבודה ע"י פקודת <git checkout <branch name אנו מחליפים את HEAD להצביע על commit ו/או branch אחר.
ייתכן מצב בו HEAD לא יצביע על tip של branch אלא על commit אחר בגרף. מצב זה יתרחש, למשל, בעקבות ביצוע פקודת <git checkout <commit's hash. לאחר פעולה זו גיט יספק הזהרה שאתם במצב של detached HEAD – כלומר HEAD לא מצביע על ה tip commit – ולכן אינו מצביע על branch.
מצב זה, למרות שנראה מוזר, הוא תקין וניתן לבצע בו commits – מה שיוביל אותנו למצב הבא:

commit E הוא לא חלק מה branch. ניתן לפתור מצב זה ע"י יצירת branch חדש מהנקודה הנוכחית ע"י
פקודת <git checkout -b <new branch name ואולי אח"כ לבצע merge בין ה branch החדש ל dev. אם לאחר זמן ממושך לא "תטפלו" ב commits שאינם שייכים ל branch כלשהו – גיט ינקה אותם בתהליך "ניקוי הזבל" שלו.

סיכום

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

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

—–

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

GitGuys – מקור טוב להבנת המבנה של גיט:
http://www.gitguys.com/topics/

Git cheat sheet – רפרנס ויזואלי של הפקודות הנפוצות וההשפעות שלהן:
http://www.ndpsoftware.com/git-cheatsheet.html#loc=remote_repo;

UnGit – כלי ויזואלי שעוזר ללמוד גיט:
https://github.com/FredrikNoren/ungit

Learn Git Branching – אתר טוב שמציע "משחק" שיסייע לכם לעקוב אחר branching מתקדם בגיט – נחמד מאוד.
http://pcottle.github.io/learnGitBranching/

כלי UI טוב מהרגיל לגיט (שומר על השפה של גיט) – SourceTree:

"A shout out to developers of SourceTree – a nice GUI for git and hg. Useful even for a command-line fan like me. " — Martin Fowler

לינק מעניין: לינוס מציג את גיט ב 2007.

אוסף נחמד של מדריכי וידאו לגיט: http://training.github.com/resources/videos/