אפשור מול ריסון: קרב האור באופל של ארכיטקטורת התוכנה?

פעמים רבות בחיי השתתפתי בשיחה שהחלה בערך כך:

“אז למה שלא נשתמש ב GraphQL / בסיס נתונים משותף / נעבור מ p2p ל broadcast / נסיר שכבת הפשטה מהתוכנה? – זה יעזור לנו לכתוב תוכנה מהר יותר!”.

“אבל מה עם המחירים?”

“איזה מחירים? מה יותר טוב מלכתוב קוד מהר יותר? למה לא?”

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

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

הוויכוח הזה לא התחיל היום. הוא לא ויכוח בין אדם אחד לאחר, ולא ויכוח ייחודי למיקום יחיד.

זה ויכוח שמתרחש בעולם התוכנה יום וליל, מסביב לגלובס.

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

במבט-על הוא נראה כמו ה Game of Life בו “שטחים” נכבשים ומשתחררים:

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

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

לופ שלא נגמר.

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

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

— ביבשת “מבנה התוכנה” —

Design Patterns נגד … מה שהיה קודם

YAGNI מול Design Patterns

S.O.L.I.D מול YAGNI

— ביבשת ה”תקשורת” —

SOA מול RPC

REST מול SOA

newer RPC (Thrift/gRPC) מול REST

GraphQL מול newer RPC

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

אז מה קורה שם?

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

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

כשמתגלה ניצול / חוסר צדק של חזקים מול חלשים – דורשים בקרה ורגולציה.

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

האם זו בעיה כ”כ סבוכה? NP-Complete? אי אפשר כבר להציב מחשב שיפתור אותה?!

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

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

ה Tradeoff בעולם התוכנה, הוא דיי דומה:

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

אז מצד אחד אפשרנו – זזנו מהר, מהרגע הראשון.

מצד שני – בטווח הבינוני או הארוך – אנחנו משלמים מחיר שמאט אותנו.

מה עדיף?

הפתרון

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

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

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

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

שפת ג’אווה (1995), למשל, הלכה צעד הלאה וקבעה נורמה של שַׁלְפנים (Getter/Setters) על האובייקטים: הקצנה של הלקח של “אסור לאפשר משתנים גלובאליים” – על scope מצומצם בהרבה, הרי הוא האובייקט. אינספור מפתחים בזבזו במצטבר שנות חיים בכתיבה של getter/setter גם למשתנים שלעולם הגישה אליהם לא תשתנה, ולעולם לא יעשה בהם שימוש לרעה.

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

הנה לנו עוד מחזור.

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

אבל:

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

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

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

בכל זאת, הסיכון הוא לא לגמרי סימטרי:

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

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

נדמה לי שבחברות תוכנה, ובתרבות הישראלית בפרט – המצב הזה הופך ליותר ויותר נדיר.

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

תסביר שוב: מה הנזק מאפשור-יתר / ריסון-יתר? אז מה עושים?

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

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

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

האם זה רעיון טוב או לא? זה תלוי בהקשר.

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

במערכת אפליקטיבית בה:

  • המודל, ומכאן סכמת בסיס הנתונים משתנה מדי-פעם.
  • שכבות אפליקציה מטפלות בנתונים (בכתיבה, אבל גם בקריאה), ומפעילות לוגיקה-עסקית עליהם – שגם היא משתנה.
  • דרישות צד-הלקוח משתנות לאורך זמן.
גישה ישירה מצד-הלקוח לבסיס-הנתונים עשויה להתגלות ככדור (תותח) ברגל. הפיתוח יתקדם מהר, אבל אז:
  • יתגלה באג שלא ברור מדוע הוא מתרחש: מישהו הוסיף קוד אפליקטיבי שאינו חל על הקריאה הישירה מבסיס הנתונים.
  • שינוי פשוט לכאורה, נניח: שינוי במודל שדורש שינוי בסכמה של בסיס הנתונים – הופך לשינוי קשה, ארוך, ובעל הזדמנויות רבות לתקלות.
    • יש כאן גם חוסר-הוגנות ארגוני פוטנציאלי: מפתח אחד אפשר וחסך זמן בפיתוח הפיצ’ר שהוא אחראי אליו, בעוד מפתח אחר נאלץ לשלם את המחיר היקר שנגרר מאותו אפשור.
    • נתקלתי במצבים כאלו פעמים רבות – והם לא מסייעים לתחושת השותפות והידידות בארגון, במיוחד אם צוות אחד מאפשר תדיר על חשבון אותו צוות אחר שמשלם את החובות-הטכניים של אותו האפשור.
ריסון הוא הרצון להגן על המערכת ע”י הצבת חסמים מלאכותיים. במילה “מלאכותיים” הכוונה היא שיש דרך לבצע את הפעולה באופן קל יותר – אך אנחנו יוצרים חוקים / מגבלות פיסיות שלא יאפשרו זאת.
דוגמאות:
  • השימוש ב private על members בפרדיגמת ה OO – הוא ריסון ברמת המיקרו. למעבד אין בעיה לגשת לכתובת הזו בזיכרון – אבל אנחנו מורים לו לא לעשות זאת (ומשלמים תקורה בביצועי התוכנה / זמן כתיבת הקוד).
  • כלים לניהול תלויות בין מודולים / מיקרו-שירותים או נהלים להגבלת ספריות ה open source שנכנסות למערכת: אין למתכנת בעיה להוריד כל ספריה ולהתשמש בה – אבל הארגון רוצה לבדוק את הספרייה מבחינת אמינות / תמיכה / כפילות / רשיונות / היבטי אבטחה – לפני שזה נעשה.
  • קונבנציות של קוד – הן ריסון. הקומפיילר יקבל סגנונות שונים ומשונים – אבל הארגון מחליט שבנקודות מסוימות הוא מקבל סגנון רק מסוים.
  • לפעמים גם בחירה של שפת-תכנות היא ריסון. שפת Go מחייבת מבנה אחיד ופשוט הרבה יותר משפה משופעת באפשרויות כמו רובי. מעבר מרובי ל Go – הוא ריסון ניכר.
דוגמה קלאסית לריסון-יתר הוא Closed Layered Architecture מרובת שכבות. בכדי להתגונן בפני בעיות שונות בכתיבת מערכת, הומצא מודל השכבות בו כל שכבה יכולה לקרוא רק לשכבות מתחתיה. בגרסה ה”סגורה” של המודל, כל שכבה יכולה לקרוא רק לקוד באותה השכבה או בקוד בשכבה אחת מתחתיה:
כלומר: אם קוד בשכבה 4 רוצה לקבל נתונים משכבה 2 עליו:
  • לקרוא לשכבה 3 ולבקש את המידע.
  • שכבה 3 תקרא לשכבה 2 ותבקש את המידע.
  • המידע חוזר במבנה הנתונים ששכבה 2 מכירה (אך שכבה 3 יכולה להשתמש בה).
  • שכבה 3 לא יכולה להעביר את מבנה הנתונים של שכבה 2 לשכבה 4 (אסור!) ולכן היא מגדירה מבנה נתונים משלה למידע שאותו היא חושפת בפני שכבה 4, לאחר המרה, כמובן.
ייעצתי פעם לארגון שעבד עם מודל של 6 שכבות סגורות. המערכת לא הייתה עשירה כ”כ ב business logic ורובה ביצעה אינטגרציה בין מערכות.
מידע רב עבר בין שכבה 2 לשכבה 6 – ורוב הקוד בשכבות 3, 4, ו 5 פשוט תרגום שוב ושוב קריאות, הלוך ושוב, למבנים שקולים – אך שונים. חשיפת נתונים בין שכבה 2 לשכבה 6 דרשה כמות נכבדת של קוד? על מה?!
אני בטוח שמישהו הגיע עם כוונה טובה. הוא קרא מאמר או שמע הרצאה על מערכת שעבדה ללא סדר / שכבות – והפכה לסיוט לתחזוקה. אני מדמיין שהוא החליט לא ליפול לפח ולעשות את “הטוב ביותר”. לא רק 3 שכבות – אלא 6! לא רק שכבות – אלא שכבות סגורות. Crème de la Crème!
הנזק כאן כמובן היה אדיר,שעות מפתחים ושחיקה רבה ממשימות בסיסיות. אני מקווה שהחברה בסוף יישמה את הרעיון הפשוט של לעבור למודל פתוח או לפחות פתוח-למחצה.
נ.ב. האוונגליסטית לשינוי הייתה דווקא מישהי שרק התמנתה לתפקיד ארכיטקטית. לארכיטקטים הותיקים והמנוסים בחברה היה blind spot למצב, ולאבסורד שבו.

סיכום

אז מה אפשר לקחת מכל הסיפור הזה?
דבר ראשון – אני מקווה שחידוד הנושא יעזור בפעמים הבאות שתתקלו בדילמה שבין אפשור וריסון – ותצליחו להסתכל על הדילמה במבט מעט יותר מפוכח.
דבר שני – הזהרו מהטיעון (נא לקרוא במבטא צרפתי): “ואז: Walla!, נפתח קוד הרבה יותר מהר!” וגם מהטיעון ההופכי “ואז, !Walla, לא עוד בעיה מסוג X במערכת – וחדל לבזבוז הזמן”.
כשמישהו מציע אפשור / ריסון משמעותי במערכת, אני אישית, הייתי רוצה לראות אותו\אותה בפנים מיוסרים (מלבטים). בדרך אני נתקל בהם כשפניהם זורחות והם מלאי אנרגיה (שלא לומר: זחיחות) – אני ארגיש הרבה יותר טוב כשהם יהיו מיוסרים, ויאמרו משהו כזה:
“אני חושב לאפשר/לרסן את הדברים כך וכך, הנה ההזדמנויות (מציאותיות), אבל הנה גם הסיכונים (שמטרידים גם אותי). מה אתם אומרים?”
זו הדרך המקצועית לגשת לדיונים כאלו. כשכולים מכירים בכך שמדובר ב tradeoffs לא פשוטים – הדיון הופך לקל ויעיל יותר.
אני משוכנע שאין פתרון אחד כולל / נכון-תמיד ל treadeoff שבין אפשור לבין ריסון.
אבל אני גם מאמין שבחשיבה נכונה, אפשר ברמת המערכת / הארגון הספציפי – למצוא tradeoffs הרבה יותר טובים מבחירה שרירותית. ואם טועים – אפשר גם לתקן.
הפסיקו להסתכל מתחת לפנס, ולהניח שהדברים עומדים להסתדר ע”פ התסריט האופטימי שבו הייתם רוצים!
לא עוד “הפיתוח יטוס מעכשיו בטיל”.
או “מערכת שהיא בטון יצוק”.
דיי. מאסתי בתיאורים כאלו.
בואו נהיה מקצועיים.
שיהיה בהצלחה!

[א] בשפות שלא תומכת משתנים גלובאליים ניתן לחשוב על משתנה שעל אובייקט שנגיש לכולם.

בחירות בתוכנה: איך לבחור נכון?

הנה דילמה יומיומית, שבטח אתם מכירים:רוצים להוסיף למערכת פיצ’ר Foo מסוים, אבל הפיצ’ר לא בדיוק מתאים למבנה המערכת, או אולי מבנה המערכת לא מתאים בדיוק לפיצ’ר.
כלומר: בכדי לספק את הפיצ’ר מהר צריך לעוות משהו במערכת: לשבור סדר קיים, לעשות משהו לא צפוי, לפתוח מרווח שיאפשר תקלות, וכו’ – משהו שמהנדס תוכנה טוב היה רוצה בכל מאודו להימנע ממנו!

מצד שני, מבנה-של-מערכת הוא לא צחוק. שינויי מבנה הם שינויים קשים וארוכים. “אין לנו זמן לזה!”

האם נכון ״להרכין את הראש״, ולהכניס את הפיצ׳ר תוך יצירת עיוות במערכת?

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

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

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

איך בוחרים?

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

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

“אם מדובר במודול קריטי של בקרה על כור גרעיני – אסור להתפשר בכלום על האיכות!” היא דוגמה מלומדת ששמעתי כמה פעמים בהקשר זה. מעולם לא פגשתי במתכנת שפיתח מודול קריטי של מערכת שתלויים בתקינותה חיים רבים. למתכנתים הללו יש כנראה guideline דיי ברור. מה איתנו, כל שאר 99.999% מאנשי התוכנה?

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

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

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

עיוותים במודל של המערכת הם לא סתם לא-אסתטיים. הם מדרדרים את המערכת למקום בו שינויים הופכים קשים יותר וההתקדמות – אטית יותר ומרובת-תקלות. אף אחד חוץ מהמהנדסים לא יחוש בהתדרדרות ויבין אותה – עד שכבר נהיה ב Technical Debt עמוק ומשמעותי.

אז מה עושים?
איך אתם לוקחים החלטה שכזו?

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

התשובה היא כמובן: It depends.

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

זה תלוי ברוח הארגון, וב DNA שלו: ישנם ארגונים, גם לא קטנים, המקדשים את ההתקדמות והיכולת לבצע שינויים, גם במחיר סיכונים מסוימים. “Move fast and break things”.
הערה: אני פותח פה פתח ל Self-Suggestion לקורא שרוצה להימנע מעימותים ופשוט יאמר לעצמו “אצלנו ב DNA הארגוני רוצים רק פיצ’רים – אז אני אזרום עם ה DNA הארגוני”. אני לא רוצה לעודד סוג כזה של מחשבה – אלא שתחשבו מה נכון לארגון שלכם באמת. עד כמה הדחיפה לפיצ’רים באמת עובדת טוב, וכמה זמן פיתוח באמת “שוקע” בגלל עיוותים במערכת.

זה תלוי בגודל העיוות: אי אפשר לכמת עיוות, אבל אפשר לומר ש”לשמור גם אובייקט Y בטבלה שנועדה לשמור אובייקטים מסוג X” [א] הוא ככל הנראה עיוות חמור יותר מלהחזיר null או 1- בכדי לחוות על מצב לא-תקין ב API (פעם זה היה best practice, אפילו).

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

חטאים…

כמה אנחנו מסוגלים להעריך את הנקודות הללו בצורה אובייקטיבית וטובה?
כארכיטקט צעיר ב SAP (וטיפוס דקדקן) חשתי רוב הזמן שאנו במצב רע טכני, ועושים יותר מדי פשרות. לא הייתה לי פרספקטיבה מאוזנת להבין שבצם זה היה [ב] ארגון שדווקא עשה הרבה יותר מדי הנדסה ופחות מדי Business. בדיעבד הייתי חוזר בזמן ומעודד לאפשר ולקבל משמעותית יותר “עיוותים” במערכת. לדחוף את הארגון לראות כבר לקוחות פעילים!

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

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

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

חשוב גם לזכור:

  • ככל שהעיוות גדול יותר ← יהיה קל להסביר אותו לאחרים. אם אתם מזהים קטסטרופה, אבל לא מצליחים לשכנע את הסביבה הקרובה – שווה לבדוק שוב אם אתם מגזימים בהערכת הבעיה.
  • אם העיוות יציף בעיות מוחשיות במהרה ← אולי דרך השכנוע הטובה ביותר היא לאפשר לעיוות להיכנס וכך “להוכיח” אותו לסביבה?
בתחום לא דטרמיניסטי כמו הערכת מבנה המערכת והשלכותיו על העתיד – מדובר במידה רבה בעניין של מוניטין. המוניטין שלכם.
חשוב שיהיה לכם יחס טוב בין Signal / Noise. אם על כל ארבע אזהרות שלכם – רק אחת מתממשת, הייתי אומר שזה המקום לצמצם קצת באזהרות. לעצמי אני אומר שלא הייתי רוצה לעלות על שלוש אזהרות לכל אחת שמתממשת. זהו כמובן Trade-off – בחרו לעצמכם את היחס שמתאים לכם – אבל זה בהחלט יחס שיש לחשוב עליו ולקחת אותו בחשבון.

סיכום

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

אז מה אני מציע לעשות כאשר יש דילמה בין הוספת פי’צר למערכת במחיר עיוות למבנה המערכת?

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

—-

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

[ב] נקודתית, בשעתו – לא יודע כיצד הוא עכשיו.

על העיקרון הפתוח-סגור (ארכיטקטורה)

פוסט זה הוא סוג של המשך לפוסט: חלוקת המערכת למודוליםהעיקרון הפתוח-סגור אומר ש:

Software Entities (classes, modules, functions, etc.)
should be open for extension, but closed for modification.

העיקרון הפתוח-סגור (בקיצור: OCP, שזה ה Open-Closed Principle), הוא חלק ממערכת ה S.O.L.I.D – מערכת כללים לבחינת איכות של עיצוב (Design) תוכנה שהגדיר הדוד בוב (Robert C. Martin) בתחילת שנות ה 2000.

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

  • מה המשמעות של “סגור” ו”פתוח”?
  • מה זה Extension? הורשה?     (תשובה: לא בדיוק)
  • מה זה modification? שינוי קוד? שינוי State?
סכנה של עיקרון לא מובן שכזה, הוא שאנשים – יבינו אותו בצורה שגויה ויפעלו לא ע”פ הכוונה המקורית.

לא עזרתם בהרבה! מקור: http://www.tomdalling.com
בגרסה היותר ידידותית, העיקרון אומר משהו כזה:

  • כתבו את הקוד כך, שרכיבי התוכנה (מחלקות, מודולים, פונקציות, וכו’) לא ישתנו לאחר שסיימו לכתוב אותם – להלן: “Closed for modification”.
  • תוספת של פונקציונליות – יש להוסיף ברכיבי תוכנה חדשים (עוד מחלקות, עוד מודולים, או עוד פונקציות) – להלן “Open for Extension”.

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

———

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

שאלות ראשונות

מדוע לא לשנות קוד שכבר נכתב?

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

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

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

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

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

מדוע “הרחבה” של קוד היא בסדר?

בשלב הראשון אנו כותבים את ה “Basic Functionality”. הקוד עובד ומתייצב עם הזמן.
כאשר אנחנו נדרשים להרחיב את הפונקצינליות אנו מוסיפים את תוספות 1, 2, ואז 3.

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

כמו כן – הרחבה תכופה תאפשר לכל איזור קוד להיות קטן ופשוט מספיק בכדי להבין אותו: “Scalability” של כתיבת קוד.

חשוב מאוד לציין: הרחבה איננה בהכרח הורשה. אמנם השתמשתי בסמנטיקה שמקובלת לתיאור הורשה (“Generalization”) – אבל אנו כבר יודעים שהורשה היא חרב פיפיות (ראו הרצאה). עדיף להשתמש בכלי אחר. הכלי המוצלח ביותר ל OCP הוא Interface: ממשק שמי שרוצה להרחיב את המערכת – צריך לממש אותו.

מדוע עדיף לנו שבאגים יופיעו באיזור החדש של הקוד, ולא באיזור הישן?

זה לא בדיוק מה שנאמר.
חלק גדול מהיתרון של יישום OCP הוא שהקוד שלנו מחולק ל”חתיכות” קטנות של קוד שלא מסתבכות יותר מדי. קטן => פשוט (או יותר נכון: גדול => מסובך).

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

כיצד ממשים OCP בפועל?

יש לי מערכת. אני רוצה ליישם את העקרון הנפלא של OCP. כיצד עושים זאת?

יש כמה וכמה דרכים. אציג דרך אחת שיצא לי לדון בה לאחרונה:

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

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

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

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

מנגנון ה Broker קורא מקובץ קונפיגורציה – באיזה סדר להפעיל את התנאים.

ובאמת לאחר כחצי שנה המנגנון נראה כך:

בכל שלב הוספתי עוד תנאי למנגנון ה Matching (העדפות של נהג, העדפות של נוסע, וכו’).
הקוד החדש נכתב ללא נגיעה בקוד שהיה קיים קודם לכן.

בפבואר הוספתי את Condition 2 – ועד אפריל הוא התייצב בצורה טובה.
במרץ הוספתי את Condition 3 – ועד מאי הוא התייצב בצורה טובה.
במאי הוספתי את Condition 4 – ועד יולי הוא התייצב בצורה טובה.

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

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

ה Design הזה כמובן הוא לא “מושלם” ואני לא ממליץ להעתיק אותו כי הוא מופיע כדוגמה לעיקרון חשוב של תכנון מערכות. הוא מתאים לצרכים מאוד ספציפיים.
ככל שיהיו יותר תנאים של Matching שלא משתלבים ב Pattern הנ”ל – הוא ילך ויסתבך. אין לו תוכנית ל “חריגים לא מוכרים” (כי הם לא מוכרים, דאא) ויהיה צורך לבצע חשיבה מחדש ברגע שיגיעו כאלו.

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

איכות המבנה / העיצוב של ה Matching System (או כל מודול אחר) תלוי בעיצוב הראשוני, ומידה לא פחותה – ברמת ההחלטות וההתאמות שידרשו עם הזמן.

ספקות לגבי OCP

ה OCP הוגדר בצורה לא ברורה. את זה כבר הזכרנו. היו כמה מאמרים שהתפרסמו בביקורת הזו (למשל: Say “No” to the Open/Closed pattern של Marco Cecconi ו The Open-Closed Principle, in review של Jon Skeet)

במאמר מאוחר של הדוד בוב מ 2013 הוא סיפק את ההגדרה הבאה ל OCP:

What it means is that you should strive to get your code into a position such that, when behavior changes in expected ways, you don’t have to make sweeping changes to all the modules of the system. Ideally, you will be able to add the new behavior by adding new code, and changing little or no old code.

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

ספק מרכזי שיש לגבי OCP, הוא האם התקדמות בטכניקות של הנדסת תוכנה בשנים האחרונות – שינו משהו לגבי הנכונות שלו?

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

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

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

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

האם בהכרח זהו קוד שכדאי להימנע ממנו?
לא בטוח. אם במשך 5 שנים של אורך חיי במערכת יצטברו במתודה 3 או 4 צורות – לא נראה לי שזה יהיה קוד רע. בואו לא נהיה פאנטיים. האם חשוב להוסיף final על כל משתנה בג’אווה? אפשר – אבל ה impact המעשי הוא כנראה באמת קטן.

אם המערכת שלנו תתרחב ל 10 צורות, וכמו המתודה DrawAllShapes יש מתודות כמו EditAllShapes, ResizeAllShapes ועוד – אז ברור לי שבסיס הקוד שלי הולך וגדל ללא היכר. זה מקרה ברור בו כן היינו רוצים ליישם את ה OCP.
בסיס קוד גדול יותר => קוד שקשה יותר לשלוט בו => קוד שקשה יותר לשנות / יותר באגים.

ברור לחלוטין שלא ניתן, ולא כדאי, להגיע ל 100% יישום של OCP במערכת.
בניגוד להוספת “final” למשתנים בג’אווה, פרקטיקה שמוסיפה מעט ערך אבל גם העלות שלה שולית – OCP משמעה כתיבת עוד קוד ולקיחת “סיכונים” בעצם הגדרת ההפשטה (למשל: ה Condition בדוגמה למעלה).
כאשר דרישה חדשה משתלבת יפה בהפשטה שיצרנו – השגנו ייתרון, אך כל דרישה חדשה שלא תתאים להפשטה – תקשה ותסבך את המערכת.

OCP מתבטא ב Patterns כמו Plug-ins, State, Strategy, ועוד. אולי גם Composite ו Delegate.
זוהי רשימה קלאסית של Patterns שאנשים נטו ונוטים לעשות בהם שימוש מוגזם: השימוש עשוי להרגיש “טוב” (המערכת שלנו היא “כמו בספרים”), אך בפועל ייתכן ויצרנו מערכת יותר מסובכת ויותר יקרה לשינוי / תחזוקה.

OCP הוא במיטבו כאשר ממשים אותו במידה, אבל מהי המידה הנכונה?

מקור: https://www.slideshare.net/PaulBlundell2/open-closedprinciple-kata

קרייג לרמן, ו PV

ובכן, אם לא הכרתם עד עכשיו – אז שווה להכיר:
במקביל לדוד בוב שהגדיר את עקרונות ה S.O.L.I.D, עבד בחור קנדי בשם קרייג לרמן על מערכת חוקים מאוד מאוד דומה בשם GRASP. המערכת של לרמן הרבה יותר פשוטה להבנה! מה שהופך אותה לעדיפה בעיני.

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

העיקרון המקביל של GRASP ל OCP נקרא Predicted Variations או בקיצור PV. והוא מוגדר כך:

Identify points of predicted variation and create a stable interface around them

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

Predicted מצוין כ “educated guesses” מכיוון שלא סביר שבאמת נוכל לחזות כיצד תתפתח המערכת לאורך חייה.
כלומר: עלינו לחשוב לאילו כיוונים סביר שהמערכת תתפתח, ולבנות את הממשק באיזור הזה כך שיהיה ניתן להוסיף קוד ע”י הוספה של יחידות קוד עצמאיות, ולא ע”י שילוב של תוספות קוד בקוד הקיים. בהמשך המאמר מוסבר:

We can prioritize our goals and strategies as follows:
1. We wish to save time and money, reduce the introduction of new defects, and reduce the pain and suffering inflicted on overworked developers.
2. To achieve this, we design to minimize the impact of change.
3. To minimize change impact, we design with the goal of low coupling.
4. To design for low coupling, we design for PVs.

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

אנסה לתמצת את הרעיונות במילים שלי:

  1. זהו במערכת מקומות שבהם אתם צופים תוספת של קוד, ונסו להכין ממשקים ידועים להרחבות הללו.
  2. בעצם מדובר ב”ניהול סיכונים”: הסבירות שאכן המערכת תתרחב לשם (תזכורת = בני-אדם נוטים לבצע הערכת יתר בציר הזה)  X  כמה קוד וסיבוכיות אתם צופים שתתווסף.
  3. עבור שינויים קלים (שורות קוד בודדות) – הערך ביישום OCP הוא גם נמוך. נסו לכוון למקומות משמעותיים.
  4. קלעתם טוב – השגתם ערך; לא קלעתם טוב – השקעתם זמן מיותר. זה בעצם העניין כאן.
  5. אפשר לנקוט בגישה אגי’לית: כשאתם רואים שאכן המערכת מתפתחת לכיוון מסוים, בצעו Refactoring והוסיפו ממשק מתאים / PV. אם יש לכם בדיקות יחידה – אין סיבה לפחד מ Refactoring.

אני מניח שאם GRASP הייתה השיטה השלטת – הפוסט שלי היה מתקצר בחצי: המטאפורה של Predicated Variations הרבה יותר קולעת מה Open-Closed Principle (סליחה, הדוד בוב).

לא מצאתי את הציוץ של הבחור שהציע: “בואו נחליף את OCP ב PV – ונתחיל להשתמש ב SPLID”.

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

—-

מקורות רלוונטיים:

ארכיטקטורה: האם לנסות שוב?!

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

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

מה עושים ב 0.1% (עשירית אחוז) מהמקרים האחרים, כאשר דווקא מודול B מסיים ראשון?

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

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

סוג אחר של מתכנתים, יחפש פתרונות אג׳יליים ופשוטים למימוש. ״בוא נוסיף sleep של 500ms למודול ב׳ – וכך הוא תמיד יסיים אחרי״. הפתרון אמנם פשוט מאוד למימוש אך הוא נדחה על הסף על ידי הצוות: להאריך את התהליך כולו סתם בחצי שניה, עבור 99.9% מהמקרים  – זה לא נשמע סביר…
ננסה שוב: ״בואו נזהה מצב שמודול ב׳ מסיים ראשון, ואם זה קרה – נפעיל את כל התהליך בשנית״.

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

מה אתם אומרים?

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

כשאני מנסה לעשות realization לרצון הטבעי שלי אני מעלה גם ש:

  • תקלה של אחד למיליון היא עדיין תקלה. למה לתכנן משהו כך שיישבר (אפילו שהמקריות היא אחת-למיליון)??
  • הפעלת אותו התהליך פעם שנייה אינו דבר צפוי. אדם נוסף שייכנס לקוד עשוי לפספס / לא להבין זאת. 
    • עובדה שאין בעיה להפעיל את התהליך פעמיים כיום. מי מבטיח שתנאי זה לא ישתנה בעתיד? וכאשר הוא ישתנה – מישהו יזכור שיש לנו טריגר שמפעיל את התהליך פעם נוספת ויידע / יזכור לתקן את המנגנון?
  • אמנם תקלה למיליון לא נשמעת חמורה, אבל מי מבטיח לנו שתחת עומס / לחץ, המערכת שלנו לא מתנהגת אחרת? אולי תחת עומס מסוים – התופעה תתרחש אחת למאה פעמים?!
מצד שני יש כמה טיעוני-נגד:
  • המערכת שלנו עוד טרייה ותעבור שינויים רבים. להשקיע עכשיו יומיים בפתרון מקרה קצה – זו השקעה לא מידתית.
  • תקלה של אחד למיליון זה סיכון שניתן לקחת. בואו נהיה דוגריים: במערכת כנראה קיימים (ובעתיד יווצרו וייפתרו עוד ועוד) באגים אחרים שיגרמו להרבה יותר מ\”תהליך אחד למיליון\” להיכשל. עשינו Framing לדיון כאילו כל המערכת מושלמת – וזו הבעיה האחרונה שנותרה בה. זה לא המצב.
  • אחד למיליון נראה בעייתי? בואו נעשה ניסיון שלישי – אם צריך. בואו נזרוק Alerts למוניטורינג כאשר זה קורה – ונלמד אם באמת יש בעיה.
באיזה גישה אתה הייתם בוחרים?

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

התבונה המקובלת היא: \”בואו נעשה את זה פעם אחת ולתמיד\”.

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

התבונה המקובלת היא: \”בואו לא נסבך את זה\”.

אלתור. יעיל, או חפלפ?  מקור: redstateeclectic

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

במשך שנים בקריירה, הצעות מסוג זה נתפסו על ידי כחוסר מקצועיות, הבנה, או סבלנות נדרשת. כסמל של התנהגות שיש להוקיע. עבדתי אז ב SAP וקיבלתי גיבוי מלא לגישה הזו מעמיתי והמנהלים. פעמים רבות עבדנו עוד שבוע או שבועיים – בכדי לא להגיע לפתרונות של \”ניסוי שני\”. לעתים, כאשר היה מדובר בשבועות רבים – היינו \”מתגמשים\” (ורושמים עוד שורה באקסל ה Technical Debt [א] שאז ניהלתי).

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

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

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

לא ספר אמיתי 🙂

אז מה אני חושב על פתרונות \”מהירים ואופטימיים\”?

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

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

\”לפתור את הבעיה אחת ולתמיד\” זו בעצם הגישה שאומרת להשקיע יותר בפתרון, ו\”בוא לא נתעכב\” זו בעצם הגישה שאומרת (you ain\’t gonna need it (YAGNI – להשקיע פחות.

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

צעד שני הוא לשאול את השאלות הבאות:

  • עד כמה חלק הקוד המדובר הוא קריטי להצלחת הביזנס? עד כמה הוא מורכב ואנו סובלים מתחזוקתו? – ככל שהוא יותר מורכב ורגיש, נכון להשקיע יותר משאבים ולצמצם סיכונים.
    • מצד שני: ככל שמדובר באיזור קריטי למערכת – סביר יותר להניח שנחזור ונתקן / נשפר את ה \”Retry\” אם הוא לא מספיק טוב.
  • עד כמה המערכת צעירה / בוגרת? ככל שהמערכת בוגרת – יש טעם להשקיע יותר בכדי לשמר את האיכות והיציבות שהיא כבר השיגה. במערכת ממש חדשה – תקלה אקראית עשויה להיבלע בזרם בעיות גדולות וחמורות יותר. בעיות גדולות יותר יכולות להיות באגים, אך לא פחות מכך הן יכולות להיות פיצ\’רים חשובים שחסרים ללקוחות, או אי-התאמה בסיסית לצרכים העסקיים.
  • עד כמה ההתנהגות שאנו יוצרים היא צפויה? (Principle of least astonishment) – ככל שהפתרון ה\”אופטימי\” שלנו הוא מפתיע ולא-צפוי (יחסית להתנהגות הסדירה והמקובלת של המערכת) – כך גדלה הסבירות שהוא יישבר עם הזמן, או יגרום לבעיות שיהיה קשה לצפות. 
  • עד כמה קל לדעת אם ה Retry עובד? אם יש לנו שטף של אירועים מבוקרים ומנוטרים – קל יותר להצדיק התנסות ב\”פתרון מהיר ואופטימי\”. סיכוני אבטחה (אירועים נדירים יחסית, אך אולי עם השפעה חמורה), למשל – הם מקום פחות מומלץ לעשות בו ניסויים.
    • הוספת Alerts למצבים בלתי צפויים סביב הפתרון – עשוי להיות מעשה נבון.
  • אל תנהגו בטיפשות. אל תעשו Retry על פעולות שאינן idempotent – כלומר פעולות שהפעלה שלהן מספר פעמים תסתיים בתוצאה שונה מהפעלה בודדת (למשל: חיוב של כרטיס אשראי).
בקיצור: It depends.
שיהיה בהצלחה!

[א] מונח המתאר את ה\”חובות הטכנולוגיים במוצר\”. כמו חובות פיננסיים, נבון לעתים לקחת אותם – אבל אם הם תופחים הריבית היא בלתי-נשלטת ועלולה להוביל לאסון.
במילה Debt, אגב, לא מבטאים את ה b. זה נשמע כמו \”Technical Det\”.

איך (לא) לבצע בחירות טכנולוגיות עבור המערכת שלכם?

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

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

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

  • דוכן ראשון – עם 6 פריטים בלבד.
  • דוכן שני – עם מבחר של כ-30 פריטים.

כיצד יהיה שונה דפוס הקנייה בשני הדוכנים?

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

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

כי אנשים מתקשים לבחור. בחירה בין 30 פריטים היא קשה יותר מבחירה בין 6 פריטים – כך שהדוכן בן ששת הפריטים מצליח למכור יותר (חיזוק חיובי לחנויות מעצבים / high-end).

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

  • אם פעם היה Framework דומיננטי לכל שפת תכנות (STL ל++C, בג׳אווה JEE, ובדוטנט – מה שמייקרוסופט סיפקה) – עם הזמן מבחר ה frameworks הלך וגדל.
    ג׳אווהסקריפט היא הדוגמה הקיצונית לשימוש ב micro-frameworks. הבדיחה הנפוצה היא על מפגש מתכנתים שמתנהל כך: ״שלום, אני ליאור – וגם אני כתבתי Framework לג׳אווהסקריפט…״.
  • כיום, ה Technology Stack – “מתפוצץ מאפשרויות”: יש לנו את Nano Server, Deis, Fastly, את Spark (על כל חלקיו) ואת Kubernetis. כל שבוע יוצאת טכנולוגיה / ספריה חדשה ומדליקה!
    האם אתם כבר מכירים את Axton, Sleepy Puppy, שפת Nim ושפת Julia? אל תחשבו אפילו לומר שלא בדקתם את OneOps של וולמארט…
  • בסיסי נתונים: אם פעם הבחירה הייתה ברורה (אורקל – באנטרפרייז, SQL-Server – ל Microsoft-shop, ו MySQL לסאראט-אפים) היום יש יותר מכמה בסיסי נתונים רלוונטיים לכל סביבת עבודה. למשל: MongoDB, Redis, ו Cassandra יכולים לשמש את כולם – בנוסף לבחירות המקוריות. בעולם ה open source כבר אין בסיס נתונים דומיננטי אחד: MySQL, אולי PostgreSQL ואולי MariaDB? מה עם Aurora?
  • המושג ״Polyglot Persistence״ או ״Polyglot Programming״ (המתאר סביבה רבת-טכנולוגיות) עלול לעזור ולהצדיק מצב של שימוש בריבוי טכנולוגיות במקביל [ד] – אבל הוא לא מפשט את הבחירה.
    Stack טכנולוגי רחב מידי – הוא עדיין בעיה תפעולית ממעלה ראשונה. איזו רמת עומק ממוצעת אתם רוצים בטכנולוגיות שאתם עובדים עימן? ככל שיהיו לכם יותר טכנולוגיות – רמת העומק הממוצעת תפחת.

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

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

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

בעולם הצרכנות יש לנו יוריסטיקות לקנייה:

  • המלצה של מגזין / מקור ״בעל סמכות״ (לכאורה!) – תשפיע עלינו לטובה.
  • המלצה של אדם ממשי אחד שאנו מרגישים שהוא דומה לנו – לרוב תהיה חזקה יותר מהמלצה של “מקור סמכותי”.
  • מה שיקר יותר – בטח טוב יותר [א].
    • טרנד של הדורות האחרונים: מה שחדש יותר – בטח טוב יותר [ב].
  • הצמדות למותגים ידועים.
    • למרות שמי שבחן פעם מוצרים לעומק – יודע שזו יוריסטיקה חלשה: מותגים ״חזקים״ לעתים קרובות מייצרים גם מוצרים חלשים / לא-תחרותיים / יקרים ללא הצדקה.
מה אנו עושים כ״מקצועני-תוכנה״? – בערך את אותו הדבר!
אנו נצמדים לשמות גדולים, למותגים, להמלצות של אתר / בלוג , והמלצות של חבר, לדברים חדשים ויקרים (במידת האפשר). הבחירות שלנו הופכות יותר “אופנתיות”, ופחות “לוגיות”.
השיטות הללו הן יוריסטיקות לא-מעולות, אבל גם לא בהכרח גרועות: להיות מ Mainstream אומר שיש יותר רפרנסים וחומר על הטכנולוגיה, יותר אנשים שמכירים (ואפשר גם לגייס), ואם יש בעיה ממש מציקה – אז יש ecosystem שיש לו מניע כלכלי לפתור את הבעיה הזו.

אז זו לא בחירה “מקצועית נטו”?

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

הנה כמה קריטריונים שעוזרים להבין אם מדובר בתהליך בחירה או תהליך הצדקה:
  • אם הדיון הוא קיצוני, והאלטרנטיבות מתוארות לחלופין כנפלאות (“Rocks”) או איומות (“Sucks”) – זהו כנראה דיון רגשי.
  • אם יש אלנטרטיבה יחידה: “Angular.js זו הבחירה הנכונה”, אבל לא מוכנים לדון ברצינות ב Ember.js או React+Flux (שהן דיי מקבילות) – כנראה מדובר בדיון רגשי.
  • אם מוכרים לנו את הטכנולוגיה כמו שמוכרים ביטוח – אז זה כנראה תהליך הצדקה.
    • איך מוכרים ביטוח? מתמקדים בתסריט קיצוני ומבהיל (“ואם הבית שלכם יהרס ברעידת אדמה…”) בכדי להצדיק מחיר מסוים (“לא היה עדיף לשלם 3000 ש”ח בשנה?”). כאילו שאם הבית יהרס ברעידת אדמה חברת הביטוח לא תציב בפנינו כל קושי אפשרי בכדי שנקבל כמה שפחות כסף… תוך התעלמות מסבירות (מעולם לא קרה…) תוך התעלמות מנזקים אחרים (שחלילה – עלולים להיות קשים יותר)….
    • איך מוכרים טכנולוגיה כמו ביטוח?
      • “אבל בריילס יש הגנה בפני cross request forgery – וב Java האתר יהיה פרוץ להתקפות (!!)”
      • או: “אם אתה צריך לטעון סט של 5GB נתונים זה ייקח פי 10 זמן (!!)” (בעוד אנו לא חיים עם dataset בגודל דומה לזה בכלל). “חבל לאבד את כל הלקוחות שמייד יעזבו את החברה, וישמיצו אותה בפייסבוק ללא הפסקה, יום – ולילה!”
מק עם סטיקרים – הצהרה אופנתית!

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

ניתן “להעריץ” ספריות ומותגים (“גוגל”, “HashiCorp”), גם מבלי להבין או להתנסות במוצר.
כיום אנו נוטים להעריץ חברות אינטרנט בעלות Scale גדול ושווי בורסאי גבוה (“חדי-קרן”). העתקת הארכיטקטורה של חברה שמטפלת בנתונים בנפח 20PB ביום – לא בהכרח ישרת את הצרכים העסקיים של החברה שלנו. הדרך להגיע ל Scale גבוה היא בפשרות כואבות. האם אנו מעתיקים פשרות שהארגון שלנו לא צריך לעשות?פידבקים חיוביים מחברים / עמיתים (“וואו! אתם עובדים עם unikernel? דוקר זה באמת כ”כ 2015!!!!”) – תבצר את עמדתנו. האם לא נרצה עוד מהתחושה הטובה הזו?!

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

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

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

פירמידת הצרכים של המתכנת?!

בענייני אופנה ניתן לזהות עוד 2 תופעות:

ישנן אופנות מתפרצות, שקצב האימוץ שלהן הוא לא-רגיל. פעם זה היה Rails, מאוחר יותר אולי MongoDB והיום זה Docker וגם Node.js ו Angular.js. הן לרוב פורצות בהובלת מובילים טכנולוגיים (שהם גם כריזמטיים), אבל לא דיי בזה להסביר את התרחבות התופעה – אולי ניתן להסביר אותה בעזרת שילוב של כמה מרכיבים [ה].
בקצב אימוץ של אופנה מתפרצת, סביר שרוב האנשים שהולכים לאחר האופנה לא ידעו להסביר מדוע באמת הכלי המדובר טוב יותר עבור המקרה שלהם. הם גם כנראה לא יהיו בעלי הבנה עמוקה בטכנולוגיה. בעיניים נוצצות הם יאמרו (“אני? אני עוד לא כזה מבין… יש לי עוד הרבה מה ללמוד”). הם יידעו בעיקר לדקלם כמה סיסמאות נפוצות “ב node.js אפשר לפתוח מיליון (!!) connections מלפטופ פשוט. אתה יכול לעשות את זה בג’אווה?!?”.

כמו בכל אופנה יש אנטי-אופנה. שימוש ב Erlang או שימוש ב MySQL כפי שמתארים Wix או Uber כ”בסיס הנתונים ה NoSQL-י הטוב ביותר”. אנטי-אופנה זה לבחור את הכלים הכי לא-נוצצים, ולהוכיח לכולם שבעזרת קצת כשרון – ניתן לעשות איתם הכל, ואפילו יותר מ buzz.js@node.

אני, דווקא מעריך יותר את האנטי-אופנה. אולי זה פשוט הסגנון האופנתי האישי שלי….

מי בוחר את הטכנולוגיה?

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

“חצץ”?!
פעם עבדתי עם צוות שנדרש לשכתב את כל קוד הג’אווה שלו ל Stored Procedures בתוך בסיס הנתונים. זו הייתה החלטה של ארכיטקט מאוד מאוד בכיר – ולמרות שהייתה התנגדות רבה, ההחלטה עמדה בעינה.
הצוות היה מאוד מתוסכל (ראיתם פעם אלפי שורות קוד של stored procedures?!) – ולאחר כמה חודשים של עבודה מייסרת, הארכיטקט המאוד מאוד בכיר נאלץ לוותר על ה”אוטופיה ארכיטקטונית” שלו (שלא היה בה שום דבר להתגאות …).

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

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

מקור: Technology Radar של Thoughtworks

ומה אומרת הארכיטקטורה?

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

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

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

הטכנולוגיה? אולי אי-אפשר בלעדיה – אבל היא גם מפריעה!

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

בסופו של דבר חשוב בצד הארכיטקטורה:

  • לצמצם את מספר הטכנולוגיות החופפות – כי ריבוי טכנולוגיות מקשה על תחזוקת המערכת, והארגון שלה.
  • למצוא טכנולוגיות “מספיק טובות” – שלא יגבילו אותנו יותר מדי.
    • ריילס – מציבה Overhead גדול של ביצועים. היא לא מספיק טובה למערכות עם High Throughput.
    • כתיבה ב C לא תציב מגבלות High Throughput אבל הקוד ייכתב באטיות ויהיה קשה לארגון ותחזוקה. היא לא מספיק טובה לצרכים עסקיים של מערכת שתפותח מהר.
אבל לבחור IDE? לבחור Application Server? ארכיטקטורה שכוללת או לא כוללת Docker? להתעקש רק על שפת תכנות ספציפית? – זו כנראה לא ממש “ארכיטקטורה”, אלא ארכיטקט שמנצל את כוחו – כי גם הוא רוצה לבחור טכנולוגיות! מה לעשות – גם הוא בן-אדם שחובב טכנולוגיה.
יש כמובן גם שיקולים מעשיים / פרויקטליים:
  • איזו טכנולוגיות אנשים כבר מכירים – ויש להן תמיכה רחבה?
  • אלו טכנולוגיות יעזרו למשוך לחברה את ה talent הנדרש – לכתיבת מערכת מורכבת ומאתגרת?
  • עלויות של טכנולוגיות שאינן חופשיות.
  • לא פחות חשוב: כיצד בונים מעורבות של הצוות הקיים? לרוב ע”י מתן משקל לדעתם בבחירת הטכנולוגיה. אפשר וחשוב להציב תנאי סף: מחיר, מגוון, או כל מגבלה שהיא עניינית וניתן לשכנע בה את “הרוב השקול”.

זו איננה ארכיטקטורה!

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

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

אי אפשר כמובן לומר – כי לא מספרים לנו בתרשים על המערכת, אלא על סט הכלים שמרכיב אותה.
זה כמו לנסות ולהבין חפץ – ע”פ הכלים שבהם הוא נבנה: “משור, פטיש ומסמרים” – מה זה יכול להיות?! כסא, שולחן, אולי ספרייה או סוכה? האם כל מה שנכון וטוב לבניית כסא – נכון ומתאים גם לבניית סוכה?

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

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

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

סיכום

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

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

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

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

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

—-

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

על העוצמה הטמונה ב”טכנולוגיות משעממות” [דעה] (פוסט שלי)

Carburetor 21: Predictions for 2016 פרק (מוצלח!) של הפודקאסט “רברס עם פלטפורמה”, שהחלק הראשון שלו עוסק בבחירת טכנולוגיות.

—-

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

[ב] – בכנס GOTO; Berlin 2014 הייתי בהרצאה על big data של הוצאת ספרים גדולה. נתון מעניין שהם הביאו שספרי מחשבים נמכרים בעיקר בשנתיים-שלוש לאחר שיצאו. באופן דומה – גם ספרי ניהול אימצו דפוס דומה, אם כי שיטות הניהול לא משתנות משמעותית במחזורים הקצרים מ 10-15 שנים.

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

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

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

עקרון זה נקרא בפסיכולוגיה עיקרון העקביות (The consistency principle). הצורך של אנשים בעקביות – וההתנהגות שלהם שנוטה באופן טבעי לחזק את העקביות בעולם האישי שלהם.

מקור: Influence של ציאדיני, פרק 3.

[ד] הבהרה: אנשים רציניים שידברו על Polyglot Programming יציגו גישה לפיה עובדים עם מספר כלים – השונים מהותית זה מזה.

“node.js ו Java” או “פייטון, רובי, ו PHP” – הן לא דוגמאות ל Polyglot Programming בריא – אלו שפות דומות מדי זו לזו, וריבוי שלהן בעיקר יציב בעיות תחזוקה. דווקא שילוב כמו “PHP וג’אווה” או “רובי ו ++C” – הם שילובים רלוונטיים.

[ה] הספר “The Tipping Point” (המעניין!) של מלקולם גלוודוול מנסה לנתח את הדינמיקה של תופעות שכאלו.