תכנון ופיתוח תוכנה: מידול קונספטואלי

כבר כמה זמן שאני רוצה לזקק את תחום תכנון (Design) יעיל של תוכנה. זה תחום שיש בו מקום למיומנות רבה, אבל מאתגר מאוד ללמד. בכדי להתמודד עם האתגר, אתמקד בפן אחד (להלן Divide & Conquer) של אומנות ה Design: מידול.

"מידול", כצפוי, הוא מונח overloaded עם כמה משמעויות נפוצות. נעשה לרגע סדר בדברים:

  • משתמשים במונח "מידול" כתהליך ההצגה של העיצוב (Design). כמטאפורה: אדריכל שבונה מודל מוקטן של הבית בכדי להציג אותו ללקוחות.
    • כאן נכנסות "שפות מידול" (UML, ERM, ADL), ומתודולוגיות מסוימות (C4, ATAM, 4+1 Views). הטכניקות הללו מעט ארכאיות לתקופתנו ואני לא מתכוון לעסוק בהן.
  • משתמשים במונח "מידול" כדי לתאר רעיון של יצירת מודל מפורט/מדויק שיחליף את הקוד / נחולל קוד ממנו. כאן המודל הוא פורמאלי, מדויק, ודורש עבודה ותחזוקה רבה. ניתן לאפיין כאן שתי גישות עיקריות:
    • מידול מבנה או נתונים – בגישות כמו Model Driver Architecture (קרי MDA), או מידול של בסיסי נתונים.
    • Semantic Modeling עבור מערכות לעיבוד שפה טבעית או Ontology modeling (אונטולוגיה – הגדרת קשרים בין מונחים) ליישומים יותר כללים של בינה מלאכותית.
    • אני לא מתכוון לעסוק בנושאים הללו בפוסט.
  • משתמשים במודל "מידול" כתהליך ביניים באיסוף דרישות, תיאור העולם / צרכים / דרישות בצורה מאורגנת ומדויקת יותר – לפני שמייצרים את העיצוב הטכני וכותבים קוד. הדרך המקובלת ביותר לתאר מודל מסוג זה היא מודל קונספטואלי (Conceptual Modeling) ועליו אני הולך לדבר בפוסט הזה.
    • על אף שראיון המודל הקונספטואלי נמצא בשימושים כבר עשורים, רבים מקשרים אותו למתודולוגית ה Domain-Driven-Design (בקיצור: DDD) – שהביאה אותו לחזית הבמה / הפכה אותו לקצת יותר מדובר ופופלארי בתחילת שנות ה 2000.

פאק! מעולם לא הגדרתי מודל קונספטואלי. האם זה אומר שאני עושה Design גרוע?!

תנו לי להרגיע: היה לכם מודל קונספטואלי בכל ה Designs שעשיתם. פשוט לא ידעתם את זה.
כמו כן: הגדרה של מודל קונספטואלי up front – הוא רעיון שנראה שלא הוכיח את עצמו לאורך השנים. אי הקדשת זמן להגדרת מודל קונספטואלי up front – הוא כנראה הדרך היעילה יותר לעשות דברים.
אי אפשר לחשוב וליצור תוכנה – בלי מודל קונספטואלי בראש. ברור שיש לכם איזו קונספציה על איך הדברים קורים / צריכים להיות. זה המודל הקונספטואלי שלכם. אולי הוא לא תתועד / מתוקשר / מזוקק – אך הוא בהחלט קיים.
הבעיה במודלים קונספטואליים היא שאנשים שונים בונים לעצמם בראש מודלים שונים, כל אחד "והאמת שלו". למה זו בעיה? האם לא טוב שיש מעט שונות (Diversity) / נקודות מבט מגוונות?
ראו גם: Mental modelRepresentation (psychology), and Cognitive model

הרבה פעמים זו לא בעיה, אולי זה מספיק טוב. לגיוון בהבנה / תמונת-עולם גם יכולה להיות תרומה.

הבעיות שצצות לרוב הן:
  • א: פערים משמעותיים בהבנה בקרב מפתחים שונים => קוד לא עקבי, או אפילו סותר.
  • ב: יש תמימות-דעים בקרב המפתחים – אבל כולם טועים / לא מבינים נכון את הביזנס.
  • ג: יש פערי-הבנה בין אנשי הביזנס, מה שמבלבל ומקשה על המפתחים להבין.
  • ד: כל התשובות נכונות.
 
כלומר, בגישת ה Lean אנחנו לא ניגשים להגדיר מודל קונספטואלי ב"תחילת הפרויקט", אנו מגדירים אותו כאשר אנחנו מזהים בעיית תקשורת משמעותית.
משמעות נוספת של האמירה הזו היא שלא ננסה אפילו "למדל" את כל הביזנס ומה שקורה בו – אלא רק איים מרכזים / חשובים בביזנס – שגורמים לנו לבעיה בכתיבת המערכת.
איך מגדירים מודל?
כל תרשים או טקסט הם טובים מספיק. המרכיבים שבעזרתם מגדירים מודלים מורכבים בעיקר מ:
  • ישויות (Entities)
  • קשרים (Relations)
  • תכונות (attributes) של הישויות. אולי גם מתודות.
  • הכללה / ריבוי-צורות.
  • הבחנה בין מחלקה/קבוצה למופע (instance).
  • כלי עזר: טקסט.
  • כלי עזר: קוד.

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

הדרכים הנפוצות להגדיר מודל, שאני נתקלתי בהן, הן pseudo-UML (לא צריך לדקדק), קוד, או פשוט מסמך וורד/מצגת.

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

דוגמה למודל קונספטואלי

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

הנה, למשל, כמה אפשרויות למודלים שונים לכסף (Money), איזה הוא המוצלח לדעתכם?

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

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

ובכן, מתוך ארבעת המודלים של Money, איזה מודל הוא המתאים / המוצלח ביותר?

כמובן, כמובן – שכל מודל מתאים לכל סיטואציה. יש מערכות / מקרים בהם מודל #4 הוא מספיק טוב – ואז מודל מורכב יותר יסרבל את הפיתוח ללא סיבה. יש פעמים שמודל #3 הוא טבעי ויש מקרים בהם הוא לא-קשור-למציאות.

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

המודל גם מתאר tradeoffs טכנולוגיים. במקרה שלנו: מחשב ספרתי, בו כל מספר הוא כפולה של 2, אינו יכול לייצג בדיוק מספר כמו 1.1 – סכום לגיטימי לחלוטין לכסף. יש דרכים שונות להתמודד עם האתגר, ולא "לאבד" סנטים בעקבות פעולות חשבוניות / פעולות עיגול. למשל: שימוש ב Big Decimal או הייצוג הסנטים כמספר שלם (Long).

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

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

אז איך אפשר "לדפוק" מודל קונספטואלי?

עד כה, יכול להיות שאתם אומרים לעצמכם: "ברור!" " "זה פשוט" " "זה בדיוק מה שאני כבר עושה". כל זה – טוב ויפה.

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

"האחרון להבין ולזהות" 

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

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

"מודל טוב מדי"

זה אולי לא נשמע כ"כ רע – אבל זו טעות נפוצה וכואבת.

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

הכל נעשה מכוונה טובה, אבל חשוב לשים לב שתהליך מידול לא הופך בטעות ל Over-Engineering. הוא בקלות יכול להגיע לשם.

"מודל נאיבי"

לא פעם אפשר לסיים את עבודת המידול בשיחה קצרה בת 30 דקות: הצלחה גדולה (!), ויוצאים לדרך.

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

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

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

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

מודל אלגנטי יותר מהביזנס

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

  • לכל מכירה יש תעודת משלוח אחת – בעוד בביזנס, בשל מורכבות אופרטיבית, לעתים מנפיקים (בטעות) שתי תעודות משלוח שונות לאותה ההזמנה.
  • זיכוי יהיה כסכום החיוב – בעוד בפועל לעתים מזכים יותר או פחות בשל נסיבות ("פיצוי", "קיזוז") שונות שלא נכנסות למודל.
תת-וריאציה היא פשוט תקלות טכניות (הרבה פעמים: בצד התוכנה), שאנו נוטים לשכוח / לא מכירים בזמן המידול:
  • ללקוח יש Cookie שמזהה אותו בצורה מדויקת – בעוד חלק לא זניח מהמשתמשים, מתקינים "חוסם פרסומות" שמוחק Cookies, והמידע לא שלם.
  • ה GPS מדווח במדויק את נקודת ההתחלה והסיום של הנסיעה – בעוד בפועל בשם טעויות / רמאויות קטנות של נהגים, המידע לעתים שגוי או אפילו חסר.
תקשורת לקויה / חסרה
ישנו מודל, הוא טוב למדי – אבל נמצא בראשם של 2-3 אנשים. מכיוון שהוא לא מתואר בצורה מספיק ברורה / מדוייקת – אנשים אחרים מסביב מפתחים בראש "וריאציות" של המודל – ובעצם לא השגנו את מטרת המודל.
לא פעם מציינים continuous integration ככלי התומך במודלים הקונספטואליים. אם הקוד של מפתחים שונים צריך להתחבר ולעבוד מוקדם זה עם זה – הפערים יצוצו ויהיה קל / זול יותר לתקן אותם.
מודל חד-כיווני

"אלו הן הדרישות של הביזנס" – הוא משפט שגור, שמהווה smell לבעיה הזו.

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

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

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

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

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

כמובן ששני מודלים שונים עשויים לגרום לבלבול וטעויות. חשוב להגדיר bounded context – גבולות ברורים וחד משמעיים ואלו חלקים במערכת ובביזנס משתמשים במודל א" ואלו במודל ב". כמו כן חשוב תרגום טוב מספיק בין המודלים – בנקודות החיבור של המערכת ב contexts השונים. יש המציעים בהשנות מקרים כאלו ליצור context map – מיפוי של כל ה contexts, הגבולות, והתרגומים בין המודלים ה"חופפים". "Good fences make good neighbours" – הם אומרים.

מודל תוכנה השונה מדי מהמודל הקונספטואלי

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

מודל הקוד הוא המחלקות בפועל של התוכנה, החתימה של הפונקציות, והסכמה של בסיס הנתונים או ה documents הנשמרים ב Mongo או ב S3.

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

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

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

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

סיכום

אם הייתם שואלים אותי איך לעשות Design טוב יותר, או איך להיות אנשי תוכנה טובים יותר – אני בטוח שתחום המידול צריך להיות תחנה חשובה בדרך.

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

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

לא נכנסתי לטכניקות קונקרטיות / מפורטות יותר של מידול – כי אין הרבה כאלו. כלומר יש את Event Storming ויש כאלו שיחשיבו את CRC Cards כטכניקה של מידול (דיי דומה, למען האמת) – אבל סה"כ אין הרבה טכניקה מדוייקת שאני יודע לקשר לנושא.

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

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

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

זמן ותאריכים בתוכנה (ועל ה JVM בפרט)

זמן עשוי היה להיות דבר פשוט.

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

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

החיסרון היחידי: בזמן שהאנגלי אוכל ארוחת צהריים ב 12:00 כשהשמש במרום השמיים, הסיני יאכל את אותה הארוחה בשעה 20:00 – רק אז השמש מגיעה למרום השמיים שלו.

זו באמת כזו בעיה?

זמן הוא דבר מורכב

בפועל הסינים לא ממש הסכימו לאכול בשעה 20:00 בצהריים. מסתבר שבני האדם, בכל העולם, מעדיפים לאכול צהריים ולתלות פושעים – בשעה 12:00.

למה דווקא שבאנגליה יהיה 12:00 בצהרי היום? (כי הם היו האימפריה הגדולה והחזקה בעולם בעת קביעת השעון הגלובאלי?)

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

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

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

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

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

אפילו לא נתנו ליבשת הקפואה והשוממת הזו להיות פשוטה…

דוגמה משעשעת אחרונה היא שבזמן הקיץ, גם ב Greenwich הזמן הוא GMT+1:00, כלומר שעה אחרי… זמן Greenwich. זה בגלל מנגנון בשם Daylight saving – שאסביר בהמשך הפוסט. מנגנון עם וריאציות משלו.

אזרוק פה כמה קישורים למי שרוצה להכיר יותר:

זמן ותאריכים על ה JVM

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

בגרסה 1.0 ג'אווה יצאה עם אובייקט Date שבעצם לא מנהל תאריך, אלא ספירה של שניות החל מתחילת 1970 – כלומר: זמן יוניקס (קרי Unix Epoch, נקרא גם Epoch Time).

כמובן שגם Epoch Time אינו כ"כ פשוט, אם אתם מתעניינים במעט פיקנטריה:

  • Epoch Time החל במקור כספירה של cycles של מעבד (שהניחו שהם 60 הרץ) החל משנת 1971. רק מעט מאוחר יותר – הוצגה הגרסה שאנחנו מכירים.
  • כשבני האדם הגדירו זמן, הם גזרו את היממה מסיבוב של כדור הארץ סביב עצמו, ואז חלקו אותו ליחידות קטנות יותר (שעה, דקה,…) – ביניהן השנייה. הם לא ידעו שכדור הארץ לא מסתובב בקצב אחיד לגמרי, מה שדורש לבצע תיקונים בזמן מדי פעם, להלן דקה מעוברת (או leap second). זמן יוניקס כיום מתעלם מהסטייה הזו.
  • כאשר סופרים שניות מ 1970 במשתנה של 32 ביט, המשתנה מגיע לערך המרבי שלו בשנת 2038 – לא מאוד רחוק, מה שנקרא גם באג 2038.

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

בגרסה 1.1 יצאה גרסה משופרת שבצעה deprecation לרוב המתודות הקיימות של האובייקט Date, והחלפתן במתודות משופרות.

כמו כן, הציגו אובייקט בשם Calendar, שלכאורה היה אמור היה להחליף את Date (אך לא החליף אותו לגמרי) וגם מחלקה בשם SimpleDateFormater בכדי לפרמט תאריכים עבור Locales שונים.

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

  • גם אחרי שנים, מפתחים מתבלבלים מתי להשתמש ב Date ומתי ב Calendar. ההסבר הפשוט ביותר שמצאתי הוא ש Calendar נועד לבצע שינויים / חישובים בתאריך, ו Date הוא ה Data Structure ששומר את המידע לאורך זמן. סוג של תכנות פרוצדורלי קלאסי. גם ההגדרה הזו לא מדויקת – אך אין טעם להרחיב.
  • הטיפול במורכבויות של תאריכים לוקה בחסר. הטיפול ב Timezones ו DST הוא קשה ו error-prone למדי. הדרך היחידה להתעדכן בחוקי אזורי-הזמן (שמתעדכנים כמה פעמים בשנה) היה לעדכן גרסה של ה JDK. בתקופת ה On-Premises זה היה פתרון לא מספיק טוב, ומערכות רבות רצו על גבי חוקים לא-עדכניים.
  • לספריה יש כמה Defaults מסוכנים מאוד. לא מזהה שם של Timezone? אין בעיה, נניח שמדובר ב GMT. נראה שהצבת 14 כמספר חודש? אין בעיה – נוסיף שנה לתאריך ונחזור לפברואר, בלי להודיע שדבר כזה קרה. יש אפשרות להחמיר את הבדיקות, אך כנראה בשל ה backward compatibility הנוקשה של ג'אווה, ברירת המחדל היא עדיין הגישה המקלה.
  • המחלקות בספריה הן Mutable, מה שאומר ששימוש חוזר בהן יוביל לבאגים. זה לא ברור מאליו שעלי לייצר Calendar חדש או Formatter חדש, בכל טיפול בתאריך שאני מבצע.
באזור גרסה 6 הפכה ספריית צד שלישי, בשם Joda-Time לסטנדרט במקובל בטיפול בתאריכים על גבי ה JVM. היא שפרה את הטיפול בזמן ותאריכים בכל ההיבטים, והציגה מודלים מוצלחים למדי, אולי אף אפשר לומר – חדשניים.
לא אפרט עליה, מכיוון שכל רעיונותיה המשמעותיים הוכנסו כחלק מהספרייה הסטנדרטית של ג'אווה 8 (השוואה שנייה), והיוצר של Joda-Time הכריז ש "תפקידה נגמר", ועודד את כל משתמשיה לעבור ל Java 8 בהקדם האפשרי.
גרסה 8 – ספריית java.time החליפה את Calendar ו Date, ובעצם הביאה את ה JVM למקום טוב הרבה יותר בכל הנוגע טיפול בזמנים ותאריכים. קפיצת מדרגה משמעותית.
הספרייה מציגה 2 מערכות זמנים שונות, בהתבסס על העבודה שנעשתה ב Joda-Time.

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

שני הרעיונות המרכזיים של java.time

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

למי שלא מכיר הכלל הראשון של עבודה בתאריכים הוא כזה: אם אתם יכולים להימנע בעיסוק ב Timezones – עשו זאת! הסיבוכיות הנלווה ל Timezones ומקרי הקצה האפשריים – הם רבים. java.time מאפשרת פרדיגמה נוחה, ליישום ה best practice הזה.

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

  • המחלקה LocalDate מייצגת תאריך, כמו 28/02/2019.
  • המחלקה LocalTime מייצגת רק זמן, כמו 16:09:00.
  • המחלקה LocalDateTime היא הרכב של שניהם (ירוק + אדום = חום?!)
  • המחלקה ZoneId מייצגת את אזור הזמן, כמו "Asia/Jerusalem"
  • המחלקה ZonedLocalDateTime – היא הרכב של כל הרכיבים: LocalDateTime + ZoneId (ירוק, אדום, וכחול = מרכיבים את הצבע הלבן).
באיזה אובייקט הכי כדאי להשתמש לרוב השימושים?
מחשבה הגיונית ונאיבית, תצביע על השלם – על ZonedLocalDateTime. אם זה "חינם", למה לא לקבל "הכל"?

דווקא ההמלצה היא להיצמד לאובייקט ה LocalDate או LocalDateTime (אם נדרש). מדוע? בכדי לפשט את העבודה עם Timezones.

Zoned vs. Local

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

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

בפרדיגמה B אנחנו מזהים אזור בתוכנה ("Program Context") שבו כל הפעולות מתבצעות באותו ה Timezone. דוגמה קלאסית: לקוח. אנו רוצים לדווח ולתקשר עם הלקוח שלנו רק על גבי ה Timezone שלו. אנו שומרים את ה ZoneId פעם אחת בצמוד ל Context, וכל תאריך שנכנס ל Context לטיפול עובר המרה ל ZoneId הזה.
מכאן והלאה כל הפעולות הופכות לפשוטות ובטוחות יותר: אנו משווים תפוחים לתפוחים.
מדי פעם עלינו להשוות או לבצע חישוב עם ערך ב Timezone אחר או Instant – ואז אנו יכולים להרכיב חזרה את ה ZoneId לאובייקט – ולבצע את הפעולה.

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

2 מערכות זמנים, זו לצד זו

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

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

ב java.time עשו את הפעולה ההפוכה המתבקשת: לקחו את עניין התאריכים והזמן הסבוך (La Vasa) ופרקו אותו ל-2 מערכות פשוטות יותר: נושאת גייסות וספינת מלחמה, כך ששתי הספינות הללו יוכלו לשוט בבטחה.

מתי נכון להשתמש בכל מערכת?

מערכת ה Epoch

מערכת זו כוללת את ה Instant ואת ה Duration. היא פשוטה למדי ונצמדת ל Java Epoch. בג'אווה, מאז ומעולם, ספרו את הזמן מתחילת 1970 – אך ברזולוציה של מילישניות. הערך נשמר בשדה 64-ביט, ויכול לתאר תאריכים עד שנת מיליארד.

כל המרה בין Unix Epoch ו Java Epoch כוללת הכפלה / חילוק פשוט ב 1,000.
בכדי לתאום ל Unix Epoch, גם Java Epoch מתעלמת מ Leap Seconds.

מחלקת ה Instant מחזיקה את ה Java Epoch ומאפשרת עליה פעולות. המחלקה Duration מתארת מרחק בין שני Instants, ומשמשת לפעולות חישוב של הפרשי-זמנים.

טבלת השוואה בין שתי מערכות הזמנים של java.time

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

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

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

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

מהם הסימנים לכך שאנחנו משתמשים לא נכון ב Instant?

  • אם זמן שבן אדם מדווח עליו נשמר כ Instant – זה רמז לשימוש בסבירות גבוהה איננו נכון. טעות נפוצה.
  • עוד כלל אצבע שאני אוהב, הוא שאל לנו לבצע פעולת השוואה בין epoch.
    • תאורטית, epoch הוא ברזולוציה אינסופית ולכן ההשוואות הנכונות הן רק: האם זמן נתון הוא לפני או אחרי ה Instant. לכאורה, נדיר מאוד שיהיו שני Epochs עם ערך זהה בדיוק – ולא נכון להסתמך על זה.
    • המחלקה Instant עובדת כברירת מחדל במילי-שניות, אך ניתן גם לעבוד ברזולוציה של ננו-שניות.
  • אם אנו עסוקים בהמרות של Timezones ל Instants – אז כנראה שאנו עושים משהו לא נכון. Instant אמור לתעד אירועים שלא תלויים ב Timezone, וריבוי המרות שכזה מעיד שאנו עושים בו abuse.
הנה דוגמה קטנה שתעזור להמחיש את הנקודה. יכולים לנחש מה הקוד הבא עושה?
instant.minus(10, ChronoUnit.YEARS)
נכון! הוא זורק Exception. יש משהו לא נכון, בביצוע פעולות ברזולוציה של שנה על Instant. זו לא הייתה כוונת המשורר.
מהו שימוש סביר? על יחידת זמן שקטנה מיום – תתקבל בברכה. ימים הם עקביים ואחידים ב Java Epoch. חודשים – כבר לא, ולכן כל יחידת זמן של חודש ומעלה – תזרוק שגיאה.
מדוע לא חסמו את האפשרות, ע"י הגדרת Enum מקביל ל ChronoUnit, המתאים רק ל Instant?
אני מניח שהשיקול הוא החיבור בין שתי המערכות הזמנים. כפי שתראו – המעבר בין שתי המערכות הוא מאוד טבעי ונוח, והגדרה של ChronoUnit שונים – היה מקשה על המעבר.
מערכת ה Date/Time
 

מערכת הזמנים הזו, באה לשרת בני-אדם, עם כל המורכבויות שבחישוב הזמן עבורם. מערכת זו כוללת כל מיני Utilities לחישובים חסרי משמעות ב Epoch. "מהו יום השישי ה-13 הבא?", "מהו יום שני הראשון בחודש הקודם?" אלו שאילתות שטבעי לעשות במערכת זמנים זו, והתשובה תלויה באזור הזמן וחוקיו. ZoneRules היא מחלקה פנימית, אך חשובה, הממפה בצורה מסודרת את מערכת החוקים התקפה לכל אזור זמן.

מה הסימנים שאנחנו משתמשים לא נכון במערכת ה Dates?

  • דיסקליימר: נתקלתי בהרבה יותר שימוש לא נכון ב Instant (הטכני, הדומה לאופן הטיפול של שפות תכנות ישנות יותר) – אז קצת קשה לי יותר לומר.
  • עבודה מופרזת עם Timezones וקוד מסובך – הוא סימן שאולי אנו עושים שימוש יתר ב ZonedDateTime.

סיכום

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

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

שיעור מס' 8 בהורשה

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

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

לצורך הדיון אני אציג רק ממשקים, ולא אתעכב על חלוקה ל Interfaces ו Concrete classes (שהוא באמת משני לדיון).

בואו נתחיל.

הבעיה

דמיינו שיש לנו תוכנה שדורשת לצייר צורות, את הצורות מתארים ע"פ הממשק הבא:

מיקום הצורה (x, y), לצורך הדיון – הוא לא חלק מההגדרה.

משהו לא מריח כאן טוב בהגדרה הזו…
יש משהו מאוד פרוצדורלי, ולא כ"כ OO בהגדרה הזו: ה Path נראה כמו מרכיב זיהוי מרכזי בהתייחסות לצורה – אבל הוא מיוצג כתכונה כשאר התכונות.

למשל: אנו יכולים לייצר Line עם fillColor – מצב לא תקין. הכוונה להשתמש ב fillColor היא רק עבור פוליגון.

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

כיצד ניתן למדל את האובייקט בצורה יותר נכונה?

יש לכם איזה רעיון?!

פתרון ראשון

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

בואו נבחן את המודל שלנו עכשיו:

  • DRY (חוסר חזרה על הקוד) – מצב מצוין. אין באמת הרבה מה לקצר את הקוד.
  • הכמסה (encapsulation) – מצב בינוני. על הפשטת העל יש כמעט את כל התכונות, מה שאומר שכל מי ש"אוחז" ברפרנס ל BaseShape יקבל גישה לכל התכונות הללו. האם יש לזה צורך? או האם "שיתפנו" יותר מדי?
    הנחת היסוד היא שהשימוש האחיד באובייקטים הוא בעזרת הפונקציה ()draw. נניח: רוצים לרוץ על מערך ולצייר את כולם, אך בכל מקרה, כל גישה מעמיקה יותר דורשת את בדיקת הטיפוס ו downcasting.

    • אין נזק מיידי באי-הכמסה ("אז שלא ישתמשו בזה! מה הבעיה?!") – אבל זה זרז לשימוש לא מבוקר בתכונות הללו. מפתח שמקבל הצעה לשימוש בתכונה ב autocomplete של ה IDE – לעתים רחוקות ישאל את עצמו אם נכון היה שהאובייקט יחשוף תכונה זו, או האם בכלל נכון להשתמש בה.
    • כל מפתח יכול להיכנס לאובייקט ולשנות את כל שדות האובייקט ל Public – אבל זה סייג המעורר הערכה מחודשת על נכונות הפעולה (ברוב, התקין, של המקרים).
  •  אמינות המודל למציאות / פשטות הבנה (presentation) – הרבה יותר טוב, אך עדיין ישנם עיוותים.
    • הסרנו את ה Path כתכונה, ופתרנו את הבעיה בה fillColor זמין להיכן שאינו רלוונטי – שזה מצוין.
    • שמות המשתנים אינם טובים. הם "פשרה" עבור "שימוש חוזר בקוד" / או סתם מכח האינרציה – פשרה לא טובה.
כשאנחנו ממדלים אובייקטים במערכת אנו משרתים כמה צרכים:

  • אנו מספקים את הצורך הבסיסי של המערכת ביישות לאחסן בה נתונים / להגדיר פונקציות. לחלופין זה היה יכול להיות מערך גלובאלי של נתונים. זו חובה כדי שהקוד יעבוד – אך זה קו האפס מבחינת הנדסת תוכנה.
  • אנו מתארים כוונה / רעיון – שאנו רוצים להנחיל לשותפנו לבסיס-הקוד (להלן Presentation), ובתקווה שקבענו את הכוונה / רעיון בצורה טובה ואמינה לעולם העסקי הרלוונטי.
  • אנו מגבילים את חשיפת הידע במערכת, בעזרת הכמסה – אם אנחנו עובדים לפי מודל Object Oriented.
  • כן, פרקטיקה טובה של קוד, היא לא לשכפל קוד (DRY = Don't Repeat Yourself) – אבל זו לא ממש מטרה בהגדרת אובייקט.
בפועל, הפרקטיקה של DRY (מסיבות כאלו ואחרות) מוטמעת היטב בקרב אנשי-תוכנה, הרבה יותר חזק מפרקטיקות לא-פחות חשובות כמו Encapsulation או פרקיטקות מידול. התוצאה: מודלים קצרים יותר בשורות קוד – אך פחות עמידים לאורך זמן, ולאורך כמות שינויים שעומדים לעבור עליהם.
כדי לחדד מדוע האובייקטים בדוגמה שלנו אינם ממודלים בצורה טובה, נשטח שנייה את האובייקטים מהיררכיית שבה הם בנויים, ונציג אותם כפי שיופיעו ב run-time, או למשל – ב IDE כאשר אנו מקבלים המלצות שימוש ב autocomplete:

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

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

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

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

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

פתרון שני

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

עבור Point, הרבה יותר מדויק להסביר שתכונה מסוימת היא קוטר (או לחלופין זה יכול היה להיות רדיוס) – ולא "line width", שמשאיר מקום לפרשנות. בוודאי שיותר טבעי לי לחשוב על color ולא "line color" – כי אין קו בנקודה.
ב Polygon יותר מדויק לדבר על Border ולא Line, עניין של דייקנות – שיכולה להשתלם מאוד לאורך חיי-מערכת.

שיתפנו בין האובייקטים רק את מה שהכרחי: הפונקציה ()draw (לצורך ריבוי-צורות).

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

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

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

ממשקים הוא כלי שתכליתו היא PolyMorphism (ריבוי-צורות): התייחסות אחידה לאובייקטים שונים ע"פ מכנה משותף משמעותי. אל תתלו עליהם משימות של Code-Reuse, כי זה לרוב ייגמר בפשרה לא-משתלמת..

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

סיכום

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

DRY היא פרקטיקה חשובה מאוד – אבל יותר באזור של תוכן הפונקציות: יש כמה שורות קוד שחוזרות על עצמן? הקפידו להוציא אותן לפונקציה משותפת.
יש משתנים כפולים (או state כפול כלשהו)? אבוי – היפטרו מהעותקים מיד! ("data duplication is the mother of all Evil").

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

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

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