ליאור בר-און לפני 10 חודשים כ- 10 דקות קריאה
חיסכון או אסון? מתי וכיצד ליישם YAGNI בסטארט-אפ?
עקרון ה YAGNI (קרי: ״You Ain’t Gonna Need It״) נזכר לראשונה בסדרת הספרים של Extreme Programming (בקיצור: XP) מהמחברים רון ג’פריס וקנט בק.
באחד הספרים הראשונים, רון מתאר דילמה שבו הוא צריך לכתוב אפליקציה שצריכה לשמור (persist) נתון מסוים. הוא מתלבט בין לכתוב את הנתונים לקובץ (הכי מהיר / פשוט) או להשתמש בבסיס נתונים (הגישה המקובלת). תחת עיקרון ה YAGNI הוא מחליט להשתמש במערכת קבצים – כי לא צריך יותר מזה בשלב הזה. אם יהיה צורך מורכב יותר – הוא יעשה Refactoring ויעבור לעבוד עם בסיס נתונים.
אני זוכר שהדוגמה הזו נראתה לי מוגזמת מדי. הרבה רעיונות בספרים של XP היו מוגזמים – באופן מוצהר ומכוון. בסיס נתונים הוא דבר דיי פשוט וקל לקנפוג – למה לעזאזל שאעבוד עם מערכת קבצים כשברור לי שהצורך בבסיס נתונים הוא ״מעבר לפינה?״ הרי ״ברור״ שאגיע לשם.
YAGNI הוא עיקרון נהדר, כי בתעשיית התוכנה אנחנו מגיעים לעשות הרבה Over-Engineering (הנדסת-יתר). החיסרון שלו, כמובן, שהוא עלול להוביל אותנו ל Under-Engineering (הנדסת-חסר), לפתרונות תוכנה פשטניים, שלא יעמדו במבחן הזמן ויאלצו אותנו להשקיע הרבה עבודה על מנת להביא את המערכת בחזרה לאיזון טוב בין הנדסת-יתר וחסר. מה שפעם נקרא ״code and fix״, קדד מהר – תקן בהמשך.
ברור לנו שגם הנדסת-חסר וגם הנדסת-יתר – אינם טובים.
התכונה המעניינת שמחזקת את עקרון ה YAGNI, היא שבעוד הנדסת-יתר ניתן לעשות עד אינסוף (כלומר: הסטייה האפשרית היא בטווח עצום / בלתי-נגמר), הסטייה האפשרית בהנדסת-חסר היא קטנה יותר: אם נחסיר יותר מדי – המערכת תפסיק לעבוד. יש לנו boundary ברור שיעצור אותנו מלהמשיך.

מכאן עולה מסקנה: גודל הטעות האפשרית בהנדסת-יתר – היא גדולה יותר מזו של הנדסת-חסר. מכאן אפשר להסיק שמשתלם תמיד להעדיף הנדסת-חסר על הנדסת-יתר, וכך לצמצם את פוטנציאל הטעות.
סטארט-אפים ישראלים מפנימים את המסר הזה דיי מהר: אין להם זמן לאבד, הסיכוי להסתבכויות של הנדסת-יתר עשויה להיות מכת מוות לסטארט-אפ שלא עומד ב milestones חשובים – ולכן כדאי להעדיף את הנדסת-החסר (בדמות קבלה שיטתית של עקרון ה YAGNI) על פני ״האלטרנטיבה השנייה״.
אַלְיָה – וקוץ בה
רגע של עברית: אַלְיָה היא רקמת שומן בזנבם של חלק ממיני הכבשים. ה"קוץ" הוא חלק פגום בנתח הבשר או לפי פירוש אחר: קוצים שדבקו לפרוות הזנב ומקשים על מלאכת הגֵּז.
הנה סיפור קצר: עבדתי בחברת הזנק שהייתה צריכה לבנות ממשק משתמש לאיש התמיכה הראשון. רבות מהפעולות היו חיפושים בבסיס הנתונים, ולכן החליטו שאין צורך לבנות כרגע UI. איש התמיכה ילמד SQL ויבצע את השאליתות שלו ישירות מעל בסיס הנתונים בפרודקשיין. So far – so Good.
עבר זמן קצר, ואיש התמיכה התקשה לעבוד עם זמנים שונים שהופיעו בבסיס הנתונים: בסיס הנתונים הציג זמנים ב UTC, בעוד החברה עבדה במדינת קליפורניה. כל פעם, היה צריך איש התמיכה לחשב בראש את ההפרש. הפתרון הפשוט היה לשנות את הגדרות בסיס-הנתונים לעבוד בזמן קליפורניה, מה שפתר את הבעיה – בזמן קצר ביותר. הפתרון יושם בשעה ואיש התמיכה היה מרוצה, ועבד יעיל יותר.
כמה שנים מאוחר יותר, בסיס הנתונים עדיין עבד בחברה הזו בזמן קליפורניה. הסיכונים האפשריים משינוי ההגדרות בשלב הזה – עלולים להיות אסוניים, ובכל נקודת זמן בה נעשתה הערכה טכנית הוחלט: מוטב להמשיך לשלם את חוסר הנוחות בעבודה בזמן קליפורניה, מלסכן את המערכת לכניסה לסחרור של טעויות בזמנים.
כלומר, להנדסת-חסר יש חסרון מובנה: היא עלולה לקבע מצב לא-טוב שבשום נקודת זמן לא משתלם לצאת ממנו.

אם היינו בוחרים בהנדסת-יתר היינו עשויים לשבת עם קוד מיותר / חסר-שימוש, שהשארתו איננו נזק מתמשך למערכת. הנדסת-חסר נוטה לכאוב לנו יותר בשוטף.
חשוב לציין: גם בהנדסת-יתר, יש מצבים בהם השארות הקוד היא כאב מתמשך. זה קיים, אך פחות נפוץ.
מציאת איזונים נכונים
השתכנעו אולי בשלב הזה שכדאי לנו להיזהר מהנדסת-יתר, אבל גם הנדסת-החסר ו״הצמדות״ לעקרון ה YAGNI, כלשונו, לא תוביל אותנו למצב בר-קיימא.
למען ההגינות והדיוק: דיונים על גבולות ה YAGNI היו ויהיו בעתיד. למשל, מרטין פאוולר כתב בבליקי עוד ב 2015:
Yagni only applies to capabilities built into the software to support a presumptive feature, it does not apply to effort to make the software easier to modify.
אני שם את הדיונים האלו בצד. בסוף, ביומיום של חברת סטארטאפ, יש לחץ אמיתי להתמקד בתוצאות מיידיות המשפיע על כל רוחב הארגון. בד״כ לא קוראים לזה בשם ״YAGNI״ – פשוט רוצים לעמוד ביעדים העסקיים. גם את הניתוח המדוקדק: ״האם אני מצמצם יכולת למשתמש או קלות לשינוי-התוכנה בעתיד״ – לא ממש עושים. אנו רואים דרך לחסוך עבודה שעדיין משיגה את התוצאה העסקית – ועושים אותה.
נפתח בכמה הנחות-יסוד:
- גדיעת הנדסת-יתר, היא פעולה חשובה. באמת יש אינספור דרכים לסבך את התוכנה שלנו מתוך רצון טוב – אך בצורה איומה.
- העתיד בתוכנה איננו ברור, במיוחד בחברות סטארט-אפ. לא פעם יבואו אנשי המוצר וילהיבו בחזון לשנה הבאה, המהנדסים יתכוננו לקראתו בדזיינים וקוד – רק כדי להיווכח שהעתיד הזה מבושש להגיע – או בכלל השתנה, ואיננו רלוונטי עוד.
- הנדסת-חסר יכולה להיות הרסנית: חסכון השבועיים עכשיו – עלול לגרום לחודשי עבודה נוספים בעוד שנה.
- אסתכן, בלי ניתוח מעמיק, להעריך שבסטארט-אפים ישראלים מדובר בנטייה ברורה להנדסת-חסר, וסה״כ: טוב שכך. תראו את ההייטק היפני, שבו יש נטייה תרבותית להנדסת-יתר (מתוך: תרבות של מצוינות ודיוק). כמה חברות תוכנה יפניות מצליחות אתם מכירים?
מה באמת אפשר לעשות?
אנסה להציע כמה קווים מנחים, שיעזרו לנו לאזן בצורה טובה יותר בין הסיכון להנדסת-יתר והסיכון להנדסת-חסר. נקודות ההחלטה העלו יעלו מתוך Design או מתוך דיון / דילמה מקצועית.
- קודם כל, עלינו להציג מספר חלופות אפשריות. אם אנו דנים בחלופה יחידה – בהכרח אנו פועלים עם ״שטח מת״ משמעותי. זה עקרון בסיסי של תכנון תוכנה.
- למשל: הדילמה האם להשתמש באפליקציה בבסיס-נתונים, או במערכת קבצים.
- כאשר יהיו לנו מספר חלופות, לא פעם הן יפלו לקטלוג אוטומטי של ״פתרון זול/פשוט ופחות טוב״ מול ״פתרון טוב אך יקר״. זה השלב שנרצה להפעיל את השאלה ״Do we Gonna need it״? (להלן DWGNI)
- התרבות הכווינה אותנו לחשוב ש "you get what your pay for״, קרי: יקר יותר = טוב יותר. חשוב שנשתחרר מההטיה הפסיכולוגית הזו ונבחן את האלטרנטיבות מבחנים אחרים.
- אילו מבחנים ניתן להפעיל:
- מבחן הדחייה, מה יקרה אם נדחה את ההחלטה? כמה יקר יותר יהיה לבצע את ההחלטה בעתיד (נניח: עוד שנה) ולא עכשיו? בארכיטקטורת תוכנה – נהיה חכמים יותר ככל שהזמן עובר, ולכן כדאי לדחות החלטות כאשר אפשר, ואפילו שווה לשלם על זה קצת.
- מבחן מחיר ההחלטה השגויה לאורך זמן, מה צפוי להיות המשמעות של בחירה באופציה א׳ או באופציה ב׳ לאורך זמן? נניח: שלוש שנים. קל לומר: ״גישה ב׳ תיצור קוד קל יותר לתחזוקה״ ולרוץ לבחור באופציה ״הטובה יותר״. כמה זה טוב יותר? אולי זה לא שווה את ההשקעה? בואו ננסה לכמת את הערך. לא פעם, בעקבות בחינה עמוקה, אנחנו מגלים שהערך הוא שולי ולא שווה את ההשקעה.
דוגמה
בואו ננסה להדגים את העקרון, על מקרה מוחשי. נניח יש לנו פיצ׳ר חדש במערכת.
דילמה: האם לחלק את הפיצ׳ר למיקרו-שירות אחד או שניים.

אנו מזהים בשירות שתי אחריויות. נניח: A. להריץ workflow ו B. לאסוף נתונים ממקורות שונים. ע״פ גישת המיקרו-שירותים טבעי שנחלק את הפונקציה לשני שירותים מובחנים. זה ייתן לנו יותר הפרדה בין השירותים ויקשה על הקוד שלנו להסתבך.
מצד שני, אפשר באמת ליצור שירות אחד ולחסוך זמן. כרגע לא ידוע על מקרה שבו אחריות B תהיה בשימוש שלא בצמוד לאחריות A. לא צריך יותר משירות אחד, וזה יותר פשוט למימוש. כל קריאה בין אחריות A לאחריות B תהיה קריאה של פונקציה (קל) ולא קריאה של API ברשת (עוד עבודה).
מה עדיף? בואו נבחן ע״פ הקריטריונים.
מה המחיר לדחות את ההחלטה בשנה? נתחיל עם האופציה הזולה למימוש (שירות אחד) ונתקדם משם.
- כמה מהר השירותים הללו עומדים להתפתח? מפתח יעבוד עליהם חודש, או כמה מפתחים יעבדו עליהם רבעון? פיצול של שירות לשניים עשויה להיות עבודה לא קטנה – וגודלה בקשר ישיר לכמות הקוד שכבר נכתב.
- יש הבדל בין להעביר קוד בלבד, מול להעביר קוד + נתונים. אם אחריות B (השירות שאולי נפצל) כוללת בתוכה נתונים, במיוחד כאלו שמתעדכנים כל הזמן – המחיר לפיצול שירותים הופך להיות יקר הרבה יותר.
בקיצור: התשובה עשויה שונה ממקרה למקרה. אם אנחנו מרגישים שמחיר ההחלטה בעוד שנה הוא קטן (נניח: שבוע-שבועיים עבודה) – ייתכן ונרצה לדחות אותה ולהחליט בעתיד, כאשר נהיה חכמים יותר. אם המחיר יקר – כנראה שנרצה להתאמץ עוד ולקבל החלטה עכשיו.
מה מחיר ההחלטה השגויה לאורך זמן?
טעות קלאסית כאן, היא לבחון רק את האופציה הזולה, מתוך הנחה סמויה שהיא הטעות האפשרית. אנחנו נבחן את שתי האופציות.
נניח שבחרנו בשירות אחד, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?
- הקוד של אחריות A ואחריות B עלולים להתערבב (כי אין הפרדה טבעית ביניהם), ויהיה קשה יותר לתחזק ולהפריד ביניהם.
- השירות המשותף עלול לנהל תלויות של שתי אחריויות ולא אחת – יותר תלויות אם שירותים שאולי לא רצינו בהם. יהיה קשה להבחין אם תלות נוצרה בגלל אחריות A או אחריות B.
- אם כמות הקוד גדלה והסתבכה – כנראה ההפרדה בשלב הזה בין אחריות A ואחריות B תהיה מאמץ משמעותי וגדול. יהיה עלינו לפנות הרבה זמן כדי להפוך את ההחלטה. האם זה ישתלם לנו, או שנמשיך ״לסבול״ כי ״מחיר השנוי הוא גבוה מדי בכדי להצדיקו בשלב הזה״?
כמובן שבראייה הזו, ניתן לעשות צמצום-נזקים (mitigation) לחלק מהסעיפים. נניח לנהל את אחריות A ו B כמודולים נפרדים בשירות שבניהם יש הפרדה נוקשה – אם יש לכם כלים כאלו בארגון.
נניח שבחרנו בשני שירותים, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?
נניח שהשירותים דיי פשוטים, או הקשר בין אחריות A ו B הוא פשוט ומאוד ברור וטבעי. הפרדנו את השירותים על מנת למנוע ערבוב, אך לאורך הזמן אנו מבינים שאין בכך באמת בעיה. יצרנו הגנה שאינה באמת נדרשת.
- כל קריאה חדשה בין אחריות A לאחריות B כוללת יצירה של API חדש – תקורה ברורה בעבודה.
- גילינו שבעצם יש קשר הדוק בין A ל B ולא היה נכון לראות בהן אחריויות שונות – ואנו עובדים קשה על התיאום בין שירות A לשירות B.
- הביצועים של המערכת נפגעו קשות מההפרדה בין A ל B, או אולי היכולת לעשות troubleshooting מורכבת יותר.
בתסריט שבו התברר שההפרדה היא הנדסת-יתר כי הקוד פשוט למדי, הנזק גם אינו גדול. יש לנו תקורה, אך היא במכפלה של בסיס-קוד קטן.
התסריט של קשר הדוק בין A ל B שלא היה נכון להפריד ביניהם יותר קשה. אם רק שילוב של אחרית A ו B באותו שירות יפתור את התקורה, ייתכן ונרצה לשלב ביניהם בחזרה. לרוב קשיים כאלו הם קשיים שניתן למתן (mitigate) במאמץ נקודתי: שיפורי ביצועים או שיפור ה API.
ניתוח שכזה עוזר גם לנהל סיכונים בלי קשר. אולי למשל, אם עשויות להיות הרבה קריאות בין אחריות A ו B אולי עדיף שנגדיר API גנרי בין שני השירותים, שיכול לטפל במגוון המקרים, בלי להוסיף APIs נוספים כל פעם. למשל:
fun collectData (type: DataType): List<DataItem>
סיכום
בחזרה לשאלתנו: האם סטארט-אפים זקוקים לעקרון ה YAGNI?
כן, הם זקוקים לו, כי הסיכון מהנדסת-יתר הוא ממשי ומשמעותי. יש מהנדסים שאם תתנו להם ״לשפר תשתיות״, הם לעולם לא יסיימו. אנשים טובים ואיכותיים.
נוח מאוד לקבוע לקבוצת ההנדסה כללים ברורים, כמו ״אנחנו סטארט-אפ ואנחנו ולכן עושים YAGNI״ – אבל אז הסכנה היא שהמערכת תקלע למקום לא טוב, ממנו לא תצליח לתמוך בביזנס – מה שלא טוב לבריאות הארגונית.
שווה וכדאי לעשות את הדרך הקשה, ליצור תרבות של ספקנות וניסיון תדיר לקצץ ב scope של פיתוחים – תוך כדי בחינה מתמדת של ההשפעות העתידיות. כפי שקצת ראינו, עצם הבחינה הזו יכולה לאפשר לנו למצוא וריאציה מוצלחת יותר של הגישה המקורית.
כמו כן, נכון ״לכייל״ את המערכת. אם הגענו למסקנה שיש לנו נטייה מוגזמת לאחד הכיוונים (הנדסת-יתר או הנדסת-חסר) – להעלות את זה למודעות ולדרוש מאנשים ״להטות לתיקון״. כמובן שיותר קל לומר את זה מאשר לעשות 🙂
שיהיה בהצלחה!