חיסכון או אסון? מתי וכיצד ליישם 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 של פיתוחים – תוך כדי בחינה מתמדת של ההשפעות העתידיות. כפי שקצת ראינו, עצם הבחינה הזו יכולה לאפשר לנו למצוא וריאציה מוצלחת יותר של הגישה המקורית.

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

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

כמה נקודות על 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 – אם אתם יודעים להפיץ לאנשים שמתעניינים – אני אודה לכם מאוד. חבל לי שהפרויקט שלי כמעט ובלתי ניתן למציאה (גוגל שכח ממנו לגמרי).

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

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

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

נעלה ונצליח!