הורשה היא הפרה בוטה של עקרונות ה Object-Oriented!

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

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

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

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

  • הורשה (inheritence)
  • ריבוי-צורות (polymorphism)
  • הכמסה (encapsulation) – או כימוס, כפי שהעירו. תודה.

זהו בדיוק סדר החשיבות ההפוך של עקרונות ה OO!

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

  • הכמסה (private)
  • ריבוי-צורות (implements / instanceof / interface / upcasting / downcasting)
  • הורשה (extends / protected / super / @Override)

(בסוגריים ציינתי את הכלים בג’אווה המשרתים כל עקרון. #C הוא דומה למדי).

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

זוהי קפיצת המדרגה העיקרית מול שפות פרוצדורליות שניהלו את המידע בצורה גלובלית (נאמר C).

ומה עם ריבוי צורות והורשה?
עקרונות אלו הם כלים המאפשרים לכתוב קוד מתוחכם יותר. במשך שנים לא הייתה בניהם הבחנה ברורה. אולי בגלל שפת ++C שאפשרה ריבוי צורות כמקרה פרטי של הורשה, ולא להיפך.

הסבר קצר: שפת ++C הכילה יכולת מקבילה ל abstract בג’אווה / #C שנקראה virtual. מפתחים יכלו להגדיר מחלקות שכל המתודות שלהן virtual (מה שנקרא pure virtual class) וכך לקבל באופן עקיף משהו מקביל ל interface בג’אווה / #C. לא היה לממשק, כפי שאנו מכירים אותו בשפת ג’אווה, שום ייצוג רשמי בשפה – זה היה [1] Pattern.

האבחנה הברורה בין ריבוי-צורות והורשה הייתה לנחלת הכלל רק כאשר הגיעה ג’אווה, שפה שלקחה את עקרונות ה OO צעד אחד קדימה והגדירה אלמנט חזק בשפה לממשק, הרי הוא ה interface.

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

מה הבעיה עם הורשה?
התובנה על המגבלות/בעיות בהורשה לא נתגלו עם ג’אווה. עוד בשנת 1992 הציג סקוט מאיירס, בספרו האלמותי “++Effective C” מספר בעיות עם הורשה והזהיר: “use inheritance judiciously”. שנתיים אחר-כך, בספרם המפורסם של GoF, הדבר נאמר יותר במפורש: “Favor object composition over inheritance” – נדבר על כלל זה בהמשך.

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

רוב הסיכויים שמפתחים ותיקים ומנוסים ירצו עדיין להשתמש בהורשה [2] ועבור מפתחים צעירים (הייתי אומר 5 שנים או פחות) – מומלץ לדסבלה (disable it).

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

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

public class InstrumentedHashSet extends HashSet {
  privateint addCount = 0;
  …
  @Override publicboolean add(E e) {
    addCount++;
    returnsuper.add(e);
  }
 
  @Override publicboolean addAll(Collection c) {
    addCount += c.size();
    returnsuper.addAll(c);
  }
 
  publicint getAddCount() {
    returnaddCount;
  }
}
InstrumentedHashMap היא הורשה שנוצרה על מנת לספור את מספר הפעולות על הרשימה.הריצו לרגע את הקוד בראש: אם אוסיף אוסף של עשרה איברים, מה תהיה התוצאה של getAddCount?
התשובה היא: It Depends.
אם אני רץ ב JDK 5 או ישן יותר התוצאה תהיה 20, אולם החל מ JDK 6 התוצאה תהיה 10. באופן רשמי ה API של HashSet לא השתנה כלל בין גרסאות ה JDK, אולם כאשר אני משתמש בהורשה אני נחשף ל API לא מפורש ולא מתועד: הקשר בין המתודות. במקרה שלנו זוהי הנקודה האם addAll קורא למתודת add או לא. כאשר אני יורש, אני יורש גם את הקשר הזה בין המתודות. בJDK 6 החליטו לוותר על הקריאה ל add מתוך addAll (עבור שיפור ביצועים) וכך שינו את החוזה הלא מפורש – אך שמשפיע בהחלט על המימוש שלי ל InstrumentedHashSet.
זהו בדיוק ה”שינוי הפנימי” שלא צריך להפריע לאף אחד אך במקרה שלנו, בעקבות ההורשה, הוא שובר פונקציונליות.הורשה פוגעת בהכמסה: מתודות כמו equals, compareTo, toString ו hashCode, שמומשו באב, מאבדות את נכונותן בעקבות הורשה. יש לכתוב אותן מחדש ולתת גם למחלקת האב (שעשויה להשתנות) לומר את דברה. לא פשוט.

הורשה פוגעת בהכמסה: אם מחלקת האב הכריזה על Serilazible, Clonable, Entity וכו’ – המחלקה היורשת יכולה לשבור הגדרה זו בכל רגע בלי יכולת להכריז על ההיפך. יותר גרוע: ירשתי ממחלקה שלא הייתה Serializable ולאחר שנה מחלקת האב הפכה לכזו – הקומפיילר לא יאמר לי דבר. הבעיה תשאר בעינה.

נו… אני מקווה שהבהרתי את הנקודה.

מה הפתרון? הפתרון הוא להשתמש בקשר הרכבה בין אובייקטים, מה שנקרא Composition. הוא כ”כ נפוץ ושימושי כך שהוא קיבל סימון משלו ב UML. אני אמנע מלכתוב על נושא שכבר תועד רבות. למי שמתעצל לחפש, הנה מקור קצת ישן, אך שנראה טוב:
http://www.artima.com/designtechniques/compoinh.html

סיכום
המסקנה ברורה: אם העיקרון החשוב (הכמסה) סובל מעקרון משני (הורשה) – זו סיבה מספיקה להחרים את העיקרון הסורר והפחות חשוב.
לצערי, עם כל זרם השפות החדשות שנוחת עלינו בשנים אלו (לינק) אף אחד לא הכחיד את ההורשה. הייתי שמח לראות וריאנט של ג’אווה או #C שהופך את extends, ברמת השפה, לפעולת composition במקום הורשה. זה פשוט ואפשרי.
עד אז – השמרו לכם מהורשה. ואם אתם שומעים את את עצמכם, או אנשים אחרים לידכם, אומרים “זה ממש וודו. התנהגות תוכנה זה לפעמים משהו מיסטי שלא ניתן להסביר” – בדקו שוב. אולי השתמשתם בכלי בעל flaws, כמו הורשה – ופשוט לא שמתם לב / אתם לא מודעים לפרטים.
—–[1] אם רוצים לדייק יש לקרוא לזה Idiom, שזה Pattern שנכון לשפת תכנות ספציפית. Patterns הם נכונים בכלל ולא רק לשפה ספציפית.

[2] הנה בשפת סקאלה הם החזירו לחיים את ההורשה המרובה. אבוי!

שיקולים בתכנון מקביליות: Beyond Threads

מקביליות (concurrency) מתורגמת ע”י לא מעט אנשים ל Thread ו synchronized (בג’אווה) – דבר שהוא נכון, אבל מסתיר כמה אלטרנטיבות חשובות.

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

Thread נתפס כ”אמצעי להאצת התוכנה”, אבל זה לא בדיוק נכון. אם יש לי משימה מקבילית שכוללת הרבה I/O (כגון client להורדת קבצים מהאינטרנט) הגדרת thread לכל קובץ או segment שמורד היא הדרך הקלה לפיתוח, אבל לא הדרך היעילה.

דרך יעילה יותר היא הגדרת thread יחיד שעובד עם ערוצים רבים של IO אסינכרוני (כגון channels בספרית java.nio. מקביל לפקודת select ב C של unix/linux, למי שמכיר). בגישה זו יש thread יחיד שמאזין לרשימת ערוצי ה IO הרלוונטים (sockets מאתר ההורדות + streams לכתיבה לקבצים) וכל פעם שערוץ IO זמין לקבל פקודה (התקבל packet ברשת או נסתיימה כתיבה של באפר לדיסק)ה thread שלנו יתעורר ע”י event. עליו לעשות איטרציה ולבדוק איזה ערוצ(י)  מוכנים, לבצע את הפעולה ולחזור לישון עד פעולת ה I/O הבאה שהסתיימה.

אז מה חסכנו בעבודה עם thread יחיד (בסדר עולה):

  • יצירה של thread היא פעולה יקרה (thread pool עוזר להתמודד)
  • תזמון ה threads השונים הוא overhead.
  • לcontext switch בין threads יש מחיר.

כתיבה ב Thread אחד היא בהחלט יותר יעילה! בכל זאת ברוב הפרוייקטים הייתי מעדיף לעבוד עם מספר threads. דוגמת אפליקציית ההורדות היא דוגמא פשוטה במיוחד שבה כל ה threads הם אחידים, אבל לרוב המצב יותר מורכב. כמות הרווח מ thread יחיד תתרום, נאמר, 5% לביצועי המערכת? לא שווה ברוב המקרים לייצר קוד מסובך בשביל שיפור שכזה.
סיכום: thread יחיד היא אופטימיזציה טובה למקרים כמו המתואר לעיל.

היבט חשוב נוסף הוא מערכת multi-core שאותה ניתן לנצל רק עם מספר threads שיתאים למספר ה cores.
אם הייתי מריץ את המערכת הנ”ל על מערכת עם ארבעה cores (וייתכן היה שיש לי מספיק ערוצי IO להעסיק את ה thread עד תום) – הייתי רוצה 4 threads שינצלו את ה CPU עד הסוף. דוגמא קלאסית היא WinZip שעובד עם thread בודד מול תוכנות דומות (winrar, 7zip) שעובדות עם מספר threads:

אפרופו multi-core: הנוסחא המקובלת לבחור כמה threads לייצר ליעילות מרבית היא:

# threads = # of cores / (1 - blocking coefficient)
כאשר המקדם (Blocking Coefficient) הוא ערך מ 0 עד 1 כמה אחוז מהזמן ה thread ישן בין פעולות IO. אם יש לי מערכת עם 4 cores שישנים 30% מהזמן, אני ארצה לעבוד עם 6 threads.
Runtime.getRuntime().availableProcessors();
ייתן לי בג’אווה את מספר הcores הלוגים (יתחשב ב hyperthreading).

אסטרטגיות מקביליות

אחד הדברים שכן כואבים במספר threads הוא עלות הסינכרון. אם לא נסנכרן – יכולים להיות שיבוש מידע, deadlocks ו livelocks. אם אנחנו מסנכרנים, אז המחיר הוא בביצועים: כל lock שמגיעים אליו n threads גורם לכך ש n-1 יאלצו “לישון” למרות שהם רוצים לעבוד. דמיינו בעבודה את קבצי ה excel שמישהו פותח ואף אחד לא יכול לעבוד עליהם (כי הם נעולים), אבל במקום לעבור למשימה אחרת – כל מי שניסה לפתוח את הקובץ חייב ללכת לישון! (ועד עובדי חברת החשמל – רעיון לעיונכם). יצירת המון threads במקום לרוב לא תשפר את המצב: תשלמו הרבה על context switch בלי לפתור את הבעיה המהותית.

יתרה מכך, סינכרון חוסם את מידת ה scalability האפשרי בריבוי cores.
בהינתן מערכת ש 5% מזמן הביצוע שלה הוא קטע מסונכרן, אפילו אם יהיה לי את ה banana bridge i9-9990EX של אינטל שיצא ב 2024 עם 6400 cores, לא אוכל להשיג יותר מפי 5 ביצועים מאשר על מעבד ה i5 ארבעה cores הסטנדרטי שלי (בהנחה שלא היה חיזוק כוחו של כל core ושזו משימה יחידה שאני מריץ). נשמע דיי מאכזב למי שמתכוון לחכות לbanana bridge מעכשיו.

עקרון זה ידוע כ Amdahl’s Law וניתן לקרוא עוד עליו כאן. הפיתרון הוא לצמצם את כמות הסינכרון למינימום.

אז איך מפחיתים את כמות הסינכרון למינימום? ישנן מספר אסטרטגיות להתמודדות עם סנכרון:

  • mutable synchronization
  • isolated mutability
  • pure immutability
  • actors

mutable synchronization
זו הגישה שרובנו בוחרים בד”כ באופן אוטומטי בלי לשים לב. העקרון הוא לשים synchronized על כל מתודה שיש בה שינוי state שיכול להשתנות בין threads. זו הגישה הקלה והפחות יעילה. דרך שיפור מהירה היא לכתוב את ה synchronized בבלוקים הכי קטנים שאפשר ולא בחתימת המתודה (למי שלא מכיר – תנסו – אפשר לכתוב אותם אפילו על שורה בודדת).

isolated mutability
גישה זו היא צעד אחד הלאה, לצמצם את כמות הסינכרון למינימום – שזה המשתנים עצמם. במקום לעבוד עם Long אני אעבוד עם AtomicLong של java.util.concurrent שמספק לי פעולות אטומיות כגון getAndIncrement או incrementAndGet
שימוש בהן יאפשר לי לצמצם את הסינכרון לנתונים עצמם ולא מעבר.

pure immutability
זו גישה הפוכה לגמרי שאומרת – אל תשנה משתנים. כל ערך שתיצור יהיה immutable (כמו String), כל פעם שתצטרך ערך אחר – צור אובייקט חדש. גישה זו דיי קשה ולא כ”כ נתמכת בג’אווה. אם פיספסתי – הקומפיילר לא יתריע וגם יש כמה מצבים בעייתים שאצטרך להתחכם בהם. Closure דוגמא לשפה שתומכת בגישה זו באופן מובנה.

actors
זו גישה שפופולארית היום ב scala (השפה שנוצרה ל multi-core ו scalability). הגישה מדברת על active objects או actors שהם אובייקטים “חיים” – כלומר ה threads. לכל actor יש תור נכנס (“דואר נכנס”) והם מתקשרים אחד עם השני רק בהודעות אסינכרוניות (שמונעות מצב של deadlock). בזכות התקשורת – אין צורך בסנכרון בכלל. גישה זו ניראית מוצלחת וישימה, לא ניסיתי בעצמי – אך שמעתי כמה סיפורי הצלחה ממקור ראשון. נראה שהיא גם עובדת יפה כשהמערכת גדלה והופכת למסובכת.
אמנם בג’אווה אין תמיכה בשפה ל actors אך יש מספר ספריות (כגון akka או GPars) שלמרות שנכתבו בשפות שונות על ה JVM – עובדות יפה מתוך Java. הגישה מתאימה, אגב, גם לסנכרון בתוך אותו JVM וגם ב remoting בין JVMs שונים (כגון nodes שונים ב cluster).

ארבעה דברים טובים שנוספו עם Java 7

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

סיכמתי כמה דברים חיוביים לאלו שיעברו ל Java 7, במקרה או בכוונה, ששווה להכיר:
פחות צורך במשמעת עצמית ב Exception Handling
Java נבנתה בצורה שתקל על המפתח להמנע משגיאות, הפחזבל (Garbage Collector) חסך מיליוני שנות פרך של debug וחיפוש אחר זליגות זכרון. בכל זאת, חברה שלא אנקוב בשמה, שילמה 17 מיליון דולר השתתפות בנזקים של הלקוח בעקבות הבאג הבא (חפשו אותו):

  Connection conn = null;
  Statement stmt = null;
  try {
    conn = connectionPool.getConnection();
    stmt = conn.createStatement();
    // Do the lookup logic
    // return a list of results
    } finally {
      if (stmt != null) {
        stmt.close();
      }
      if (conn != null) {
        conn.close();
    }
  }

אני מניח שהבאג לא צועק, הקוד נראה במבט ראשון מסודר ונכון. אבל מה – אם ()stmt.close יזרוק exception (בגלל מנגנון ה keep alive  של Oracle?), ה connection לא יתנקה וכשפעולה זו תחזור שוב ושוב, Oracle יסרב לקבל עוד קריאות. במערכת Mission Critical המשמעות היא לבצע restart לבסיס הנתונים כל מספר דקות, חוסר יכולת לעבוד, יום בטלה לעובדים והפסד אדיר ללקוח.

הפתרון הוא לעטוף ב try-catch נוספים (ה catch יישאר כנראה ריק) את קריאות ה ()close של stmt ו conn. זה היה ה best practice שדרשנו ממפתחים לעשות, שוב ושוב, וברגשות מעורבים.

איך מוודאים שבקשה זו תיושם בפועל? ביצוע תחוף של Code Reviews, הגדרת המודעות בקרב המפתחים ובארגון שעובד נכון – בניית Connection Pool שיודע לזרוק Connections בשעת לחץ.
בג\’אווה 7 ניתן לכתוב את הקוד הבא:

  try (Connection conn = connectionPool.getConnection();
       Statement stmt = conn.createStatement()) {
    // Do the lookup logic
    // return a list of results
    } catch {
      // respond to exception

    }

  }


שדואג ש conn ו stmt ייסגרו, אם לא שווים ל null ולמרות exceptions שיכולים להזרק. הוא מקביל לקוד הארוך והמכוער שתארתי למעלה. נחמד!

פחות חזרה חזרה חזרה ב catch statement
תופסים שלושה סוגי Exception ורוצים לעשות אותו הדבר?
מעכשיו כתבו:

} catch (IOException | SQLException | SQLStatementException ex) {
  log.warn(ex);
  throw ex;
}
המשתנה ex יתייחס לסוג ה Exception (מתוך השלושה) שנזרק. יופי.

Performance Profiling: שחרור VistualVM
אם תחפשו בתוך ספריית Bin ב JDK שהורדתם, תגלו שיושב לו שם Profiler בשם VisualVM. מלבד כך שהוא חינמי, מתחבר לכל ה IDEs החשובים (עם plugin) ויש לו ממשק משתמש נחמד, הוא מספק נקודת איזון מאוד מוצלחת של פשוטות מול עוצמה. הוא ידידותי – אבל גם אינו צעצוע. הוא יכול להספיק לרוב משימות ה profiling הנפוצות, גם בתוך tomcat, ואינו דורש הגדרות מיוחדות לJVM או לשרת שאתם עובדים איתו.

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

נ.ב. – אופס, תפסתם אותי. VisualVM מסופק החל ממרץ כבר ב JDK 1.6 ואינו חידוש של Java 7. בכלל הוא יכול לעבוד (תחת מגבלות) עם אפילקציות שקומפלו ב JDK1.4. הוספתי אותו לכאן מכיוון שהוא תוספת מכובדת ל JDK שקל לפספס בגלל שיצא ב update ולא ב major release.

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

בהצלחה!

האם Java גוססת?

כבר בסביבות שנת 2008 התחיל לצבור תאוצה הקרב על הירושה: איזו שפה תחליף את שפת Java? אם היה קרב לרשת את סטלין בעודו בחיים, מחנות החינוך מחדש בסיביר היו מקבלים מספר חברים חדשים לשורותיהם, אבל שפת Java עם הקהילה הפתוחה שלה, היא רחוקה מאוד מדיקטטורה*. אחת התלונות הקשות על Java היא הקצב האיטי בו גוף התקינה של Java מקבל החלטות. JDK 1.7 יצא לאחר חמש שנים ולא כלל שיפורים יוצאי דופן. C#, שפה מאוד דומה, מציגה שיפורים משמעותיים (לדוגמה LINQ, closures) באופן תדיר.

האם צריך להחליף את Java? מה רע בה?
if it’s not broken – don’t touch it!” יאמרו רבים.
if it’s ain’t broke – break it” יענו המהפכנים. “חובה עלינו לחפש שיפורים כל הזמן”, בתרגום חופשי. “ואם נוח לנו במצב הקיים, אדרבא”
התלונות העיקריות לגבי Java הן הבאות:

שפה  verbose (מרבה במילים) ומסורבלת.
יש אינספור דוגמאות של: 10 שורות בג’אווה, 4 שורות ב X (שפה אחרת) . דברים מרגיזים כוללים:

  • Checked exceptions
  • Java Beans (הסינטקסט המסורבל של getXXX/setXXX שהופך משפטים לקשים לקריאה)
  •  הצורך לכתובanonymous class  בכדי להעביר פונקציה פשוטה כפרמטר (שפות אחרות פשוט נותנת להקליד את הקוד הרצוי במקום הנכון)

גוף התקינה הוא שמרן ואיטי.

“מדוע היה צריך לחכות לג’אווה 7 בכדי שמשפט switch יוכל לקבל String?” היא תלונה נפוצה וקלה להבנה (אך קצת קטנונית). הבעיה האמיתית היא ריבוי ה cores שהפך למציאות תיכנותית, וחוסר היכולת של Java להתאים את עצמה למציאות החדשה. Fork-Join שהגיע באיחור ולא יושב יפה בתוך השפה ו Actors (בדקו בתחתית הפוסט בבלוג זה להסבר על Actors) שמתכנתי Java אוספים משדות זרים.

האם שפה שהיא verbose (מרבה במילים) משפיעה באופן משמעותי על פריון המתכנתים? השאוות הראו שלספריות העומדות לרשות המפתח, ה IDE, ניסיון והכישרון של המפתח יש השפעה גדולה מהשפה עצמה. רק אסמבלי (ואולי קצת קובול) נשארה משמעותית מאחור (מקור: ספר Code Complete) אני מכיר היטב את הטענה שכתיבה ברובי או פייטון היא הרבה יותר מהירה אך אין לזה חיזוק אמפירי. הסיכוי לטעויות בשפות אלה בגלל שאין בדיקת קומפיילר מפצה לרעה על ה syntax הקליל. אני מניח שזו עניין של הרגשה ואולי מתכנתים מעולים שלא צריכים קומפיילר (שמעתי TDD?) באמת כותבים מהר יותר בשפות סקריפט. 
בואו נתבונן בשני האלמנטים האובייקטיבים (לא תלוי במתכנת) שכן משפרים את הפריון, וכיצד השפות החדשות יכולות להתמודד מול Java:
1.כלים – auto-complete, קומפיילר טוב, Refactoring וכו’. כל שפה חדשה שמוצגת לשוק מהר מאוד מייצרת כלים בסיסיים (לפחות syntax highlighting ו auto-complete), קשה לשכנע שהשפה שלך תקצר זמני פיתוח אם אתה אמור לעבוד ב Notepad. כל מערכות ה IDE המרכזית (Eclipse, VS, IntelliJ ו NetBeans) בנויות להרחבה ותמיכה בשפות נוספות.
2.  ספריות – האם עלייך לכתוב ספריית Scheduling בעצמך, או שאתה מוריד את Quartzומתחיל לעבוד? ב C++ לא היו הרבה ספריות משותפות, אולי בגלל שהיו קומפיילרים שונים למערכות הפעלה שונות. ברגע שJava הציגה את “Write Once, Run Everywhere“** – כמות הספריות הזמינות להורדה (ובחינם) נערמה והפכה לכוחה הגדול של Java – ה Community. אז איך מציגים שפה עם 0 ספריות מול Java עם אלפי ספרות בדוקות ובוגרות? רובי הצליחה להתרומם בעזרת Rails – ספרייה חדשנית ומוצלחת במיוחד לפיתוח אפליקציות Web (וגם בעזרת שפה מוצלחת בפני עצמה, כמובן) – אבל זה לא הספיק. ואז – נתגלתה השיטה: ב JDK 1.6, ה JVM – Java Virtual Machine הורחב לתמוך ב JavaScript שיתקמפל ל Byte Code – וירוץ בצד השרת. לצורך העניין נעשו שינויים שאפשרו ל JVM לתמוך בתכונות של שפה דינמית (כלומר פונקציה היא evaluated רק ברגע שצריך להריץ אותה, שמאפשר לתוכנה לבצע שינויים עד אותו הרגע). הסכר נפרץ ושפות רבות קפצו על ה JVM: בוגר, יעיל, מוצלח,highly optimized, עם התקנה לעשרות מערכות הפעלה ואולי הכי חשוב – היכולת  להשתמש באופן טבעי בכל מאגר ספריות הJava הקיימות. אפילו שפות שהיה להן Virtual Machine – פיתחו וריאציה עבור ה JVM.
 
היכן המהפכה?
אוקיי, אז עברו שלוש שנים, וJavaעדיין כאן. השפות שעשו זינוק בשנה האחרונה הן דווקא LUA ו F# – שאינן קשורות ל JVM. השפות המדוברות תופסות הרבה נפח בכנסים ובבלוגים, אך עדיין הנוכחות שלהן סה”כ קטנה.
ברוכים הבאים לעולם העסקי (ניתן לומר: האמיתי). מקום שבו טכנולוגיה טובה לא מבטיחה הצלחה, מקום שבו שיתופי פעולה והרגלים הם בעלי כוח מכריע.
הדעיכה של Java כנראה התחילה, אבל Java לא עתידה להעלם סתם כך. הייתי מעריך שיעברו עוד שנים רבות עד שJava תחשב כשפה לא רלוונטית. כמות הקוד הקיים, הספרות והכי חשוב – האנשים שמושקעים באופו אישי בJava הוא עצום והוא לא יעלם סתם כך. (בעולם הJVM) תמיכת ה IDE בJava היא עדיין הטובה ללא עוררין, וכך גם כמות החומר הלימודי. יתרונות השפות המדוברות – הן עדיין הדרגתיות וצריך סיבה טובה כדי לעשות מעבר.למשל: אם יש לי בעיות Concurrency קשות באמת, אני יכול לכתוב מודול ב Scala, לייצר .jar ולצרף אותו לפרוייקט הJava הקיים.

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

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

Scala  – כנראה המתמודדת הבולטת ביותר. משלבת אלמנטים פונקציונליים ו Object Oriented. מאפשרת גם תכנות דינמי (כמוJavaScript  או רובי) אבל גם Static Typing – כלומר הקומפיילר יתפוס לכם את רוב הבעיות. הייחוד שלה היא ההצלחה לשלב פרדיגמות שונות בצורה מוצלחת והתכנות מהיסוד לשפה לתכנות מקבילי. היא תהיה יחסית נוחה למתכנת Java, C++ או C# אבל תציע הרבה כוח ואפשרויות שלא היו זמינות בשפות אלה.
Groovy– שפה מגניבה, שכוללת אלמנטים של python Syntax ותאימות רבה לJava. קוד Java ללא שינוי צפוי להתקמפל בגרובי ב 95% מהמקרים. נעשתה עבודה לא כ”כ מסודרת בפרטים – וכבר יצאה גרובי++ שמטפלת בכמה מהבעיות. הביאה לעולם את Grails שזה העתק של Rails של רובי, ואת Griffon – שזו גרסת ה Desktop לאותה ארכיטקטורה. נראתה מועמדת מובילה לפני שנתיים-שלוש (בעיקר בעקבות backward compatibility), אך לאחר שהמפתח שלה הצהיר “אם הייתי מכיר את סקאלה – לא הייתי טורח לפתח את גרובי” גורלה כנראה נחרץ.
JRuby– רובי היא רביזיה Pure-Object-Oriented של שפת Python מתחילת שנות ה-90 שהוזכרה קודם. יש לה את ספריית Rails שזכתה להצלחה רבה. JRuby היא פשוט מאוד גרסת ה של רובי JVM, שילוב שגרם לרובי לרוץ מהר הרבה יותר. יש לה מומנטום חזק כ”כ בעולם ה web – כך שנראה שהיא כאן בכדי להשאר. השאלה בעיקר היא האם תתפשט מחוץ ל Web UI לכיוונים אחרים.
Closure– מין LISP מודרני, דינאמי ופונקצינאלי. גמישה מאוד ביכולת הביטוי. משפט אחד בלתי-קריא יכול להיות שקול ל 10 שורות בכל שפה אחרת. האם זה ייתרון? אם אתם באקדמיה, אוהבים קדרים וקודרים (המבין יבין) – לבריאות. אני לא נהנה לקרוא אותה ואיני מבין על מה המהומה. 
Jython – פייטון (ע”ש להקת מונטי-פייטון, כדרך אגב) היא הותיקה בשפות, אפילו ותיקה מ Java. מאז הצלחת Java ירדה מהכותרות אך חזרה אליהן לאחרונה, אולי גם בגלל Jython ה porting ל JVM. היא פחות סקסית וחדישה, אך יש בעולם הרבה מאוד קוד פייטון ומתכנתי פייטון (יותר מרובי, למשל) בעוד שרובי תפסה את הנישה הוובית, פייטון נשארה general purpose. באופן אישי, אני מכיר בעיקר סקריפטים וכלים שכתובים בה ומעט מוצרים ולכן יש לי רושם שנתוני השימוש היבשים קצת מזייפים. כמו לשאול כמה מתכנתי Bash יש בעולם…המצב הקיים:

< הלינק מת… היה כאן תרשים של אחוזי השימוש בשפות JVM שונות + לאורך זמן >

 
לסיכום: קרב הירושה בעיצומו ועוד עשוי להמשך זמן רב. לא חייב להיות בהכרח מנצח אחד. הJVM  יאפשר למספר שפות לחיות זו לצד זו, כאשר כל אחת תוכל להתמקד בנישה שלה. 
Java עשתה את שלה, עכשיו ה JVM הוא העיקר.
* מאז שאורקל, חברה דיי כוחנית, לקחה שליטה על Java היא הצליחה בעיקר להבריח מפתחים מקהילת המפתחים של Java ולייצר תביעות שונות, בחשד להפרת פטנטים של Java. היא ניסתה לדלל את כוחה של הקהילה הפתוחה, אך עם הצלחה מוגבלת.
** Java לא הייתה המערכת הראשונה שרצה על מערכות שונות: פייטון הגיע לשם כמה שנים מוקדם ממנה עם Virtual Machine משלה. מה שהכריע את הכף הוא כנראה ש Java קסמה למפתחי ה C++ (שהיו אז הרוב) בכך שאימצה תחביר דומה וגם תמיכה גורפת מגופי ענק כמו IBM, אורקל, SUN ועוד.