2019-10-05

TCR, או: מתי TDD כבר יוכרז כמיושן?


TCR היא טכניקת תכנות חדשה מבית היוצר של Kent Beck.

אם זה לא הרשים אתכם עד כה, אני אזכיר שקנט בק הוא הבחור שהיה שותף להמצאה של ה Wiki והבאת CRC Cards (שיטת Design) לקהל הרחב. הוא גם המציא את (Extreme Programming (XP וגרם למהפכה בעולם התוכנה. ביחד עם Erich Gamma (מה GOF) הוא כתב את JUnit ועוד גרסאות בשפות שונות. לבסוף הוא המציא את TDD - שגם השאירה חותם גדול על התעשייה.

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

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

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




רקע


קנט עבד בשנים האחרונות ב... פייסבוק. הוא חווה את הגדילה של הארגון מ 700 לכ 5,000 מהנדסים בתוך כמה שנים - מה שגרם לו לעסוק הרבה בתרבות הנדסית (Engineering Culture) בחברה - וכיצד לשמר ולהצמיח אותה.

למי שלא מבין את הקושי, כלל האצבע של התכנותיקה גורס שגדילה של ארגון הנדסי (נניח: גדול מ 25 עובדים), בקצב של מעל 50% בשנה - הוא הרסני:
  • הקונצנזוסים לגבי פרקטיקות ותהליכים - נשחקים. מתרבים הוויכוחים על בסיס טעמים שונים.
  • רמת הידע הממוצעת וההיכרות הממוצעת עם המערכת - הולכת ויורדת. אם למהנדס לוקח 6-18 חודשים להכיר מערכת בצורה טובה, אזי הארגון צומח במהירות הוא ארגון בו רוב המהנדסים הם חדשים, וחסרים ידע. ייתכן והמצב הזה נמשך לאורך שנים.
  • התרבות הארגונית, כאשר כ"כ הרבה אנשים מצטרפים - עלולה ללכת לאיבוד. לעבוד תהליך של Shuffle שבסופו תישאר תרבות אקראית, ואולי לא רצויה.
  • היכולת לכנס את השורות, ליצור ולחזק תרבות ארגונית ותרבות הנדסית - הולכת ונהיית קשה ככל שהזמן עובר. דרושה מנהיגות שמתפקדת בצורה יעילה תוך כדי סופת טורנדו.

המצב של גדילה כ"כ גדולה (יותר מ 50% בשנה) נקרא Hyper-growth או Blitzscaling, ואין לו פתרון פשוט.

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

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


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

התנגשות (merge או גרוע הרבה יותר: build שבור) - הוא נזק סביבתי חמור. כאשר ה build שבור:
א. מספר לא חסום של מפתחים - חסום לעבוד לפרק זמן.
ב. ייתכן וחלק מהמפתחים שכפלו ל branch שלהם את התקלה ואז:
     ב1 - היא עלולה לגזול להם זמן נוסף באיתור ותיקון התקלה בעצמם.
     ב 2 - היא יכולה, בצורה מסוימת, לחזור ל master מחדש כחלק מתהליך ה merge חזרה.

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


תרשים של קנט בק על ההבדלים בין TDD ל TCR


ל TDD - המתודולוגיה הקודמת של קנט, שעוזרת לשמור על קוד בשליטה וניתן-לשינוי בכל רגע, יש כשל עמוק כאשר מדברים על Scale: מתודולוגית ה TDD לא מחייבת אנשים ל commits קטנים. מתכנת יכול לכתוב 20 בדיקות, ואז לעבוד עוד יום או יומיים או שלושה עד שכולם יהיו ירוקים. הוא כבר לא יעמוד בקריטריון הרשמי של Continuous Integration [א] - אבל לכאורה הוא עדיין עושה TDD.

קנט בא לתקן את מתודולגיית ה TDD לעבוד ב Hyperscale וקרא לה Test && Commit:
הרזולוציה המחויבת מעכשיו היא של בדיקה בודדת:
  • בצע שינוי קטן של קוד.
  • כתוב בדיקה שמאמתת את השינוי.
  • הרץ את הבדיקה, ואם היא עברה - עשה merge ל master.

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

השם של השיטה מתוך הסקריפט הבא, שאמור לרוץ בסוף כל cycle קצר שכזה:

test && git commit -am working

הרעיון של "Living Integration" במקום "Continuous Integration" (שאולי עכשיו נכון יותר לקרוא לו Daily integration - הוא אטי הרבה יותר מדי מ"הנדרש") - נקרא ע"י קנט והצוות שלו בשם "Limbo", המצב האינסופי של העולם הבא.

יש קונצנזוס רחב בתעשייה, כבר כשני-עשורים לפחות, שעבודה ב batches קטנים ככל האפשר - היא דרך ליעל תהליכי עבודה ולהימנע מהתברברויות יקרות. הלימבו אמור להיות המצב התאורטי הגבוה ביותר של batches הקטנים ביותר והאינטגרציה הרציפה ביותר האפשרית. רעיונות שעלו בצוות היו "בואו נכתוב סקריפט שעושה push ל master כל פעם שקובץ נשמר" או "בואו נאסור על commit לכלול יותר מ 25 שורות שהשתנו" (שני רעיונות קיצוניים למדי, ותאורטיים - לפחות כיום).

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

קנט לא אהב את הרעיון, ולכן לפי דבריו "הוא היה חייב ללכת לנסות אותו!". בעצם נראה שקנט אהב את הרעיון - ומשם המתודולוגיה הפכה ל: (Test && (Commit || Revert - מכאן שמה הוא TCR.

מעכשיו המתודולוגיה עובדת כך:
  1. בצע שינוי קטן של קוד.
  2. כתוב בדיקה שמאמתת את השינוי.
  3. הבדיקות עברו? - בצע commit (ושלח את הקוד ל master)
    הבדיקות נכשלו? בצע revert --hard לקוד - והתחל מהתחלה.

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


הרעיון של TCR הוא די קיצוני: למחוק את הקוד אם לא הצלחנו בפעם הראשונה?

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


סקריפט שימושי להריץ בזמן שאתם עובדים על ה master branch. כולם צריכים לעבוד על ה master branch - כמובן!


שימוש מעשי



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

בספר "Extreme Programming Explained" קנט בק הסביר שב XP הוא עורך בעצם ניסוי. הוא לוקח כל מיני פרקטיקות טובות שכרגע נמצאות על רמה 2 או 3 - לרמה 10, לראות מה קורה.

"אם Code Review הוא טוב - עשו Code Review כל הזמן!" - כך נוצרה הפרקטיקה של Pair Programming, למשל - כחלק מ XP.

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

גם TDD - לא ממש התממש לפי החזון. אומרים שרוב מי שעושה TDD - לא באמת מבין את המתודולוגיה, וגם מי שמבין אותה - לא עושה אותה "By the Book".

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

ברור שהרעיונות של TCR הם אבסורדים ולא מעשיים! זהו קנט בק, וטוב שיש מישהו כמוהו - שמעלה אותם.

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

בואו נראה כמה יישומים מעשיים שמדוברים עכשיו:


לימוד / אימון


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

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

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



LDR הוא סוג של TCR. בחיי.



"30 דקות ל commit"

את הגרסה הזו אני ניסיתי בחודשים האחרונים:
  1. התחל לעבוד על קוד. 
    1. חתור למצב בו יש מצב יציב שעובר commit מהר ככלל האפשר: 5 עד 10 דקות.
    2. עדיף מאוד עם בדיקות - אבל לא כל commit שלי באמת נבדק בטסט חדש.
  2. אם הגעת ל 30 דקות בלי commit - בצע revert לקוד והתחל מחדש.

בגרסה הזו מתמקדים ב commit מהיר כל 5-10 דקות (מקסימום: 30 דקות) - ולא ב merge מהיר ל master, מה שעוזר "לדלג" מעל קושי גדול ביישום ריאלי של TCR. ה merge עדיין מכיל מספר commits, ועובר תהליך qualification ארוך ומורכב (יחסית לדרישות של TCR).

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

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

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

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


It’s 2019. Blocking, asynchronous code reviews are the dominant method for collaboratively developing software. If I draw one certain conclusion from my experiments with TCR and Limbo, it’s that blocking, asynchronous code reviews are not the only effective workflow for collaboration. While I don’t know if TCR and/or Limbo will be the future, I think that something different  is coming -- Kent Beck


TCR /w Async Merge

תהליכי ה Integration בחברות לא מאפשרים כיום לעשות merge כל כמה דקות. מי שאינו קנט בק חי בדרך כלל עם Suite של בדיקות מסוגים שונים שלוקח להם לפחות 10 דקות לרוץ, ולעתים גם כחצי שעה או שעה (זה הקצה הקצת פחות טוב...).
ויש גם את ה Code Review שיכול להסתיים רק אחרי כמה שעות. גם היסטוריה ב Git תהיה בלאגן אם כל מפתח יכניס commit כל 10 דקות. המערכות שלנו לא מוכנות כרגע באמת ל commit ל master כל 10 דקות, ע"י מאסות של מפתחים.

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

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

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

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


הנה קישור למאמר של עוד כמה וריאציות שבחור בשם תומאס מציע: BTCR, Relaxed TCR וכאלו.
אני חושב שהמסקנה מהניסיונות הללו היא ש:
  • יש משהו עוצמתי ב TCR שגורם לאנשים להמשיך ולנסות אותו. אני לא זוכר כזו דבקות מסביב ל Implementation Patterns.
  • הדרכים העיקריות ליישום שימושי של TCR עדיין לא נמצאו.
אני אישית חושב ש "30 דקות ל commit" היא האופציה המעשית ביותר. לי היא עובדת כשינוי גישה מרענן.


סיכום



  • מה אתם חושבים על TCR?
  • האם ניסיתם? האם היו תוצאות טובות?


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


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




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


---

[א] ע"פ ההגדרה: כל מפתח עושה merge ל trunk/master כל יום.



8 תגובות:

  1. https://www.youtube.com/watch?v=3QbheQitF74

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

    השבמחק
    תשובות
    1. היי,

      אני מסכים שמי שאמור לעשות revert זה אתה - ולא מישהו אחר.

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

      מחק
  3. לכתוב בפעם השנייה את הקוד שלך זה כמו לעשות code review לעצמך שלדעתי זה תמיד מבורך.

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

      מחק
  4. אם הגישה היא לעשות כל יום PUSH ל-MASTER
    אז כיצד מתמודדים עם פיתוח שכמה אחראים עליו ויהיה מוכן רק כשכולם יסיימו.
    האם על ידי TOGGLE FEATURE?

    השבמחק
    תשובות
    1. כן, בהחלט!
      Feature Flags אמורים להיות כלי נוח מאוד לשימוש. הייתי אומר שזה נכון ל CI בכלל, ולאו דווקא ל TCR.

      מחק
  5. זה לא נקרא לימבו בגלל "העולם הבא", אלא על שם הריקוד לימבו - How low can you go
    הרעיון הוא לאתגר את המפתחים לבצע שינויים כמה שיותר קטנים (ובדוקים), כדי למזער מרג'ים ולעודד טסטביליות של הקוד.

    השבמחק