פעמים רבות בחיי השתתפתי בשיחה שהחלה בערך כך:
"אז למה שלא נשתמש ב 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 - הוא ריסון ניכר.
- לקרוא לשכבה 3 ולבקש את המידע.
- שכבה 3 תקרא לשכבה 2 ותבקש את המידע.
- המידע חוזר במבנה הנתונים ששכבה 2 מכירה (אך שכבה 3 יכולה להשתמש בה).
- שכבה 3 לא יכולה להעביר את מבנה הנתונים של שכבה 2 לשכבה 4 (אסור!) ולכן היא מגדירה מבנה נתונים משלה למידע שאותו היא חושפת בפני שכבה 4, לאחר המרה, כמובן.
סיכום
---
[א] בשפות שלא תומכת משתנים גלובאליים ניתן לחשוב על משתנה שעל אובייקט שנגיש לכולם.