על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019?

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

בתכנות ישנן כמה פרדיגמות תכנות מקובלות. הפרדיגמות המשפיעות ביותר כיום הן פרדיגמת ה Objection-Orientation (בקיצור: OO) ו Functional Programming (תכנות פונקציונלי) – אך גם יש אחרות מוכרות יותר ופחות.

פורסם מעט אחרי שסיימתי לכתוב את הסדרה

באיזה פרדיגמה אתם מאמינים? עם איזו פרדיגמה אתם עובדים?

עבור הרוב המוחץ של אנשי-התוכנה, התשובה היא זו:

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

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

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

מה עדיף?

איזו פרדיגמה היא הטובה ביותר?

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

אם אנחנו הולכים לכתוב קוד קצר בן כמה עשרות שורות קוד – שימוש ב OO, או תכנות פונקציונלי, או Reactive Programming – יהיה סיבוך ובזבוז זמן!

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

לשימוש בפרדיגמות מתקדמות – יש מחירים:

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

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

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

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

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

הדברים מסתבכים

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

הדיונים על נכונות השימוש בפרדיגמה הם כמובן חשובים: לא כל פרדיגמה מורכבת יותר – היא אכן טובה יותר. לאימוץ פרדיגמות יש מחיר ניכר, ולא כל פרדיגמה ש"תפסה" בארגון X – תצליח בהכרח גם בארגון Y. למשל: ב C עדיין משתמשים במשפטי goto – בעיקר בשם היעילות הגבוהה (קרבה למעבד).
בקרנל של לינוקס, למשל, יש כ 13K משפטי goto (בדקתי הרגע).

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

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

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

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

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

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

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

הורשה מרובה, למשל – היא לא בהכרח דבר רע, וב 90% מהפעמים – אפשר להשתמש בה בצורה נשלטת. אני זוכר שבעבודה עם ++C היו שטענו שכל הפחד מהורשה-מרובה – הוא מוגזם.
10% מהפעמים שבהם היא יצאה משליטה – היו מספיק קשות — שהתעשייה (!) באופן גורף החליטה לזנוח את האפשרות הזו.

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

גם היום, עדיין יש בשפות, וב Frameworks יכולות בעייתיות: שמדי פעם מקצרות את הקוד – אבל בפעמים אחרות הן זרז לבעיה. מדוע חשיש יצא מחוץ לחוק ואלכוהול לא? – קשה לומר.

חשוב לזהות אלמנטים במערכת שגורמים לבעיות רבות מדי – ולחסום אותם. אני בטוח שיהיו מתנגדים, אבל זכרו שמערכות לא הופכות עם הזמן לפשוטות יותר, או קלות יותר לתחזוקה. הסתמכות על משמעת ודיוק של כל וכל מהנדס במערכת – היא מתכון לכישלון.
על זה נאמר: "It must be simple – or it simply won't be"

?Monty Python: What have the Romans ever done for us
(זכורה לי טענה שזה במקור דיון שהופיע בתלמוד — אבל לא מצאתי מקורות המאשרים זאת)

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

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

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

למשל: המצאת ה"בלוק". בתחילה – לא היה דבר כזה.

למשל: משפטי if היו יכולים באותה התקופה להפעיל רק ביטוי (expression) יחיד. לו היו 4 פעולות שלא היינו רוצים שיפעלו אם x הוא שלילי – היה עלינו לכתוב 4 משפטי if שכל אחד חוזר על הבדיקה "האם x שלילי" ואז מבצע עוד פעולה.

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

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

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

סיזיפי? סיזיפי אך אפשרי? – כך נראתה החלוציות בתחום התוכנה…

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

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

ארצה להדגיש שני עקרונות שצצו מהגישה הזו ושווים דיון, גם ממרום שנת 2019:

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

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

בצורה כוללנית יותר, הרעיון אומר שננסה לצמצם את ההיכרויות במערכת – למינימום האפשרי.
כל אלמנט שאנו מגדירים (משתנה, מחלקה, וכו') – נגדיר ב scope המצומצם ביותר שאפשר.
למשל: אם יש Enum שזקוקים לו רק במחלקה מסוימת – סגרו אותו ב scope של המחלקה – ואל תחשפו אותו לשאר העולם.
היום, בעולם ה IDE המתקדם – המשמעות המידית של scoping היא מה יציע לנו ה IDE ב Auto-complete. אם נגדיר הכל ב Scope הגלובלי – כל הקלדה תציע לנו את כל מה שיש.
אם נקפיד על הגדרת ישויות ב scopes מצומצמים ככל הניתן – הצעות ה Auto-complete יהפכו לממוקדות, והרבה יותר רלוונטיות.שנים רבות אחרי שהוגדר, העיקרון הזה עדיין לא תמיד מופנם ומקובל. עדיין אנשים מגדירים אלמנטים ב scopes רחבים מהנדרש מחוסר מודעות, או מתוך מחשבה שזו "הכנה למזגן": אולי מישהו יצטרך את זה בעתיד? "למה לא לחסוך ולשים אותו זמין – כבר עכשיו"

מתלבטים מה עדיף?
– אני לא.
שווה לציין ש Scoping הוא צעד ראשון בכיוון של Information hiding ו Encapsulation. שוב ושוב עלו, לאורך ההיסטוריה – רעיונות בכיוון הזה.

Single Exit Principle

בכדי להגדיר מהו מבנה צפוי ומנוהל של רצף הרצת התוכנה, ולהפסיק "לקפוץ לשורה אחרת בקוד", הגדירו בפרדיגמת התכנות המובנה את "עיקרון היציאה האחידה" (מפונקציה). שם נוסף:"Single Entry, Single Exit". לכל בלוק קוד (=פונקציה) צריך להיות רק מקום אחד להיכנס דרכו – ורק מקום אחד שניתן לצאת ממנו.

העיקרון הזה הוא מובנה בשפות שאנו עובדים איתן – עם כמה חריגות. קשה מאוד להסתדר ב 100% מהמקרים עם Single Exit יחיד.
נסו לרגע לחשוב: האם אתם מצליחים לחשוב היכן בשפה שלכם מפרים את העיקרון?

הנה דוגמאות עיקריות:
  • Exceptions שנזרקים – הם לא Single Exit.
  • continue או break בלולאה ל Label (בג'אווה, למשל) – הם לא Single Exit.
  • אפשר גם לטעון ש break ו continue באופן כללי – הם לא Single Exit, לא ברמת הבלוק.
    • בשפות פונקציונליות – לעתים אין להם מקבילה.
  • coroutines (על עוד לא ממתינים באותו הבלוק לסיומם) – הם גם לא Single Exit. הפעלנו קוד – שרק בונה-עולם יודע מתי בדיוק הוא יסתיים.
  • אחרון וחביב: יש גם מי שרואים בקריאת return מתוך גוף הפונקציה – הפרה של עקרון ה Single Exit. אמנם נקודת היציאה נתונה וקבועה – אך דילגנו על קוד – להגיע אליה. הטענה העיקרית – היא שזה לא צפוי. אני מצפה שהפונקציה תגיע תמיד לשורתה האחרונה.


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

סיכום

שוב לא הצלחתי לסיים את הפוסט ב 400 מילים.

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

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

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

—-

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

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

מודל אנמי (Anemic Domain Model) – דפוס עיצוב שלילי

מודל אנמי (Anemic Domain Model, או בקיצור ADM) הוא תיאור שמתייחס למודל "מוחלש" בצורה שאתאר מיד.הצורה הנפוצה והפשוטה של מודל אנמי היא מצב בו קיים פער או מרחק בין ה state – הנשמר במחלקה אחת, לפעולות – הנשמרות במחלקה אחרת.

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

באופן טיפוסי המחלקה בעלת המתודות תיקרא בשמות כגון xxxManager, xxxService, xxxHandler או xxxUtils – שמות המעידים על קשר ברור, אך אחריות כללית שקשה להגדירה.

כאשר אנחנו מזהים מודל אנמי, הדבר הראשון שמציק לנו הוא ש "זוהי איננה גישת Object Oriented: הרי מחלקות צריכות להכיל גם את ה state וגם את הפעולות על ה state – באותו המקום".

בצורה יותר פורמאלית ניתן לומר שיש לנו שתי מחלקות בעלות high cohesion (קרבה רעיונית גדולה) אבל גם high coupling (תלות חזקה). מדוע אם כן אלו שתי מחלקות, ולא מחלקה אחת?!

מה הבעיה לחבר ביניהן?

ובכן – לכו ובדקו.

בד"כ זה לא עניין של קובץ גדול מדי (כמה גדול יכול להיות ה state?!). סיכויים טובים שתגלו ש:

  • אובייקט ה Order הוא Data Access Object (מכיל ידע על שמירה לבסיס הנתונים) או Data Transfer Object (מכיר ידע על serialization בכדי לעבור על גבי הרשת) – קוד שמרגיש לא נכון לערבב אותו עם "Business Logic", או
  • יש יותר ממחלקה אחת המבצעת פעולות על Order. למשל:
דפוס שכדאי לשים לב אליו הוא מתודות ב Service/Manager/Handler המקבלות את האובייקט כולו לצורך פעולות של שינוי state. משהו בנוסח:
updateItemPrices(Order o) // updates the order with up-to-date item prices

מתודה כאלו נקראות external methods.

מקור

"אחי, מה כואב לך?"

מה אכפת לנו, בעצם?

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

אז מה זה משנה שיש לנו Anemic Domain Model (בקיצור: ADM)?

נתחיל בדוגמה ממקום קצת אחר.

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

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

מה פתאום!!!

בסיטואציה כזו, הקונצנזוס הוא גורף ומיידי!

  • "ומה אם נרצה להחליף את מערכת צד השלישי לספק אחר?"
  • "ומה אם נרצה להוסיף פונקציונליות (למשל throttling או logging) גורפת לכל העולות מול אותו ספק?"

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

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

  • מה יקרה אם נרצה לשנות את הייצוג של ה state של Order / לבצע refactoring?
  • מה יקרה אם נרצה להוסיף פונקציונליות (אימות נתונים, או Logging) גורפת על ה state הזה?
הסיטואציות הן שקולות.
יתרה מכך: שינוי state באובייקט ליבה של הדומיין הוא נפוץ יותר מהחלפה של ספק חיצוני.
אני מניח שהסיבה שאיננו מוכנים לקבל את הסיטואציה בתצורה אחת, בעוד אנחנו מוכנים לקבל את אותה הסיטואציה (וחמורה יותר) בתוצרה שונה – היא פשוט עוד הטיה אנושית, לא רציונלית.
בטבע פותרים אנמיה בעזרת תזונה טובה יותר.
בתוכנה פותרים אנמיה בעזרת הכמסה ו/או Rich Domain Model.

מה הבעיה עם ADM? – ניתוח פורמאלי

הבעיה העיקרית של ADM היא המחסור בהכמסה (Encapsulation).

האובייקט Order לא יכול "לשלוט" במה שקורה ל properties שלו – כי "כל העולם נוגע ב properties בלי לשאול אותו". כלומר: אין מתודות שליטה בגישה ל properties – בהן אפשר לבצע validations או לקבוע התנהגויות.

כתוצאה מכך:

  • יווצר קוד כפול שבודק את תקינות ה properties של Order – באופן אקראי במחלקות A, B, C.
  • קוד הבדיקה, ותפעול (שינוי הערכים) של Order properties, במחלקות A, B, C – לא יהיה עקבי, וייתכן שיכיל חוסר-התאמות ואף סתירות.
  • מכאן יווצרו באגים, שיאטו את הפיתוח בשני אופנים:
    • הצורך לתקן את הבאגים – שקשה למנוע את היווצרותם.
    • הפחד מלבצע שינויים במערכת (המחשבה ש״שינוי״ יגרום ל״באג״) -> הקוד פחות מתחדש, פחות מתעדכן לצרכים העדכניים מהמערכת -> יותר עבודה להכניס שינויים.
  • הבעיה תלך ותחמיר ככל שהמערכת תגדל: יותר קוד (A עד F שעובדים עם Order, במקום A עד C), ויותר מפתחים שעובדים על ה codebase.
    • הבאגים שיווצרו יהיו גם הם – קשים יותר לאיתור.
  • עם הזמן הערכת הזמנים לשינויים בקוד הופכת להיות פחות אמינה: שיניתי ב A את הדברים, אבל רק לקראת סיום גיליתי שב C יש מנגנון שלם מסביב ל state שנגעתי בו – וצריך להמשיך בפיתוח בלתי-מתוכנן.
  • זו לא בעיה מסוג הבעיות ש״מתפוצצות״, אלא מסוג הבעיות שהן רגרגסיות הדרגתיות שלא מבחינים בהן בזמן שהן מתרחשות.
אני לא יודע כיצד להדגיש מספיק עד כמה הבעיה הזו הרסנית למערכת, במיוחד אם מדובר בקוד בליבת המערכת (core domain model), ובמיוחד כאשר מדובר במערכת בעלת חוקים עסקיים מורכבים.

מצב לא תקין שחלחל. גורם לקושי לאתר את מקור התקלה – ולצורך בתיקונים מורכבים יותר. הסתכלו על ה flow הבא:

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

הנטיה הטבעית, קצרת הטווח, היא להוסיף הגנות נוספות ב Class W, ולאחר שגילינו שדברים הסתבכו – עוד בדיקות ב Class Y. התוצאה: כפילות קוד, חוסר קונסיסטניות, ואי פתרון שורשי של הבעיה!

תופעת לוואי נוספות הן בדיקות פחות יעילות: מכיוון שקוד אימות ה state נמצא ״עמוק״ ב branching של הקוד – סביר יותר שהבדיקות יריצו תסריטים בעייתים מבלי שהמצב יתגלה:

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

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

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

אני רוצה לחדד פעם נוספת את הבעיה שביכולת לבצע שינויים ב state ממקומות שונים, ומדוע בעיות של data integrity  (להלן ״שלמות נתונים״) גורמות להאצה וסיבוך של באגים בקוד.

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

הנה דוגמה:

יש המון וריאציות של משולשים תקינים, אך לא כולם כאלו.

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

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

הנה שיפור, עם יותר constraints, ויותר ערכים מחושבים (derived values):

כאן כבר לא נוכל לקבוע משלוש עם סך זוויות פנימיות של 190 מעלות, או ארבע זוויות פנימיות. זהו שיפור.

אבל עדיין נוכל לקבוע:

  • זווית של 0 מעלות
  • זווית של 200 מעלות, ולגרור מכך זוויות עם ערכים שליליים של זוויות
  • 3 זוויות שמסתכמות ל 180 מעלות, ושלוש צלעות סבירות לכאורה – שהן עדיין לא משולש הגיוני.
שימוש ב derived values הוא כלי רב-עוצמה לאימות נתונים פשוט ויעיל, אבל לא תמיד פשוט ו/או נכון לגזור נתון מנתונים אחרים – זה יכול לסבך את הקוד יתר על המידה.
המגבלה שאיני יכול לקבוע את הזווית השלישית בצורה ישירה – יכולה בקלות לסרבל את הקוד שמתשמש ב Triangle.
המשולש הוא מטאפורה, לאובייקט עסקי מורכב.
ואובייקט עסקי מורכב הוא כזה שקל לייצר ב state שלו מצבים בלתי-נכונים.
מכיוון שאין כוונת זדון, לא סביר באמת שמישהו יקבע זווית פנימית של 3600000-.
מצד שני יש אינספור דוגמאות, שקשה מאוד לצפות, למצבים לא תקינים, אך אפשריים.
הנה דוגמה לאובייקט משולש עם אימות נתונים קפדני יותר:

הקוד לא נבדק. זהו pseudo code שנכתב בזריזות
עדכון: תודה לעידו שהעיר שהקוד הזה לא ישים. יש לאפשר מתודה set המקבלת את שלושת הזווית / צלעות – כי כמעט כל שינוי במשולש כך שיישר תקין מצריך לפחות זוג של שינויים. אני מניח שעדיין אפשר להבין את העיקרון.
הערה נוספת: שווה להוסיף בדיקה ל value גדול מ 0.

אימות הנתונים הקפדני שווה רק כל עוד יש הכמסה על ה state.

אם מסירים את ה private מהשדות הפנימיים – כל האימות הופך לפרוץ עד חסר-משמעות.

מודל אנמי – שלב 2

עוד בעיה שמתרחשת עם מודל אנמי היא טרנזיטיביות של התלויות.

נניח שיש לנו מודל מורכב יותר (composite) של הזמנה – ופריט בהזמנה (להלן LineItem). למשל: ההזמנה היא הזמנה מהסופר, וה LineItems הם קורנפלקס, חמאת בוטנים, ולחם אחיד.

האם נכון שה Service Classes (או אובייקטים אחרים) המכירים את Order, יכירו גם את LineItem ואם כן – באיזו מידה?

הערה חשובה: מדובר על מצב בו בין Order ו LineItem יש קשר של composition (ב UML: מעוין שחור / "כתף מלאה"), כלומר: לאובייקט LineItem אין קיום או משמעות ללא אובייקט Order.

אם מחלקת שירות יכולה לשנות ערכים מ LineItem מבלי שה Order יידע או יוכל להגיב – מדובר בהפרת ההכמסה.
אם, גרוע יותר, מחלקת שירות יכולה לכתוב ולקרוא LineItem ישירות מה DB מבלי שה Order יהיה מעורב – מדובר בהפרת הכמסה.

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

הסכנה אם כן היא שמחלקת שירות תבצע שינויים על LineItem כלשהו, ותיצור:

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

מה עושים?
בד"כ בעזרת עבודה עם Rich Domain Model (להן RDM).
אובייקט שהוא Composite (נקרא גם Aggregate) הוא בעל האחריות לגישה לאובייקטים הכפופים שלו.

פתרון מקובל הוא לחשוף interface המכיל גישה read-only (ומגובלת מעבר לכך ע"פ הצורך) לאובייקט הבן.
אובייקט ה Order יחזיר LineItemInfo ע"פ הצורך במתודות שלו.
בכדי לבצע שינוי ב LineItem, יש לקרוא ל Order, עם אובייקט ה LineItemInfo כפרמטר: "זה? תן לו 2 בכמות", או לסירוגין "כל האובייקטים שנוצרו אתמול – בטל אותם".

אופי האינטרקציה הזה מאפשר ל Order לוודא ש "2 בכמות" הוא מצב תקין, שלא מפר אף כלל עסקי.

כלל אצבע שימושי הוא שאסור לאפשר למחלקת השירות – לגשת ל id (מזהה ייחודי) של אובייקט ה LineItem.

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

אם מחלקת השירות לא יכולה להגיע ל Id – אז היא לא יכולה להתדרדר לעוולות הללו.

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

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

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

קונטרה

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

Rich Domain Model הוא Overhead מיותר. כתיבה של קוד מיותר.

זה נכון: Anemic Model היא גישה חסכונית יותר בכתיבת קוד. אפשר לפתח איתה מהר יותר – עד גודל מסוים של קוד. גישה זו נקראת גם transaction script.

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

גם ניהול ה state במשתנים גלובאליים יקצר את זמני הפיתוח.
לחלוקת המשתנים ל scopes, יש תקורה – אך גם תועלת.

אם הקדמתם סטארט-אפ, ולאחר שנה אתם מסתכלים על הקוד והוא נראה טוב – כנראה שעשיתם over-engineering.
בכל זאת, בשלב מסוים – חשוב לעשות את המעבר ולכתוב קוד robust יותר.

אם המערכת היא מערכת CRUD, שעיקר עיסוקה הוא לשלוף נתונים, לקבץ אותם ולהעביר לצד הלקוח – יש מצב ש RDM אינו מצדיק את התקורה הנלווית.

בואו נעשה RDM בצורה עצלה: נתחיל לכתוב קוד כ ADM, וע"פ הצורך – נעבור ל RDM.

לכאורה זהו טיעון הגיוני מאוד. מאוד אג'ייל.
התשובה שלי היא כזו:

  • ככל שהאובייקט גדל, עלות ההמרה ADM->RDM תגדל גם כן. זה יכול להגיע לשעות, ואולי אפילו ימים.
  • עלות שכזו, באופן מעשי, היא חסם ממשי לביצוע המעבר. למשל:
    • אני עובד על פיצ'ר ואז מבין שהוספתי שדה שיכול להיות חלק מ state לא תקין.
    • מה יותר טבעי? להעלים עין ולתת למפתח הבא לבצע מעבר ל RDM, או להשקיע עוד 3 שעות בלתי צפויות לקראת סיום כתיבת הפיצ'ר?
  • בקיצור: לאנשים מאוד ממושמעים, זה יכול לעבוד. כישראלים – עדיף להשקיע את עלות ה RDM בעוד האובייקט קטן, כל עוד זוהי "מדרגה" קטנה ולא כ"כ מורגשת. נוסיף בכך עלות קטנה לפיתוח – אך עלות שתשתלם ככל שהמערכת תגדל ותסתבך.
העיקרון הפתוח סגור (OCP) מחזק את גישת ה ADM. נראה לי ש ADM הוא יותר SOLID מ RDM, לא?
למשל: למה לחזור ולשנות את Order שוב ושוב ולהסתכן בשבירת קוד? יותר אמין לכתוב קוד חדש ב Class A, ולאחר מכן ב Class B, וכו' – וכך להיות "open for extension, but closed for modification"!על זה אני עונה:

  • ה OCP הוא עקרון שנוי-במחלוקת. הוא מבוסס על אמיתה נכונה, אך יש בו חור גדול: חסרה בו הנחיה מתי יישום OCP הוא טוב, ומתי הוא גורם לנזק.
    • כן – הוא בהחלט עלול לגרום לנזק.
  • core domain model הוא בדיוק המקום שאתם רוצים לשנות קוד שוב ושוב, תחת הסיכון ליצירת רגרסיה בהתנהגות.
    • אחרת מה? הקוד שלכם יתפזר לכל עבר?
    • איך באמת נראה לכם שזה יהיה יותר SOLID?!
ADM הוא בעצם תכנות פונקציונלי (Functional Programming). תכנות פונקציונלי הוא בטוח דבר טוב, והפרדה בין נתונים לפעולה – היא בעצם Best Practice.
  • יש פיל שעיר, מדיף ריחות עזים, ורועש במרכז החדר – שמשנה את התמונה מקצה לקצה. בואו לא נתעלם ממנו.
    אם לא הבחנתם בה – זוהי תכונת ה Immutability.
  • בתכנות פונקציונלי ה Service Classes לא מסוגלים להגיע ל state לא תקין, כי הם לא יכולים לשנות state של אובייקט שהוא immutable.
  • בנוסף, גם בתכנות פונקציונלי טוב שאובייקט המתאר state יבצע בעצמו את כל האימותים. מקום אחד + אחריות מלאה.
  • להזכיר: כל רעיון טוב ניתן לממש בצורה שגויה. Functional Programming – הוא לא יוצא דופן.

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

סיכום

Anemic Domain Model, או בקיצור ADM, הוא דפוס שלילי (Anti-Pattern) של מבנה שעלול לגרום לנזק מתמשך והרסני למערכת שלכם, בצד הארכיטקטורה.

כמו כל כלל, יש גם לו יוצאים מן הכלל. כשהמערכת בחיתוליה ו/או כאשר יש אפליקציית CRUD [א] – RDM עשויה ליצור עלות נוספת ולא-משתלמת.

בסה"כ, אם אתם בעולם של Business Software, תוכנה המבטאת ומיישמת כללים עסקיים מורכבים – Rich Domain Model הוא כנראה הבסיס לכתיבת קוד יעיל לצמיחה ולתחזוקה.  No way around it.

נ.ב – ברשת ניתן למצוא רפרנסים ל RDM שגם אחראים על גישה לבסיס הנתונים / Persistence.

זה לא באמת RDM, אלא דפוס עיצוב אחר בשם Active Record. יש לו יתרונות, וחסרונות – אבל זה כבר דיון אחר.

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

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

כמובן שגם ב Bliki יש רשומה על ADM. איך לא?

Anemic vs. Rich Domain Objects—Finding the Balance
כתבה שביסודה שגיאהThe Anaemic Domain Model is no Anti-Pattern, it’s a SOLID design, אך שגיאה נפוצה.

[א] למי שלא מכיר crud = "קקי" באמריקאית. אני משוכנע שראשי התיבות לא יצא סדר האותיות הזה במקרה.

כללי התיכנותיקה – 19 כללים של הנדסת תוכנה שלא ניתן להתחמק מהם

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

מכיוון שאין להם שם מוכר, נתתי להם שם: \"כללי התיכנותיקה\" (הלחם לשוני של \"תכנות\" ו\"פיסיקה\").

מקור: http://potluckcomics.com/links-laws-of-physics

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

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

הנה הם לפניכם:



Conway’s Law ארגוני ארכיטקטורה
\"מבנה התוכנה והקשרים בין חלקיה, סופם שיהיו שיקוף של המבנה הארגוני של הארגון בה היא מפותחת\" (רפרנס)
הסבר: המבנה הארגוני הוא כוח חזק מאין כמוהו, שלא ניתן להתעלם ממנו. למשל: אם אתם מתכננים קומפיילר בין 4 שלבים, אך יש לכם 3 צוותי פיתוח – סביר שבסופו של דבר אחד השלבים יהיה אנמי, ובפועל יהיה לכם קומפיילר עם 3 שלבים. הפתרון – להכיר במציאות: או שתתכננו קומפיילר בין 3 שלבים, או שתארגנו את האנשים ב 4 צוותים.


עיקרון פארטו כללי
\"במקרים רבים, 80% מההשלכות (effects) נובעות מ 20% מהגורמים הפעילים (causes)\"
רקע: ידוע גם בשם \"כלל 80-20\", והוא כלל שנוסח על ידי הכלכלן האיטלקי וילפרדו פארטו, לאחר שראה את פיזור העושר באיטליה במאה ה-19 (כיום, פיזור העושר הוא קיצוני הרבה יותר, ואולי לא משקף כבר \"התפלגות טבעית של סיבה ותוצאה\").
וריאציה א: מוצר
\"80% המערך של מוצר, ניתן להשיג ב 20% מההשקעה\"
וריאציה ב: קוד
\"80% מהבאגים המשמעותיים, נובעים מ 20% מאזורי הקוד\"
וריאציה ג: ארגוני
\"כ 66% מהעשייה המשמעותית, נעשית על ידי כ 33% מהמתכנתים\", מה שמתבטא גם ב:




כלל הכישרון ארגוני טכנולוגיה
\"האלמנט שמנבא בצורה הטובה ביותר הצלחה או כישלון של תוכנה הוא לא הטכנולוגיה, שפת התכנות, או ה Framework – אלא המתכנתים שכותבים את התוכנה\"



עקרון הדורות המקוצרים (short generations) כללי
\"כל עשור יהיה על איש התוכנה ללמוד 50% מהמקצוע מחדש\"
וריאציה: \"כל עשור, 100% מהטכנולוגיות שבשימוש – יתחלפו\".
מי שלא מוכן / מבין את ההשקעה הנדרשת בכדי להישאר במקצוע – ייפלט מהתחום במשבר הראשון לאחר מעבר של \"דור\".


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


עקרון ה Not Invented Here (בקיצור: NIH) קוד הטיה אנושית
\"הדשא של השכן ירוק יותר, אך הקוד של השכן הוא גרוע, לא מובן, ולעתים פשוט מטופש\"

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


העיקרון הביולוגי של הקוד (הדוד בוב) קוד

\"Code Rots\"
כלומר: קוד שלא עובר חידוש (refactoring) – הולך ו\"נרקב\" ואיכותו פוחתת. השקעה רק בפיצ\'רים, ללא השקעה בחידוש הקוד תביא את הקוד למצב שאיננו ניתן יותר לתחזוקה, ויש לכתוב אותו מחדש. Guideline מקובל לאורך החיים של תוכנה עד לשלב בו יש לכתוב אותה מחדש (אם לא בוצעו פעולות refactoring, \"הזרמת חמצן\") הוא 3 עד 5 שנים.
היבט נוסף הוא העיקרון ש\"קוד שלא נקרא כחצי שנה – הופך לקוד זר ולא מוכר\" (ידוע כ Eagleson\'s Law). רמת הקריאות של הקוד, ועוצמת המסרים והעקרונות המשתקפים מהקוד – משפיעים רבות על קצב ה\"ריקבון\" של הקוד.


Lubarsky\'s law of Cybernetic Entomology קוד
\"תמיד יש עוד באג אחד במערכת\"
למשל: לאחר 11 שנה של שימוש המוני, נתגלה באג באלגוריתם החיפוש הבינארי של ספריית Java הסטנדרטית. (באג מאוד פינתי, כמובן).


לחלופין: Linus’s Law – הופרך כמיתוס
\"Given enough eyeballs, all bugs are shallow\".
כלומר: בהינתן מספיק עיניים בוחנות – כל הבאגים יימצאו.
הכלל נקרא על שם לינוס טורבאלדס, יוצר הלינוקס, מכיוון שהוא מבטא סוג של הנחה סמויה של קהילת הקוד הפתוח. הכלל לא הוגדר ע\"י לינוס, אלא על ידי אדם אחר, אריק ריימונד.

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



ללא שם (Michael Nygard) פרודשיין
\"משתמשים מגבירים בצורה בלתי-מוגבלת את אי-יציבות המערכת\"
בנוסח אחר: \"לעולם לא ניתן ליצור סביבת stating/testing שתחווה את כל הבעיות של סביבת production\".
זו גם סוג של עקיצה למי שכותב תוכנה ללא משתמשים, ונמצא ב\"אשליה\" שהקוד שלו יציב, scalable, וכו\'.
הכלל הוזכר לראשונה בספר (המצוין!) \"!Release It\"

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


Sturgeon’s Revelation כללי
\"90% מכל דבר זה שטויות\"

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


The Hype Cycle טכנולוגיה

הסבר: טכנולוגיות חדשות נוטות לחזור אחר אותו מחזור של Hype שטכנולוגיות עבר עברו:

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

הנה דוגמה למיפוי של טכנולוגיות עכשוויות, והמיקום שלהן ב Hype Cycle:

לחצו להגדלה. מקור: Gartner.


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


כלל ההקשר השגוי מתודולוגיה
\"אין רעיון טוב שלא ניתן להשתמש בו בצורה שגויה לחלוטין\" (ידוע גם כ Flon’s Axiom)



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

  • \"הערכת חסר תינתן, גם כאשר אנו יודעים שאנו נוטים לתת הערכות חסר.\" (Hofstadter\'s Law)
  • \"הזמן המוערך לסיום פרויקט תוכנה הוא פונקציה מונוטונית עולה.\"
  • \"הזמן שנותר לסיום הפרויקט – הוא תמיד קבוע\". (Hartree\'s Law)

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



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


Brook’s Law ניהול זמנים / פרויקטים
\"הוספת אנשים לפרויקט מאחר ובשלב מתקדם – רק תגרום לפרויקט לאחר יותר\" (רפרנס)
הסיבות:

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

צורה אחרת: \"The bearing of a child takes nine months, no matter how many women are assigned\"


The Bergman Dilation (\"התרחבות ברגמן\") ניהול זמנים / פרויקטים
\"לא תמיד יש זמן לכתוב את הקוד בצורה נכונה, אך ברוב הפעמים יש זמן לכתוב את הקוד מחדש\".

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


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



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

ארכיטקטורה של Hyper-Scaling

נושא ה Scalability הוא פופולרי-במיוחד בכמה השנים האחרונות.

לאחרונה עברתי על עשרות קורות חיים של מועמדים – ובכמעט כולם צוינו מושגים כמו \"High Scalability\", או \"Big Data\", \"NoSQL\", \"Hadoop\" – וכו\'. כנראה שכל מי שעבד במערכת עם הרבה transcriptions per seconds או נפחים גדולים של נתונים – סיפר על כך בהרחבה, ומי שלא – התאמץ להראות איזו זיקה. זה \"המשחק\" היום, ונראה לי שהייתי עושה בעצמי את אותו הדבר בדיוק!

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

No Scale

אני רוצה להזכיר שהמונח \"Scalability\", מתייחס בהנדסת תוכנה לשני סוגים של אתגרים:

  • Software Scalability – התמודדות עם יותר משתמשים, יותר פעילות, יותר נתונים.
  • Development Scalability – היכולת להתנהל עם צוות פיתוח גדול יותר.
ב Gett יש לנו Software Scale מסוים, שהוא לא קטן – אבל גם לא ענק. ככה וככה נתונים, ככה וככה פעולות בשנייה.
ההתמודדות העכשווית שלנו היא דווקא יותר עם Development Scalability, שכמו שאנסה להראות במהלך הפוסט – יש לה דמיון לא-קטן ל Software Scalability.
לפני כחצי שנה, כשהגעתי ל Gett היו בצוות צד-השרת כשישה מתכנתים. הגעתי מעט לאחר גיוס ענק של 150M$ שהחברה ביצעה. עם הגיוס, החברה החליטה להגדיל משמעותית את קבוצת ה R&D – בכדי לקבל משמעותית יותר תפוקה. בעת כתיבת הפוסט יש לנו כבר עשרים וחמישה (!!!) מתכנתי צד-השרת – ואנחנו עוד מגייסים.

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

Scale

ההקבלה בין Scale של תוכנה ו Scale של קבוצות-פיתוח

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

ככל שהמערכת גדלה – סביר שנחווה מצב בו כל מחשב נוסף שאנו מוסיפים הוא פחות יעיל מקודמו. מדוע? מכיוון ש:
  • פעולות על כמות גדולה יותר של נתונים – אורכות יותר זמן. למשל: אינדקסים בבסיס נתונים רלציוני, הפחתת הרציפות בדיסק, caches פחות יעילים או סתם פעולות joins גדולות יותר (merge על work set גדול יותר).
  • יותר תקשורת שנדרשת בין המחשבים השונים במערכת. יש הבדל בין הודעות עדכון שנשלחות ל 6 מחשבים – וכאלו שנשלחות ל 25 מחשבים. פעם נתקלתי במערכת שהוספה של מחשבים למערכת, מעל 16 מחשבים, כבר לא הגדילה את ה scale – בגלל ריבוי של הודעות כאלו.
  • כמות הקוד שלא ניתן למקבל (parallelism) באופן טבעי תגדל, ולא תקטן – אלא אם הייתה השקעה משמעותית בצמצום קוד שכזה.
  • חוסרי יעילות שונים – שצצים במערכת באקראיות טבעית.

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

  • כל עבודה רוחבית במערכת (למשל: Refracting גדול), הופכים להיות קשים וארוכים פי כמה – כאשר כמות הקוד גדולה יותר.
  • יותר תקשורת וסנכרון נדרשת בין המפתחים בקבוצה. אם פעם היה מספיק להרים את הראש מבהייה במסך – בכדי ליצור קשר עם מתכנת שמכיר היטב היבט מסוים של המערכת, היום כבר צריך לקום מהמקום, לחפש – ולעתים לגלות שצריך לדבר עם כמה אנשים בכדי לקבל תמונה מלאה.
  • תמונות-העולם של המפתחים בארגון מתבזרות במהירות: בניגוד לצוות שהיה לו זמן להתגבש במשך תקופה ארוכה – כעת יש זרימה של אנשים חדשים, שכל אחד רגיל לשיטות שונות וגישות שונות.
    הגישות הללו, עבור כל אחד, \"הוכיחו את עצמן מעל ספק סביר – בעבר\". אמת. אבל מה עושים כאשר הגישות הפוכות זו לזו? האם ORM הוא טוב להכל, טוב רק לקונפיגורציה, או \"רעה-חולה שיש להסיר מהמערכת בהקדם האפשרי!\"?
  • יותר ידיים עובדות => יותר קוד => מערכת מורכבת יותר. כל שינוי במערכת מורכבת יותר – אורך יותר זמן בעצמו (מגמה לחוסר יעילות מובנה).
  • נוצרים יותר צווארי בקבוק (\"רק משה מכיר את הקוד הזה\") – שהולכים ומקשים יותר ויותר על התקדמות הפיתוח.
  • יותר ישיבות, יותר המוניות, יותר אנשים שיש להכיר ולהתרגל לעבוד איתם – חוסרי יעילות שונים, שצצים במערכת באקראיות כל-כך טבעית.
ההשקעה ב Development Scale בפיתוח אמנם עוסקת במידה רבה בגיוס עובדים (\"Capacity\"), אבל לא פחות מכך – בשיפור היעילות של כל עובד (\"Performance\"). תהליכי ה Continuous Integration (ליתר דיוק: on-going integration) – מוגדרים מחדש, אנו משקיעים יותר בשיתוף הידע – ופישוט שלו, וכאן יש לצוות הארכיטקטים תפקיד חשוב.
אנו חותרים ל Continuous Delivery (הפעם: באמת) – בכדי לשפר את יכולת התגובה לתקלות במערכת, וכדי לעשות אותה יציבה יותר. באופן פרדוקסלי משהו, הניסיון בתעשייה מראה שדווקא העלאת תכיפות ה deployments מגדיל את יציבות המערכת – בטווח הבינוני-ארוך. יותר deploys = יותר \"שבירות\", אבל אז גם יותר לקחים, יותר מנגנוני-התאוששות ובקרה, ויותר אוטומציה. כל עוד אין מנגנון ארגוני שמותיר \"לעצור את הקצב, ולצמצם את קצב ה deploys\" – האנרגיות ינותבו לשיפור המערכת, ומנגנוני הייצוב שלה.
ב Software Scale, יש את השאיפה התמידית ל Linear Scalability: האידאל שלפיו הוספה של כל מכונה למערכת, תתרום את החלק היחסי שלה. למשל: הכפלת כמות השרתים – תכפיל את הספק העבודה (למשל: כמות בקשות בשנייה).
לא ממש Linear Scaling: ככל שמספר הבקשות עולה – יש להוסיף חלק יחסי גדול יותר של שרתים בכדי לענות על הביקוש.
יש במערכת הזו צווארי בקבוק מסוימים ל scalability.
בקבוצת ה R&D כולנו מבינים שככל שהמערכת גדלה – היעילות של המתכנים הולכת וקטנה. אין לנו שאיפות ל Linear Development Scalability. אנחנו גם מכירים במגבלות המקבילות האנושית (\"תשע נשים לא יכולות ללדת ילד בחודש אחד\").

בשונה מאיתנו ל Business דווקא יש ציפיות ל Linear Scalability – מפורשות יותר או פחות.
\"פי-2 אנשי support עונים לפי-1.9 קריאות במוקד?\" – הם מספרים, \"כן… אנחנו מבינים שהנדסה זה קצת יותר מורכב. הגדלנו את הפיתוח פי 4, ואי-אפשר לקבל פי-4 פיצ\'רים – אבל גם אי אפשר כבר לצפות לפחות לפי-3 יותר פיצ\'רים, או לקבל אותם לפחות פי-3 יותר מהר?\"

הלחץ מצד הביזנס הוא אולי משני, אבל הוא משפיע – וגורם לנו להתייעל יותר בכל הנוגע ל Development Scalability של הפיתוח. בעיקר ע\"י צמצום חוסר-היעילות שהמערכת יוצרת למפתח הבודד.

ב Software Scale, יש \"קסם\" שיכול לסייע למערכת לצמוח ב Scale שהוא יותר מלינארי: מצב בו פי-2 שרתים, משרתים יותר מפי-2 משתמשים. כיצד זה קורה? יש כמה דרכים, אבל ה\"קסם\" הנפוץ ביותר הוא Cache (או memoization – בגרסה התאורטית שלו).
כאשר אנו יכולים לבצע חישוב מורכב רק פעם ב 5 דקות, ואז להפיץ את התוצאות לעוד ועוד משתמשים – כמות גדולה אפילו יותר של משתמשים תגדיל את הלחץ רק על ערוץ ההפצה (CDN?) – ולא על יצירת התוכן (החישוב).

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

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

Scaling שהוא טוב מ Linear-Scaling: הוספת שרת למערכת – מוסיפה יכולת לספק קצת יותר משתמשים מחלקו במערכת.

ב Development Scaling יש גם כמה \"קסמים\" שכאלו. הבולט בהם – הוא code re-usability: היכולת להשתמש בקוד שנכתב בעבר – עבור פיצ\'ר חדש.

פתרון שונה-דומה הוא Generalization: כתיבת קוד כללי יותר – שיכול לשרת מטרות דומות, אך שונות.

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

מהנדסים צעירים, ואולי אף מהנדסים בכלל – נוטים לבצע הערכת יתר (גסה?) ליכולת שלהם לייצר קוד יעיל לשימוש חוזר / קוד כללי יעיל. משם נוצר הכלל You Ain\'t Gonna Need It (בקיצור: YAGNI) המציע פשוט לא לנסות לחזות מקרים אלו מראש, אלא לעשות Refactoring לקוד כללי רק ברגע שהוכח הצורך – מעל ספק סביר.

בכל מקרה: שימוש חוזר בקוד והכללה, גם אם נעשים בדיעבד – הם כלים חשובים מאוד לשיפור ה Development Scalability.

אז מה עם ארכיטקטורה ל Hyper-Scaling?!

אולי אתם מאוכזבים מעט מהפוסט: יש בו הרבה דיבורים כללים, ואין בו Hadoop, HPC או Big Data משום צורה!

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

הארכיטקטורה, או תוכנית-העל שלנו להתמודד עם בעיות ה Development Hyper Scaling הן כאלו:

  • בראש ובראשונה – מעבר ל Micro-Services: הפיכת מערכת אחת מורכבת – לכמה מערכות קטנות יותר, ומורכבות פחות. המעבר הוא אינטנסיבי, אבל הוא מאפשר להבין ביתר קלות את כלל המערכת – ולדעת להיכן לצלול בעת הצורך. כמו כן – הוא מצמצם במידה רבה את הצורך בתקשורת מרובה, לטובת תקשורת בסיסית יותר וממוקדת יותר, למשל: סך האחריויות של כל שירות, וה APIs שלו – שמוגדרים היטב (אנחנו משתמשים ב Swagger לתיעוד – כלי שמשרת אותנו היטב).
    את השימוש ב MSA להתמודדות עם Development Hyper-Scaling לא המצאנו בעצמנו: למדנו מ case-studies על חברות שעמדו באתגר דומה (למשל: אמזון).
    • שימוש-חוזר בקוד, הוא רעיון שקשה לממש (מעבר לפונקציה פה ושם). דווקא Micro-services, בכך שאנו מגדירים שירותים עם שימוש עסקי ברור, ו APIs מוגדרים היטב – מסייעים לנו ליצור יחידות גדולות של קוד שמתאימות לשימוש-חוזר. כבר בחצי-שנה האחרונה, היו לנו כמה הצלחות יפות.
  • אנו עוסקים בצורה פרואקטיבית בארגון בשיתוף ידע על חלקי המערכת השונים, האחריויות שלהם, וה flows העיקריים במערכת. לא עובר כמעט שבוע שאני לא עושה session שכזה, לצוות כלשהו בפיתוח – ואני לא היחידי. עוד פעם ועוד פעם – עד שלכולם כבר יימאס (אנחנו עוד רחוקים משם…).
    שמות פשוטים, מטפורות טובות, וסיפורים קליטים – הם מרכיב עקרי בבניית והפצת הידע.
  • צוות הארכיטקטים לוקח תפקיד קצת יותר ריכוזי מהרגיל (אולי: יותר מהאידאל האישי שלי?!) בהגדרת superflows חדשים במערכת. כן! אנחנו רוצים לעבוד יותר agile ולתת לאנשים יותר ויותר אחריות והשפעה, אבל בנקודת הזמן הזו – תוצאות טובות יותר מושגות כאשר לפחות את ה flows העיקרים – מוגדרים מרכזית ע\"י הארכיטקטים.
    כאשר מפתחים עושים שינויים ושיפורים ב flows – זו סיבה לשמחה (אלא אם בכך הם סותרים עקרונות או flows אחרים במערכת).
  • אנו מנסים לקדם בקוד כמה עקרונות:
    • קידום תרבות של הצגת פתרונות – ולא רק בעיות (בכל ה R&D).
    • קוד פשוט להבנה – עדיף יותר על פני קוד קצר או מתוחכם. אם אתם קוראים של הבלוג זמן רב, אתם אולי יודעים שזו הנטייה הטבעית שלי – אבל זה לא הסטנדרט הברור של ריילס (שם עקרונות של קוד קצר ו DRY – מושרשים עמוק בקהילה).
    • יותר כלי monitoring ו supportability עבור הפיתוח. בכתיבה של כל פיצ\'ר – לחשוב איזו השקעה תהיה משתלמת כאשר לפיצ\'ר הזה תהיה בעיה ב production. כלי supportability יכולים להציג חווית שימוש עלובה למדי – כל עוד הם עוזרים.
    • הכנסה של אוטומציה / בדיקות יחידה / בדיקות-API / בדיקות-אינטגרציה. בכל ארגון שראיתי בעשור האחרון זו הייתה המגמה – אבל אנחנו עכשיו צריכים את זה יותר.

אני מודע לכך שעדיין אין פה Design Patterns הנדסיים משמעותיים (אולי מלבד MSA) – אבל זה מה שעובד, ואנו עושים את מה שעובד – ולא רק מה שמתאים לציפיה מסוימת (ארכיטקטורה = \"תרשימים של ריבועים\"). סורי! 🙂

זהו… מקווה שנהניתם, ואולי אף השכלתם. כרגיל – אשמח לתגובות.

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

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

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

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

חשבו על התרבות שלנו (קולנוע, ספרים, חדשות, וכו\'): האם יש בה מקום גם למתמחים או רק למומחים? המתמחים יהיו מומחי-על בעוד 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 דילוגים ביום בין נושאים, הלוך ושוב, בזבזנו שעתיים וחצי של פעילות המוח שלנו ביום הזה.

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