מדוע סחיפה לעולם לא מתה Image: AI generated

חוזרת בדיוק למקום שתיקנת

בניתי כלי שסוגר סחיפה.

התזה של yongol פשוטה. החלטות שלא חיות במקור סמכותי יחיד (SSOT) — נסחפות. לכן שמתי את ההחלטות ב-SSOT, והפכתי את הקוד להיטל חד-פעמי (projection) שמצויר מחדש בכל מחזור ייצור. סחיפה של לוגיקה עסקית — שבה עמודה שהוגדרה כ-BIGINT מתגנבת חזרה ל-INT אחרי ריפקטורינג אחד — נסגרה כך.

אבל לפני זמן לא רב, כשניתחתי אשכול פגמים בקוד ש-yongol ייצר, ראיתי משהו מוזר. הפגמים התוודו באותו מבנה תחבירי בדיוק. “איסוף import מנותק מ’האם הטיפול באמת משתמש ב-time’.” “הסקת requiredness מנותקת מ’האם ה-API היעד באמת דורש פרמטר זה כ-required’.” פרמטר נתיב הוא תמיד required, import רק כשהטוקן בשימוש בפועל. אותן החלטות מבניות בדיוק היו קבועות כפרוקסי מקומי נוח בתוך קוד המחולל, מבלי שנרשמו באף SSOT.

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

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

השורש: החלטה ופרט הם דברים שונים

נבנה מחדש מהפיזיקה.

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

הנדסת תוכנה צפתה בהתפרקות הזו מזמן. חוקי האבולוציה של תוכנה של להמן (Lehman) אומרים שמורכבותה של מערכת E-type גדלה אלא אם קיים מאמץ מפורש לצמצם אותה (1980). פיזיקת המידע יורדת עמוק יותר. לנדאואר (Landauer) הראה ב-1961 שאפילו מחיקת סיבית אחת דורשת עלות תרמודינמית מינימלית (kT ln 2). שינוי מידע ושמירתו אינם חינמיים מעיקרון. כדי לשמור על החלטה במקומה, צריך להוציא אנרגיה באופן מתמשך.

כדי שמידע ישרוד, נדרשים שני דברים. אחסון סמכותי (authoritative store), והיטל מחדש פעיל ובלתי פוסק משם (error correction). כך פועל ה-DNA בגופנו, וכך פועלים סיביות זוגיות באחסון דיגיטלי. שומרים את המקור בנפרד, ובכל פעם משחזרים ממנו.

סחיפה מתרחשת כשהשחזור הזה נשבר. המנגנון אחד. אני קורא לו קשירת פרוקסי (proxy binding). כשהמדיום לא מבחין בין החלטה לפרט ולא משמר אותם בנפרד, האדם הבא (או הסוכן הבא) לא קורא את ההחלטה מהמקור הסמכותי אלא מסיק אותה מחדש מאות-מתאם נוחה שנמצאת בסמוך. “העמודה הזו היא timestamptz, אז כנראה צריך import של time” — ניחוש כזה. ברוב המקרים זה נכון. ולכן זה מסוכן. לפעמים זה שגוי, וכשזה שגוי ההחלטה נעלמת בשקט.

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

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

למה היא עולה למעלה

עד כאן — סיפור ידוע. מה שחדש בא אחר כך.

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

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

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

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

א-סימטריה של הטיפול: מה שניתן להצהיר לעומת מה שניתן רק לאמת

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

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

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

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

מה שכתבתי במאמר אחר — “הפכו ביקורת אנושית לקוד” — מכוון בדיוק לנקודה הזו. יש הבטחות שניתן להצהיר עליהן, ו-SSOT שומר עליהן. ויש הבטחות שלא ניתן להצהיר עליהן, ושער שומר עליהן. אי אפשר לכתוב באף SSOT שהקוד שהמחולל מפיק יתקמפל. רק על ידי הרצת קומפילציה בכל פעם. בלי השער הזה, ההבטחה “generate הצליח = בנייה אפשרית” צפה מחוץ לארכיטקטורה, ו-validate עובר 0/0 בזמן שהתוצר שבור.

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

אותו נהר, סכרים אחרים

המבנה הזה חוזר גם מחוץ לקוד.

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

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

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

מסקנה: לא השמדה אלא דחיפה כלפי מעלה

המלחמה בסחיפה לא יכולה לשאוף להשמדה. השמדה בלתי אפשרית. כי הכלי הסוגר תמיד פותח את השכבה הבאה.

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

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

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

מאמרים קשורים

לקריאה נוספת (חיצוני)

מקורות

  • Perry, D. E. & Wolf, A. L. (1992). Foundations for the Study of Software Architecture. ACM SIGSOFT Software Engineering Notes, 17(4), 40-52. ACM — ההבחנה בין שחיקה (erosion) לסחיפה (drift).
  • De Silva, L. & Balasubramaniam, D. (2012). Controlling software architecture erosion: A survey. Journal of Systems and Software, 85(1), 132-151. ScienceDirect
  • Lehman, M. M. (1980). Programs, Life Cycles, and Laws of Software Evolution. Proceedings of the IEEE, 68(9), 1060-1076. IEEE — חוק גידול המורכבות וחוק השינוי המתמשך.
  • Landauer, R. (1961). Irreversibility and Heat Generation in the Computing Process. IBM Journal of Research and Development, 5(3), 183-191. IBM — העלות התרמודינמית המינימלית של מחיקת מידע.
  • Shannon, C. E. (1948). A Mathematical Theory of Communication. Bell System Technical Journal, 27, 379-423. DOI — היסודות של מידע, אנטרופיה ותיקון שגיאות.
  • Cunningham, W. (1992). The WyCash Portfolio Management System. OOPSLA ‘92 Experience Report. c2.com — חוב טכני ו"הריבית על קוד לא תקין".