Site icon בלוג ארכיטקטורת תוכנה

חיסכון או אסון? מתי וכיצד ליישם 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 או מתוך דיון / דילמה מקצועית.

דוגמה

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

דילמה: האם לחלק את הפיצ׳ר למיקרו-שירות אחד או שניים.

אנו מזהים בשירות שתי אחריויות. נניח: A. להריץ workflow ו B. לאסוף נתונים ממקורות שונים. ע״פ גישת המיקרו-שירותים טבעי שנחלק את הפונקציה לשני שירותים מובחנים. זה ייתן לנו יותר הפרדה בין השירותים ויקשה על הקוד שלנו להסתבך.

מצד שני, אפשר באמת ליצור שירות אחד ולחסוך זמן. כרגע לא ידוע על מקרה שבו אחריות B תהיה בשימוש שלא בצמוד לאחריות A. לא צריך יותר משירות אחד, וזה יותר פשוט למימוש. כל קריאה בין אחריות A לאחריות B תהיה קריאה של פונקציה (קל) ולא קריאה של API ברשת (עוד עבודה).

מה עדיף? בואו נבחן ע״פ הקריטריונים.

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

בקיצור: התשובה עשויה שונה ממקרה למקרה. אם אנחנו מרגישים שמחיר ההחלטה בעוד שנה הוא קטן (נניח: שבוע-שבועיים עבודה) – ייתכן ונרצה לדחות אותה ולהחליט בעתיד, כאשר נהיה חכמים יותר. אם המחיר יקר – כנראה שנרצה להתאמץ עוד ולקבל החלטה עכשיו.

מה מחיר ההחלטה השגויה לאורך זמן?

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

נניח שבחרנו בשירות אחד, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?

כמובן שבראייה הזו, ניתן לעשות צמצום-נזקים (mitigation) לחלק מהסעיפים. נניח לנהל את אחריות A ו B כמודולים נפרדים בשירות שבניהם יש הפרדה נוקשה – אם יש לכם כלים כאלו בארגון.

נניח שבחרנו בשני שירותים, זו הייתה טעות, והמשכנו כך לאורך שלוש שנים. מה המחיר?

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

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

התסריט של קשר הדוק בין A ל B שלא היה נכון להפריד ביניהם יותר קשה. אם רק שילוב של אחרית A ו B באותו שירות יפתור את התקורה, ייתכן ונרצה לשלב ביניהם בחזרה. לרוב קשיים כאלו הם קשיים שניתן למתן (mitigate) במאמץ נקודתי: שיפורי ביצועים או שיפור ה API.

ניתוח שכזה עוזר גם לנהל סיכונים בלי קשר. אולי למשל, אם עשויות להיות הרבה קריאות בין אחריות A ו B אולי עדיף שנגדיר API גנרי בין שני השירותים, שיכול לטפל במגוון המקרים, בלי להוסיף APIs נוספים כל פעם. למשל:

fun collectData (type: DataType): List<DataItem>

סיכום

בחזרה לשאלתנו: האם סטארט-אפים זקוקים לעקרון ה YAGNI?

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

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

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

כמו כן, נכון ״לכייל״ את המערכת. אם הגענו למסקנה שיש לנו נטייה מוגזמת לאחד הכיוונים (הנדסת-יתר או הנדסת-חסר) – להעלות את זה למודעות ולדרוש מאנשים ״להטות לתיקון״. כמובן שיותר קל לומר את זה מאשר לעשות 🙂

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

Exit mobile version