להבין Dependency Injection [מעודכן]

עדכון: ספטמבר 2014 – הפוסט חודש כליל. הפוסט המקורי פורסם בספטמבר 2012.

לשימוש ב Dependency Injection (בקיצור: DI) יש שני מניעים עיקריים:

  • הקפדה על ה Single Responsibility Principle[א] (בקיצור: SRP).
  • ביצוע בדיקות יחידה / בדיקות אינטגרציה.
לעתים, השימוש ב DI הופך מוטמע כ”כ עמוק בפרויקט / בתהליכים (״התשתית שלנו היא Spring״) – עד שניתן לשכוח מהיכן הכל התחיל. כאשר מקפידים מאוד על שימוש ב DI, אך מחלקות בפרויקט עושות מטלות שונות – זה נראה סוג של “פספוס”: זה כמו להאכיל את הילדים בקינואה וברוקולי בצהריים, אבל צ’יפס וקרמבו בשאר הארוחות. מותר… אבל לא מומלץ.
DI הוא גם פתרון ממשי ומעשי לבעיות שצצות בביצוע בדיקות יחידה, ואפילו יותר: בביצוע בדיקות אינטגרציה. אני עבדתי עם קבוצות פיתוח שהוסיפו DI בכדי לבצע בדיקות יחידה, וללא קשר ל SRP (ל SRP היה לנו כלי אחר).
כיום, נראה שביצוע בדיקות יחידה/אינטגרציה הוא יותר נפוץ מהקפדה מלאה על SRP. רוב הארגונים עושים היום בדיקות יחידה, אבל חלק קטן מהם (מניסיוני האישי) באמת מקפיד באמת על SRP. ויש עוד אחוז מסוים של ארגונים שמשתמשים בכלי DI – “כדי שיעשה סדר”, אך ללא יכולת להסביר מהו בדיוק הסדר הזה שהם מצפים לו וכיצד הוא יקרה. לצפות ל”פחות באגים” בעקבות שימוש בכלי DI זה נחמד, אבל ניתן באותה מידה לצפות לכזו תוצאה משימוש ב Log4J… ללא שימוש נכון ויעיל – הציפיה היא תקוות שווא.

בפוסט זה אני מנסה גישה חדשה: פרסמתי את דוגמאות הקוד (העובדות) מהפוסט בגיטהאב: https://github.com/baronlior/di-post
בואו נראה איך זה עובד…

DI בהיבט של SRP

בואו נתבונן על הקוד הבא:

הממשק
ספק השירות
צרכן השירות

הנה השקענו, בהגדרת Interface (ע”פ עקרון ה Program to an interface), בהפרדה בין נותן שירות לצרכן השירות. הפרדה זו ניתן לקטלג כ”ניהול תלויות” / “Low Coupling” – ערכים בסיסיים בהנדסת תוכנה.

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

אמנם המשתנה המקומי artist הוא מסוג הממשק Artist – אך השימוש ב new מחייב שיהיה import ותלות למחלקה הקונקרטית. זרעי הפרת-התלויות – נזרעו.

מדוע הפסימיות? המתכנת ישתמש בממשק מבלי קשר ל imports הזמינים…

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

באופן דומה חשבו על המתכנת: הממשק הוא כמו גדר – זהו מחסום פיסי לא מאתגר. מה הבעיה ללכת לקוד של ה Interface ולשנות אותו? או לשנות את ההגדרה בקוד של המשתנה מטיפוס Artist (הממשק) לטיפוס Michelangelo (המחלקה הקונקרטית)? ממש כלום.

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

מלבד החשש לטשטוש הגבולות שהוגדרו, יש פה עניין של “קוד מיותר”.
יצירת הקשרים בין המלקות הוא לא חלק מה “Single Responsibility” של המחלקה – ופשוט עדיף שלא יהיה שם. עדיף ליצור מחלקה מיוחדת (למשל: Factory) שזהו ייעודה בחיים.

איך אפשר לעשות זאת?
– פשוט “נעיף” את ה import של המחלקה הקונקרטית מצרכן השירות ונוסיף מחלקה שהכרת המחלקות הקונקרטיות הוא ייעודה. Repository בו מחלקות יכולות להירשם, ולמצוא אחת את השנייה ע”פ “סימנים” מסוימים (שם, שם התפקיד, ממשק, וכו’). נקרא לו Service Locator:

דוגמא לשימוש ב Service Locator

הנה, כרגע אין לנו תלויות במימוש הקונקרטי – סגרנו את “הפרצה בגדר”!

עם השנים התפתחו ספריות של Service Locator שמקלות על המפתח, ועם הזמן החליטו להחליף את סדר האחריויות: במקום שמחלקה תגדיר למה היא זקוקה (“the artist”), מחלקת ה Service Locator תתפוס פיקוד והיא “תזריק” לצרכן השירות את מה שהוא צריך. מימושים אלו נקראו בהתחלה “Inverse of Control” (בקיצור: IoC), עד שמרטין פאוולר טבע את המונח “Dependency Injection” (בקיצור: DI) אותו אנו מכירים היום.

הנה דוגמה לשימוש ב JSR-330 (הסטנדרט של ג’אווה ל DI):

ה Annotation של Inject@ מסמנת שלבנאי של Michelangelo “יוזרק” המימוש הקונקרטי של המחלקה מסוג ה Painter לה הוא זקוק – מה שמותיר את הקוד של Michelangelo נקי למדי מכל עניין של ניהול הקשרים בין המחלקות.

היכן מוגדר הקשר בין המחלקות? במחלקה אחרת, ייעודית לעניין:

מחלקה זו אינה מוגבלת בתלויות שלה – התפקיד שלה הוא “לשאת בנטל” התלויות של האפליקציה.

הקישור בין Inject@ והפונקציה ()bind נעשית בספריית ה DI בשם Google Guice – ה Reference Implementation הרשמי של JSR-330.

אי אפשר לא להזכיר את Spring – הספרייה שבעצם הפכה את DI לכ”כ נפוץ, והופיעה זמן מה לפני JSR-330 או Guice. ספרינג היא עדיין כנראה ספריית ה DI הנפוצה ביותר בעולם הג’אווה.

ההיסטוריה של ה DI/IoC. מקור

הערה: בעוד המינוח “Dependency Injection” היה ברור יותר מ “IoC” המעורפל-משהו, בד בדד הוא כנראה הותיר גם לאנשים מספיק מרחב לדמיין מהי “הזרקה”. הזרקה, למשל, יכולה להיות גם השמה ב Reflection של Private member של מחלקה, לא? למשל כך:

זהו תחביר מקוצר לדוגמה האחרונה. במקום לקבל את הערך כפרמטר לבנאי המחלקה, ושם להציב אותו כ private member – אנו פשוט מציינים את ה Inject@ ישירות על ה private member של המחלקה.

מצד אחד חסכנו שורת קוד משעממת, מצד שני “עקפנו” את ה encapsulation של ג’אווה והתרנו לגשת ל private member של המחלקה.

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

  1. טשטוש הגבולות של מושג ה private בג’אווה.
  2. בעוד בנאי עם הרבה (נאמר 10) פרמטרים יציק לנו, וידרבן אותנו לנהל תלויות בצורה קפדנית / לחלק את המחלקה לכמה מחלקות קטנות יותר ועם התמחות מוגדרת יותר – (מניסיון) לא כ”כ מפריע לנו שיש מחלקה עם 10 פרמטרים עליהם יש Inject@.
  3. שימוש מוגבר ב class members ולא משתנים עם scope מצומצם יותר – כי כ”כ קל להגדיר בדרך זו משתנים.
סה”כ “הזרקה” של משתנים לבנאי המחלקה נחשבת הדרך המועדפת לניהול תלויות.
הנחת היסוד הוא ששימוש ב DI בא להבטיח ניהול תלויות מדויק וקפדני, ולא רק “לקצר את כתיבת הקוד”. הדרך הכי קצרה לנהל תלויות היא כנראה שימוש ב new, בכל מקום שרק רוצים.
ולכן, השמה ל private member נראית קצת מוזרה: כמו אכילת קינואה וברוקולי עם צ’יפס מטוגן בצד. מותר, אבל מוזר.

ריבוי צורות (Polymorphism)

אם שמתם לב, הדרך להגדיר קשר בין הממשק למחלקה הקונקרטית (פקודת ()bind) היא על בסיס הטיפוס (class) בג’אווה. בתחילת הדרך נהוג היה דווקא לרשום מחלקה תחת שם (מחרוזת מסוימת) ואז לבקש אותה בחזרה ב Service Lookup / בהזרקה. עם הזמן הגיעו ל 2 תובנות:

  • שם המחלקה הוא כנראה התיאור הברור והצפוי ביותר שלה.
  • השימוש ב class object בשילוב עם Generics מאפשר ליצור ספריית DI / Service Lookup ללא הצורך המרגיז בביצוע downcasting.
מה קורה כאשר רוצים להשתמש בריבוי צורות?

ברור לכם שברגע שהמיפוי הוא לא חד-חד ערכי, ה framework לא יצליח לבצע resolving נכון ב runtime. יש frameworks שמתירים מצבים שהם לא חד-חד ערכיים, בהנחה שהם יצליחו לנחש נכון, ויש כאלו שפשוט זורקים שגיאה בזמן ריצה, למשל: “binding xxx was already configured”.

מה עושים? יש לכך כמה פתרונות (ל Spring Framework, למשל, יש כמה דרכים משלה), אך הפתרון המקובל ב JSR-330 הוא להוסיף Qualifiers (מעין תגיות) על ה binding ועל הבקשה (ועל צרכן השירות – בהתאמה), וכך לבצע את ההתאמה: התאמה ע”פ תיאור הכוונה.

רישום של 2 מימושים לממשק Painter, תחת שמות שונים

הנה המימוש המחלקה Michelangelo שמקבלת שני מימושים שונים של Painter:

הדוגמה היא רק בכדי להמחיש את נושא ה DI

DI בהיבט של בדיקות יחידה/אינטגרציה

זווית אישית: כפי שכתבתי בפוסט קודם על בדיקות יחידה, אני מעדיף בבירור את הבניית הקוד כך שלא יידרשו Mock Objects עבור בדיקות יחידה – זו פשוט תקורה מיותרת. השימוש ב Mock Objects כן שימושי בעת ביצוע בדיקות אינטגרציה. יש כל-מיני פרשנויות ל “בדיקות אינטגרציה”, אני מתכוון לבדיקות שרצות offline (למשל בתוך ה IDE) אך בודקות את השילוב של כמה מחלקות לידי תסריט גדול יותר.

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

ראינו את גישת ה Qualifiers של DI, שהתאימה לנו עבור מקרים של Polymorphism אך לא תתאים למקרים של בדיקות. צרכן השירות לא יכול לבקש משהו אחד בזמן הרצה רגילה, ומשהו אחר בזמן בדיקות. הוא לא אמור להיות מודע לסוג ההרצה שהוא עובד (אבוי אם כן…).

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

אזור ירוק – הכנסת הסביבה הבדיקה; אזור הכתום – הרצת הבדיקה.

  1. אנו בודקים את מחלקת ה Artist ולכן צריכים מופע שלה. כדי שה DI Framework יוכל לעבוד, אנו צריכים ליצור את Michelangelo בעצמו בעזרת ה Framework.
    יש פה הזרקה ל member. מה שאני לא מרשה לעצמי לעשות בקוד production, אני מרשה לעצמי לעשות בקוד של בדיקות – כדי לכתוב קוד קצר יותר.
  2. אני יוצר mock ל Painter. לא רציתי להתעסק עם ספריית mocking (כמו Mockito או PowerMock) ולכן יצרתי mock כ Anonymous Class.
  3. פונקציה זו היא בעצם inline של הקונפיגורציה. בעוד שבקוד production אנו רוצים להפריד את ה wiring מהקוד, דווקא בבדיקה ה wiring הוא חשוב להבנת הבדיקה, ולכן אני מעדיף שיהיה באותו הקובץ. שוב השתמשתי ב Anonymous Class.
  4. זו הבדיקה עצמה, הפעלה של המתודה ()createArt מול התוצאה הצפויה (הבדיקה עוברת).

סיכום

בפוסט זה סקרנו את נושא ה Dependency Injection ושורשיו, וניסינו להפוך “שימוש אוטומטי” בכלי DI, לשימוש מושכל יותר.

מה השתנה בשנתיים האחרונות שגרם לי לחדש את הפוסט?
אני חושב שבאופן אישי יצא לי להיחשף בעבר ל DI בעיקר ככלי לביצוע בדיקות יחידה – את ניהול התלויות העיקרי עשינו במערכת (הישנה) על גבי JNDI של JEE (סוג של Service Locator). את תמונת עולם זו שיקפתי בפוסט בצורה דיי נחרצת – כפי שהגיב והעיר ויטלי.

כמו כן החלטתי לתת דוגמאות קוד ופחות “הסברים”, בהנחה שההשקעה הנוספת מוסיפה ערך (לכתוב קוד ולבדוק אותו לוקח קצת יותר מלכתוב כמה פסקאות…)

השמטתי מהפוסט המקורי פסקה שלמה בשם “רגע של ספקנות”. אני אזכיר כמה רעיונות מרכזיים בקצרה:

  • אין צורך ב Framework בכדי לבצע DI. בסה”כ, DI הוא רעיון, וה Frameworks השונים רק יוצרים אוטומציה של התהליך.
  • באוטומציה (או Framework) יש יתרון שהוא גם בעיה: היא חוסכת מאיתנו את הצורך בהבנה עמוקה. ללא הבנה עמוקה ניתן לשכוח מדוע אנו עושים מה שאנו עושים (במקרה שלנו – DI) – ולפתע למצוא את עצמנו עושים משהו אחר מהכוונה המקורית.
    אני נוטה בשנים האחרונות להטמיע רעיונות ראשית ללא Framework, בכדי להבין לעומק את הנושא – ורק בסיבוב השני להוסיף את ה Framework שמקל על העבודה. זה כמו ללמוד “חשבון” (מתמטיקה בסיסית) עם מחשבון ביד מהרגע הראשון: האם לא עדיף ללמוד כמה חודשים ללא מחשבון – ורק אז להוסיף אותו?
  • DI הוא לרוב רק חלק קטן במערך המודולריזציה של המערכת. שימוש ב DI לא מבטיח איכות או מודולריות, בפני עצמו. הוא רק כלי (נוסף) להשיג מטרות אלו.
  • אני לא שולל את הרעיון של אי-שימוש ב DI. למשל: להפסיק ליצור interface לכל מחלקה (מלבד מקרים של ריבוי צורות), להסתמך על public vs. private להגדרת הממשק, ולצמצם את השימוש ב DI למינימום האפשרי (למשל: עבור בדיקות אינטגרציה, או ניהול תלויות ברמת השירותים בלבד). בסופו של דבר אנו משלמים לא מעט עבור ההגנה שמספקת גישת ה Program to Interface (בצורה הדקדקנית שלה) וצוותים מסוימים / פרוייקטים מסוימים – יכולים להסתדר מספיק טוב עם פחות הגנות, ועדיף לתת להם להתקדם מהר יותר.
את דוגמאות הקוד כתבתי ב Guice ולא בספריה הנפוצה יותר Spring Framework בעיקר בגלל ש Guice מצומצם ופשוט יותר. ספרינג מספקת הרבה מאוד יכולות – שיכולות מאוד שימושיות למערכת גדולה ומסובכת, אך לא היו תורמות דבר להבנת הרעיונות הבסיסיים.
שיהיה בהצלחה!

—-

[א] עקרון זה מוכר גם כ Separation of Concerns או Segregation of Duties. הכוונה היא שכל יחידת קוד (פונקציה, מחלקה, מודול) יעשו דבר אחד, ודבר אחד בלבד. צד שני של אותו המטבע הוא שכל אחריות תהיה בידיים של יחידת קוד אחת גם כן. כלל זה עוזר ליצור סדר במערכת, ולוודא שמי שכותב את הקוד יראה לנגד עיניו את התמונה המלאה ולא חלקים ממנה (כי כל האחריות נמצאת באותו המקום).

קישורים:

DI קליל ברובי
הפסיכולוגיה של בדיקות תוכנה – מישקו הארווי (היוצר של AngularJS ו jsTestDriver).

הורשה היא הפרה בוטה של עקרונות ה 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] הנה בשפת סקאלה הם החזירו לחיים את ההורשה המרובה. אבוי!

ארכיטקטורה מונחית-שירותים (SOA): על מה המהומה?

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

לא מדובר ביהדות או בנצרות, אם כי בדת לא פחות אדוקה – ושמה: Service Oriented Architecture – SOA.

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

(אם אתם עדיין כאן, אתם יכולים לחזור שנייה ללינק ולהתבונן על התמונה \”דוגמא לארכיטקטורת SOA\”. לשם בדיוק אני רוצה לא להגיע)

ישנן גישות שונות ל SOA, רובן כוללות אלמנטים בירוקרטיים של ארגון גדול:

  • יש כאלו שחושבים שסט תקני ה WS-* הם הבסיס לכל פתרון, גם לאינטגרציה בין ארגונים וגם פנימית בתוך הארגון. כיום, זהו קומץ קטן וסהרורי, דיברתי על האלטרנטיבות כאן.
  • יש כאלו שמאמינים ש SOA היא לא רק טכנולוגיה, אלא כמעט דרך-חיים. SOA חוצה את הצד הטכנולוגי וממפה את תהליכי העבודה הקיימים באירגון. כשרוצים להוסיף תהליך עסקי / אירגוני חדש יש ללכת ולמדל אותו ב SOA. סוג של איחוד שמיימי בין אדם ומכונה – שהגבולות הולכים ומטשטשים. כדי לתאר כמעט-כל תהליך בארגון יש לנהל אלפי שירותים. מי ששולט בשירותים שולט בארגון ויש לשים Governance מרכזי על השירותים והשימוש בהם. אלו הם האורתודוקסים של SOA. הם לוקחים את התורה רחוק, רחוק. לפעמים נדמה שעבורם SOA היא כמעט-מטרה ולא \”עוד כלי\”. האמונה שלהם ב SOA – גדולה.
  • יש כאלו שמנסים להעמיק בלמידת  הצדדים הטכנולוגיים של SOA. הם מתמודדים עם בעיות שונות ומשונות בעזרת כל מיני משחקים (Patterns) ברמת השירותים והרשת. הם משתמשים ב REST, אך גם ב WS-*. מנסים למצוא איזון בין השניים ולחתור במאמץ לא קטן ל\”ארכיטקטורת SOA נכונה\”. הם מאמינים. אלו הם ה\”כיפות הסרוגות\” של SOA, הם הכי קרובים בתפיסה לחילונים, להם אין כל רגשות ל SOA, אבל לעיתים נדמה שאנשי ה\”כיפות הסרוגות\” מתאמצים קצת יותר מדי בכדי לפתור בעיות דווקא בעזרת SOA.
בפוסט זה אנסה להציג כמה רעיונות מוצלחים מהדת שגם לחילוניים כדאי לאמץ גם ממקומם הנוכחי. מן סוג של \”כבד את אביך ואת אמך\” – עצות חברתיות שימושיות.

בראשית

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

לפני עשור (וקצת) דובר הרבה על Component-Based Development. הרעיון היה להחיל את עקרונות ה OO על \”קוביות גדולות יותר\”. \”אם תכנון מונחה אובייקטים הוא טוב, למה לא לעשות אותו גם ב High Level?\” שאלו.

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

…ואז באה SOA. מאיפה היא באה? השגחה עליונה? בתולה קדושה? זינוק אבלוציוני בתובנה של האנושות? – אף אחד לא יודע.
מדי פעם נאמרים דברי כפירה כגון \”הא – חזרנו לתכנון פרוצדורלי של שנות ה 60\” או \”זוהי פשוט התפחות צפויה נוסח \’הברירה הטבעית\’ של עקרונות ארכיטקטונים בסיסיים, למשל הגדרת ממשקים מוגדרים-היטב\”. כדאי לשקול דיבורים שכאלו בשיחה עם אנשים מאמינים!

אמנם החשיבה של שירותים סותרת לכאורה את סגנון התכנון ה Object Oriented הנפוץ כיום, אך יש לה שני יתרונות:

היא פשוטה והיא עובדת. אין מניעה, ואולי אפילו מומלץ, שכל שירות יהיה בנוי בעצמו בצורה Object Oriented.
\”כל מה שעשינו עד היום – רע, ומהיום הכל יהיה טוב\” רומזת כרזה שיווקית של SUN, אחת המשקיעות הגדולות ב SOA.
נדמה לי שאני זוכר את השקף השמאלי מככב בצד ימין לפני עשור כשדובר על ארכיטקטורת N-Tier 🙂  מקור sun.com

שמות
ואלו שמות ומושגים בעולם ה SOA:
WS-*, ESB, EAI, SOAP, MOM, WSDL, UDDI, REST, SLA, DDS, JBI, CEA, E-SOA, Event-Driven SOA, Middleware, Business Process, BPM, BPMN, BPEL, SOMA ועוד ועוד…

סתם, לספוג קצת אווירה.

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

לא כל מערכת עם כמה שירותים נפרדים היא SOA. הנה מספר עקרונות מרכזיים ההופכים את הארכיטקטורה שלכם ל SOA:

הכמסה של שירותים
על השירות לכמוס (= לא לשתף) את פרטי המימוש שלו. האם הוא בנוי Object Oriented או Data Oriented? האם הוא משתמש בבסיס נתונים או בזיכרון? האם הוא משתמש ב Cache או לא? – כל אלה פרטים שכדאי למנוע ממשתמשי השירות כך שלא ייקשרו אליהם ויפגעו משינויים במימוש.

Service Contract
לכל שירות יש חוזה פעולה ברור. החוזה של השירות הוא לרוב ה API.
עד כמה ששפות התכנות מפותחות – הן עדיין מוגבלות ביכולת הביטוי. חשוב להבין שה API הוא רק חלק מהחוזה הכולל. כלומר, בעזרת Interface בשפת #C או Java אני יכול לומר מהן המתודות והמשתנים שהשירות מצפה לקבל, אך אני לא יכול לבטא דברים כמו:

  • האם קריאה לשירות היא יקרה או זולה (Performance)
  • לאיזה התנהגויות הוא תוכנן ולאיזה לא (הנחות יסוד). באיזה מקרים הוא לא תומך או לא נכון להשתמש בו.
  • התנהגות במקרי קיצון
  • וכו\’
כל אלה מרכיבים את החוזה של השירות. כדאי מאוד שהחוזה יהיה מפורש וברור: גם למי שמפתח את השירות (נשמע ברור, אך לא תמיד זה כך) וגם למי שצורך אותו.
הכלים לתיאור החוזה הם:
  • אכיפה ברמת השפה  (למשל טיפוסים) / פרוטוקול (למשל MIME TYPE).
  • שמות נכונים של השירות, המתודות והמשתנים.
  • מטאפורה, חלוקה לשירותי משנה בצורה שמכוונת את המשתמש נכונה.
  • תיעוד, תיעוד תיעוד.
באופן תאורטי היה נחמד אם היה אפשר להחליף את שירות אחד בשירות עם מימוש אחר שעונה לאותו contract, מבלי שאף אחד מהלקוחות שלו ישים לב.
עצה טובה היא להתחיל שירות בתכנון החוזה ורק אח\”כ לעבור למימוש, בדומה מאוד לתכנון ממשק משתמש. מה שחשוב הוא שהחוזה היה נקי, יציב ומלוטש. על המימוש אפשר להמשיך לעבוד אח\”כ.
Service Policy

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

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

אין הגדרה חד משמעית מה אמור להכיל ה Policy – יש גישות שונות. יש כאלו שיכילו אלמנטים של אבטחה וגישה – יש כאלו שיכללו בהם SLA או צורת ההתקשרות (messaging, request-response). העניין המעניין הוא שזהו חלק בחוזה שמשותף להרבה שירותים ואין טעם להגדירו כל פעם מחדש, יהיה החלק מה שיהיה.

ומחשבה קצרה בצד: אם נחשוב בצורה יותר מופשטת, אין קשר חזק בין איך עובד השירות לאיך מוצאים אותו, מבצעים Authentication אליו, היכן השירות ממוקם גיאורפית וכו\’. יש decoupling אמיתי בין מה שהשירות מספק לאופן הצריכה של השירות. על בסיס זה ניתן לעשות הרבה משחקים ווריאציות. זו יכולה להיות הזדמנות לכמה פתרונות ארכיטקטוניים מעניינים ולא מעט דיונים ופילפולים הנגמרים בהסתבכויות בלתי-רצויות. ל SOA יש על כך גם \”גמרה\” וגם \”משנה\” – יש היסטוריה של אנשים שהלכו רחוק מדי עם SOA אל חזון שנשמע מתוחכם אך לא היה מעשי – כדאי להזהר.

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

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

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

סכנה שנייה היא יצירה של המון שירותים. שמעתי כבר את המשפט \”מעולם לא הצטערנו על שירות שחילקנו ל 2\” מפי אנשים שאני מעריך, אבל בעיה נפוצה היא להפוך כל 3 פונקציות בסיסיות לשירות – ה overhead הפוטנציאלי הוא אדיר. שירות צריך להיות coarse-grained ולתאר פונקציה גדולה ומשמעותית מספיק.

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

סכנה אחרונה שאציין היא התעסקות עם תקני WS-*. אלו תקנים סבירים לחיבור בין מערכות שונות מאוד (שפה שונה, אירגון שונה, מערכות Legacy) – אבל דיי overkill לרוב השימושים האחרים. התחילו בקטן עם שירותים מבוססי HTTP Messages או REST.

דברים
כמה דברי סיכום:

SOA קושרה בצדק או לא (תלוי את מי שואלים) ל Web Services ו WS-* עם הבעיות הרבות שלהם. SOA יכולה להיות מוצלחת למדי – אם מצליחים לעשות אותה במידה ובמקומות המתאימים.

ל SOA יש כמה ייתרונות ברורים:

  • צורה פשוטה יחסית לבניית מערכת מבוזרת – מערכות שקל מאוד להסתבך איתן.
  • פוטנציאל טוב לשימוש חוזר בקוד (רעיון נשגב, שפעמים רחוקות יוצא אל הפועל). שירות הוא רמה מצויינת לשימוש חוזר: בעל משמעות, אך לא נקודתי מדי.
  • פרדיגמה פשוטה שקל להבין.
  • בניה של שירותים עצמאיים עם תלות נמוכה תקל על ה Upgrade ותפחית את ה down-time של המערכת: ניתן לעשות upgrade לשירות אחד בלבד – ושאר השירותים יתמודדו עם ה down time בצורה טובה. יכולת זו תקל על יישום של Continuous Delivery.
  • הגדרה של שירותים הפועלים ע\”פ חוזים תקל על מתכנני המערכת לבקר את נכונות הארכיטקטורה של המערכת: ניתן להתמקד בחוזה של השירותים והתלויות בין השירותים – ללא הצורך להכיר כל פינה במערכת.

שמים לב לקשר בין הנקודות?
מערכות מבוזרות…? שימוש-חוזר בקוד…? Continuous Delivery ו Availability…?

SOA והענן. מקור: http://webtechnologiesarticle.blogspot.com/

צודקים לגמרי! SOA פורחת מחדש בעולם הענן. היא מתאימה למדי ואכן היא זוכה לשימוש נפוץ. ציינתי אותה כתכונה עשירית ב 10 המאפיינים של שירותי ענן.

בהצלחה והמשיכו בזהירות. נתראה בחגים!

על Domain Driven Design

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

הקדמה
הקשר בין גודל התוכנה לסיבוכיות שבה הוא קשר ברור וידוע.

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

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

כיצד ניתן לשנות זאת?

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

עד כאן, לא חידשנו כלום.
כיצד מקבלים דרישות איכותיות יותר? “מתמקדים בלקוח”? “שמים את הלקוח במרכז”? “חושבים טוב-טוב”?
ואיך בכלל יוצרים מערכת פשוטה יותר? “חושבים פשוט”? “עושים KISS”? “עובדים לאט ובזהירות”?

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

ובכן, היכונו! אוונס וה DDD (כלומר [Domain Driven Design [1) הולכים ללמד אתכם כיצד לקבל דרישות איכותיות יותר וכיצד לבנות מערכת שתהיה גמישה יותר לקבלת דרישות עתידיות. הדרך להתמקצעות היא לא פשוטה, אך כל מי שיש לו גישה לדרישות של לקוחות וקצת חשיבה מופשטת יכול די מהר להגיע לתוצאות ראשונות.

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

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

על התהליך הנפוץ לתכנון תוכנה
לא משנה אם אתם עובדים Waterfall או Agile, קרוב לוודאי שהמתכנתים לא אוספים את הדרישות מהלקוח בעצמם. יתרה מכך, גם שמדברים עם הלקוח לא תמיד יהיה זה המומחה העסקי. ייתכן ואתם מדברים עם מנהל התפעול בבנק (“לקוח”) שרוצה מערכת לניהול משכנתאות, אבל אתם לא מדברים עם מי שמתעסק עם זה בפועל (Domain Expert). על מנת לפשט לכם את הנושא, מנהל התפעול משתמש במושגים שיותר קלים להבנה. ה PM לוקח צעד אחד הלאה ומשתמש במושגים יותר מקובלים מהעולם-הלא-מקצועי כי “מפתחים יאבדו כיוון אם נציף אותם בכל המידע הזה”. זהו הרי תפקידו.
הארכיטקט או המומחה הטכנולוגי מפשט את הדברים קצת יותר להתאים לתשתית הקיימת , זה תפקידו, והופ – המפתחים מבינים בקלות על מה מדובר וניגשים מייד לעבודה.

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

מקור: softwarearchiblog

כפי שסיפרנו ה Domain Expert (אשתמש ב DE מעכשיו) משתמש בטרמינולוגיה משלו וכל שאר הגורמים בדרך מתרגמים, שלב אחר שלב, כך שלמפתחים יהיה ברור מה “עליהם לעשות”. הכל טוב, לזמן מה – עד אשר מגיע שינוי לא-צפוי בדרישות:

מקור: softwarearchiblog

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

בואו נעצור לרגע: אנו מדברים במושגים (“חשבונית”) ובתסריטים (Use-Cases) אך מאחורי כל אלה יש לנו מודל תפיסתי (Conceptual Model) שמתאר איך הדברים מתרחשים. בני-אדם, כמונו, נוטים להרגיש אי-נוחות בעת שהם עוסקים בנושא לא מוכר. כתרופה, מנגנוני החשיבה שלנו גורמים לנו להשלים, באופן כמעט מיידי, את סט העובדות שניתנו לנו למודל תפיסתי הגיוני (מבחינתנו). איכות העובדות שהועברה לנו, כשרון ומזל יכתיבו עד כמה נכון המודל שבנינו. לא מעט ריבים / חוסרי-הבנה נוצרים בגלל הפער שבין המודל של אדם א’ לאדם ב’, שניהם ניזונו מאותן עובדות.

הפתרון “הרשמי”
אז מה עושים? אחד העקרונות המרכזיים של DDD הוא שפה-רוחבית (ubiquitous language). על מישהו, ה business analyst, לחפור ללמוד ולהגדיר עם ה DEs שפה אחידה ומוסכמת על פיה כולם יעבדו, שפה שבעצם תגזר מתוך Conceptual Model ברור ומלוטש (במידת האפשר).

מקור: softwarearchiblog

הנה הנקודה החשובה: כאשר אנו אוספים דרישות מלקוח אנו נוטים הרבה פעמים “להתמקד בחלקים שרלוונטיים לתוכנה”, להתמקד בדרישות בטווח הקרוב ולהתמקד ב UI – כלי שעוזר מאוד להמחשה של רעיונות.

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

בפועל, הדרישות ותיאורי ה use-cases שאספנו – ישתנו, גם ב-UI שנראה נהדר על הנייר – יתגלו חוסרים. הכל נתון לשינוי.
אפשר לצעוק ולהאשים “אנחנו עושים את הדרישות האלו – הנה יש לנו חוזה כתוב”, אך בפועל – האם לא נרצה לצמצם את הטעות הצפוייה?

אז מה יותר יציב מרשימת use-cases ומסכי UI? היכן אנו יכולים להשקיע בלמידה של המקור ממנו באות הדרישות? ידע יציב שאינו נוטה להשתנות בקלות?
ובכן – זהו ה Domain, תחום העיסוק, הביזנס, הידע שמי שעוסק בתחום חייב לדעת על מנת לעסוק בו. הביזנס אינו משתנה במהירות.

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

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

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

מודל קונספטואלי המתאר פעולה בנקאית. האם המפתחים, מנהלי המוצר והלקוחות שותפים לאותה תמונה (או הבנה)? מקור: martinfowler.com

חזרה למציאות
סביר להניח שאין לכם בחברה תפקיד של Business Analyst, ושרוב הלקוחות שלכם לא מוכנים שה DEs שלהם ישבו שעות וימים בכדי לצייר UML, או בכלל ילמדו UML. ארגון שרוצה להגיע ל DDD מלא כיעד אסטרטגי כנראה יכול לעשות זאת, אך לא נתקלתי בחברה בישראל שעובדת כך בצורה שיטתית.

אילוסטרציה: domain experts.
כמעט ושמתי תמונה אומנותית של מנהל חשבונות זקן משנות ה-50, אך כנראה שבישראל בשנות ה-10, תתקלו יותר ב domain experts שדומים לאלו, שני מנהלים ב-888.
מקור: כתבה ב”אנשים ומחשבים”.

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

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

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

הקשיבו לשפה
כלל #1: אם בשיחה עם הלקוח (DE), הלקוח משתמש הרבה במושג (למשל, “חשבונית”) ואין לכם אובייקט מרכזי בשם “חשבונית” – זהו סימן לפער במודל. תקנו את המודל שייתן משקל מתאים למושגים החשובים בהם משתמש הלקוח בשיחה היום-יומית.

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

זכרו: “(You can run, but you cannot hide (a bad conceptual model”. אם “תשקרו” את המערכת ותעבדו עם מודל לא מדוייק, זה יחזור אליכם בדמות תחזוקה קשה ודרישות עתידיות הקשות ליישום.
לדוגמא: תיאור של מחשב עם 2 כתובות IP כשני מחשבים עם אותו השם, כל אחד עם כתובת IP אחת – אולי יקצר את הפיתוח בשלב הראשון, אך יחזור אליכם כמוברנג כחלק מדרישות עתידיות. ניסיתם “לשקר” במודל.

סיכום
למי שהתנסה בהצלחה במידול בכלל או DDD בפרט, מידול נראה חלק הכרחי בבנייה של כל מערכת. תורת המידול אינה נפוצה ואינה קלה – אך היא מביאה תוצאות ממשיות. המקור הטוב ביותר שאני נתקלתי בו עד היום הוא הספר Analysis Patterns של Martin Fowler, ספר קצת לא-מסודר אך בעל תובנות עמוקות.

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

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

[1] בעברית: תכנון מונחה-תחום, תרגום לא כל-כך מוצלח לדעתי – אך לא מצאתי טוב יותר.

כיוונים חדשים-ישנים במתודולוגיות פיתוח תוכנה (Data-Oriented Programming)

תשאלו אנשים הכותבים מערכות ב #C או Java מהי מתודולוגית הפיתוח הנפוצה ביותר כיום וקרוב לוודאי שתשמעו \”תכנות מונחה-עצמים, ברור!\”.

הייתי מקשה ושואל: \”איך עובד לכם תכנות מונחה-עצמים, טוב? עומד בציפיות?\” והתשובה היא לרוב \”האמת, ביננו? קצת חטאנו ויש מה לשפר – אבל בגרסה הבאה קצת נשפר / במערכת הבאה נכתוב הכל \’כמו שצריך\’ והכל יהיה טוב…\”

ובכן, לצערי רוב המערכות שראיתי (וראיתי) היו לא-מוצלחות בהיבטי ה Object-Oriented. כינויים נפוצים למערכות אלו הן:

  • Anemic Object Model – מצב שבו האובייקטים הם דלילים ולרוב מחזיקים רק נתונים או רק פונקציות.
  • או Big Ball Of Mud – \”גוש גדול של בוץ\” (שהאמת, מתייחס למגוון רחב יותר של בעיות).

רבים מאיתנו רוצים ליצור תוכנת Object-Oriented הבנויות לתפארת עם Domain Model עשיר, אך אנו נכשלים לעשות זאת שוב-ושוב. האם רוב המתכנתים בעולם גרועים? או שאולי מתודולוגית ה Object-Oriented אינה טובה? (השם ירחם – דברי כפירה)

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

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

פיתוח מונחה-נתונים (Data Oriented Programming)
בתכנות מונחה-עצמים, האובייקטים הם במרכז ובתכנות פרוצדורלי – הפונקציה היא במרכז. בתכנות מונחה-נתונים (DOP מותר לבטא כ Dope) תשומת הלב נעה מן הקוד לכיוון הנתונים: מהו הקלט, איזה טרנספורמציות (עיבוד) המידע עובר וכיצד הוא נראה בסוף.

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

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

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

על האנומליה של הזכרון
בוודאי למדתם באוניברסיטה קורס \”מבני נתונים\” – קורס חשוב המכסה חומר לא טריוויאלי.
למדתם שיש רשימה משורשרת עם הכנסה של (O(1 ו\”טיול\” (traversal) של (O(n, ויש וקטור (נקרא ArrayList ב #C וג\’אווה) עם מחיר הכנסה של (O(1 או (O(n (עבור הכנסה באמצע או מילוי המחסנית שהוקצה) ו\”טיול\” (traversal) של (O(n.

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

התאוריה – כבודה במקומה מונח, אך בעולם ה\”אמיתי\” שימוש ברשימה משורשרת למקרה זה – תהיה טעות קשה.

פיזור מקובל של תפוסת-זכרון של רשימה משורשרת (באדום). מקור: תוכנת ה disk defrag שלי 😉

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

  • אנו יודעים שמערכת ההפעלה עובדת עם Virtual Memory. אם בלוקים של זכרון בהם שמור אלמנט אחד לפחות מהרשימה שלנו הם paged out (כלומר נשמרו לדיסק על מנת לשחרר זכרון פיסי), מערכת ההפעלה תקבל Page Fault שיגרור Context Switch וטעינת הדף / כתיבת דפים אחרים לדיסק – פעולה יקרה!
  • זכרון המטמון (בעיקר L2 ו L3) במעבד נוטים לעשות prefetch ואחזקה של בלוקים של זכרון – לא תאי זכרון בודדים. כאשר אנו משתמשים בזכרון רציף גישה זו תהיה מועילה, אך עבור רשימה משורשרת היא יכולה אפילו להזיק ולבצע prefetch לזכרון לא רלוונטי [1].
\”אבל זכרון הוא נורא מהיר!\” – אתם עלולים לטעון. \”אנו יודעים שעבור ביצועים-גבוהים יש לבצע הכל בזכרון\”. ובכן יחסית לפעולות IO זה נכון – אבל יש גם הבדל בין שימוש בזכרון כאשר זכרון המטמון יעיל או כאשר הוא לא יעיל.
במשך 30 השנים האחרונות – המעבדים הלכו והפכו מהירים עוד ועוד , משמעותית מהר יותר מהקצב בו התפתח הזכרון. אם ב 1980 המעבד המתין Cycle אחד לקריאה מהזכרון, היום הוא ממתין בערך 400 Cycles. הבעיה מחמירה כאשר הזכרון הזמין גדל (המעבר לעבדי 64 ביט פרץ את גבולות 4GB זכרון) ואנו רוצים להשתמש בזכרון על מנת לעבד כמות רבה יותר של נתונים – בעיה הידועה כ\”צוואר הבקבוק של פון-ניומן\”. מעבדים מודרניים יודעים לעבוד עם Bus רחב בהרבה לזכרון, כלומר קריאה של יותר ביטים במקביל שמושגת ע\”י קריאה (וכתיבה) במקביל מ 2 עד 4 יחידות זכרון (עקרון שדומה מאוד ל RAID 0 בכוננים קשיחים).
שיפורים בביצועי המעבד מול הזכרון ב30 בשנים האחרונות. מקור: Computer architecture: a quantitative approach

כיצד מתכנתים ב Data Oriented Programming
אלו שפות הן שפות \”Data Oriented\”? ובכן, השפה היחידה שאני יכול לחשוב עליה ככזו היא SQL: אנו מודעים לטבלאות וחושבים בפעולות על הנתונים. לטבלה B יש Foreign Key המצביע על טבלה A? אנו יכולים לבצע Select על טבלה B ולמצוא את כל ההצבעות – אין צורך (או משמעות גדולה) בהצבעה כפולה (כמו באובייקטים). ב #C יש את LINQ שהיא אמנם שפה לטיפול בנתונים, אך טבעית מאוד גם לנתונים במודל אובייקטלי – לכן אני לא בטוח שהיא דוגמא טובה.

העקרון של תכנות מונחה נתונים Per-Se הוא להחזיק נתונים (כאשר יש רבים מהם) בזכרון בצמידות. ממש כמו שמירה של טבלאות של בסיס נתונים רלציוני. כך יהיו לנו הרבה Collections גלובאליים של \”אובייקטים\”, כאשר האובייקטים הם רזים (יותר דומים ל struct של נתונים ופחות אובייקטים קטנים ועשירים). מצד שני יהיה ניתן להפעיל כל פעם פונקציה אחרת על אותו struct – מעבר שהוא \”זול\” מבחינת עלות זכרון (מכיוון שיש יחסית מעט פונקציות נפוצות שיכולות להשמר ב Cache).
בעוד תכנות מונחה-עצמים יוצר מבנה דומה ל LinkedList – אובייקטים הפזורים לכל עבר בזכרון, כאשר קריאות getX.GetY.GeyZ המפורסמות של ג\’אווה מדלגות בזכרון לא-רציף, תכנון מונחה-נתונים הוא דומה יותר לוקטור (ArrayList) רציף בזכרון המאפשר להשתמש ב Cache בצורה יעילה ופונציות שונות שפועלות בצורה ממוקדת על הנתונים ללא \”קפיצה\” תכופה לאובייקטים אחרים.

השימוש בData-Oriented Programming כמתודולוגיה מקובלת חזר בשניים האחרונות בעקבות אפליקציות ה Mobile (כלומר Android, iPad, iPhone) – אפליקציות קטנות הנכתבות עבור מכשירים בעלי כח עיבוד חלש.
מתכנתים מדווחים על ביצועים גבוהים פי 2 עד 4 בהמרה של אפליקציות Object Oriented להיות אפליקציות Data Oriented ובקלות פיתוח גבוהה יותר.

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

כמו שאנשים חששו להשתמש ב\”אובייקט פשוט של ג\’אווה\” (והשתמשו ב EJB) עד שניתן לו השם המכובד POJO, כך גם אין לחשוש בתכנות מונחה נתונים ולהתייסר שאנו לא כותבים OOP, הנה עכשיו יש לו שם מכובד: DOP.
בסופו של דבר כל אלו הם כלים, עם יתרונות וחסרונות, ומקצוען אמיתי ידע לבחור בניהם בחוכמה ולא ייקלע למלכודת של \”חייבים לעבוד ב EJB או OOP או – כי כולם עובדים כך\”.
דוגמא קרובה נוספת היא ההצלחה של REST – מודל פשוט וממוקד נתונים / פרוטוקול רשת, שהצליח יותר בשטח ממודלים מונחי אובייקטים או שירותים, ארכיטקטורות נבונות מגובות בהמון תאוריה.

אני חושב שאפשר בהחלט לכתוב מודולים במערכת שהיו מונחי נתונים, ועדיף שזה יהיה מהלך מודע. תכנות מונחה-עצמים הוא בהחלט לא קדוש.
גישה אחת ל DOP היא הגישה הקלאסית[2] (מערכיים של structs) וגישה אחרת היא עבודה עם בסיס נתונים רלציוני בזכרון (כגון H2, HSQLDB או SQLite), עם היכולת לשמור את הנתונים לדיסק בקלות ובכל רגע.

היתרונות של DOP על OOP
אני מניח שהיתרונות של OOP (הכמסה, מידול, ריבוי-צורות ועוד) מוכרים לכולם, בואו נסקור כמה יתרונות של ה DOP:

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

Unit Testing
מי שעובד עם unit tests יודע שהכי קל לכתוב בדיקות לפונקציות המרה פשוטות של נתונים, כמו פעולות parsing, למשל. שימוש ב DOP יהפוך גם את ה unit tests לפשוט וטבעי יותר מכיוון שיהיו הרבה יותר פונקציות ש\”רק מעבדות נתונים\” ויפחית משמעותית את הצורך ב mocks, stubs וחברים. אימוץ Unit Tests הוא לא דבר קל, ושימוש ב DOP יכול להיות סיוע משמעותי.

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

שימוש יעיל בהרבה בזכרון / מנגנוני cache
כמו שדובר למעלה. ברוב במקרים זו לא סיבה לעבור, אך בהחלט נחמד לקבל.

מאגר אדיר של בסיסי נתונים (רלציוניים) התואמים למודל כמו כפפה.
מודל ה OOP לא רק הקשה על החשיבה הלוגית שלנו ועל ארגון התוכנה לאובייקטים, הוא גם הקשה מאוד על שימוש בבסיסי הנתונים הרלציוניים, אשר בטבעם הם מונחי-נתונים.
במשך שנים ניסו לבנות כלי (ORM (Object-Relational Mapping עם הצלחה חלקית בלבד. אפילו Hibernate/NHibernate – ה framework הפופולארי ביותר, הוא נחמד לשמירת קונפיגורציה אך כושל כאשר אנו זקוקים לביצועים טובים על הרבה נתונים. גם אני האמנתי למצגות שמספרות שברוב המקרים Hibernate יבנה סכמה יעילה יותר מהמתכנת ויספק Cache שישפר את הביצועים אפילו יותר. הנסיון שלי הוא שכאשר יש דרישה לביצועים טובים, ישנו מאבק ארוך עם Hibernate שבסופו Hibernate מוצא את עצמו מחוץ למשחק.
אמנם אם היינו יוצרים באופן ידני את אותה הסכמה ש Hibernate מייצר – הוא יכול היה להיות מהיר יותר, אך ההבנה שלנו בנתונים מובילה אותנו לסכמות שונות לחלוטין ממה ש Hibernate ייצר.
אחד המניעים של תנועת ה NoSQL היא השתחררות ממיפוי אובייקטים למודל רלציוני ועבודה ישירות מקוד OO למודל שמירת נתונים OO (כגון בסיסי נתונים KV). על אותו מטבע אם הקוד שלנו הוא מונחה-נתונים, כך גם בסיסי נתונים רלציונים ישרתו אותנו היטב ובקלות – ויש המון כאלה.

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

היתרונות של חלוקה של מערכת למודולים עם אחריויות ברורות, הכמסה נוקשה (encapsulation) של כל מודול ותיאור המערכת כשיקוף של העולם האמיתי / הבעיה העסקית (מה שנקרא גם DDD – Domain Driven Design) – הם יתרונות ברורים שעובדים היטב בשטח.

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

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

בהצלחה.

[1] בשקפים 31 עד 101 של מצגת זו – ניתן למצוא הסבר מפורט של התופעה.

[2] יש לזכור שמערך בג\’אווה הוא רשימה רציפה של מצביעים. האובייקטים עצמם (אליהם אנו מצביעים מה ArrayList) עדיין מפוזרים באופן שרירותי בחלק הזכרון הנקרא Heap. ב #C המצב נוח יותר – יש structs שיכולים להיות מוגדר באופן רציף. בכל מקרה אני רוצה להדגיש ולחזור שאופטימזציית performance היא לא עצם העניין, עצם העניין הוא מודל שקל יותר לשימוש המפתח – אופטימזציות הביצועים בדיון זה היא משנית.