בפרק הקודם בסדרה לקחנו חובות.
הרבה חובות.
חלקכם כעסתם עלינו ,כמו אורח32f23# שרשם, ואולי בצדק:
״יא מניאקים, אתם מהנדסים או מנהלי מוצר נחותים? מה זה השטויות האלה להתפשר על קוד? שימות הביזנס, העיקר שיהיה פה Kafka.״
חוץ מהשפה הבוטה של אורח32f23#, והעובדה שאנחנו לא מנהלים משא ומתן עם טרוריסטים, יש משהו בדבריו.
כלומר, הוא לא בכיוון, וממש לא אהבנו איך שהוא מדבר על אנשי פרודקט.
אבל היו גם תגובות טובות וטענות מעניינות שנתנו קונטרה לתפיסה שלנו ואתגרו אותה.
המטרה של הפוסט הזה היא להאיר נקודות חשובות שלא הספקנו להתייחס אליהן בפוסט הקודם, ולהתמודד עם טענות שאנחנו לא מסכימים איתן.
אבל תחילה, הבהרה.
מפתחים זה עם
מפתחים ומפתחות לא סתם רוצים לעשות דברים נכון.
הם צריכים לעשות דברים נכון.
כשמפתחת מתעקשת על איכות הקוד, היא עושה את זה כי היא דואגת להתקדמות מהירה של הפרויקט ולמערכת יציבה.
היא לא מתעקשת על תשתית טובה ונכונה, טסטים מלאים, וקוד קריא וברור מתוך אידיאולוגיה.
כן, יש חבר׳ה שפעילים בכת הקוד הנקי, וגם אנחנו היינו שם בכמה מפגשים, מודים, אבל בסופו של דבר ההתעקשות של המהנדסת נובעת מאותה תחושת אחריות של מנהל המוצר. כמוהו, גם היא רוצה:
שיהיה קל לשחרר פיצ'רים חדשים מהר.
שהמערכת תהיה כמה שיותר יציבה ולא תקרוס בדיוק כשהלקוח עושה subscribe לשירות premium שלנו.
עכשיו כשאמרנו את זה, אפשר לחזור לשחוט פרות קדושות (או לקצוץ כוסברה שובבה, לטבעונים שבינינו) ולהמשיך לריב עם מפתחים פיוריסטים.
אנחנו תמיד משלמים את החובות שלנו. השאלה היא, מתי?
הנקודה הראשונה שעלתה לגבי החובות הטכנים הייתה - מתי אתם כן מטפלים בהם?
במילים אחרות, הבנו שאתם פשרנים חסרי אחריות, אבל איך אתם מגיעים למצב שבו החוב הטכני הזה לא הופך להיות משהו נורא ואיום שאין, פשוט אין.
״מתי הזמן הנכון לשלם חובות טכניים״ היא שאלה שחוזרת גם מהצד של מנהלי המוצר וגם מהצד של המפתחים, והתשובות לה רבות ומגוונות.
אנחנו ננסה לספר מה עובד לנו.
בכל ספרינט אנחנו מקצים בממוצע 20%-30% מהזמן של כל מפתח למשימות אינפרה (Infrastructure, יענו תשתית).
משימות אינפרה במהלך הספרינט הן כמעט תמיד טיפול בחובות טכניים קלים יחסית, למשל: העברה של חישוב כבד מסוים שאנחנו עושים on the fly לאיזשהו state ב-DB. או, סידור של API מסוים.
ההגדרה של משימת אינפרה היא: משימה שהיוזר לא ירגיש בהבדל שלה ואין לה חשיבות מהפרספקטיבה של היוזר (כלומר, מהפרספקטיבה של מנהל המוצר).
במילים אחרות, הלקוח של משימת האינפרה הוא המפתח, או אם מסתכלים לטווח הרחוק - הארגון (כולל הפרודקט והמנכ״לית) שעדיין לא מודעים לכך.
בנקודה זו, חשוב להדגיש: שיפור ביצועים של שירות מסוים, לא יחשב כמשימת אינפרה. זה פיצ׳ר לכל דבר שכן הוא משפיע ישירות על היוזר ופותר בעיה עבורו. לפרודקט מאוד חשוב שמשימה כזו תתבצע.
מלבד העבודה על משימות אינפרה במהלך הספרינט, אחת לכמה ספרינטים אנחנו עושים ספרינט אינפראי™(
(על משקל ״תמונה יפואית״, טריידמארק של הטוכנע).
בגלל שאנחנו רצים מהר מאוד, מצטברים לנו המון פיצ'רים חדשים בכל ספרינט ואחת לתקופה גם מנהלת המוצר צריכה לעצור רגע, לחשב מסלול מחדש מול היוזר, ולתת לדברים לשקוע לפני שמתקדמים למשימות נוספות.
זו הזדמנות טובה בשבילנו לקחת ספרינט שלם ולטפל בחובות הכבדים יותר, שצריך להקדיש בשבילם הרבה זמן. למשל, עבודת תשתית לפטפורמה חדשה.
בחיים לא מתפשרים על קוד. בחיים.
נקודה נוספת שעלתה היא - ע״י צמצום ה-scope, ניתן להגיע למצב בו לא מתפשרים על הקוד בכלל.
כלומר, אם רק ננהל משא ומתן אחר עם מנהלת המוצר, ונסכים על פיצ'ר עם scope מצומצם יותר (שיגדל לאט לאט בספרינטים הבאים), נוכל לצמצם את כמות העבודה ולהשיג איכות קוד אידיאלית.
נתחיל במה שאנחנו מסכימים איתו והוא צמצום ה-scope.
לגמרי.
חלק מהותי במשא ומתן עם מנהלי המוצר הוא צמצום ה-scope ככל הניתן ע״מ לאפשר לנו לאכול את הפיצ'ר בביסים קטנים.
זה חשוב משני הכיוונים:
מהצד שלנו, זה יאפשר לנו לכתוב קוד כמה שיותר איכותי ולעמוד במסגרת הזמן של הספרינט. פחות עבודה היא בתקווה פחות קוד.
מהצד של מנהל המוצר, אין טעם לייצר פיצ'ר ענק עם המון יכולות לפני שוידאנו שיש לו ערך.
אבל, וזה אבל חשוב - את ה-scope אי אפשר לצמצם באופן מוחלט.
בסופו של יום מנהל המוצר רוצה לראות ערך ללקוח, ולא יעזור לו אם נגיד לו: ״שומע, את הספרינט הזה נקדיש בשביל לעשות מיגרציה רצינית ב-DB, איך זה נשמע לך כמו scope קטן יותר לאיטרציה הראשונה?״
זה לא מעניין אותו.
ולכן, לפעמים גם כשמצמצמים את ה-scope ככל שניתן, עדיין נשארים ביד עם פיצ'ר יחסית מורכב.
במילים אחרות ועדינות - אנחנו עוד לא ראינו ארגון שמצליח לדלוור 100% מהפיצ'רים שלו, אלה שנותנים ערך ללקוח, בצורה מהירה מאוד, ומבלי להתפשר נקודתית על הקוד.
היינו שמחים לראות את זה.
לא קוראים לכם שקרנים או משהו.
אז רגע, אתם פשוט יורקים קוד ואז אחת לתקופה מסתירים את זה?
נקודה חשובה שכנראה השתמעה מהפוסט שלנו, וזו פאדיחה רצינית, אז חשוב לנו להבהיר אותה.
גם את הפשרות שאנחנו לוקחים, אנחנו מדלוורים כמו שצריך.
זה שהתפשרנו ויצרנו חוב טכני לא אומר שאנחנו לא צריכים למקסם את איכות הקוד לוקאלית.
זה אומר שיהיו טסטים.
ויהיו שמות טובים.
ותהיה הפרדה טובה בין חלקים שונים בקוד.
וזה יעבור CR כמו שצריך.
ואם צריך לקרוע ב-CR, אז נקרע ב-CR.
יתרה מכך, אנחנו יודעים מראש שאנחנו הולכים לשנות את האזור הזה בקוד, ופיתוח טוב יאפשר לנו לכתוב קוד שיהיה קל לשנות.
שאלה הבאה בבקשה.
אז במילים אחרות, אתם מפתחים פחות טובים.
תראו, זה לא יפה. אנחנו העלבנו אתכם?
אנחנו לא חושבים שאנחנו מפתחים פחות טובים כתוצאה מהפשרות שלקחנו.
נהפוכו. אנחנו חושבים שזה עושה אותנו למהנדסים טובים יותר, מהסיבה הפשוטה: פתאום הבעיה שאנחנו פותרים כבר לא אקדמית.
הבעיה הרבה יותר down to earth, ומצריכה אותנו להתמודד עם constraints רציניים יותר.
תראו, אנחנו מעולם לא היינו חכמים יותר ממה שאנחנו עכשיו, ונחשו מה - בעתיד נהיה חכמים יותר.
לקיחת חוב טכני מגיעה לרוב מתוך אמונה שבעתיד תהיה לנו הבנה יותר טובה של הבעיה.
כלומר, אנחנו לא יודעים כרגע איך לפתור את הבעיה בצורה המיטבית, כי אנחנו לא מבינים אותה בשלמותה וקשה לנו לדמיין לאיזה כיוון היא הולכת להתפתח.
זה אומר שבמקרה שננסה ליצור אבסטרקציה חדשה (שלרוב לוקחת הרבה זמן) נשלם כנראה מחיר משמעותי כדי לשנות אותה כשנהיה חכמים יותר.
אנחנו נדחה את היצירה של האבסטרקציה עד לשלב שבו אנחנו מאמינים שהמחיר שאנחנו משלמים על חסרונה יקר יותר ממחיר השינוי שלה (במידה וידרש).
נגיד יותר מזה.
להתחיל להיכנס לסאגות הטכניות בלי הצדקה פרודקטית ראויה זה דבר מסוכן, בלשון המעטה.
זה שקול ל-premature optimization, יענו מה שנקרא בשפת הילידים: אופטימיזציה מוקדם מדי.
בדומה ל-premature optimization, שם המפתחים מנסים לגרום לקוד ״לעבוד טוב יותר״ שלא לצורך (לרוץ יותר מהר, לטפל ב-use cases לא רלוונטים), גם כאן המפתחים מנסים לגרום לקוד להיות נקי יותר ו״איכותי יותר״ שלא לצורך.
בדיוק כמו שאופטימיזציה ללא הצדקה יכולה לסרבל את הקוד ולגרום לנו לחשוב ולהתעסק בדברים לא ראויים, אנחנו צריכים להימנע מאבסטרקציה או ״פיתוח נכון״ מוקדם שמייצר סרבול בקוד לפני שהבנו היטב את הבעיה שאנחנו באים לפתור.
אז תמיד אתם מתפשרים? תמיד תמיד?
כנראה שלא.
יש נקודות שגם אנחנו אומרים בהן:
Enough is enough.
Finito La Comedia
אִסְתְּרָא בִּלְגִינָא קִישׁ קִישׁ קָרְיָא
מגיע שלב שבו מנהל המוצר צריכים לשמוע את המשפט הבא: ״שמע חביבי, בשביל הפיצ'ר הנחמד הזה אנחנו הולכים לעשות עבודת תשתית.״
זה מגיע בד״כ אחרי משא ומתן רציני.
אנחנו מאמינים שכדי לתת אמירה כזו, נצטרך לסמן V על 2 מ-3 כללי האצבע הבאים:
מדובר בפיצ׳ר גדול שיש לו כבר ולידציה דיי רצינית מצד הלקוח.
מדובר בפיצ׳ר שכבר רוכב על חובה טכני רציני שלא טיפלנו בו - בושה בושה.
אנחנו כבר רואים כמה ספרינטים קדימה את הררי הפיצ'רים שנבנים על בסיס הפיצ'ר הזה.
באופן כללי, אם הגעתם למצב שבו יש לכם פיצ׳ר כזה אתם יכולים לטפוח לצוות שלכם על הגב - הוכחתם שיש פה משהו שמספק ערך לחברה ושווה להשקיע בו ספרינט אינפראי.
אלופים.
סיכום
בפוסט הקודם כתבנו על הפשרות שאנחנו עושים כמהנדסים במשא ומתן מול מנהל המוצר, ועל החובות הטכניים שאנחנו לוקחים נקודתית ע״מ לאפשר לארגון להתקדם מהר ולעשות ולידציה לפיצ׳רים חדשים.
מי שלוקח חובות צריך גם לדעת לשלם אותם (נראה לנו שככה לפחות זה עובד, ספרו לנו אם יש שיטה אחרת).
לכן, אנחנו מקצים 20%-30% מהספרינט של כל מפתח לטובת טיפול בחובות טכניים, ואחת לתקופה מקצים ספרינט שלם לכך.
קוד שיצרנו במסגרת חוב טכני אנחנו משתדלים לדלוור ברמה גבוהה ככל הניתן: טסטים, הפרדה למודולים ודגש על הסטייל של הארגון.
לפעמים גם אנחנו לא מתפשרים על האיכות של הקוד, אבל זה ממש לא הדיפולט שלנו, ואנחנו משתדלים שתהיה הצדקה ממש טובה ל״למה אסור להתפשר על הקוד כרגע״.
בשורה התחתונה, ניסינו להעביר קצת מתפיסת העולם שלנו ומהדברים שאנחנו מרגישים שעובדים לנו כצוות אנג׳ינירינג שבונה מוצר חדש ומצליח לרוץ יחסית מהר עם פיצ'רים מורכבים וגדולים תוך שמירה על סטנדרטים לא רעים.
מקווים שזה עבר.
ואם לא, אז זה עוד חוב שלקחנו ונשתדל לשלם אותו מתישהו בעתיד.
כשיהיה לזה ערך.
מנהל המוצר שלכם נשמע בחור ממש חכם. ורציני. ואדיב. וסבלני. כל הכבוד לו. איזה בן אדם