כמה נקודות על Large Language Models

LLM מעסיק רבים מאתנו בחודשים האחרונים. חשבתי לשתף בכמה נקודות / תובנות בסיסיות. נתחיל:

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

בכל מקרה – התוצאות מעוררות התפעלות, אפילו באינטראקציות קצרות ופשוטות:

מרשים: הוא ״מרחיב את השיחה״, לנושא הירחים של ירחים, מתוך ״תובנה״ שזה כיוון שיחה ״מעניין״.

(במקרה הספציפי הזה, Gemini ולא ChatGPT)

נקודה שנייה:
LLMs ״יורשים״ הטיות תרבותיות של הטקסטים / נתונים שהם מאומנים עליהם. אם לדוגמה היו מעט נשים בתפקידי מפתח בטקסטים שעליהם אומן GPT – הוא יציע פחות נשים בתשובות שלו (אלא אם תהיה התערבות לנסות ולשנות את זה, כמו Gemini שלא הצליח לצייר אפיפיור לבן או צייר נאצים גם כשחורים). ישנן התערבויות שונות במודל כדי לשמור אותו ״מוסרי״ ו Politically correct.

נקודה שלישית:
LLM הוא הקיצור, כמובן, ל Large Language Model – ו״הסיפור״ מאחורי המודלים האלו, ופריצת הדרך הגדולה שלהם לאחרונה, הוא סיפור של Brute Force. בניגוד לאידאל של ״תחכום מוביל לתוצאות טובות יותר״, ה LLMs הם דווקא מודלים פשוטים יחסית (יחסית למודלים אחרים כמו: LSTM או XGBoost), שייחודם ב:

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

כלומר, במידת מה, אפשר לצפות לשיפור משמעותי במודלים הבאים (GPT-6, GPT-5) רק מתוך התחזקות כח המחשוב, וגם אם לא יהיו התקדמויות משמעותיות באלגוריתמיקה של המודלים או אימונם.

אין נתונים ברורים, אבל יש הערכות שכדי רק להריץ את המודל של GPT-4 זקוקים לכ 1TB של זיכרון, מפוזר על פני מספיק GPUs (עשרה לפחות). על מחשב ביתי חזק, גם לו היה לנו הזיכרון, ייתכן והיינו מחכים יממה בכדי לקבל תשובה בודדת.

עלויות האימון של מודל, בזמנים, ובכח המחשוב – ומכאן לכסף. לא סתם מסתובבות שמועות על Stargate – ה DataCenter בעלות של 100 מיליארד $ ש OpenAI ומייקרוסופט מתכננות להקים, שרק צריכת החשמל שלו, תהיה גדולה יותר מההספק של הכור הגרעיני הגדול ביותר בארה״ב.

נקודה רביעית:
בבסיס ה LLM ישנה רשת נוירונים (אני מניח שאתם יודעים מה זה) גדולה במיוחד, מה שנקרא גם Deep Learning. מודדים את גודל הרשתות באלו ב״כמות פרמטרים״ (ג׳מיני 1.5 – 1.5 טריליון פרמטרים, GPT-4 עם 1.76 טריליון פרמטרים) – במה בדיוק מדובר?

גרף לוגריתמי של כמות הפרמטרים במודלים לאורך השנים

בגדול, כמות הפרמטרים היא סכום ה biases, היושבים על nodes (מלבד השכבה הראשונה, ה input layer, שבה אין biases) + ה weight, היושבים על הקשרים ביניהם. ב GPT-4 יש רשת נוירונים בעומק 120 שכבות עם סה״כ 1,760,000,000,000~ node + קשרים. מטורף!

הנה קצת סדר / קשרים בין מונחים רלוונטיים:

נקודה חמישית:
מהם אותם tokens בהם מודדים עבודה של LLM? שלושה סנט לעיבוד של 1000 tokens ב GPT-4, למשל.

Tokens הם בקירוב מילים או תתי-מילים. הם השפה הפנימית של ה LLM, המתמקדת בסמנטיקה (משמעות) של המלים.
המילה Home היא token, ו GPT-4 מכיר כ 50,000 tokens שונים בשפה שלו.
המילה schoolhouse מתחלקת לtokens הבאים: school ו house. פחות מלים ל LLM להכיר + דיוק במשמעות של המלים. ה LLM משתמש בקשרים סמנטיים של כל אחד מה tokens והפירוק ל school ו house מאפשרים לו להשתמש בהקשרים של ה tokens האלו.

בעברית, המילה ״הביתה״ הייתה כנראה מתחלקת ל-3 tokens: ״ה-הידיעה״, ״בית״, ו״ה-המגמה״ (צפונה, מצריימה). מצד שני, ישנם מצבים בהם Token יכול להיות יותר ממילה אחת. למשל: "New York״ עשוי להיות token אחד, כי יש משמעות גדולה יותר לשם New York מאשר למלים המרכיבות אותו.

אני באמת ממליץ לכם לקחת כמה דקות ולשחק עם ה OpenAI Tokenizer ולראות כיצד הוא עובד. חלק מהדוגמאות הנ״ל לא עובדות ב GPT כמו התאוריה שתיארתי. בעברית, למשל, כל אות היא token – בכדי להכביד כמה שפחות על המודל של GPT, ובאמת ChatGPT לא מוצלח עם עברית. אם תבדקו מודלים שאפשרו יותר tokens בעברית, כמו Claude או Dikta (מודל עברית!) – תראו שהתוצאות טובות בהרבה.

תהליך ה tokenization הוא בעצם פעולת קלאסית של NLP, קרי Natural Language Processing.

בכדי להיות יעיל (או לפחות: יעיל-יותר), הרשת של ה LLM לא מייצגת token כ string (בזבזני בזיכרון), אלא לכל token בשפה יש id מספרי. בכניסה לרשת עושים מיפוי בין ה tokens ל ids (״השפה של ה LLM״) – וביציאה מהרשת – בחזרה ל strings.

אם נצלול יותר לפרטים, נראה שהייצוג הפנימי של token הוא לא מספר שלם, אלא מספר עשרוני שמבטא סמנטיקה, ולא מספר יחיד, אלא בעצם וקטור מספרים עשרוניים המייצג הקשרים סמנטיים של אותו ה token (לאילו מלים אחרות בקלט הוא ״קרוב סמנטית״ + היכן הופיע בטקסט) – אבל זו כבר צלילה מסוג אחר…

נקודה שישית:
רציתי לסיים את הפוסט, ולשחרר אותו לאוויר, אבל הרגשתי חובה פנימית לקנח בעוד נושא אחר: ה RAG.

בגדול, אם אנחנו רוצים לעשות שימוש ב LLM מעבר ל vanilla offering (קרי ChatGPT, Claude, Gemini) יש כמה אסטרטגיות עיקריות לעשות זאת:

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

בכל מקרה, אם אין לכם תקציב של כמה מיליונים לאמן מודל LLM משלכם, ואם אתם לא רוצים לשנות את אופי המודל (שפה רשמית יותר, העדפת נושאים מסוימים על פני אחרים) מה שאומר שלא מדבר ב Fine Tuning של המודל (תהליך שעשוי גם קשה-לשליטה בתוצאות שלו) – נשארו לכם שתי פרקטיקות עיקריות: Prompot Engineering / Enhancment (שיכול להופיע כאימון למשתמשים, או כתוכנה ״שמהנדסת״ את ה prompt בזמן ריצה) או RAG. בהנחה שכולם מבינים מהו Prompt Engineering, אסביר מהו RAG – אסטרטגיה סופר-חשובה בתחום.

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

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

אם הייתי רוצה ליצור chatBot לבורגר סאלון זו הייתה בעיה ממשית. מכיוון שמדובר ברשת קטנה (יחסית), ואימון של מודל LLM הוא כנראה מחוץ לתקציב השירות של הרשת.

הפתרון הוא לשלוף מידע נוסף (Retrieval) כדי להעשיר את ה prompot/query (להלן Augmented) של ה GenAI (להלן: Generation). בקיצור: RAG.

במקום לשלוח את השאלה ״מתי בורגר סאלון פתוחים?״ ל LLM כפי שהיא, אנחנו:

  1. נוסיף הקדמה בראש ה prompt: ״בבקשה התייחס למידע הבא בשאלה:״
  2. נוסיף את הזמן הנוכחי, ומיקום של המשתמש (ששלפנו מהדפדפן)
  3. נבדוק ב DB של הרשת מה שעות הפתיחה (אפשר את כל השעות והסניפים, או לפלטר את הסניף הספציפי והיום הספציפי) ונוסיף את המידע ל prompt.

ורק אז נשלח אותו ל LLM, ומשם ככל הנראה תהיה לנו תשובה נכונה ועדכנית. הידד!
כמובן שאסטרטגיית ה RAG חשובה במיוחד כאשר יש מידע ארגוני שאינו זמין למודל / פתוח לעולם.

יש כאן כמה שאלות פתוחות של מימוש:

  1. כיצד אנחנו מנתחים את ה raw prompt ויודעים באיזה מידע בדיוק להעשיר את ה prompt שלנו?
  2. כיצד לנהל את מאגר הנתונים שלנו (להלן: knowledge base)? טכנולוגיות, אינדקסים, וכו׳
  3. כיצד להזין מידע עדכני למאגר הנתונים? מתוך המערכת האופרטיבית או מקורות שונים באינטרנט? איזה מידע לסנן / לנקות / לשפר?
  4. כיצד ״להרכיב״ prompt אופטימלי מהנתונים שהבאנו? למשל: יותר נתונים לא תמיד מובילים לתוצאה טובה יותר (קשה למודל להחליט במה להתמקד, קרי בחירת ה attention)? יש פה פרקטיקות של Prompt Engineering
  5. איך מאבטחים מידע פרטי, במאגר הנתונים וב prompt – שלא ידלוף?

בקיצור: יש פה עבודה הנדסית לא מעטה.

שווה אולי לציין, ש BingChat (בניגוד ל Gemini או ChatGPT) משתמש ב RAG באופן מובנה, ומבצע חיפושים ב Bing Search, על מנת לשפר את התשובה שניתנת לכם. לכל תשובה הוא יצרף את הקישורים. ישנן גם ביקורת, היכן הוא עושה את זה בצורה פחות טובה.

סיכום

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

רוב כתיבת הפוסט הזה, היא בעצם שליפת נתונים, הכללה, בחירת attention, ועיבוד לטקסט קוהרנטי (עם קצת גרפיקה). אני מצפה ש GPT-7, או לכל היותר GPT-9 – יוכלו לעשות את זה במקומי, ואז אני אתפנה לדברים ש LLM לא מסוגל לעשות – כמו קיפול כביסה.

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

קישורים רלוונטיים
מה LLM מתקשה לעשות? (באנגלית)

על צורת העבודה של צוות ארכיטקטים בארגון תוכנה

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

יוצא דופן אחד שאני מצליח לחשוב עליו הוא The software architect elevator של גרגור הופ (המחבר של Integration Patterns), וגם הוא דיי ״טהור״ / ״תאורטי״ ופחות ממחיש את היומיום בשטח.

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

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

  • כ 150 מהנדסי תוכנה. בישראל, וקצת בארה״ב.
  • צוות הארכיטקטים פועל רוחבית על כל הארגון.
  • חברת-מוצר (ביטוח לעסקים קטנים)
  • מוצר SaaS (מיקרו-שירותים, לצורך העניין)
  • קוטלין בצד השרת, TypeScript ואנגולר או ראקט בצד לקוח (לא ממש חשוב).
  • חברה בת כשמונה שנים, כאשר האתגר הטכני העיקרי הוא מורכבות עסקית גדולה מאוד (המון business logic שצריך להכיל)

תהליך העבודה של צוות הארכיטקטים

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

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

איך יכולים כמה אנשים, נבונים ככל שיהיו למנוע ממערכת מורכבת עליה עובדים עוד 150 מהנדסים -מלהסתבך?!

הנה סיכום גרפי קצר של תהליך העבודה של צוות הארכיטקטים שלנו, ב high level:

Identify & Study system flaws

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

Insight

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

Design Solutions

  • תובנה היא עדיין לא יכולת לפעול. צריך לחשוב ולמצוא את תוכנית הפעולה הטובה והנכונה (בהתאם לנסיבות).
  • הארכיטקטים יושבים ועושים תכנון בקנה מידה גדול (Architectural Concept או High Level Design) לפתרון טוב יותר לבעיה.
    • לא פעם, הם היחידים שישקיעו זמן ומאמץ בשלב מוקדם – על בעיה ״כ׳׳כ רחוקה״.
    • בדרך כלל, לא יהיה להם מספיק ידע לתכנון מוצלח. הם יהיו חייבים לעבוד עם הצוותים שמכירים את הפרטים.
    • חשוב מאוד לצלול לקוד, ולחלץ תובנות משמעותיות מה domain experts הרלוונטיים. להתבסס על עובדות והבנה מספיק טובה של המצב.
      • בתכנון high level וארוך טווח שכזה – קל לקחת הנחות אופטימיות (או פסימיות), וליצור פתרון עם פערים מהמציאות בשטח. ראייה מפוכחת, ניסיון, ספקנות, ותקשורת טובה – הם כלים הכרחיים להצלחה בכזו משימה. הצלחה היא לא תוכנית שנראית מבטיחה. הצלחה היא תוכנית שיש לה סיכוי טוב להצליח באמת.
  • השלב הזה מתבטא בעדכון שני תוצרים עיקריים:
    • ה Target architecture (בקיצור: TA) הוא סט מסמכים / Architectural Concepts / HLDs שנוצרו ומתארים את השינויים העיקריים שצוות הארכיטקטים מאמין שיש להחיל על המערכת בעתיד הנראה לעין. למשל: להוסיף אחריות חדשה במערכת, לשנות מידול, להחליף טכנולוגיה, להעביר אחריויות ממקום למקום במערכת, או לשנות גישת עבודה בחלקים שונים של המערכת.
      • טכנית, ה TA הוא תיקיה משותפת, עם המסמכים הנ״ל.
      • כמובן שצריך לרענן ולעדכן את התכנים כל הזמן. לא מעט רעיונות יהפכו ללא רלוונטיים עם הזמן.
      • כמובן שמסמכים ארוכים ומפורטים לא משרתים את ה TA היטב. מסמכים תמציתיים, שקל לקרוא, קל לעדכן, לא כואב לארכב – מתאימים יותר. היכולת לתמצת רעיונות למינימום הכרחי – חשובה מאוד.
      • סקירה תקופתית של הרעיונות עם מנהלים בכירים ומהנדסים רלוונטיים – יכולה לעזור מאוד, גם לתקף את הרעיונות, וגם לעזור ולרתום אליהם עוד אנשים.
      • רעיונות ב TA שלא הגיעו מצוות הארכיטקטים, ואפילו לא זכו לתמיכת הארכיטקטים בשלב מוקדם – הם חשובים ל TA. ה TA צריך להיות סיכום / קונצנזוס של הארגון (שהארכיטקטים אחראיים עליו) – ולא דעה אחת, או דעת מיעוט.
    • ישנן תובנות או לקחים שלא מתמפים לשינוי רכיב במערכת, מודל, או Flow במערכת. למשל: איך משדרגים ספריות, משנים סכמה בבסיס הנתונים, או עקרון שאומר שלכל מידע Immutable במערכת נדרש Audit Trail כזה וכזה – לצורכי troubleshooting, למשל.
    • תבנית מתאימה יותר היא ה Best Practices & Guidelines – שורה של כללים ותהליכים הנדסיים שיעזרו למערכת להיות ברת-קיימא (Sustainable) ויעילה יותר. דברים שאם לא נעשה אותם, יש סיכון גובר שנשקע ב״ביצת הסיבוכיות״.
    • גם כאן, הארכיטקטים הם ה Facilitators ואולי האחראים – אבל לא ״המוח״ היחידי להגדיר את אותם כללים.
    • גם כאן, נדרשת עבודה משותפת עם המהנדסים: Guidelines לא טובים, או Guidelines שרק יושבים במגירה – יכולים בהחלט להיות מזיקים.
    • גם כאן, נדרשת התעמקות ומקצועיות על מנת להוביל להחלטות טובות. דיון ״דמוקרטי״ שמנהל ארכיטקט שבו מהנדסים שונים מציעים Guidelines שונים – עתיד להוביל לבינוניות. הארכיטקט צריך להיכנס לעובי הקורה, לאתגר ולחפש שיאתגרו אותו – ולהציע Guideline שלם, הרמוני, ו ״battle tested״ בביקורת עמיתים. עליו יש אחריות שהתוצר הוא נכון ויעיל – ולתקן אותו לאורך הדרך, אם הסיכום הראשוני אינו טוב מספיק.

Driving TA and applying Guidelines through projects

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

  • פרויקטים אסטרטגיים – פרויקטים גדולים ומורכבים, המזיזים ומחדשים, בכל מקרה, חלקים במערכת. פרויקטים אלו הם הזדמנות נכונה להתקדם לכיוון ה Target Architecture.
    • ייתכן ששינוי שהיה לוקח 4 חודשי עבודה בעצמו, פתאום משתלב לתוספת של חודש או חודשיים עבודה כחלק מפרויקט אסטרטגי – ולכן זו הזדמנות לבצע אותו.
    • לא פעם, לא להתקדם לכיוון ה Target Architecture משמע להקשות יותר על ההגעה לשם בעתיד (עיבוי החוב הטכני). הפרויקט יהפוך עבודה עצמאית של 4 חודשים – להיות 5 חודשים.
    • פרויקטים אסטרטגיים הם גם הזדמנות להחיל וליישם Best Practices במקומות חשובים במערכת – כחלק מהעבודה.
  • ״פרויקטים הנדסיים״ – הוא מונח פנימי בנקסט לפרויקט שלא משרתים ישירות צורך עסקי – אלא שינוי ושיפור ארכיטקטורה. לפעמים נדרש שינוי גדול שלא נכון לקשור או לחכות לפרויקט עסקי אסטרטגי.
  • ״פרויקט הנדסי״ הוא כלי משמעותי לקדם את ה TA מהר יותר – היכן שחשוב. לרוב הם יתרחשו רק כאשר נוצר קונצנזוס רחב על הצורך בשינוי המדובר. קונצנזוס שנכון מאוד שארכיטקטים יפעלו ללא ליאות כדי לבנות – אבל נחמד לפעמים לראות שדווקא אנשים אחרים בארגון (ראשי קבוצות, אחרים) הם אלו שמקדמים וגורמים להם לקרות. בסופו של דבר, המערכת היא של כולנו – ומערכת טובה יותר היא אינטרס משותף.

תוצר חשוב נוסף: ה Service Registry

במובן מסוים, ארכיטקטורה היא סדר:

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

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

כלי נוסף ששכללתי לאורך השנים – הוא ה ״Service Registry״: תיאור הארכיטקטורה הנוכחית, על צדדיה הטובים יותר והטובים פחות – במסמך וורד פשוט, ללא תרשימים. הפשטות הזו – היא הכוח של הכלי.

ה Service Registry מתאר בכמה bullets כל מיקרו-שירות במערכת. תיאור שקל לקרוא ולהבין – שצריך להיות מאוד תמציתי ומדויק.

דיסקליימר: בעשור האחרון עבדתי רק עם מידי-שירותים, שירותים בגודל בינוני, ולא המון שירותים קטנים. אני לא יודע לומר כיצד הכלי הזה היה עובד במערכת עם מאות מיקרו שירותים קטנים. בנקסט, יש לנו בעת כתיבת הפוסט כ 50 שירותים (כ 10-15 מהם הם באמת ״מיקרו״-שירותים) על 150 מהנדסים.

לכל שירות במערכת יש קטע קצר (שורות מספר, עד עמוד לשירותים המורכבים ביותר) הכולל 3 חלקים:

  • (אופציונלי) רקע – לתאר קצת את ה domain של המיקרו-שירות, אם הוא לא מובן-מאליו.
    למשל: שירות ניהול-מיקום יכול קצת להסביר מהו geolocation ומה מידת הטעות האפשרית באיסוף שלו.
  • תחומי-אחריות – תיאור (לרוב בנקודות) של סוגי הפונקציונליות שצריכות לשבת בשירות.
    תחת עקרון ה SRP (single responsibility) – לא אמורים להיות שני שירותים בעלי אותה אחריות.
    • אם יש חריגה משמעותית מהרצוי (היסטוריה) – אנחנו מציינים אותה במפורש.
  • תחומי-אי-אחריות – תיאור (לרוב בנקודות) של סוגי הפונקציונליות שלא צריכות לשבת בשירות. זו לא רשימה כוללת (ואינסופית), אלא רשימה קצרה של המועמדים הטבעיים לטעות + הסבר קצר היכן במערכת הם כן צריכים לשבת.

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

הכלי הזה עוזר לצמצם ולפתור ויכוחים טכניים בין צוותים. הוא עוזר למהנדסים לקבל החלטות שיתיישבו עם הארכיטקטורה, והוא עוזר להציף אי-התאמה של הארכיטקטורה ע״י מהנדסים – שאחרת אולי והיינו מבינים רק מאוחר הרבה יותר. כמו שנאמר: Good fences make good neighbours.

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

סיכום

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

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

נ.ב. – אני מגייס ארכיטקט נוסף לצוות. אם את/ה ארכיטקט/ית שרוצה לנסות לעבוד בגישה הנ״ל, בחברה צומחת בעולם המוצר – baronlior[at]gmail.com היא הכתובת.

קוטלין (Kotlin) למפתחי ג'אווה ותיקים – חלק א': הבסיס

קוטלין?!קוטלין (על שם האי קוטלין) היא שפה static-typed, עם תחביר מודרני, הרצה מעל ה JVM. היא פותחה ע"י JetBrains – חברת ה IDEs הנודעת מרוסיה.

החבר'ה של JetBrains כתבו הרבה קוד בג'אווה כחלק מפיתוח ה IDEs שלהם. התחושה – שהקוד verbose מדי. הם התלבטו בין Ceylon (של RedHat, המתקמפלת גם לג'אווהסקריפט) וסקאלה, אבל ממספר סיבות – החליטו שאף אחת מהשתיים אינה מתאימה להם.

הפתרון: לפתח שפה בעצמם.

שש שנים אחרי, חברות טכנולוגיות כמו Uber, Netflix, Pinterest ועוד – משתמשות בקוטלין: בצד השרת, ובאנדרואיד.
קוטלין היא דיי חדשה, ה GA שלה הוא החל מפברואר 2016.

בכל זאת, גוגל הכריזה לאחרונה על קוטלין כשפת First Class באדרואיד – נקודת מפנה של ממש באימוץ השפה.

למרות סיכויים שנראו (לי אישית) קטנים בהתחלה – קוטלין הולכת ותופסת מקום משמעותי מאוד כשפה אלטרנטיבית וחשובה על ה JVM.

בקצרה:

  • לקוטלין יש תחביר דומה מאוד לג'אווה – אבל מודרני ומינימליסטי. אלמנטים רבים בתחביר שלה מזכירים את השפה האחרונה שסקרתי בבלוג – Go.
  • פונקציות הן First Class Citizens. שמעתי טענה ש Kotlin היא "שפת Object-Oriented ו Functional" – טענה מוגזמת לטעמי: immutability ו pure functions הן לא משהו מפותח בשפה.
  • לקוטלין יש Interoperability טוב מאוד עם ג'אווה – הרבה יותר טוב מסקאלה.
  • לקוטלין יש coroutines (יכולת שהיא עדיין ניסיונית) – היכולת להריץ תהליכים רזים ברמת ה runtime של השפה, עם כלים פשוטים לטיפול במקביליות, כמו async, yield ו await.
  • לקוטלין יש IDE מצוין – עם תמיכה מתקדמת. זה חשוב.
  • קוטלין יכולה להתקמפל לג'אווהסקריפט. שימו לב שלא כל תוכן הספריות הסטנדרטיות תומכות גם ב JVM וגם ב JS – ויש סימון מתאים. ה Interoperability עם ג'אווהסקריפט הוא קצת יותר מורכב, בשל האופן הדינאמי של ג'אווהסקריפט.
  • בדומה ל Swift – הגבילו בשפה את השימוש ב Null.
  • אין Checked Exceptions (יששש!)
מצד שני:
  • קוטלין היא שפה עשירה ולא פשוטה. היא יותר מורכבת מג'אווה – אם כי יותר פשוטה מסקאלה.
  • קוטלין מתקמפלת מעט לאט יותר מג'אווה (היינו רוצים שהתהליך יהיה מהיר יותר).

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

שלום, קוטלין!

תודו שפאן (fun) נשמע הרבה יותר טוב מדף' (def – נשמע כמו מוות)

הנה ה Hello World של קוטלין.

אני חושב שלמי שכתב בג'אווה, אבל מכיר גם שפות מינימליסטיות יותר (פייטון?) – זה נראה כמו ברכה!
קוטלין היא עדיין שפת Object-Oriented עשירה לכל דבר ועניין. ישנם קיצורים לשימושים נפוצים (למשל: println)

הקוד בקוטלין מתקמפל ל bytecode של ה JVM (עם הסיומת kt לשם קובץ ה class.):

מה שמאפשר לי ליצור שני מחלקות בשם Hello בפרוייקט, ג'אווה וקוטלין, זה לצד זה.

את ה class שנוצר בקומפליציה לא ניתן להריץ, ללא ה runtime של קוטלין: kotlin-runtime.jar.

אם עובדים עם command line הכי פשוט לארוז את הפרויקט ב jar עם הקומפיילר של קוטלין, לצרף את ה runtime – ואז להריץ:

$ kotlinc -include-runtime -d
$ java -jar
משתנים והשמות
  1. משתנה מגדירים בעזרת המילה השמורה var. קודם מופיע שם המשתנה – ורק אז הטיפוס (הפוך מג'אווה או C).
  2. ניתן באותה השורה גם לאתחל ערך.
  3. בעצם, ונראה את זה שוב ושוב: מה שהקומפיילר יכול להסיק לבד – לא צריך להגדיר לו. c יוכר כ Int כי זה הערך שהשמנו לו.
    var אינו "כלי קיבול גנרי". c מעתה והלאה – הוא static type מסוג Int.
  4. המילה השמורה val מגדירה ערכים שהם "immutable" (ליתר דיוק: פשוט לא ניתן לקבוע ערך אחר – בדומה ל final בג'אווה).
    בדומה לרובי, ניתן להשתמש במספר בקו תחתון, בכדי להקל על קריאת אלפים (או כל חלוקה אחרת שתרצו).
  5. בדומה לג'אווה, אני יכול להכריז שערך הוא מטיפוס Long ע"י הוספת האות "L" בסוף המספר.
  6. זו שגיאת קומפילציה. מדוע?
    כי המשתנה a לא אותחל, וקוטלין לא מקבלת השמה לערך שאינו מוגדר. אין אתחול באפס או null.
    בקוטלין כל המספרים הם אובייקטים. אין פרמיטיביים.
    אובייקטים לא יכולים לקבל ערך null, אלא אם הגדרתי אותם ככאלו (על כך בפוסט המשך).
  7. גם זו שגיאת קומפילציה! מדוע?
    אם אני מציב Long ב Integer (או להיפך) – עלי להצהיר על הטרנספורציה במפורש.
    אין casting, ואופן הצרנספורמציה הוא שימוש בפונקציה שהתנהגותה מוגדר היטב.
  8. כך צריך לבצע את ההמרה. להזכיר: b הוא אובייקט לכל דבר – אין פה autoboxing.

מחרוזות

  1. מחרוזת היא עוד אובייקט בקוטלין, כאשר יש ערך – לא צריך להכריז על טיפוס.
  2. כמו בפייטון (או עוד שפות) ניתן להגדיר Raw String בעזרת מרכאות משולשות.
    1. Raw Strings לא מקבלות escaping: הביטוי n\\ הוא מחרוזת, ולא שורה חדשה.
    2. כמו בשפות אחרות, ה margin הוא חלק מהמחרוזת.
    3. בכדי לצמצם margin ניתן לסמן את ה margin הרצוי בעזרת סימן "|" בתחילת כל שורה, ולקרוא ל ()trimMargin.
  3. בקוטלין יש interpolation למחרוזות בעזרת סימן ה $. כאשר הביטוי שם אטומי (יחיד) של משתנה – מספיק רק סימן הדולר.
  4. כאשר הביטוי הוא יותר מאטום אחד – יש לעטוף אותו בסוגריים מסולסלים.
  5. אם רוצים פשוט להקליד $ – יש לעשות לו escaping.
  6. בקוטלין יש custom operators, והשוואה בין מחרוזות מתרחש באמצעות האופרטור == (המקביל ל ()equals)

למען הסר ספק, הנה הפלט של הקוד לעיל:

לולאות

  1. כמו בפייטון, ניתן להשתמש ב range.
    rangeTo (כלומר: ..) הוא בעצם אופרטור של אובייקט ה Int, המחזיר אובייקט מסוג IntRange שהוא <Iterable<Int.
  2. ניתן, כמובן, לעשות איטרציה גם על <Iterable<T. דאא.
    מעניין לציין ש Iterable הוא לא Lazy, והופך לרשימה מלאה בעת ה evaluation שלו. השיקול: ביצועים. זיכרון רציף יעיל יותר לגישה ע"י המעבד.
  3. אפשר גם לספור לאחור, בעזרת downTo ויש גם step.
    downTo ו step הן לא מלים שמורות בשפה, אלא infix functions – כלי דומה לאופרטור, ששווה להסביר עליו בפוסט המשך.
  4. ()listOf היא פונקציה גנרית שמקבל רשימה (varargs) של ארגומנטים – ומחזיר אותם כרשימה <List<T. כמובן ש List, הוא גם Iterable.
  5. אם אני רוצה לבצע איטרציה ולעבוד עם אינדקס – זה התחביר.
  6. אם אני רוצה להתעלם ממשתנה (להימנע ב wanrnings / קוד קריא יותר), כמו בשפת Go – אני מחליף את המשתנה שלא אשתמש בו ב _ .
    (כמובן שבמקרה הזה שלב 4 הוא הדרך הפשוטה לכתוב את הקוד).

הנה הפלט:

נמשיך עוד קצת:

בזריזות:

  1. יש while.
  2. יש do {} while.
  3. בקוטלין אפשר להגדיר Label (מסתיים ב @) אליו אפשר להתייחס בלולאות מקוננות.
    כמה פשוט ונוח לומר: "אני רוצה להפעיל continue, אבל לא ללולאה הפנימית – אלא ללולאה החיצונית".
    הפקודה הן <break@<Label או <continue@<Label.
  4. יש גם repeat. האנוטציה ":times" היא של ה IDE – ואינה חלק מהקוד.
    זה מה שנקרא: "Five Whys".
הנה הפלט:
קצת על Control Flow
 
  1. בקוטלין אין ternary operator, קרי: v = cond ? a: b.
    הדרך המקבילה הכי פשוטה היא one liner if..else.
  2. משהו נחמד הוא ש if..else נחשב כ expression.
    1. אם, ורק אם יש לנו גם if וגם else (הרי אנחנו רוצים להימנע מ null / ערך לא מוגדר) – ניתן "לאסוף" את הערך מתוך ה expression.
    2. הערך הוא תוצאת ה evaluation של השורה האחרונה בכל בלוק – ממש כמו בשפת רובי.
    3. זה בעצם מה שמתרחש בסעיף #1 – רק שסעיף #1 הוא הצורה הפשוטה יותר לתבנית הזו.
  3. במקום switch, יש בקטולין את when.
    1. כפי שאתם רואים אפשר להשתמש במגוון ביטויים.
    2. בניגוד ל switch, ה evaluation של התנאים יפסיקו ברגע שימצא התנאי הראשון שהוא אמת. לכן הפלט יהיה ש "5 הוא ספרה בודדת", אף על פי ש 5 הוא גם מספר ראשוני, ואינו בסדרת ה 20. (בניגוד לג׳אווה / ++C / וכו׳)
    3. הקומפיילר מוודא שתנאי when על Enum או Sealed Class (עליהם נדבר בהמשך) הם Exhaustive (״ממצים״). כלומר: אם יש enum עליו אני רץ ב when אך לא כללתי את כל הערכים – זו שגיאת קומפילציה. זו יכולת חשובה ושימושית – למנוע באגים של ״אי טיפול בכל המקרים״ (ומתכתב נהדר עם עקרונות של Functional Programming).
      כמובן שניתן להוסיף else case ל when, וניתן גם לעשות break ו continue (שימושי לעתים רחוקות).
הנה הפלט:

סיכום

זה נראה לי מספיק בתור התחלה.
אמנם מה שעברנו עליו הם בעיקר דברים טריוויאליים – אבל חשוב להכיר אותם.
בכדי לכסות את קוטלין בצורה טובה, נראה לי שאזדקק לעוד… 5 פוסטים בערך (יצאו 8, הנה הסדרה המלאה):

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

—-

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

Cheat Sheet למעבר בין ג'אווה לקוטלין. https://github.com/MindorksOpenSource/from-java-to-kotlin

הדומיין שאבד

קרו הרבה דברים לאחרונה. לא דברים קלים.

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

״נו באמת, אז מי כבר קורא?״ תהיתי לעצמי. למדת לקח לעצמך, תעשה 10 דקות סיכום לעצמך בראש – ואת הרווח שלך עשית בשבריר השקעה.

אני יודע שזה לא מדויק.

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

ב 5 באוקטובר פג לי תוקף הדומיין (החלפתי כרטיס אשראי, והתעלמתי ממיילים של אזהרות) – ואז גיליתי כמה כואבת ויקרה יכולה להיות הזנחה שכזו. ידעתי בראש שיש לדומיינים 90 ימים grace גם אם לא היה תשלום – אבל מסתבר שבתוכנית שלי זה לא היה כלול. $10 שלא שילמתי בזמן – הפכו למפעל חיים שפתאום נעלם.

אני רוצה להודות לגו-דדי, שנתנו לי שירות מצוין, גבו כסף לפני שהסבירו שזו רק מקדמה לתשלום הרבה יותר גדול, וידעו להסביר שיש ביקוש גדול לשם SoftwareArchiBlog (כי המלים software ו blog הן פופולריות בשמות דומיין) ולכן התמחור הגבוה של לרכוש את הדומיין בחזרה. ידעו גם לראות שאני מתעניין – ואיכשהו המחיר עלה עוד קצת תוך כדי שיח איתם.

אמנם יש תמונות שלי מנופף להם לשלום – אבל זה לא היה באהבה.

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

בקיצור: עם הדומיין של softwarearchiblog.com, הלכו גם כ 2000 קוראים שלא ימצאו אותו עכשיו, קישורים שלא עובדים, ו reputation בגוגל שנבנה במשך עשור (ובמאמץ קידום רב). לא כיף.

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

הדומיין החדש של הבלוג הוא מעתה https://softwarearchiblog.co.il – אם אתם יודעים להפיץ לאנשים שמתעניינים – אני אודה לכם מאוד. חבל לי שהפרויקט שלי כמעט ובלתי ניתן למציאה (גוגל שכח ממנו לגמרי).

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

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

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

נעלה ונצליח!

לחשוב 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 של ההודעה – רק בגלל שאני מומחה ואני יכול, נכון?

מה מפתחים חושבים שמבזבז להם זמן – סקר של Pluralsight
על מי המפתחים סומכים שיודע מה שואב להם זמן – סקר של Plusralsight שקצת סותר את סקר המנהלים/מומחים שהצגתי בתחילת הפוסט.

DX ברמת ארגון הפיתוח

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 ולא תפעול.

סיכום

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

האם המטאפורה העדיפה (בשאיפה) – תוביל לתוצאות טובות יותר? אתם תגידו – אבל יש לי אופטימיות שייתכן וכן.

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