ליאור בר-און לפני שנה כ- 11 דקות קריאה
לחשוב Developer eXperience (DX)
[הפוסט עודכן קלות ב 2023 – בעיקר שיפור ניסוחים.]
מדי כמה זמן יוצא לי "להמציא" מונח, שפשוט נשמע הגיוני – ואז לגלות שהוא בעצם נמצא כבר בשימוש. כך היה עם "יהלום הבדיקות" (כ contra לפירמידת הבדיקות) שפשוט כתבתי עליו בפוסט – ואז ראיתי אותו מופיע במקורות אחרים (ומכובדים). כנ"ל על DX (על משקל User eXperience, קרי UX) שהשתמשתי בו בפוסט – ופתאום נוכחתי שזה מונח שכבר בשימוש.
בפוסט – לא אציג שום טכניקה, פרקטיקה, או סגנון חדש לשיפור התוכנה שלנו, רק מטאפורה.
אז, בעצם, מה היא שווה? למה לדבר עליה?
DX היא מטאפורה חזקה – וזה הכל. אם אתם עוסקים בעולמות הניהול ו/או הארכיטקטורה – אתם בוודאי מודעים לעוצמה ולערך של מטאפורה מוצלחת. כשהמנהל של חברת דיסני רצה לשפר את חווית הבילוי בפארקים של החברה, אחד הדברים שהיו לנגד עיניו הוא איך לגרום לעובדים שעוטים תחפושות של דמויות של דיסני – לספק חוויה טובה יותר למבקרים.
הפתרון היה הכלל (להלן: מטאפורה) "כל הפארק הוא במה". כלומר: כל עובד שעוטה תחפושת הוא שחקן, ובכל רגע שהוא בשטח הציבורי של הפארק הוא חייב לנהוג כשחקן, ולשחק את הדמות עד הסוף. לא מכניסים יד לתחפושת כדי לגרד בראש, לא מעשנים סיגריה או מורידים את התחפושת בכדי לשתות.
זה עבד! ומאז המטאפורה הזו התפרסמה כדוגמה.
"DX" שימושי כמטאפורה כדי להזכיר לנו, כאנשי תוכנה, לפתח תוכנה טוב יותר בכמה היבטים. ידענו כבר קודם שקוד טוב הוא חשוב, כפי שהעובד של דיסני ידע כנראה שמיקי-מאוס מעשן זה לא לגמרי לעניין – אבל החידוד יכול לעזור לנו, כדי להישאר מפוקסים בכתיבת קוד טוב יותר.
באיזה היבט של "חווית מפתח" אנחנו מתמקדים? ממה מורכבת "חווית הפיתוח" של מתכנת?
כפי שאפשר לטעון שחווית המשתמש במיון של בית חולים לא מוגבל לאפליקציית הרישום בכניסה לבית-החולים, אלא כולל כיצד ניגש אליו הצוות, כיצד/האם מדברים מעל ראשו, וכמה רעש יש מסביב – כך DX יכול להתחיל מקוד, ולהמשיך לתיעוד, כלים, סביבת עבודה, ועוד.
בעולם בו מחסור במפתחים הוא מחסום עיקרי לצמיחה של ארגונים, DX הופך למושג שמעניין את הנהלות-הארגונים: הן בהיבט שימור/גיוס עובדים, והן בהיבט של פריון. למשל: אם כלים טובים יותר (הוצאה כספית) יגרמו למפתחים להיות מרוצים יותר ויעילים יותר – זו החלטה קלה להנהלה. אין ספק שארגוני תוכנה רוצים מפתחים מרוצים ויעילים.

Code-level DX
אני רוצה להתחיל בשימוש במטאפורה של DX לרמת הקוד, בכדי לעזור לנו לכתוב קוד טוב יותר.
נתחיל בדוגמה קצת קשוחה… אובייקט ה java.util.Date (שהוחלף לחלוטין בג'אווה 8)
println(new Date(2020, 5, 35)) // Result: Mon Jul 05 00:00:00 IDT 3920 println(new Date(120, 4, 1)) // Result: Fri May 01 00:00:00 IDT 2020
כאשר אני מאתחל את אובייקט ה Date בג'אווה ל 35 במאי 2020, אני מקבל את התאריך 5 ביולי 3920.
למה?? איך?!
אובייקט ה Date הציג Interface שהוא פשוט זוועה, במיוחד בגרסאות הראשונות שלו (ה constructor בו השתמשתי הוא deprecated):
- את השנה מצפים שאספק כהפרש מ 1900 (על משקל Java Epoch 🤯), כך כ 2020 היא 120.
- את החודש מצפים שאספק באינדקס-0, כך ש 5 הוא יוני (כי 0 הוא ינואר… לא ברור?)
- אם יש גלישה של ימים (35 ביוני) אז לא תיזרק שגיאה, אלא יוסיפו את הימים כבר לחודש הבא (ההפך מ Fail Fast) – וכך הגענו ל 5 ביולי.
ניתן לטעון: "זה ה API של ה Class. הכל כתוב בתיעוד. קראו את התיעוד – ותלמדו למה לצפות ואיך לעבוד עם זה. מה אתם רוצים?"
אני רואה את הטיעון הזה עולה מפעם לפעם, בדוגמאות פחות קיצוניות אמנם – אך אותו טיעון, וזה טיעון שגוי בעיני.
קוד טוב = DX טוב.
אם אתם רוצים לעשות DX טוב אתם צריכים לכתוב API/Interface שמפתחים ייהנו לעבוד אתו. לא… לא "יצליחו לעבוד" אתו – ייהנו לעבוד אתו!
השאיפה הזו להנאה עשויה להציב את הרף הנכון לרמת הקוד שאנו מבקשים, כפי ש"כל הפארק הוא במה" עזר לעובדים של דיסני להבין את רף החוויה שהם מצופים היו לתת למבקרים בפארקים של דיסני.
הנה דוגמה הרבה פחות קיצונית:
fun DataPoints.plus(other: DataPoints): DataPoints { val intersectKeys = this.keys.intersect(other.keys) val intersectEntries = (this.entries + other.entries).filterKeys { it in intersectKeys } return DataPoints(intersectEntries) }
אני משתמש בפונקציה בשם plus, שבעצם מבצעת חיתוך של איברים – כלומר: אני מסיים עם פחות איברים.
יכול להיות שבהקשר של Data Points זה הגיוני (מי יודע?! – נניח שאני לא מכיר את הדומיין), אבל זה לא אינטואיטיבי לי כאדם ולשכל הישר שלי. האם אני כותב את הקוד כ Domain Expert או כאדם?
ברגע שאני מסיר את "כובע" ה Domain Expert שלי, וחושב בהיגיון פשוט של אדם – זה מבלבל. אם בזבזתי עכשיו חצי שעה לדבג קוד רק כדי להבין שהפונקציה plus מצמצת את מספר האיברים – זה כנראה יוביל לכך שלא אהנה מה interface של הפונקציה – וזה אומר נקודת כישלון של DX. המחשבה על DX אמורה לשים את כל הטיעונים של "הצודקים לכאורה" של ה Domain בצד "אבל ברור ש…", "אבל ידוע ש…", אם אני מתבלבל כי לרגע חשבתי כאדם ונותרתי עם חוויה לא טובה – זה לא DX טוב.
הפתרון: לקרוא לפונקציה combine שתתאים גם לתאר את הדומיין המורכב, אבל גם לא "תכשיל" חשיבה אנושית בסיסית שלי. שם כללי כמו combine יחייב אותי לבדוק, ולא להניח – מה הפונקציה עושה. בדיוק כמו שאנשי UX מכים על חטא אם כיתוב על כפתור גורם לאנשים להתבלבל ולבצע את הפעולה הלא-הנכונה, והם לא יאמרו לכם (אני מקווה) "המשתמש היה צריך לחשוב טוב יותר", או "הוא לא מבין מספיק, מה הוא חשב שיקרה בשלב הבא?"
דוגמה אחרונה, הסתכלו על ה interface הבא:
interface NoFun { fun updateCustomerDataPoints(customerId: CustomerId, dataPoints: List<DataPoint>) data class DataPoint( val type: DataPointType, val value: DataPointValue, val effectiveDate: LocalDate ) }
במקרה הזה אנו מעדכנים את אובייקט הלקוח עם DataPoints מסוימים, אבל בתסריטים מסוימים חשוב לעדכן את ה effective Date על Data Points נוספים שלא היו בעדכון, בשל תלות סמנטית בין ה DPs. ברור למומחה העסקי מדוע זה נכון, אבל אני מקווה שברור לכם הקוראים – מדוע לצרכן של ה API זה לא אינטואיטיבי (במקרה הטוב) או הפתעה גמורה (במקרה הפחות טוב).
איך מסבירים למשתמש לוגיקה מורכבת שכזו? ועוד בחתימה של API?
שינוי קטן ב API (הוספה של שדה אופציונלי של effectiveDate) יכול לשדרג את השימושיות שלו בצורה משמעותית:
fun updateCustomerDataPoints(customerId: CustomerId, dataPoints: List<DataPoint>, effectiveDateForAdditionallyUpdatedDataPoints: LocalDate)
בחתימה הזו אני מציף את ההתנהגות המורכבת / לא-צפויה החוצה. אני מבקש מצרכן ה API – לשחקת את המשחק. הוא קודם כל מבין שיש ״משחק״ שהוא לא מודע לו. ״מה זה effectiveDateForAdditionallyUpdatedDataPoints לעזאזל?״. אם הוא ילך בעקבות זאת לתיעוד (דבר שלא עושים בד״כ) – אז כבר השגתי את שלי.
״מה עושה הפרמטר״ – אתם שואלים? בונה ציפיות ומקשה על המשתמש לשכוח את ההתנהגות המרוכבת. בפועל, הייתי משווה אותו ל effectiveData של ה DataPoints שהתקבלו – ואם הוא לא זהה – הייתי זורק Exception עם הודעת שגיאה משמעותית, למשל:
Due to this operation, some additional data points may update their 'effective date'.
Please provide an effective date that matches the effective date of the original DataPoints or read the function doc to understand more.
This check is added to ensure you are aware of this additional functionality.
האם זה יצליח? האם ״התרגיל״ הזה יעשה את העבודה? צריך לראות בשטח. לפחות זה הכיוון הנכון לתקוף כזו בעיה: לא להעלים התנהגות חשובה בלי להשאיר לה זכר. להשאיר מספיק סימנים והכוונה – כדי להבין מה מתרחש.
בצד ההפוך – את תחצינו פרטי מימוש לא חשובים שלא משפיעים על הצרכן. את תשומת הלב של המשתמש ״דורשים״ רק מתי שזה חשוב מספיק.
ה API (או חתימת הפונקציה) אמור לשרת ולהיות הכי טבעי / קל / נוח עבור צרכן ה API – היא לא רעיון חדש (כתבתי עליו בבלוג בעבר). אבל לפעמים המחשבה ש "כל מפתח צריך להבין מה קורה במערכת" יכולה לגרור אותנו לפספס וליצור APIs פחות טובים.
הטיעון ש "אנחנו רוצים לתת API שכיף ונוח לעבוד אתו" (בלי קשר לרמת המומחיות של הצרכן) עשוי להכריע את הכף לטובת השימושיות הגבוהה / יישום POLA. אולי לא כולם יסכימו שה API הראשון מבלבל / מכשיל את המשתמש, אבל כולם יסכימו בוודאי שהוא לא "מפנק" ואפשר לעשות אותו נוח ו"כיפי" יותר לשימוש. זה מספיק. UX גבוה הוא קונספט מוכר ומוסכם, ואף אחד לא יצפה ש Gmail יבקש ממני להגדיר את ה HTTP Headers של ההודעה – רק בגלל שאני מומחה ואני יכול, נכון?


DX הוא מטאפורה מצוינת למפתחים, שיעזור להם ליצור APIs, Intefaces, ובכלל קוד טוב וקריא יותר. השאלה "האם מי שישתמש בקוד שלי עומד ליהנות?" – תוביל למסקנה שונה (והרבה פעמים: טובה יותר) מאשר השאלה "האם הקוד הזה בסדר / תקין / מסודר?"
DX היא גם מטאפורה חשובה מאוד ברמת ארגון הפיתוח, בתקופה בה חשוב לארגונים מאי פעם לשמר את העובדים הללו, להפיק מהם את המיטב, וליצור מותג חיובי של החברה – כחברה שטוב לעבוד בה.
תלונות כמו:
- "ה Build אטי"
- "ה Security Tools והדרישה להכניס סיסמה כל פעם – פשוט מטרידים"
- "לפתוח באג בג'ירה זה פשוט סיוט… למה זה כ"כ מסובך?"
הן תלונות שעשויות לעבור מתחת לרדאר, תחת המחשבה ש"הם אנשים חכמים, הם יסתדרו" או ש"גם בימי היה ככה – זו המשמעות של לפתח תוכנה בארגון".
הלחצים על ארגוני הפיתוח – הם תמיד אדירים: יש המון מה לעשות, המון מה להספיק, ותמיד אנחנו רוצים טוב יותר ומהר יותר. פעם שמעתי כלל שלארגון פיתוח יהיו תמיד דרישות לעשייה גדולה פי 5 – ממה שהוא מסוגל לספק. ככל שהוא יגדל – כך יגדלו הדרישות.
את חווית המפתחים – אפשר לשכוח, ולהזניח, ובתקופה שלנו, זה אולי פחות טוב אפילו מבעבר.
השאלה למנהלים היא "האם כיף לפתח בארגון / מערכת שלנו – יותר מחברות מקבילות?" היא שאלה רלוונטית. פיתוח, במיוחד של מערכות מורכבות, תמיד כולל אלמנטים שוחקים ומתסכלים. תמיד יהיה את הבאג המתיש ברמת התשתית שנחקור לאורך ימים או שבועות, ונחווה שוב ושוב אכזבות שכמעט פתרנו אותו – אבל עדיין לא.
זול יותר וקל יותר לספק לעובדים עוד יום כיף, עוד הטבות, עוד מתנה מגניבה. זה רק עניין של כסף.
שיפור חווית הפיתוח דורש מאמץ אמיתי, חשיבה, ומיומנות גבוהה – ולא כל מאמץ יסתיים בהכרח בהצלחה. זה מסע. תמיד גם יהיו תלונות, ומפתחים לא מרוצים, על כל מצב שלא נגיע אליו – מה שמרפה את ידי המנהלים להשקיע בנושא.
מצד שני, התמורה בחוויית פיתוח טובה יותר עשויה להיות מכפיל כוח אמיתי לארגון:
- פריון גבוה יותר: מפתחים כותבים יותר פיצ'רים / משפרים את המערכת, במקום לבזבז זמן במקומות אחרים.
- שביעות רצון: הגיוני שמפתחים יעריכו את השיפור שנעשים בסביבת העבודה שלהם, במיוחד אם מראים את זה, ומשתפים את המפתחים בזה.
קראתי כמה מאמרים שטענו ש DX גבוה יותר (איך מודדים?) משפיע בצורה משמעותית על הצלחת הארגון ("פי 4-5") ועל שביעות הרצון של העובדים – אבל לא מצאתי נתונים משמעותיים ששווה להציג. רק דיבורים.
אני מניח שאלו הדברים שבשלב הזה הם עניין של אמונה: כמה אתם מאמינים ששיפור חווית הפיתוח של המפתחים בארגון שלכם תשתלם בפריון ושביעות רצון גם בלי יכולת למדוד כל שיפור? או עד כמה אתם נצמדים למה שניתן למדוד ותשקיעו רק היכן שיש ROI מוכח (כנראה: בעיקר בפריון).
העיקרון הראשון בשיפור ה DX למפתחים ברמת הארגון הוא להקצות בצורה בלתי-מפשרת משאבים איכותיים ומשמעותיים לנושא. אלו לא נושאים קלים.
העיקרון השני הוא לבדוק ולתעדף היכן להשקיע. גם אם אנחנו מאמינים שזו ככלל השקעה טובה – ברור שניתן להשקיע המון מאמץ בשיפורים שלא יהיו משמעותיים.
מונח מתאים מעולם ה UX הוא ה Friction Log – תיעוד אזורים בהם קשה למשתמשים להתקדם ב UI המערכת והם חווים בהם תסכול. באופן דומה כנראה שכדאי לבקש מהמפתחים לשתף ולתעד – היכן הם חווים קושי, עיכובים, ותסכול ברמת הפיתוח, ובטוח שיש הרבה כאלו.
חשוב להקשיב ולהראות עניין בכאבם של המפתחים, קודם כל מתוך ניהול בסיסי. דבר שני – דרך ה Friction Log אנו יכולים לגלות ולתעדף טוב יותר, נושאים שנכון לשפר אותם.
כמו בארכיטקטורה של מערכת, כדי להיות יעילים הרבה פעמים שווה להשקיע בלהפוך בעיות ברמת חומרה "High" לבעיות ברמת חומרה "low" או אפילו "רק medium" ולעבור לבעיה הבאה – מאשר לנסות להעלים את הבעיה לגמרי. Build Pipeline איטי מציק אם הוא אורך 30 דקות, אבל כנראה ש 10 דקות זה בסדר. המאמץ להביא אותו ל 2 דקות – עשוי להיות אדיר, ולא משתלם.
נתקלתי בפונקציות לא טכניות של Developer Enablement – מישהו בארגון שמזמין קורסים, קונה כלים, מארגן כנסים וסקרים בקרב המפתחים בכדי לשפר את החוויה וההווי שלהם. קשה לי להאמין שזה מודל יעיל ש"יפגע" במקומות הקשים והכואבים. כנ"ל לגבי מישהו מצוות ה Operations (או "DevOps") שאחראי על העניין. בטוח שיש להם מה לתרום, אבל עדיין לא ראיתי את האדם שאינו מפתח – ומצליח להבין עד הסוף את הכאבים של המפתחים, ולהביא פתרונות עומק למה שבאמת כואב.
אני מאמין ש DX גבוה יכול להגיע רק מתוך שיתוף פעולה פרואקטיבי עם המפתחים וגם מניהול של העניין ע"י מישהו שמגיע מעולמות הפיתוח. לא Operations ולא תפעול.
סיכום
בסך הכל מה שיש לי להציע בפוסט הזה היא מטאפורה חדשה – לבעיות ורעיונות קיימים.
האם המטאפורה העדיפה (בשאיפה) – תוביל לתוצאות טובות יותר? אתם תגידו – אבל יש לי אופטימיות שייתכן וכן.
שיהיה בהצלחה!