הנה ההרצאה שלי על Evolutionary Design מברברסים האחרון.
תכננתי לקחת פוסט שכתבתי על Design "ולהרצות" אותו – אך סיימתי עם משהו אחר.
הנה ההרצאה שלי על Evolutionary Design מברברסים האחרון.
תכננתי לקחת פוסט שכתבתי על Design "ולהרצות" אותו – אך סיימתי עם משהו אחר.
– אבל גם שפה שהגיל ניכר בה:
בהמשך, הסבו אותה ל"ג'אווה" כשפה ל Applets – המספקת את היכולת לכתוב קוד שירד ברשת – וירוץ בדפדפן. JavaScript נקראה על שמה של ג'אווה – ובאה להציג גרסה "קלילה" יותר לג'אווה, בתחביר ובצריכת המשאבים.
ואז הבינו שהיכולת של "Write Once, Run Anywhere" (או בקיצור: WORA) היא יכולת שמצוי הפוטנציאל שלה הוא לא בטלוויזיות חכמות (יחסית לשנות ה-90), ולא בדפדפנים – אלא דווקא בצד השרת.
כתוב את קוד השרת על מחשב ווינווס (היכן שקל לפתח) – אבל הרץ אותו על Unix ("שרתים אמיתיים") או Linux ("שרתים זולים – למי שמתעקש").
היום, "שרתים רציניים" מריצים Linux, ופיתוח הכי נוח לעשות על Mac (ה Linux Subsystem על "חלונות 10" מנסה קצת לאזן את התמונה), אבל היתרון שב WORA – חשוב כבעבר.
את התובנה הגדולה זו – הציגו בכך שהכריזו (ובצדק) שג'אווה עברה לשלב הבא: ™Java 2!
נוספו מאז ה J2EE, ה JVM נפתח כקוד פתוח – והשאר היסטוריה.
![]() |
| מקור: https://plumbr.io/blog/java/java-version-and-vendor-data-analyzed-2017-edition |
חוק תיכנותיקה ידוע, אומר שלא ניתן לבצע שינויים גדולים – ללא disruption.
קהילת ג'אווה, שהייתה קנאית ל Backward Compatibility מאז ומעולם – יודעת זאת.
יכולות כמו Generics או Streams הגיעו לשפה באיטיות – ונעשו בהן פשרות מוחשיות.
פרויקט ה Jigsaw ("פאזל", ה Java Module System) החל בשנת 2005. הוא תוכנן להשתחרר כחלק מג'אווה 7 (שהתעכבה ל 2011) – אבל שוחרר עכשיו, רק בג'אווה 9 – עשור ושלוש שנים מאז שהחלה העבודה עליו.
האימוץ של גרסה 9 של ג'אווה עתיד להיות אטי – וכואב. מכיוון שג'אווה 9 איננה backward compatible, אנשי התוכנה יהססו לאמץ אותה גם מסיבות פסיכולוגיות, ולא רק סיבות טכניות גרידא.
אני לא טוען שהתאימות לאחור של ג'אווה היא טעות. אותה תאימות לאחור, מתישה ומכאיבה – היא אחת החוזקות של שפת/קהילת ג'אווה.
בשפת ג'אווה עצמה – היכולת לבצע התקדמויות משמעותיות הולכת וממצה את עצמה משנה לשנה. לפחות: התקדמויות ללא disruption. ההתאוששות מחוסר התאימות-לאחור של ג'אווה 9 צפויה לארוך כמה שנים – מה שמפחית מאוד את הסיכוי לשינויים דרמטיים בשפה בשנים הקרובות.
הפתרון הברור הוא להציג את שפת Java גרסה 3: גרסה מחודשת מהשורש, ולא תואמת-לאחור – של השפה.
החבר'ה ברדווד סיטי יכולים לקרוא לה "ג'אווה 300", מבחינתי. שיזרמו עם תחושת ההתקדמות.
![]() |
| הרבה מימים אפשר לייצר עם המספר 300 |
המעבר מ"גרסה 2" ל"גרסה 3" – אמור לצלצל לכם מוכר. מזהים משהו?
לא? … נסו שוב.
אי אפשר (כך נדמה לי) שלא לחשוב על המעבר ההיסטורי של שפת פייטון מגרסה 2 לגרסה 3 – וההשלכות של המעבר הזה.
המעבר היה מתבקש, מכיוון שהיה כמו תכונות "ישנות" של פייטון שכבר היו "בלתי-נסבלות":
ראשי הקהילה תיארו זאת כך:
"באותה תקופה שעבדנו על פייטון 3 – השימוש בפייטון בעולם צמח בקצב מהיר מאוד. הנחנו שבשל המגמה, יהיה יותר קוד בפייטון 3 מאשר בפייטון 2 תוך מספר שנים – ולכן החלטנו "לספוג את הכאב" שבמעבר לא-תואם לאחור בכוונה לייצר עתיד טוב יותר לקהילת הפייטון. היום ברור שיעבור עוד עשור או יותר – עד שיהיה יותר קוד פייטון 3 מאשר קוד פייטון 2".
התוכנית לא כ"כ הצליחה: בסיסי קוד שברמת תחזוקה / שליטה בקוד גבוהה – היה ניתן להמיר במאמץ לא גדול – אבל המפתחים לא המירו את הקוד – כי ספריות רבות שהשתמשו בהן עדיין לא עברו לפייטון 3.
מתחזקי ספריות ותיקות כבר הפחיתו את ההשקעה בספריה – ולא היו מוכנים לעשות את המעבר. גם להם היו לפעמים ספריות אחרות שהם תלויים בהן – שלא ביצעו מעבר לפייטון 3. ה Distros העיקריים של לינוקס המשיכו להיות מסופקים עם פייטון 2 – וחוזר חלילה.
וכך – כל העגלה נתקעה. קריאה לקהילה לבצע את המעבר לא נענתה בחיוב, או לפחות לא בקצב מהיר. הוקמו אתרים כמו http://py3readiness.org ו http://python3wos.appspot.com שמדדו ועודדו – את האימוץ של פייטון 3.
פייטון 3 שוחררה ב 2008.
ב 2010 יצא עדכון 2.7 שהיה אמור להיות העדכון האחרון של פייטון 2 – אך הפך במהרה לגרסה הנפוצה ביותר של השפה.
בלית ברירה מפתחי השפה עשו downport לפיצ'רים רבים מפייטון 3 לגרסה 2.7 – על מנת להקל, בהמשך, על המעבר לפייטון 3.
![]() |
| מקוור: http://blog.thezerobit.com/2014/05/25/python-3-is-killing-python.html |
ביקורת על המהלך – לא הייתה חסרה. אינספור דיונים התקיימו בעד ונגד פייטון 2/3. השאלה הנפוצה ביותר בפייטון באותה תקופה הייתה ככל הנראה: "אני מתחיל פרויקט חדש. האם להתחיל בפייטון 2 או פייטון 3?".
היום, כבר עשור מאז שוחררה הגרסה הראשונה של פייטון 3.
הגרסה הנוכחית של פייטון 3 היא גרסה 3.6 – וקראתי כתבה שטענה ש 80% מהקוד החדש נכתב כיום בפייטון 3.
95% מהספריות הנפוצות כבר תומך בפייטון 3, וחלקים מהן מפסיק לתמוך בפייטון 2 (דוגמה: Django). במצב הזה, כנראה – קיוו יוצרי פייטון להיות בשנת 2010. זה קרה "רק" 8 שנים מאוחר יותר.
![]() |
| "הגרסה הבאה של פייטון תהיה פייטון 8, והיא לא תתאם לאחור בכלום" – מתיחת 1 באפריל |
נראה שהזעקות נשמעו היטב. לא רק בקהילת הפייטון – אלא בכל עולם התוכנה. "מה קרה שם לחבר'ה בפייטון? – הסתבך להם משהו עם הגרסאות…" היה הדיבור המקובל.
![]() |
| מקור: https://snarky.ca/why-python-3-exists |
אם הייתי האחראי העולמי לשפת ג'אווה, ובמיוחד אם אני עובד בתאגיד גדול (שמרן מטבעו) כמו אורקל – בוודאי (!!) שלא הייתי נוגע בתפוח האדמה הלוהט הזה שנקרא Disruption עמוק בשפה עצמה. הייתי משאיר את זה לבא אחרי – שיבוא עוד 10-12 שנים. הוא בטח יעשה את זה, לא?!
נכון, ג'אווה 9 שברה תאימות לאחור לג'אווה 8 – וזה לא עניין של מה בכך. בכל זאת, השינוי נוגע לניהול packages ותלויות – ולא לשפה עצמה. מכיוון שג'אווה היא strongly typed ו static – לא קשה ליצור כלים שיגלו וינחו בשינויים הנדרשים, להלן jdeps. כן צפוי קושי עם ספריות שעושות שימוש כבד ב reflection, ספריות כמו Hibernate או AspectJ.
שינוי בתחביר של השפה – הוא סיפור אחר לגמרי.
מה עושים? האם אין דרך בה ג'אווה תוכל להתחדש – וליישר קו עם שפות מודרניות?
לבעיה של מיגרציה בין ישן וחדש, מיגרציה שלא יכולה להתרחש בזמן קצר – יש פתרון מקובל ומוכח בעולם התוכנה. הפתרון הזה נקרא: Interoperability.
אנו נותנים למנגנון החדש והמנגנון הישן לחיות זה-לצד-זה, תוך כדי אפשרות גם לעבוד זה עם זה.
בהדרגה, וללא לחץ – נעביר עוד ועוד קוד למודול החדש, תהליך שגם יכול לארוך שנים.
במקרה שלנו: נספק שני סוגי סיומות קבצים:
לו פייטון 3 הייתה נוקטת בגישה דומה .- אני משוכנע שהסיפור שלה היה אחר.
האם נרצה לבצע את השיפורים רק בתחביר השפה, או גם ב JVM?
יש כמה דברים טובים שאפשר לשפר ב JVM:
הבשורות המשמחות הן שתהליך כזה כבר החל בשנת 2011!
ג'אווה 3 כבר מוכנה ועובדת. יש עשרות אלפי מתכנתים שעובדים בה במשרה מלאה. היא production-ready ועם interoperability מוצלח למדי לג'אווה 2!
אני יכול להעיד זאת לאחר עבודה לאורך החודשיים האחרונים עם JavaV3 בסביבת פרודקשיין.
לא פחות משמח: חברת אורקל תמשיך לתמוך ולפתח את Java.V2 באהבה ובמסירות עוד שנים רבות. זה לא פחות חשוב – כי סביר שקוד Java.V2 עוד יחיה שנים רבות.
הקושי העיקרי של Java.V3 היא שהיא זוכה להתעלמות רועמת מצד קהילת הג'אווה הרשמית.
אצילות היא לא דרישה בעולם העסקים, ולהיפך: על אצילות (שפוגעת בשורה התחתונה / נכסים "אסטרטגיים") – הולכים הביתה.
אף אחד מקהילת הג'אווה הרשמית לא הולך לומר לכם ששפת קוטלין היא בעצם Java.V3.
ולכן, זה הזמן לקהילה החופשית לעשות את השינוי.
יצא לי לשחק בזמנו בסקאלה – שפה אפית, רחבת-יריעה – בצורה מזיקה, לטעמי. יש מי שאמר ש "Scala היא לא שפת תכנות – היא פלטפורה לשפת תכנות. אתה בוחר את subset היכולות שאתה רוצה לעבוד איתן – ויוצר בכך בפועל מופע של שפת-תכנות". כמו כן, ה Interoperability בין ג'אווה 2 לסקאלה – הוא לא חלק, בלשון המעטה.
יצא לי לכתוב גם קצת בגרובי. שפה פשוטה וכיפית – אך הבחירה באופי דינאמי, והתאימות המלאה (אך הלא-מחייבת) לתחביר של ג'אווה 2 – יצר שפה ללא פילוסופיה ועקרונות ברורים. יוצר השפה המקורי נטש אותה (ותרם קצת קוד לקוטלין), וגם Pivotal הפסיקה לספק תמיכה – לפני כשנתיים.
בכל מקרה, ל2 השפות הנ"ל הייתה ההזדמנויות להגיע ל adoption משמעותי – אך הן לא צלחו. מכיוון שהן תוכננו לפני עשור ויותר – הן כבר לא יהיו Java V3. לכל היותר – Java V2.5 שנראה עם פוטנציאל – אבל לא הצליח.
![]() |
| מקור: http://pypl.github.io/PYPL.html – בעת כתיבת הפוסט |
קוטלין כן הצליחה ליצור spike של adoption – בעיקר על בסיס מתכנתי אנדרואיד.
בעקבות האימוץ הטבעי של קוטלין בקהילת האנדוראיד, וגם בשל הלחץ שהופעל עליה מצד קהילת ה iOS (מול Objective-C Java.V2 נראתה טוב, אבל בהשוואה ל Swift – היא כבר נראית עייפה ומסורבלת) – גוגל הפכה את קוטלין לשפה רשמית של אנדוראיד.
קוטלין היא שפה מודרנית בהחלט: תחביר פשוט, פונקציות הן 1st Class citizens בשפה עצמה, יש לה הגנה בפני nulls, שיפורים לספריות, extension functions, ועוד). קוטלין גם מאפשרת Interoperability מוצלח מאוד מול Java.V2, ויש לה IDE חינמי, הרץ על שלושת מערכות ההפעלה – עם תמיכה מעולה בשפה.
בקיצור:
קוטלין היא המועמדת הטובה ביותר לתפקיד Java.V3!
אני מוכן להעיד על כך מניסיון אישי. זו פשוט שפה טובה.
מה נותר? נותר לנו – הקהילה, לאמץ אותה!
אני באמת לא רואה סיבה מדוע לא לאמץ אותה בחום בצד-השרת. עשינו זאת בחברת Next Insurance – בהצלחה רבה!
השיפור בפריון וקריאות הקוד – הוא לא שיפור ש Java.V2 תוכל איי פעם להשיג – במגבלות שחלות עליה. מודל ה Interoperability למעבר בין גרסאות שפה – הוא מצוין. לא יהיה כאן שוד ושבר.
האם באמת מפתחי הג'אווה שבעו מחידושים, והם מסתפקים בזחילה לכיוון ג'אווה 9, או ב Spring Boot – כהתקדמות בסביבת העבודה שלהם?!
אני חושב שבאמת יש כאן בשורה, עם פספוס גדול של ה Mainstream של קהילת צד-השרת הג'אווה.
אם אתם עוסקים בג׳אווה בצד השרת – הייתי ממליץ ללמוד ולבחון קצת את השפה. להבין מה היא מחדשת מול ג'אווה.
אם אתם מתחברים לפילוסופיה – אפשר להתחיל מיקרו-שירות או פרויקט צדדי ולהתנסות בשפה. אפשר, באותה המידה, לכתוב מודול בתוך פרויקט קיים. השפות חיות בשלום זו לצד זו.
מי יותר מקהילת הסטאראט-אפים בישראל מתאים להחיל כזה שינוי?
משפת ג'אווה כבר קשה לצפות לשיפורים משמעותיים, ובטח לא בעתיד הקרוב.
גרסה 8 של ג'אווה "סחטה" דיי הרבה שיפורים – מהמצב הנתון, וזו הייתה התבלטות לטובה. בשנים הקרובות – נראה שקהילת הג'אווה תהיה עסוקה בעיקר במעבר ל Java Module System. שיפור חיובי – אך הדרגתי. שיפור שאגב יחול – על כל שפות ה JVM.
בכלל, כמה וכמה חידושים ברמת השפה בג'אווה 9 נראים דומים בצורה מחשידה ליכולות שיש כבר בקוטלין: Stream API improvements? – כבר יש. private interface methods – כבר יש. REPL? – ברור שיש. גם default interface methods הגיעו לקוטלין לפני שהגיעו לג'אווה.
אני חושד שהיוצרים של קוטלין מסתכלים על ה JSRs המתגלגלים של ג'אווה, ומביאים אותם לקוטלין – פשוט יותר מהר.
אם אתם מפתחים ג'אווה רוצים לשפר את כלי העבודה שלכם, שיפור משמעותי – קוטלין היא אופציה ממשית וחשובה.
אני מניח שאם אורקל הייתה מכריזה שקוטלין היא המשך טבעי לג'אווה – הייתה על קוטלין הסתערות רבתי.
אורקל לא תכריז זאת, אבל האם רק הכרזה של חברת ענק, בעלת אינטרסים וצרכים משלה – הוא מה שיזיז את הקהילה?
קהילת האנדרואיד אימצה את קוטלין בצורה "טבעית". למען ההגינות: כנראה שהיה שם לחץ גדול יותר מזה המונח על קהילת צד-השרת: קהילת האנדרואיד הייתה "תקועה" עם ג'אווה 6.
כרגע, האימוץ של קוטלין בצד-השרת נראה ממש זניח. עבורי – זהו פער בלתי-מובן מול הפוטנציאל.
הייתי מצפה שחברות חדשניות יסתערו על קוטלין – פשוט בגלל שהיא מוצר מוצלח.
האם אני מפספס משהו? האם אתם יודעים להסביר מדוע מעט כ"כ אנשי ג'אווה (צד-שרת) מתעניינים באימוץ של קוטלין?
אשמח לתגובות 🙂
מדוע? כי דרישות חדשות יגיעו מתוך המודל העסקי.
אם התרגום בין המודל העסקי למודל התוכנה הוא ישיר – יהיה קל לתרגם את הדרישות לשינוי בתוכנה.
אם המיפוי מסובך – נצטרך "לכופף" שוב את המיפוי, מה שעלול לגרום להסתבכות, פיצ'ר שקשה יותר לממש, וחוב טכני שילך ויתפח.
כל זה מוכר לרוב הקוראים, אני מקווה. אני חוזר על התובנה (החשובה) הזו שוב ושוב.
בפוסט הזה אתמקד בבעיית מידול נפוצה, כזו שיכולה להסתיים במידול ישיר ופשוט, או מידול סבוך שקשה לתחזק.
ההתחלה המודעת של הסיפור היא, בד"כ – כבר לאחר כברת-דרך.
לצורך הפוסט בחרתי בדוגמה של מערכת לניהול משלוחים בסופרמרקט. כולנו לקוחות של סופרמרקט כזה או אחר – ולכן מכירים את הדומיין בצורה לא-רעה.
המערכת שלנו היא מערכת לניהול משלוחים בסופרמרקט. הלקוח מזמין רשימה של פריטים ומקבל אותם במשלוח לביתו תוך 24 שעות מרגע ההזמנה. החיוב נעשה רק לאחר שהמשלוח הגיע ללקוח.
מידלנו את ההזמנה כאובייקט Order, המכיל רשימה של אובייקטי ListItem.
ListItem הוא "Pattern" נפוץ ושימושי למקרים כאלו.
מכיוון שהעלות של פריט (Item) היא תכונה המשתנה לאורך הזמן (temporal) זו תהיה טעות להצביע מההזמנה ישירות לפריט. מחיר הפריט השתנה לאחר שנה? גם מחיר ההזמנה השתנה, לא משנה שהיא כבר הסתיימה.
במקום זאת, אנו מעתיקים ברגע ההזמנה את הפריטים לאובייקט בשם LineItem שתפקידו לספק snapshot רלוונטי לרגע ההזמנה. לא נרצה שהעלאה או הפחתה של מחיר בפרק הזמן בין ההזמנה למשלוח יחול על הלקוח. בכלל, כסף הוא נושא רגיש אצל לקוחות. חשוב לדייק ולהיות צפויים בכל מה שקשור אליו.
עד כאן – טוב ויפה!
הבעיה מתחילה (לכאורה) כמה חודשים לאחר ההשקה:
נציגי שירות הלקוחות מתלוננים על תלונות חוזרות ונשנות של לקוחות שקשה לטפל בהן: כאשר חסרים פריטים ו/או לקוח מסרב לקבל פריט מסוים במשלוח (החברה מאפשר ללקוח לעשות זאת. סוג של החזרה) – לפעמים יש בלבול בסכומים שצריך לשלם / לזכות את הלקוח – וקשה להבין מה הסכום הנכון שצריך להיות. במיוחד – קשה להסביר את זה ללקוח.
נציגי שירות הלקוחות מבקשים שהחיוב של לקוח יתבצע שבוע אחרי הקניה, אחרי שכל העניינים הוסדרו, במקרים מסוימים – אולי שבועיים. אולי הם מבקשים UI עשיר יותר שיעזור להם לסדר את הבלבול.
מצד שני יש גם את מנכ"ל הכספים שכמובן מתנגד. הוא תוהה בקול רם "ממתי הפכנו לבנק שנותן הלוואות ללקוחות?", והוא גם הביע חשש שלפיתוח יהיה קשה לממש כזאת יכולת – וחשוב שהפיתוח יושקע בפיצ'רים שמכניסים עוד רווחים לחברה.
דרישות כמו הנ"ל מריחות כמו בקשות ל"פיצ'ר פירוס" (Pyrrhic Feature) – פיצ'ר שלא באמת יפתור את בעיה, אלא רק יקבע ויחמיר אותה. על פיצ'ר כזה נאמר: "עוד פיצ'ר כזה – ואבדנו".
אנחנו ניגשים לקוד (או ל Database) ומתחילים להזיז את הקוד לכיוון פתרון. עוד תנאי פה, ועוד בדיקה שם – אבל הקוד הולך ומסתבך. מי היה מאמין שמערכת של סופרמרקט היא נושא כ"כ מסובך?!
כאשר המודל שלנו "עקום" או "לא מתאים" טבעי שיצוצו בעיות. הניסיון לפתור אותן ב"פיצ'פוצ'ים" עלול להתארך – וגם מקבע את המצב הבעייתי.
לאחר כל אותם "פיצ'פוצ'ים" שאיכשהו הצליחו – מי יעז לבצע Refacotring משמעותי באותו קוד?!
הבעיה הנ"ל בעצם התחילה במידול האובייקטים הבסיסי. כמה שגרתי.
כשהתחלנו לתכנן את המערכת זיהינו אובייקט "הזמנה" ואובייקט "משלוח."
דיי מהר – הבנו שהאובייקטים בעצם זהים. הסכמה ב Database היא זהה לחלוטין. המשלוח הוא ההזמנה, וההזמנה היא המשלוח.
למה לתחזק נתונים כפולים ב Database? למה סתם לשכפל קוד? הרי אומנו "לחפש ולהשמיד" כל כפילות קוד אפשרית.
הבעיה העקרונית במצב הזה הוא ששני אובייקטים שנראים אותו הדבר כרגע – לא בהכרח יראו אותו הדבר בהמשך הדרך.
אני ארשה לעצמי להתחיל בדוגמה מגוחכת:
זה בוודאי דבר שלא נעשה, וגם עם עשינו – קל מאוד לתקן.
ככל שהדוגמה יותר מורכבת, יותר קשה להבחין בטעות. למשל:
באמת נראים אותו הדבר. דיי סביר שנאמר לעצמו "מה זו כפילות הקוד הזו?!" – ונאחד את האובייקטים.
השאלה המהותית היא: האם האובייקטים במקרה זהים כרגע, או האם הם באמת מייצגים את אותו הדבר לטווח ארוך?
בכלל, האם "הזמנה" ו"משלוח" הם באמת אותו הדבר?
הנה כמה דוגמאות מדוע הם לא אותו הדבר (וטיעוני-נגד נפוצים):
פריטים (lineitems) שקיימים בהזמנה עשויים לא להיות במלאי – ולכן לא להיכלל במשלוח.
ייתכן ופריט שונה נשלח מזה שהוזמן. טעות במשלוח.
אולי המשלוח יתבצע למקום אחר מאשר מה שהוזמן במקור? ("כן, אדוני השליח! אני לא בבית. תביא את זה בבקשה לחמתי או לשכן").
התנהגות המערכת לגבי החזרת פריטים במשלוח, וביטול פריטים בהזמנה היא כנראה שונה.
למשלוח יש סטטוס-מעקב (state) שונה מזה של הזמנה: "המשלוח יצא", "המשלוח נמסר", "המשלוח התקבל" מול: "הזמנה התקבלה" / "הזמנה בוטלה".
![]() |
| הכל בסדר שם? |
בעיית הטווח הבינוני: חוסר בהיסטוריה
לכן:
אבל, חשבו לרגע מה הסיכון? הוא לא כ"כ גדול: נניח שיש אובייקט "הזמנה" שבשלב מסוים משוכפל (בבסיס הנתונים / בזיכרון) לאובייקט "משלוח" והטיפול עובר אליו. האם המערכת תהיה קשה הרבה יותר לתחזוקה?
מזמן רציתי לכתוב על Analysis Patterns, כלומר Patterns של מידול מערכת.
אני מוצא אותם מאוד שימושים ומעניינים. בעבר, בעידן המפלים והדינוזאורים, היה מקובל לבצע מידול לפני התכנון המערכת הטכני – ולשלב הזה קראו Analysis. היום אנו מבצעים מידול כחלק מהתכן הטכני של המערכת, וגם תוך כדי ריצה, וגם בדיעבד – ולכן השם Analysis Pattern קצת לא מתאים. הייתי קורא להם Modeling Patterns.
שיהיה בהצלחה!
אני מפרסם את המצגת של הסשן שלי מרוורסים האחרון.
בעוד זמן-מה תהיה הקלטה – אפרסם גם אותה.
תודה לכל מי שבא, ותודה על הפירגונים – זה ממש שימח אותי!
ליאור
אם עקבתם עד עכשיו אחר הסדרה, כנראה שיש לכם ידע משמעותי כבר בקוטלין – אבל יהיה עליכם עדיין קשה להבין קוד קיים בקוטלין בגלל חוסר אחד עיקרי: הפונקציות של הספריה הסטנדרטית – שנעשה בהן שימוש רב.
נתחיל בפונקציות ה"חדשות" ביותר למפתחי ג'אווה – או כך לפחות נדמה לי: ה scope functions.
הדמיון הרב ביניהן – הוא דיי מבלבל!
נפתח בהגדרות:
![]() |
| הטבלה כאן כ reference – ולא כדי לנסות ולשנן. |
את הפונקציה ()with, אני מניח שכולם מכירים. אני זוכר אותה עוד מימי Object Pascal…
הפונקציה ה"כמעט-תאומה" שלה היא apply:
במשך שנים ג'אווה "חטפה" על היותה שפה verbose. בסה"כ מתכנני-השפה ניסו לתכנן שפה שבה הכל מפורש וקריא – אבל התברר שהם "הגזימו" עם המפורשות של השפה.
אחת הנקודות הכואבות ביותר הייתה היכולות "הפונקציונליות" של שפות כמו פייטון, רובי, או סקאלה: היכולת לעבד נתונים בקלות בעזרת פונקציות כמו filter, map, או max.
בהשוואה בזכוכית מגדלת, כל מימוש "פונקציונלי" היה קצר פי כמה – מהמימוש המקביל בג'אווה.
בג'אווה השתפרו עם הזמן, ובג'אווה 8 הציגו את יכולות ה Stream – יכולות פונקציונליות בשפת ג'אווה ועל גבי ה Collections הסטנדרטיים שלה… עם כמה wrapper שנדרשים.
אי אפשר היה להתעלם מצהלת השמחה בקהילת הג'אווה, שחשה גאווה רבה:
אמנם צריך להוסיף את המילה המעצבנת stream, וגם Collector לפעמים – אבל זה היה בהחלט נסלח, מול היתרונות.
האמת?
קוטלין הוציאה גם פה את ג'אווה באור לא מחמיא. היא מצליחה לפשט משמעותית תחביר ה stream של ג'אווה לתחביר נקי הרבה יותר.
למשל, הדוגמה הלא-מחמיאה הבאה:
יכולה להיכתב בקוטלין כך:
השילוב של ה Collections של קוטלין, ויכולות השפה (פרימיטיביים הם אובייקטים, extension functions, ועוד) – הופכות את היתרון -> למשמעותי מאוד.
הערה של קורא: נכון: הדוגמה של קוטלין עושה רק print ולא println ולכן היא קצרה יותר. טעות שלי.
חזרה על עקרונות הבסיס של Streams
ביטוי סטרימי (Stream-י) יהיה בנוי מ:
חשוב לציין גם ש streams הם Immutable ולכן ניתן למקבל חלק מהפעולות שלהם. עבור streams ארוכים – זה עשוי שיפור ביצועים ממשי (אבל גם: לא תמיד).
קוטלין מימשה מנגנון Steam משלה, שנראה מאוד דומה – אך בנוי באופן מובנה על ה collections של השפה.
המימוש ה default-י של streams בקוטלין אינו כולל lazy evaluation ואופטימיזציות או יכולות מקבול כמו בג'אווה. אפרט עוד על ההבדלים בהמשך.
את תחביר ה Streams בקוטלין מפעילים ללא פעולת ה ()stream – בכדי להתחיל stream, ולא צריך את פעולות ה ()collect על מנת להמיר אותו חזרה ל collection ולטפל בטיפוסים שונים:
בקוטלין אפשר פשוט לסיים את פעולת ה Stream ב ()toList בכדי לקבל רשימה.
רוצים מערך? השתמשו ב: ()toList().toTypedArray.
מפה? השתמשבו ב ()associate או ()associateBy:
גם נושא האינדקסים סודר בקוטלין, ויש פונקציות (למשל: ()mapIndexed) המאפשרות לגשת לאינדקס האיברים ב stream.
האמת: עברתי על 10 השאלות הנפוצות של התג "java-stream" באתר stackoverflow כדי לראות במה כדאי לעסוק בפוסט – ובקוטלין כיסו בצורה אלגנטית את כל הבעיות שהופיעו ב 10 השאלות הללו. נראה לי שגם הם הסתכלו – על אותה הרשימה בדיוק.
הפעולות בקוטלין בעלות שמות זהים ברוב המקרים. הנה כמה הבדלים:
מעבר לכך – הטיפול ב Streams הוא ממש דומה.
בואו נראה קצת דוגמאות:
לביטוי סטרימי יש overhead של ביצועים. בקוטלין מדובר בעיקר בשימוש של ביטויי הלמבדה – שגורמים לעוד הפעלות של פונקציות. ברוב השימושים הנפוצים (לא עשרות-אלפי איברים, לא עשרות הפעלות בשנייה) – ה overhead הזה הוא שולי מול קריאות הקוד שמושגת.
חשוב לזכור שהסיבה להשתמש בביטויי למבדה היא להפוך את הקוד לקריא יותר – ולא להפגין יכולות אישיות גבוהות של תפיסת לוגיקה מורכבת.
כאשר הביטוי מתחיל להיות מאתגר לקריאה, בחייאת דינאק: פרקו אותו לכמה ביטויים פשוטים יותר.
החלוקה הזו, וגם פעולות כמו ()onEach, יכולות לעזור לכם ב debugging – כאשר נדרש.
בכל זאת, יכולים להיות מצבים בהם יש לנו רשימה ארוכה של איברים, ואז הפעלה של ביטויים סטרימיים מורכבים מהווים overhead רציני. למשל: stream של עשרות אלפי איברים, כאשר לא כל פונקציה צריכה לפעול – כי לוקחים בסוף רק את האיבר הראשון.
לצורך כך בקוטלין יש מנגנון דומה לזה של ג'אווה של lazy evaluation הנקרא Sequences (שם שונה על מנת למנוע התנגשות בשם מחלקות).
בקוד הקוטלין, כל מה שצריך להוסיף הוא ()asSequence בתחילת הביטוי.
הפונקציה asSequence ממירה את ה collection ל lazily evaluated sequence, בדומה ל Steam של ג'אווה.
לאובייקט ה Sequence יש מימושים מתאימים ל filter, map, first ועוד – כל הפונקציות שיכולות לאפשר מצב של אופטימיזציה.
למשל בדוגמה: במקום לרוץ על 5 מיליון איברים ולסנן מי גדול מאפס, ואז לקחת חמש מיליון איברים ולבדוק מי ראשון, ב sequence עובדים ב batches של יחידים: לוקחים איבר, בודקים אם הוא גדול מ 0 – ואז ממשיכים הלאה.
כאשר ה terminator מסופק – מפסיקים, ומכאן שיפור הביצועים.
המחיר של השימוש ב sequence הוא שלא יהיו לנו זמינות סט הפעולות שלא יכולות לעבוד במוד של lazy eval כמו ()takeLast או ()foldRight. במקרים מעטים, בהם יש עבודה אינטנסיבית שנהנית מ memory / resource locality – ה Sequence עלול להיות פחות יעיל.
בקוטלין יש עוד כלים ל lazy evaluation כמו הפונקציה הסטנדרטית lazy או המילה השמורה by – אבל זה נושא לפוסט אחר.
במידה ואתם כותבים תשתית חישובית ל big data, רוצים parallel streams – עליכם להשתמש בתשתית ה Streams של ג'אווה (עדיין אפשר לכתוב את הקוד בקוטלין).
הפונקציות שהצגתי בפוסט הן פשוטות ברובן – אך יכולות להציג שיפור מיידי ומשמעותי על קריאות הקוד שלכם.
חבל לעבוד בקוטלין – ולא להשתמש בהן!
שיהיה בהצלחה!