על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019? – חלק ב׳

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

התכנות הפרוצדורלי – מה הוא נתן לנו?

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

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

Functional Decomposition

הרעיון החזק ביותר שהציג התכנות הפרוצדורלי [א] נקרא Functional Decomposition. הוא הציל את עולם התוכנה מכבלי הקדמוניות – אבל מאז הפך גם ל Anti-Pattern מסוכן ונפוץ.

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

הרעיון, הולך בערך כך:

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

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

החלקים הם:

  • תיאור state ראשוני – נתון.
  • תיאור state סופי – רצוי.
  • סדרה של טרנספורמציות שיהפכו את ה state הראשוני ל state הסופי.
את הטרנספורמציות אתאר כפרוצדורות (~= וריאציה מוקדמת יותר של הפונקציות), ואז למרות שאני לא ידוע בדיוק איך לממש כל פונקציה – שברתי את הבעיה הגדולה לבעיות קטנות. עכשיו יהיה עלי להתמודד עם פונקציה בודדת בכל פעם – ולפתור בעיות קטנות יותר, ולכן קלות יותר.
יתרה מכך – הפרוצדורות / פונקציות הללו – הן בסיס טוב ל code reuse. טרנספורמציה על מחרוזת כגון ()trim או ()replaceAllCaptialsWithNonCapitalLetters – יכולה לשמש אותי במקרים רבים נוספים!

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

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

מה הבעיות של Functional Decomposition שהפכו אותו ל Anti-Pattern בעולם ה OO?

  • אי התייחסות / מחסור ב Information Hiding ו Encapsulation:
    • האם ה State הראשוני חשוף לכלל המערכת? האם הוא גלובאלי, למשל? – כך היה בהתחלה.
    • האם כל פונקציה שאפעיל רשאית לעשות כל שינוי ב state?
      אם ה state הוא רשימה של מחרוזות – אז אין בעיה. ככל שה state גדול ומורכב יותר (קיצונות אחרת – לב הנתונים של המערכת) – קל יהיה יותר לאבד שליטה ובקרה: איזה פונקציה עושה מה – על הנתונים הללו.
  • סיכויים גוברים לארגון נחות של המערכת:
    • כאשר הפונקציות מפוזרות במערכת ללא סדר וללא היגיון – קל להגיע לבלאגן.
    • כאשר אין סדר במערכת, סביר יותר שלא אמצא פונקציה קיימת ומספקת – ואכתוב אותה מחדש = קוד כפול.
  • סיכויים גדולים למידול נחות של המערכת:
    • חשיבה על המערכת בצורה של ״נתונים״ ו״טרנספורמציות״ אינו טובות למערכת גדולה ומורכבת. במערכת מורכבת, חשוב מאוד למצוא הפשטות (abstractions) טובות לפרטי המערכת, כך שיתאפשר לנו לחשוב ברמה גבוהה יותר של הפשטה. ה Functional Decomposition לא מוביל אותנו לשם, ואולי אפילו מפריע.
    • ביטוי של הסיכון הזה עשוי להגיע בדמות מודל אנמי – דפוס עיצוב שלילי שכתבתי עליו בהרחבה.
למרות ש Functional Decomposition נשמע כמו נאיביות של העבר, השימוש בו צץ שוב ושוב גם היום –  גם בשנת 2019. בעשור האחרון נתקלתי בעשרות מקרים של ״נסיגה״ מ OO ל Functional Decomposition שלא הועילו למערכת. שווה ללמוד את ה Anti-Pattern הזה ולהבין אם אתם מיישימים אותו במערכת. אולי ההבנה הזו – תעזור לכם לפשט את המערכת, ולהפוך אותה לקלה יותר לשינויים ותחזוקה.

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

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

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

  • כל פיסת קוד שנכתבת תהיה ״מקוטלגת״ או ״מיוחסת״ לאזור מסוים בקוד – להלן ״מודול״.
  • הקרבה הזו היא קרבה רעיונית, ולא בהכרח פיסות קוד שקוראות אחת לשנייה.
  • אין ברירה, קוד ממודול אחד – יקראו לקוד במודול שני. אבל, אנחנו רוצים לחלק את הקוד למודולים כך, שנצמצם את הקריאות בין המודולים, ונמקסם את הקריאות / ההיכרות בתוך אותו המודול.
    • הרעיון הזה נקרא גם: ״High Cohesion / Low Coupling״. בעברית: אחידות גבוה (בתוך המודל) / תלות נמוכה (בין המודולים).
למרות שרעיון החלוקה למודלים הוא לא מדויק (לא מדויק בהגדרה, ולא מדויק במימוש) – הוא רעיון רב עוצמה שיש עליו קונצנזוס גדול: רבים ממובילי דעת הקהל בעולם התוכנה מאמינים שאם נחלק את המערכת לחלקים ע״פ סדר משמעותי – יהיה יותר קל להשתלט עליה ולנהל אותה. גם אם אין לכך הוכחה מתמטית ברורה.
בהמשך הדרך, האזורים של הקוד, “המודולים” – צמחו גם להיות יחידות נפרדות של קומפילציה, או linking/loading ואולי גם deployment. הנה, למשל, רעיון ה Micro-Services – על קצה המזלג.
כל אלו הם רק שיפורים – על הרעיון הבסיסי.

הפרדיגמה מונחית-העצמים (OO)

מי שחי בתקופה בה צמחה הפרדיגמה מונחית העצמים, בעיקר בשנות ה-80 וה-90 – חווה דיון נוקב בהבדלים בין תכנות מונחה-עצמים (OOP), ותכנון מונחה-עצמים, Object-Oriented Design, או בקיצור: OOD.
אפילו לא פעם נשמעה הטענה ש”OOD הצליח – אך OOP נכשל”. טענה שנויה במחלוקת, הייתי מוסיף.

אני לא רוצה להשקיע זמן על שחזור הוויכוח, ועל הדקויות הנלוות – ולכן אתייחס בעיקר ל OO כ OOD+OOP.

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

על פייטון ניתן לומר שהיא יותר שפת OOD מאשר שפת OOP…

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

Everything is an Object

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

לרעיון יש שני פנים:

  • לארוז קוד ביחידות קטנות (יותר) של מודולריזציה – להלן מחלקות.
  • למדל את המערכת ליחידות המתארות את העולם האמיתי. זה עיקרון ליבה של ה OOD, שלעתים לא מודגש מספיק: זה לא מספיק לחלק את הקוד ל״מחלקות״. חשוב מאוד שהחלוקה הזו תתאר את עולם הבעיה (העסקית) – בצורה נאמנה והגיונית. המיפוי / מידול הזה – מקל על הבנת המערכת, ועל התקשורת לגביה – מה שמקל על לקבוצה גדולה של אנשים להיות שותפים אליה ולעבוד בצורה קוהרנטית.
    • אם בעולם שאותו המערכת משרתת יש מונחים שלא מתבטאים במערכת כאובייקטים – אז כנראה עשינו מידול לא מוצלח.
    • אם הקשר בין האובייקטים לא תואם להיגיון מקובל – אזי כנראה שהמידול שלנו לא מוצלח.
    • אחד מכלי המחשבה למידול לאובייקטים הוא המחשבה על ״תחום אחריות״ (להלן: SRP). אם האובייקט היה בן-אדם, מה הוא היה דורש שרק הוא יעשה? שיהיה רק באחריותו?
      • טכניקה מעניינת של מידול שצמחה, למשל, היא CRC cards.
האובייקטים תוארו בעזרת המטאפורה של כ״תא ביולוגי עצמאי״. לתא יש מעטפת קשיחה המגנה עליו מהעולם (על כך – בהמשך) והוא מכיל בפנים את מה שהוא זקוק לו לפעולה עצמאית. כלומר: אנו מצמדים את הנתונים (state) לפעולות (functions). זה עוזר לנו גם בכדי להגן על ה state – אבל גם בכדי ליצור סדר הגיוני וצפוי במערכת.
שפות תכנות מודרניות, המתוארות כ OO, לרוב מספקות מבנה בשם מחלקה (Class) – שמקל עלינו למדל את המערכת כאובייקטים. האחריות לתוכן, והמידול עצמו – היא כמובן בידנו. שום שפה לא תעשה את זה במקומנו.
אם המערכת שלנו בנויה מ”מחלקות של נתונים”, ומנגד, מ”מחלקות של פעולות” – אז פספסנו את הרעיון המרכזי הזה של Everything is an Object. זה לא משנה בכלל אם אנחנו עובדים ב Enterprise Java או Shell Script. יותר מהכל, OO הוא לא כלי – אלא רעיון.
אל תבטלו את העניין הזה סתם: אנחנו עדיין כותבים הרבה קוד שלא מציית לרעיון של Everything is an object. הרעיון הזה קל אולי להבנה – אבל לא תמיד קל ליישום.

Information Hiding

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

OO חידשה בכך ששמה את ה Information Hiding כעקרון ליבה מחייב – ולא עוד כהמלצה ״לפעמים כדאי לצמצם את הידיעה של חלק אחד בקוד על חלקים אחרים״. ב OO מדגישים: ״בכל מקום במערכת אנחנו נסתיר כל מה שלא נחוץ – לחלקים האחרים״.

  • Encapsulation הוא השילוב של הרעיון הזה, בשילוב הרעיון של Everything in an Object. ההכמסה היא ״קרום התא הביולוגי״ המגן כל פנים התא הרגיש (ה state הפנימי) – מהשפעות חיצוניות.
    בשפות OO לרוב יש לנו directives הנותנים לנו לשלוט על הנראות: private, package, protected – וכו’.

    • אובייקטים הרוצה משהו מאובייקט אחר, שולח לו ״הודעות״. לא עוד אלך ואפשפש לי (ואולי גם אשנה) State של חלק אחר במערכת. יש מעתה Gate Keeper, שלו אני שולח רק את המידע הנדרש (“ההודעה”) – ואקבל בחזרה – תשובה.
      • אם אתם יוצרים הודעות בין אובייקטים ושולחים את כל ה State של האובייקט, אז אתם מממשים משהו שקרוב יותר לתכנות פרוצדורלי – מאשר ל OO.
  • יש צורות נוספות של Information Hiding מלבד הכמסה של אובייקטים:
    • הפרדה של בסיס הנתונים לסכמות, תוך הגבלת הגישה לסכמות השונות – הוא information hiding
    • תכנון המערכת כך שיהיה צורך של פחות אובייקטים להכיר אחד את השני – הוא information hiding מעבר להכמסה של האובייקט הבודד.
    • העברה ל Front-End רק את פיסות המידע הנדרשות (לפעמים נדרש עיבוד נוסף לצורך כך) – הוא information hiding.
  • המוטיבציה העיקרית ל Information Hiding היא היכולת לבצע שינויים בחלקים מסוימים במערכת, בלי שהדבר יגרור צורך לשינויים באזורים נוספים. להגביל את גודל השינוי.
    • אם אני לא יודע על חלקים אחרים – שינוי בהם לא אמור לדרוש ממני להתעדכן.
    • זה לא תמיד נכון. לפעמים יש השפעות עקיפות – שכן ידרשו ממני להתעדכן. למשל: סדר שונה של פעולות שמרחש במערכת. לא נחשף לי מידע חדש / שונה, אבל עדיין אני צריך להתאים את עצמי.
לרעיון של Information Hiding (או הכמסה: אנו נוטים היום להשתמש במונחים לחליפין) – יש מחיר: הסתרה של נתונים דורשת עבודה נוספת – עוד כתיבת קוד, וחשיבה ותשומת לב – שבעקבותיה עוד כתיבת / שינוי קוד.
ברור שנקודת האופטימום איננה למקסם את ה Information Hiding עד אינסוף. אין כלל ברור כיצד למצוא את נקודת האופטימום, ורוב אנחנו מוצאים אותה (פחות או יותר) תוך כדי גישוש.
לאורך חיי ראיתי מערכות רבות שחסר בהן Information Hiding, וראיתי גם מערכות שיש בהם עודף של Information Hiding – והתקורה לכל שינוי, הייתה רבה מדי.

הורשה

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

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

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

שלא תגידו שלא ידעתם.

non-Objects

מהה?? אם “Everything is an Object” – אז איך יש דברים שאינם אובייקטים?

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

כש OO אומץ בצורה יותר ויותר רחבה, עלו עוד ועוד קולות שרצו להבחין בין מחלקות (classes) לבין מבני-נתונים (structs). בניגוד למחלקה, מבני-נתונים רק “מחזיקים” כמה נתונים ביחד – ואין עליהן פעולות (כך טענו בתחילה). יותר מאוחר הסכימו שיכולות להיות גם על מבני-נתונים פעולות (פונקציות) – אבל רק כאלו שעוזרות לגשת לנתונים במבנה, ובוודאי לא Business Logic. ההבחנה הזו לא השתנה – ולא נראה שיש קולות לשנות אותה.

ג׳אווה, שפה מאוד פופולארית ומשפיעה – דילגה על ההבחנה בין מחלקות למבני-נתונים. אם אנחנו רוצים להגדיר בה מבנה-נתונים – אנחנו משתמשים ב classes. בעצם כך, שפת ג’אווה “מנעה” במידת מה, מההבחנה בין מחלקות למבני-נתונים לחלחל בתעשייה.

  • האם Map או List הם מחלקות? לא – אלו הם מבני-נתונים. כל מטרתם היא להחזיק ולחשוף נתונים. הכמסה? זה לא הקטע שלהן [ב].
  • האם DTOs או Enum הם מחלקות? לא – אלו מבני-נתונים. בשפת ג׳אווה נממש DTOs כ Class, אך מבחינה רעיונית הן לא מחלקות: אין להן הכמסה [ב], ואין להם אחריות לתאר פעולות בעולם.
    • בג’אווה הרבה פעמים כאשר ממשמשים DTO (לרוב מצוין ע”פ Suffix בשם) – מאפשרים לכל השדות של ה DTO להיות public – ומדלגים על השימוש ב getter/setters. הגיוני.

חוסר ההבחנה בין מחלקות ומבני-נתונים לא מאפיין את כל השפות: ב #C קיימים structs, בקוטלין יש Data Classes, ובפייטון היו named tuples כסוג של ייצוג, ולאחרונה הוסיפו גם Data classes.

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

יש כמה סימנים שיעזרו לנו לזהות מבני-נתונים:

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

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

סיכום

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

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

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

—–

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

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

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

על פרדיגמות תכנות, ומה ניתן ללמוד מהן בשנת 2019?

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

בתכנות ישנן כמה פרדיגמות תכנות מקובלות. הפרדיגמות המשפיעות ביותר כיום הן פרדיגמת ה Objection-Orientation (בקיצור: OO) ו Functional Programming (תכנות פונקציונלי) – אך גם יש אחרות מוכרות יותר ופחות.

פורסם מעט אחרי שסיימתי לכתוב את הסדרה

באיזה פרדיגמה אתם מאמינים? עם איזו פרדיגמה אתם עובדים?

עבור הרוב המוחץ של אנשי-התוכנה, התשובה היא זו:

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

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

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

מה עדיף?

איזו פרדיגמה היא הטובה ביותר?

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

אם אנחנו הולכים לכתוב קוד קצר בן כמה עשרות שורות קוד – שימוש ב OO, או תכנות פונקציונלי, או Reactive Programming – יהיה סיבוך ובזבוז זמן!

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

לשימוש בפרדיגמות מתקדמות – יש מחירים:

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

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

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

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

למשל: בתיאורים המקודמים של Alan Key ל Object-Oriented – יש דגש גדול על העיקרון ש”אובייקטים מתקשרים ע”י שליחת וקבלת הודעות“. אפשר להסביר למה ההדגשות הללו היו חשובות בזמנו, ומה משמעותן, אבל היום ההגדרות הללו נכנסות תחת הגדרת ההכמסה (Encapsulation). כלומר: אובייקט שולח הודעות – כי הוא לא יכול לגשת לנתונים הפנימיים של האובייקט האחר בעצמו. בכלל, המונח “שליחת הודעות” מתקשר היום חזק יותר לפרדיגמה של Event-Driven – וזה כבר נושא אחר.

הדברים מסתבכים

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • האם חייבים הכמסה? לא נתקלתי שמערערים על חשיבות של ההכמסה, אלא האם לא ניתן פה ופה – לוותר.
  • האם כתיבת בדיקות אוטומטיות / בדיקות יחידה – שווה את המאמץ?
  • האם נכון בכלל לחלק את המערכת למודולים? או אולי: למה לא לללכת כמה צעדים קדימה – ולפרק את המערכת להמון פונקציות בודדות?
    • (הרעיון הזה סותר כ”כ הרבה פרדיגמות שהוכיחו את עצמן לאורך שנים, אך הבאזז דילג מעל כולן בקלילות מפתיעה).
  • למה לשאת את האילוצים של Strong Typing? “זה מאט את הכתיבה, ומעצבן”
  • ועוד…

הדיונים על נכונות השימוש בפרדיגמה הם כמובן חשובים: לא כל פרדיגמה מורכבת יותר – היא אכן טובה יותר. לאימוץ פרדיגמות יש מחיר ניכר, ולא כל פרדיגמה ש”תפסה” בארגון X – תצליח בהכרח גם בארגון Y. למשל: ב C עדיין משתמשים במשפטי goto – בעיקר בשם היעילות הגבוהה (קרבה למעבד).
בקרנל של לינוקס, למשל, יש כ 13K משפטי goto (בדקתי הרגע).

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

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

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

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

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

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

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

הורשה מרובה, למשל – היא לא בהכרח דבר רע, וב 90% מהפעמים – אפשר להשתמש בה בצורה נשלטת. אני זוכר שבעבודה עם ++C היו שטענו שכל הפחד מהורשה-מרובה – הוא מוגזם.
10% מהפעמים שבהם היא יצאה משליטה – היו מספיק קשות — שהתעשייה (!) באופן גורף החליטה לזנוח את האפשרות הזו.

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

גם היום, עדיין יש בשפות, וב Frameworks יכולות בעייתיות: שמדי פעם מקצרות את הקוד – אבל בפעמים אחרות הן זרז לבעיה. מדוע חשיש יצא מחוץ לחוק ואלכוהול לא? – קשה לומר.

חשוב לזהות אלמנטים במערכת שגורמים לבעיות רבות מדי – ולחסום אותם. אני בטוח שיהיו מתנגדים, אבל זכרו שמערכות לא הופכות עם הזמן לפשוטות יותר, או קלות יותר לתחזוקה. הסתמכות על משמעת ודיוק של כל וכל מהנדס במערכת – היא מתכון לכישלון.
על זה נאמר: “It must be simple – or it simply won’t be”

?Monty Python: What have the Romans ever done for us
(זכורה לי טענה שזה במקור דיון שהופיע בתלמוד — אבל לא מצאתי מקורות המאשרים זאת)

אז מה הפרדיגמות המוקדמות תרמו לנו?

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

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

למשל: המצאת ה”בלוק”. בתחילה – לא היה דבר כזה.

למשל: משפטי if היו יכולים באותה התקופה להפעיל רק ביטוי (expression) יחיד. לו היו 4 פעולות שלא היינו רוצים שיפעלו אם x הוא שלילי – היה עלינו לכתוב 4 משפטי if שכל אחד חוזר על הבדיקה “האם x שלילי” ואז מבצע עוד פעולה.

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

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

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

סיזיפי? סיזיפי אך אפשרי? – כך נראתה החלוציות בתחום התוכנה…

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

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

ארצה להדגיש שני עקרונות שצצו מהגישה הזו ושווים דיון, גם ממרום שנת 2019:

Scoping [ב]
הרעיון אומר כך: כאשר אנו נותנים שם (name binding) למשתנה, פונקציה, קבוע, וכו’ – נאפשר להגביל את מרחב הקוד שיהיה חשוף לשם הזה.בתחילה היה קיים רק המרחב הגלובלי, אך בשל התנגשויות / טעויות שנבעו מכך – החלו להגדיר scopes מצומצמים יותר.

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

בצורה כוללנית יותר, הרעיון אומר שננסה לצמצם את ההיכרויות במערכת – למינימום האפשרי.
כל אלמנט שאנו מגדירים (משתנה, מחלקה, וכו’) – נגדיר ב scope המצומצם ביותר שאפשר.
למשל: אם יש Enum שזקוקים לו רק במחלקה מסוימת – סגרו אותו ב scope של המחלקה – ואל תחשפו אותו לשאר העולם.
היום, בעולם ה IDE המתקדם – המשמעות המידית של scoping היא מה יציע לנו ה IDE ב Auto-complete. אם נגדיר הכל ב Scope הגלובלי – כל הקלדה תציע לנו את כל מה שיש.
אם נקפיד על הגדרת ישויות ב scopes מצומצמים ככל הניתן – הצעות ה Auto-complete יהפכו לממוקדות, והרבה יותר רלוונטיות.שנים רבות אחרי שהוגדר, העיקרון הזה עדיין לא תמיד מופנם ומקובל. עדיין אנשים מגדירים אלמנטים ב scopes רחבים מהנדרש מחוסר מודעות, או מתוך מחשבה שזו “הכנה למזגן”: אולי מישהו יצטרך את זה בעתיד? “למה לא לחסוך ולשים אותו זמין – כבר עכשיו”

מתלבטים מה עדיף?
– אני לא.
שווה לציין ש Scoping הוא צעד ראשון בכיוון של Information hiding ו Encapsulation. שוב ושוב עלו, לאורך ההיסטוריה – רעיונות בכיוון הזה.

Single Exit Principle

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

העיקרון הזה הוא מובנה בשפות שאנו עובדים איתן – עם כמה חריגות. קשה מאוד להסתדר ב 100% מהמקרים עם Single Exit יחיד.
נסו לרגע לחשוב: האם אתם מצליחים לחשוב היכן בשפה שלכם מפרים את העיקרון?

הנה דוגמאות עיקריות:
  • Exceptions שנזרקים – הם לא Single Exit.
  • continue או break בלולאה ל Label (בג’אווה, למשל) – הם לא Single Exit.
  • אפשר גם לטעון ש break ו continue באופן כללי – הם לא Single Exit, לא ברמת הבלוק.
    • בשפות פונקציונליות – לעתים אין להם מקבילה.
  • coroutines (על עוד לא ממתינים באותו הבלוק לסיומם) – הם גם לא Single Exit. הפעלנו קוד – שרק בונה-עולם יודע מתי בדיוק הוא יסתיים.
  • אחרון וחביב: יש גם מי שרואים בקריאת return מתוך גוף הפונקציה – הפרה של עקרון ה Single Exit. אמנם נקודת היציאה נתונה וקבועה – אך דילגנו על קוד – להגיע אליה. הטענה העיקרית – היא שזה לא צפוי. אני מצפה שהפונקציה תגיע תמיד לשורתה האחרונה.


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

סיכום

שוב לא הצלחתי לסיים את הפוסט ב 400 מילים.

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

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

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

—-

[א] דילגתי עם קוד מכונה, אסמבלי, ועל הדיון של המכונה של טיורינג לזו של פון-ניומן. זו היסטוריה low level מדי, ולא מעניינת לדיון.

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

מודל אנמי (Anemic Domain Model) – דפוס עיצוב שלילי

מודל אנמי (Anemic Domain Model, או בקיצור ADM) הוא תיאור שמתייחס למודל “מוחלש” בצורה שאתאר מיד.הצורה הנפוצה והפשוטה של מודל אנמי היא מצב בו קיים פער או מרחק בין ה state – הנשמר במחלקה אחת, לפעולות – הנשמרות במחלקה אחרת.

  • במבט ראשון המחלקות נראות הגיוניות: יש להן גודל הגיוני, תחום התעסקות הגיוני, מתודות הגיוניות וכו’.
  • במבט שני אנו שמים לב שבעצם רוב הפעולות של ה state מתבצעות במקום “מרוחק” – כל פעילות על Order מתבצעת ב OrderManager.

באופן טיפוסי המחלקה בעלת המתודות תיקרא בשמות כגון xxxManager, xxxService, xxxHandler או xxxUtils – שמות המעידים על קשר ברור, אך אחריות כללית שקשה להגדירה.

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

בצורה יותר פורמאלית ניתן לומר שיש לנו שתי מחלקות בעלות high cohesion (קרבה רעיונית גדולה) אבל גם high coupling (תלות חזקה). מדוע אם כן אלו שתי מחלקות, ולא מחלקה אחת?!

מה הבעיה לחבר ביניהן?

ובכן – לכו ובדקו.

בד”כ זה לא עניין של קובץ גדול מדי (כמה גדול יכול להיות ה state?!). סיכויים טובים שתגלו ש:

  • אובייקט ה Order הוא Data Access Object (מכיל ידע על שמירה לבסיס הנתונים) או Data Transfer Object (מכיר ידע על serialization בכדי לעבור על גבי הרשת) – קוד שמרגיש לא נכון לערבב אותו עם “Business Logic”, או
  • יש יותר ממחלקה אחת המבצעת פעולות על Order. למשל:
דפוס שכדאי לשים לב אליו הוא מתודות ב Service/Manager/Handler המקבלות את האובייקט כולו לצורך פעולות של שינוי state. משהו בנוסח:
updateItemPrices(Order o) // updates the order with up-to-date item prices

מתודה כאלו נקראות external methods.

מקור

“אחי, מה כואב לך?”

מה אכפת לנו, בעצם?

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

אז מה זה משנה שיש לנו Anemic Domain Model (בקיצור: ADM)?

נתחיל בדוגמה ממקום קצת אחר.

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

האם נרשה לכל מחלקה במערכת לגשת אליה ישירות?

מה פתאום!!!

בסיטואציה כזו, הקונצנזוס הוא גורף ומיידי!

  • “ומה אם נרצה להחליף את מערכת צד השלישי לספק אחר?”
  • “ומה אם נרצה להוסיף פונקציונליות (למשל throttling או logging) גורפת לכל העולות מול אותו ספק?”

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

מדוע אם כן אנו שלווים, כאשר יש מספר מחלקות הניגשות ל state במערכת, שלא ע”י נקודת גישה אחת?

  • מה יקרה אם נרצה לשנות את הייצוג של ה state של Order / לבצע refactoring?
  • מה יקרה אם נרצה להוסיף פונקציונליות (אימות נתונים, או Logging) גורפת על ה state הזה?
הסיטואציות הן שקולות.
יתרה מכך: שינוי state באובייקט ליבה של הדומיין הוא נפוץ יותר מהחלפה של ספק חיצוני.
אני מניח שהסיבה שאיננו מוכנים לקבל את הסיטואציה בתצורה אחת, בעוד אנחנו מוכנים לקבל את אותה הסיטואציה (וחמורה יותר) בתוצרה שונה – היא פשוט עוד הטיה אנושית, לא רציונלית.
בטבע פותרים אנמיה בעזרת תזונה טובה יותר.
בתוכנה פותרים אנמיה בעזרת הכמסה ו/או Rich Domain Model.

מה הבעיה עם ADM? – ניתוח פורמאלי

הבעיה העיקרית של ADM היא המחסור בהכמסה (Encapsulation).

האובייקט Order לא יכול “לשלוט” במה שקורה ל properties שלו – כי “כל העולם נוגע ב properties בלי לשאול אותו”. כלומר: אין מתודות שליטה בגישה ל properties – בהן אפשר לבצע validations או לקבוע התנהגויות.

כתוצאה מכך:

  • יווצר קוד כפול שבודק את תקינות ה properties של Order – באופן אקראי במחלקות A, B, C.
  • קוד הבדיקה, ותפעול (שינוי הערכים) של Order properties, במחלקות A, B, C – לא יהיה עקבי, וייתכן שיכיל חוסר-התאמות ואף סתירות.
  • מכאן יווצרו באגים, שיאטו את הפיתוח בשני אופנים:
    • הצורך לתקן את הבאגים – שקשה למנוע את היווצרותם.
    • הפחד מלבצע שינויים במערכת (המחשבה ש״שינוי״ יגרום ל״באג״) -> הקוד פחות מתחדש, פחות מתעדכן לצרכים העדכניים מהמערכת -> יותר עבודה להכניס שינויים.
  • הבעיה תלך ותחמיר ככל שהמערכת תגדל: יותר קוד (A עד F שעובדים עם Order, במקום A עד C), ויותר מפתחים שעובדים על ה codebase.
    • הבאגים שיווצרו יהיו גם הם – קשים יותר לאיתור.
  • עם הזמן הערכת הזמנים לשינויים בקוד הופכת להיות פחות אמינה: שיניתי ב A את הדברים, אבל רק לקראת סיום גיליתי שב C יש מנגנון שלם מסביב ל state שנגעתי בו – וצריך להמשיך בפיתוח בלתי-מתוכנן.
  • זו לא בעיה מסוג הבעיות ש״מתפוצצות״, אלא מסוג הבעיות שהן רגרגסיות הדרגתיות שלא מבחינים בהן בזמן שהן מתרחשות.
אני לא יודע כיצד להדגיש מספיק עד כמה הבעיה הזו הרסנית למערכת, במיוחד אם מדובר בקוד בליבת המערכת (core domain model), ובמיוחד כאשר מדובר במערכת בעלת חוקים עסקיים מורכבים.

מצב לא תקין שחלחל. גורם לקושי לאתר את מקור התקלה – ולצורך בתיקונים מורכבים יותר. הסתכלו על ה flow הבא:

כאשר אנחנו “מגלים” שיש לנו מצב לא תקין בשלב 4 – נעשו כבר נזקים, ויותר קשה לנו לאתר את שלב 1 – בו באמת קרתה התקלה.

הנטיה הטבעית, קצרת הטווח, היא להוסיף הגנות נוספות ב Class W, ולאחר שגילינו שדברים הסתבכו – עוד בדיקות ב Class Y. התוצאה: כפילות קוד, חוסר קונסיסטניות, ואי פתרון שורשי של הבעיה!

תופעת לוואי נוספות הן בדיקות פחות יעילות: מכיוון שקוד אימות ה state נמצא ״עמוק״ ב branching של הקוד – סביר יותר שהבדיקות יריצו תסריטים בעייתים מבלי שהמצב יתגלה:

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

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

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

אני רוצה לחדד פעם נוספת את הבעיה שביכולת לבצע שינויים ב state ממקומות שונים, ומדוע בעיות של data integrity  (להלן ״שלמות נתונים״) גורמות להאצה וסיבוך של באגים בקוד.

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

הנה דוגמה:

יש המון וריאציות של משולשים תקינים, אך לא כולם כאלו.

אם לדוגמה נשנה את ערכי אחת הזוויות כך שסך הזוויות יהיה 190 מעלות – חישוב השטח שלנו לא יהיה נכון.
האם יעוף exception באותו הרגע? לא.
יותר סביר שהלקוח יבוא אחרי חודש וישאל מדוע חייבנו אותו על שטח כזה וכזה…
חקירה כזו למציאת הבאג – תהיה ארוכה ומורכבת, וייתכן שהבאג כבר פגע בעוד אלפי לקוחות שצריך לתקן להם את הנתונים.
זו לא דוגמה ליעילות תפעולית.
זו גם דוגמה לבעיה שאפשר להניף בה הרבה אצבעות מאשימות, ולפספס בכלל שמדובר בבעיה ארכיטקטונית (הרי מישהו כתב קוד במחלקה C. למה הוא לא בדק מה מתרחש במחלקה B?!).

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

הנה שיפור, עם יותר constraints, ויותר ערכים מחושבים (derived values):

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

אבל עדיין נוכל לקבוע:

  • זווית של 0 מעלות
  • זווית של 200 מעלות, ולגרור מכך זוויות עם ערכים שליליים של זוויות
  • 3 זוויות שמסתכמות ל 180 מעלות, ושלוש צלעות סבירות לכאורה – שהן עדיין לא משולש הגיוני.
שימוש ב derived values הוא כלי רב-עוצמה לאימות נתונים פשוט ויעיל, אבל לא תמיד פשוט ו/או נכון לגזור נתון מנתונים אחרים – זה יכול לסבך את הקוד יתר על המידה.
המגבלה שאיני יכול לקבוע את הזווית השלישית בצורה ישירה – יכולה בקלות לסרבל את הקוד שמתשמש ב Triangle.
המשולש הוא מטאפורה, לאובייקט עסקי מורכב.
ואובייקט עסקי מורכב הוא כזה שקל לייצר ב state שלו מצבים בלתי-נכונים.
מכיוון שאין כוונת זדון, לא סביר באמת שמישהו יקבע זווית פנימית של 3600000-.
מצד שני יש אינספור דוגמאות, שקשה מאוד לצפות, למצבים לא תקינים, אך אפשריים.
הנה דוגמה לאובייקט משולש עם אימות נתונים קפדני יותר:

הקוד לא נבדק. זהו pseudo code שנכתב בזריזות
עדכון: תודה לעידו שהעיר שהקוד הזה לא ישים. יש לאפשר מתודה set המקבלת את שלושת הזווית / צלעות – כי כמעט כל שינוי במשולש כך שיישר תקין מצריך לפחות זוג של שינויים. אני מניח שעדיין אפשר להבין את העיקרון.
הערה נוספת: שווה להוסיף בדיקה ל value גדול מ 0.

אימות הנתונים הקפדני שווה רק כל עוד יש הכמסה על ה state.

אם מסירים את ה private מהשדות הפנימיים – כל האימות הופך לפרוץ עד חסר-משמעות.

מודל אנמי – שלב 2

עוד בעיה שמתרחשת עם מודל אנמי היא טרנזיטיביות של התלויות.

נניח שיש לנו מודל מורכב יותר (composite) של הזמנה – ופריט בהזמנה (להלן LineItem). למשל: ההזמנה היא הזמנה מהסופר, וה LineItems הם קורנפלקס, חמאת בוטנים, ולחם אחיד.

האם נכון שה Service Classes (או אובייקטים אחרים) המכירים את Order, יכירו גם את LineItem ואם כן – באיזו מידה?

הערה חשובה: מדובר על מצב בו בין Order ו LineItem יש קשר של composition (ב UML: מעוין שחור / “כתף מלאה”), כלומר: לאובייקט LineItem אין קיום או משמעות ללא אובייקט Order.

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

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

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

  • סתירה / חוסר-התאמה עם תכונה של אובייקט ה Order. 
    • למשל אובייקט ה Order צריך לדעת אם צריך משלוח עם קירור, אבל הוספה של קוטג’ למשלוח תשנה את המצב מבלי שהוא יהיה מודע לו.
  • סתירה / חוסר-התאמה עם ה state של LineItem אחר.
    • למשל: יש כלל שאסור להכניס לאותה הזמנה בשר וחלב. אם יש פריטים מסוג “חלבי” אין לאפשר הוספהש של פריטים מסוג “בשרי” להזמנה (ולהיפך).
הערה: אם אף אחד מהתנאים הללו לא מתרחש, ולא הגיוני שיתרחש עם התפתחות המערכת – אז כנראה שאין בעיה לאפשר למחלקות אחרות גישה לאובייקט הכפוף (LineItem) ללא מתן שליטה לאובייקט המרכז (Order).
אם כן אפשרנו גישה ללא-הכמסה, התוצאות הן שוב:
  • שכפול לוגיקת אימות-נתונים בין הצרכנים של Order.
  • ריבוי תלויות במערכת, הגברת ה fragility (הסבירות ליצירת באגים תוך כדי שינויי קוד – עולה) ול rigidity (שינוי אחד במערכת, מאלץ אותנו לשינויים לא-צפויים בחלקים אחרים של המערכת).
  • באגים קשים לאיתור, ובדיקות פחות יעילות.
שום “פיצוץ” – רק דעיכה מתמשכת.
אני מזכיר את זה בשל קיומם של מנהלים שרגישים לשבירות (“באג בפרודקשיין! חמור מאוד!”), אבל אטומים בפני הסדקים שגורמים לשבירות הללו (“קוד לא יפה? קוד שלכם, באחריותכם. לא מעניין אותי.”).

מה עושים?
בד”כ בעזרת עבודה עם Rich Domain Model (להן RDM).
אובייקט שהוא Composite (נקרא גם Aggregate) הוא בעל האחריות לגישה לאובייקטים הכפופים שלו.

פתרון מקובל הוא לחשוף interface המכיל גישה read-only (ומגובלת מעבר לכך ע”פ הצורך) לאובייקט הבן.
אובייקט ה Order יחזיר LineItemInfo ע”פ הצורך במתודות שלו.
בכדי לבצע שינוי ב LineItem, יש לקרוא ל Order, עם אובייקט ה LineItemInfo כפרמטר: “זה? תן לו 2 בכמות”, או לסירוגין “כל האובייקטים שנוצרו אתמול – בטל אותם”.

אופי האינטרקציה הזה מאפשר ל Order לוודא ש “2 בכמות” הוא מצב תקין, שלא מפר אף כלל עסקי.

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

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

אם מחלקת השירות לא יכולה להגיע ל Id – אז היא לא יכולה להתדרדר לעוולות הללו.

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

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

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

קונטרה

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

Rich Domain Model הוא Overhead מיותר. כתיבה של קוד מיותר.

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

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

גם ניהול ה state במשתנים גלובאליים יקצר את זמני הפיתוח.
לחלוקת המשתנים ל scopes, יש תקורה – אך גם תועלת.

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

אם המערכת היא מערכת CRUD, שעיקר עיסוקה הוא לשלוף נתונים, לקבץ אותם ולהעביר לצד הלקוח – יש מצב ש RDM אינו מצדיק את התקורה הנלווית.

בואו נעשה RDM בצורה עצלה: נתחיל לכתוב קוד כ ADM, וע”פ הצורך – נעבור ל RDM.

לכאורה זהו טיעון הגיוני מאוד. מאוד אג’ייל.
התשובה שלי היא כזו:

  • ככל שהאובייקט גדל, עלות ההמרה ADM->RDM תגדל גם כן. זה יכול להגיע לשעות, ואולי אפילו ימים.
  • עלות שכזו, באופן מעשי, היא חסם ממשי לביצוע המעבר. למשל:
    • אני עובד על פיצ’ר ואז מבין שהוספתי שדה שיכול להיות חלק מ state לא תקין.
    • מה יותר טבעי? להעלים עין ולתת למפתח הבא לבצע מעבר ל RDM, או להשקיע עוד 3 שעות בלתי צפויות לקראת סיום כתיבת הפיצ’ר?
  • בקיצור: לאנשים מאוד ממושמעים, זה יכול לעבוד. כישראלים – עדיף להשקיע את עלות ה RDM בעוד האובייקט קטן, כל עוד זוהי “מדרגה” קטנה ולא כ”כ מורגשת. נוסיף בכך עלות קטנה לפיתוח – אך עלות שתשתלם ככל שהמערכת תגדל ותסתבך.
העיקרון הפתוח סגור (OCP) מחזק את גישת ה ADM. נראה לי ש ADM הוא יותר SOLID מ RDM, לא?
למשל: למה לחזור ולשנות את Order שוב ושוב ולהסתכן בשבירת קוד? יותר אמין לכתוב קוד חדש ב Class A, ולאחר מכן ב Class B, וכו’ – וכך להיות “open for extension, but closed for modification”!על זה אני עונה:

  • ה OCP הוא עקרון שנוי-במחלוקת. הוא מבוסס על אמיתה נכונה, אך יש בו חור גדול: חסרה בו הנחיה מתי יישום OCP הוא טוב, ומתי הוא גורם לנזק.
    • כן – הוא בהחלט עלול לגרום לנזק.
  • core domain model הוא בדיוק המקום שאתם רוצים לשנות קוד שוב ושוב, תחת הסיכון ליצירת רגרסיה בהתנהגות.
    • אחרת מה? הקוד שלכם יתפזר לכל עבר?
    • איך באמת נראה לכם שזה יהיה יותר SOLID?!
ADM הוא בעצם תכנות פונקציונלי (Functional Programming). תכנות פונקציונלי הוא בטוח דבר טוב, והפרדה בין נתונים לפעולה – היא בעצם Best Practice.
  • יש פיל שעיר, מדיף ריחות עזים, ורועש במרכז החדר – שמשנה את התמונה מקצה לקצה. בואו לא נתעלם ממנו.
    אם לא הבחנתם בה – זוהי תכונת ה Immutability.
  • בתכנות פונקציונלי ה Service Classes לא מסוגלים להגיע ל state לא תקין, כי הם לא יכולים לשנות state של אובייקט שהוא immutable.
  • בנוסף, גם בתכנות פונקציונלי טוב שאובייקט המתאר state יבצע בעצמו את כל האימותים. מקום אחד + אחריות מלאה.
  • להזכיר: כל רעיון טוב ניתן לממש בצורה שגויה. Functional Programming – הוא לא יוצא דופן.

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

סיכום

Anemic Domain Model, או בקיצור ADM, הוא דפוס שלילי (Anti-Pattern) של מבנה שעלול לגרום לנזק מתמשך והרסני למערכת שלכם, בצד הארכיטקטורה.

כמו כל כלל, יש גם לו יוצאים מן הכלל. כשהמערכת בחיתוליה ו/או כאשר יש אפליקציית CRUD [א] – RDM עשויה ליצור עלות נוספת ולא-משתלמת.

בסה”כ, אם אתם בעולם של Business Software, תוכנה המבטאת ומיישמת כללים עסקיים מורכבים – Rich Domain Model הוא כנראה הבסיס לכתיבת קוד יעיל לצמיחה ולתחזוקה.  No way around it.

נ.ב – ברשת ניתן למצוא רפרנסים ל RDM שגם אחראים על גישה לבסיס הנתונים / Persistence.

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

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

לינקים רלוונטיים

כמובן שגם ב Bliki יש רשומה על ADM. איך לא?

Anemic vs. Rich Domain Objects—Finding the Balance
כתבה שביסודה שגיאהThe Anaemic Domain Model is no Anti-Pattern, it’s a SOLID design, אך שגיאה נפוצה.

[א] למי שלא מכיר crud = “קקי” באמריקאית. אני משוכנע שראשי התיבות לא יצא סדר האותיות הזה במקרה.

כללי התיכנותיקה – 19 כללים של הנדסת תוכנה שלא ניתן להתחמק מהם

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

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

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

הנה הם לפניכם:
Conway’s Lawארגוניארכיטקטורה
“מבנה התוכנה והקשרים בין חלקיה, סופם שיהיו שיקוף של המבנה הארגוני של הארגון בה היא מפותחת” (רפרנס)
הסבר: המבנה הארגוני הוא כוח חזק מאין כמוהו, שלא ניתן להתעלם ממנו. למשל: אם אתם מתכננים קומפיילר בין 4 שלבים, אך יש לכם 3 צוותי פיתוח – סביר שבסופו של דבר אחד השלבים יהיה אנמי, ובפועל יהיה לכם קומפיילר עם 3 שלבים. הפתרון – להכיר במציאות: או שתתכננו קומפיילר בין 3 שלבים, או שתארגנו את האנשים ב 4 צוותים.

עיקרון פארטו כללי
“במקרים רבים, 80% מההשלכות (effects) נובעות מ 20% מהגורמים הפעילים (causes)”
רקע: ידוע גם בשם “כלל 80-20”, והוא כלל שנוסח על ידי הכלכלן האיטלקי וילפרדו פארטו, לאחר שראה את פיזור העושר באיטליה במאה ה-19 (כיום, פיזור העושר הוא קיצוני הרבה יותר, ואולי לא משקף כבר “התפלגות טבעית של סיבה ותוצאה”).
וריאציה א: מוצר
“80% המערך של מוצר, ניתן להשיג ב 20% מההשקעה”
וריאציה ב: קוד
“80% מהבאגים המשמעותיים, נובעים מ 20% מאזורי הקוד”
וריאציה ג: ארגוני
“כ 66% מהעשייה המשמעותית, נעשית על ידי כ 33% מהמתכנתים”, מה שמתבטא גם ב:


כלל הכישרון ארגוניטכנולוגיה
“האלמנט שמנבא בצורה הטובה ביותר הצלחה או כישלון של תוכנה הוא לא הטכנולוגיה, שפת התכנות, או ה Framework – אלא המתכנתים שכותבים את התוכנה”

עקרון הדורות המקוצרים (short generations) כללי
“כל עשור יהיה על איש התוכנה ללמוד 50% מהמקצוע מחדש”
וריאציה: “כל עשור, 100% מהטכנולוגיות שבשימוש – יתחלפו”.
מי שלא מוכן / מבין את ההשקעה הנדרשת בכדי להישאר במקצוע – ייפלט מהתחום במשבר הראשון לאחר מעבר של “דור”.


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


עקרון ה Not Invented Here (בקיצור: NIH) קוד הטיה אנושית
“הדשא של השכן ירוק יותר, אך הקוד של השכן הוא גרוע, לא מובן, ולעתים פשוט מטופש”

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

“Code Rots”
כלומר: קוד שלא עובר חידוש (refactoring) – הולך ו”נרקב” ואיכותו פוחתת. השקעה רק בפיצ’רים, ללא השקעה בחידוש הקוד תביא את הקוד למצב שאיננו ניתן יותר לתחזוקה, ויש לכתוב אותו מחדש. Guideline מקובל לאורך החיים של תוכנה עד לשלב בו יש לכתוב אותה מחדש (אם לא בוצעו פעולות refactoring, “הזרמת חמצן”) הוא 3 עד 5 שנים.
היבט נוסף הוא העיקרון ש”קוד שלא נקרא כחצי שנה – הופך לקוד זר ולא מוכר” (ידוע כ Eagleson’s Law). רמת הקריאות של הקוד, ועוצמת המסרים והעקרונות המשתקפים מהקוד – משפיעים רבות על קצב ה”ריקבון” של הקוד.


Lubarsky’s law of Cybernetic Entomology קוד
“תמיד יש עוד באג אחד במערכת”
למשל: לאחר 11 שנה של שימוש המוני, נתגלה באג באלגוריתם החיפוש הבינארי של ספריית Java הסטנדרטית. (באג מאוד פינתי, כמובן).


לחלופין: Linus’s Law – הופרך כמיתוס
“Given enough eyeballs, all bugs are shallow”.
כלומר: בהינתן מספיק עיניים בוחנות – כל הבאגים יימצאו.
הכלל נקרא על שם לינוס טורבאלדס, יוצר הלינוקס, מכיוון שהוא מבטא סוג של הנחה סמויה של קהילת הקוד הפתוח. הכלל לא הוגדר ע”י לינוס, אלא על ידי אדם אחר, אריק ריימונד.

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


ללא שם (Michael Nygard)פרודשיין
“משתמשים מגבירים בצורה בלתי-מוגבלת את אי-יציבות המערכת”
בנוסח אחר: “לעולם לא ניתן ליצור סביבת stating/testing שתחווה את כל הבעיות של סביבת production”.
זו גם סוג של עקיצה למי שכותב תוכנה ללא משתמשים, ונמצא ב”אשליה” שהקוד שלו יציב, scalable, וכו’.
הכלל הוזכר לראשונה בספר (המצוין!) “!Release It

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


Sturgeon’s Revelation כללי
“90% מכל דבר זה שטויות”

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


The Hype Cycleטכנולוגיה

הסבר: טכנולוגיות חדשות נוטות לחזור אחר אותו מחזור של Hype שטכנולוגיות עבר עברו:

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


כלל ההקשר השגוי מתודולוגיה
“אין רעיון טוב שלא ניתן להשתמש בו בצורה שגויה לחלוטין” (ידוע גם כ Flon’s Axiom)



חוק ה 90-90 ניהול זמנים / פרויקטים
“90% מהקוד – ייכתב ב 90% מהזמן. יתר עשרת האחוזים מהקוד – ייכתבו ב 90% נוספים מהזמן.” (רפרנס)
יש כמה כללים דומים שמבטאים רעיון דומה:

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

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



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


Brook’s Law ניהול זמנים / פרויקטים
“הוספת אנשים לפרויקט מאחר ובשלב מתקדם – רק תגרום לפרויקט לאחר יותר” (רפרנס)
הסיבות:

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

צורה אחרת: “The bearing of a child takes nine months, no matter how many women are assigned”


The Bergman Dilation (“התרחבות ברגמן”) ניהול זמנים / פרויקטים
“לא תמיד יש זמן לכתוב את הקוד בצורה נכונה, אך ברוב הפעמים יש זמן לכתוב את הקוד מחדש”.

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


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



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

ארכיטקטורה של Hyper-Scaling

נושא ה Scalability הוא פופולרי-במיוחד בכמה השנים האחרונות.לאחרונה עברתי על עשרות קורות חיים של מועמדים – ובכמעט כולם צוינו מושגים כמו “High Scalability”, או “Big Data”, “NoSQL”, “Hadoop” – וכו’. כנראה שכל מי שעבד במערכת עם הרבה transcriptions per seconds או נפחים גדולים של נתונים – סיפר על כך בהרחבה, ומי שלא – התאמץ להראות איזו זיקה. זה “המשחק” היום, ונראה לי שהייתי עושה בעצמי את אותו הדבר בדיוק!

בפוסט הזה אני רוצה לספר על תהליך של Hyper-Scaling שאנו עוברים בחברת Gett – וכיצד הוא משפיע על עבודת הארכיטקטורה.

No Scale

אני רוצה להזכיר שהמונח “Scalability”, מתייחס בהנדסת תוכנה לשני סוגים של אתגרים:

  • Software Scalability – התמודדות עם יותר משתמשים, יותר פעילות, יותר נתונים.
  • Development Scalability – היכולת להתנהל עם צוות פיתוח גדול יותר.
ב Gett יש לנו Software Scale מסוים, שהוא לא קטן – אבל גם לא ענק. ככה וככה נתונים, ככה וככה פעולות בשנייה.
ההתמודדות העכשווית שלנו היא דווקא יותר עם Development Scalability, שכמו שאנסה להראות במהלך הפוסט – יש לה דמיון לא-קטן ל Software Scalability.
לפני כחצי שנה, כשהגעתי ל Gett היו בצוות צד-השרת כשישה מתכנתים. הגעתי מעט לאחר גיוס ענק של 150M$ שהחברה ביצעה. עם הגיוס, החברה החליטה להגדיל משמעותית את קבוצת ה R&D – בכדי לקבל משמעותית יותר תפוקה. בעת כתיבת הפוסט יש לנו כבר עשרים וחמישה (!!!) מתכנתי צד-השרת – ואנחנו עוד מגייסים.את הכלל של “לא להגדיל גוף פיתוח ביותר מ 50% בשנה” – שברנו כבר מזמן… מה עושים עכשיו? ואיך מתמודדים עם זה מצד הארכיטקטורה?

Scale

ההקבלה בין Scale של תוכנה ו Scale של קבוצות-פיתוח

נוסחה מקובלת בעולם ה Software Scale היא זו:
אנו מגיעים ל Scale כאשר יש לנו כמות משאבים (למשל: שרתים) מסוימת, וכל שרת מבצע עבודה בקצב מסוים.
גדילה ב Scale, כלומר: Scaling – מתבצעת ע”י הוספת שרתים או לחלופין ע”י שיפור הביצועים של כל מחשב בודד במערכת.
ככל שהמערכת גדלה – סביר שנחווה מצב בו כל מחשב נוסף שאנו מוסיפים הוא פחות יעיל מקודמו. מדוע? מכיוון ש:
  • פעולות על כמות גדולה יותר של נתונים – אורכות יותר זמן. למשל: אינדקסים בבסיס נתונים רלציוני, הפחתת הרציפות בדיסק, caches פחות יעילים או סתם פעולות joins גדולות יותר (merge על work set גדול יותר).
  • יותר תקשורת שנדרשת בין המחשבים השונים במערכת. יש הבדל בין הודעות עדכון שנשלחות ל 6 מחשבים – וכאלו שנשלחות ל 25 מחשבים. פעם נתקלתי במערכת שהוספה של מחשבים למערכת, מעל 16 מחשבים, כבר לא הגדילה את ה scale – בגלל ריבוי של הודעות כאלו.
  • כמות הקוד שלא ניתן למקבל (parallelism) באופן טבעי תגדל, ולא תקטן – אלא אם הייתה השקעה משמעותית בצמצום קוד שכזה.
  • חוסרי יעילות שונים – שצצים במערכת באקראיות טבעית.

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

  • כל עבודה רוחבית במערכת (למשל: Refracting גדול), הופכים להיות קשים וארוכים פי כמה – כאשר כמות הקוד גדולה יותר.
  • יותר תקשורת וסנכרון נדרשת בין המפתחים בקבוצה. אם פעם היה מספיק להרים את הראש מבהייה במסך – בכדי ליצור קשר עם מתכנת שמכיר היטב היבט מסוים של המערכת, היום כבר צריך לקום מהמקום, לחפש – ולעתים לגלות שצריך לדבר עם כמה אנשים בכדי לקבל תמונה מלאה.
  • תמונות-העולם של המפתחים בארגון מתבזרות במהירות: בניגוד לצוות שהיה לו זמן להתגבש במשך תקופה ארוכה – כעת יש זרימה של אנשים חדשים, שכל אחד רגיל לשיטות שונות וגישות שונות.
    הגישות הללו, עבור כל אחד, “הוכיחו את עצמן מעל ספק סביר – בעבר”. אמת. אבל מה עושים כאשר הגישות הפוכות זו לזו? האם ORM הוא טוב להכל, טוב רק לקונפיגורציה, או “רעה-חולה שיש להסיר מהמערכת בהקדם האפשרי!”?
  • יותר ידיים עובדות => יותר קוד => מערכת מורכבת יותר. כל שינוי במערכת מורכבת יותר – אורך יותר זמן בעצמו (מגמה לחוסר יעילות מובנה).
  • נוצרים יותר צווארי בקבוק (“רק משה מכיר את הקוד הזה“) – שהולכים ומקשים יותר ויותר על התקדמות הפיתוח.
  • יותר ישיבות, יותר המוניות, יותר אנשים שיש להכיר ולהתרגל לעבוד איתם – חוסרי יעילות שונים, שצצים במערכת באקראיות כל-כך טבעית.
ההשקעה ב Development Scale בפיתוח אמנם עוסקת במידה רבה בגיוס עובדים (“Capacity”), אבל לא פחות מכך – בשיפור היעילות של כל עובד (“Performance”). תהליכי ה Continuous Integration (ליתר דיוק: on-going integration) – מוגדרים מחדש, אנו משקיעים יותר בשיתוף הידע – ופישוט שלו, וכאן יש לצוות הארכיטקטים תפקיד חשוב.
אנו חותרים ל Continuous Delivery (הפעם: באמת) – בכדי לשפר את יכולת התגובה לתקלות במערכת, וכדי לעשות אותה יציבה יותר. באופן פרדוקסלי משהו, הניסיון בתעשייה מראה שדווקא העלאת תכיפות ה deployments מגדיל את יציבות המערכת – בטווח הבינוני-ארוך. יותר deploys = יותר “שבירות”, אבל אז גם יותר לקחים, יותר מנגנוני-התאוששות ובקרה, ויותר אוטומציה. כל עוד אין מנגנון ארגוני שמותיר “לעצור את הקצב, ולצמצם את קצב ה deploys” – האנרגיות ינותבו לשיפור המערכת, ומנגנוני הייצוב שלה.
ב Software Scale, יש את השאיפה התמידית ל Linear Scalability: האידאל שלפיו הוספה של כל מכונה למערכת, תתרום את החלק היחסי שלה. למשל: הכפלת כמות השרתים – תכפיל את הספק העבודה (למשל: כמות בקשות בשנייה).
לא ממש Linear Scaling: ככל שמספר הבקשות עולה – יש להוסיף חלק יחסי גדול יותר של שרתים בכדי לענות על הביקוש.
יש במערכת הזו צווארי בקבוק מסוימים ל scalability.
בקבוצת ה R&D כולנו מבינים שככל שהמערכת גדלה – היעילות של המתכנים הולכת וקטנה. אין לנו שאיפות ל Linear Development Scalability. אנחנו גם מכירים במגבלות המקבילות האנושית (“תשע נשים לא יכולות ללדת ילד בחודש אחד”).
בשונה מאיתנו ל Business דווקא יש ציפיות ל Linear Scalability – מפורשות יותר או פחות.
“פי-2 אנשי support עונים לפי-1.9 קריאות במוקד?” – הם מספרים, “כן… אנחנו מבינים שהנדסה זה קצת יותר מורכב. הגדלנו את הפיתוח פי 4, ואי-אפשר לקבל פי-4 פיצ’רים – אבל גם אי אפשר כבר לצפות לפחות לפי-3 יותר פיצ’רים, או לקבל אותם לפחות פי-3 יותר מהר?”הלחץ מצד הביזנס הוא אולי משני, אבל הוא משפיע – וגורם לנו להתייעל יותר בכל הנוגע ל Development Scalability של הפיתוח. בעיקר ע”י צמצום חוסר-היעילות שהמערכת יוצרת למפתח הבודד.

ב Software Scale, יש “קסם” שיכול לסייע למערכת לצמוח ב Scale שהוא יותר מלינארי: מצב בו פי-2 שרתים, משרתים יותר מפי-2 משתמשים. כיצד זה קורה? יש כמה דרכים, אבל ה”קסם” הנפוץ ביותר הוא Cache (או memoization – בגרסה התאורטית שלו).
כאשר אנו יכולים לבצע חישוב מורכב רק פעם ב 5 דקות, ואז להפיץ את התוצאות לעוד ועוד משתמשים – כמות גדולה אפילו יותר של משתמשים תגדיל את הלחץ רק על ערוץ ההפצה (CDN?) – ולא על יצירת התוכן (החישוב).

ככל שנתכנן את המערכת שלנו בצורה בה ניתן יהיה להשתמש יותר ויותר ב Caches שכאלו – נשפר את ה Scalability של המערכת. תכנון שכזה כולל, הרבה פעמים – משא ומתן עם אנשי הביזנס (“תקבלו את זה מעודכן פעם בשעה – לא כל הזמן”).

(פתרונות אחרים כוללים העברת עבודה ל Clients, או צמצום העבודה לקצב קטן מבו עולה ה scale, למשל: ביצוע חישוב על 500 משתמשי-מדגם, ללא קשר למספר כלל-המשתמשים במערכת).

Scaling שהוא טוב מ Linear-Scaling: הוספת שרת למערכת – מוסיפה יכולת לספק קצת יותר משתמשים מחלקו במערכת.

ב Development Scaling יש גם כמה “קסמים” שכאלו. הבולט בהם – הוא code re-usability: היכולת להשתמש בקוד שנכתב בעבר – עבור פיצ’ר חדש.

פתרון שונה-דומה הוא Generalization: כתיבת קוד כללי יותר – שיכול לשרת מטרות דומות, אך שונות.

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

מהנדסים צעירים, ואולי אף מהנדסים בכלל – נוטים לבצע הערכת יתר (גסה?) ליכולת שלהם לייצר קוד יעיל לשימוש חוזר / קוד כללי יעיל. משם נוצר הכלל You Ain’t Gonna Need It (בקיצור: YAGNI) המציע פשוט לא לנסות לחזות מקרים אלו מראש, אלא לעשות Refactoring לקוד כללי רק ברגע שהוכח הצורך – מעל ספק סביר.

בכל מקרה: שימוש חוזר בקוד והכללה, גם אם נעשים בדיעבד – הם כלים חשובים מאוד לשיפור ה Development Scalability.

אז מה עם ארכיטקטורה ל Hyper-Scaling?!

אולי אתם מאוכזבים מעט מהפוסט: יש בו הרבה דיבורים כללים, ואין בו Hadoop, HPC או Big Data משום צורה!

אני אנסה לתמצת:
הפיתוח של Gett עובר כרגע תהליך של Development Hyper-Scaling. יש גם בעיות של Software-Scaling – אבל הן (עדיין) פחות מאתגרות – אולי אזכיר לקחים משם בפוסט אחר.

הארכיטקטורה, או תוכנית-העל שלנו להתמודד עם בעיות ה Development Hyper Scaling הן כאלו:

  • בראש ובראשונה – מעבר ל Micro-Services: הפיכת מערכת אחת מורכבת – לכמה מערכות קטנות יותר, ומורכבות פחות. המעבר הוא אינטנסיבי, אבל הוא מאפשר להבין ביתר קלות את כלל המערכת – ולדעת להיכן לצלול בעת הצורך. כמו כן – הוא מצמצם במידה רבה את הצורך בתקשורת מרובה, לטובת תקשורת בסיסית יותר וממוקדת יותר, למשל: סך האחריויות של כל שירות, וה APIs שלו – שמוגדרים היטב (אנחנו משתמשים ב Swagger לתיעוד – כלי שמשרת אותנו היטב).
    את השימוש ב MSA להתמודדות עם Development Hyper-Scaling לא המצאנו בעצמנו: למדנו מ case-studies על חברות שעמדו באתגר דומה (למשל: אמזון).

    • שימוש-חוזר בקוד, הוא רעיון שקשה לממש (מעבר לפונקציה פה ושם). דווקא Micro-services, בכך שאנו מגדירים שירותים עם שימוש עסקי ברור, ו APIs מוגדרים היטב – מסייעים לנו ליצור יחידות גדולות של קוד שמתאימות לשימוש-חוזר. כבר בחצי-שנה האחרונה, היו לנו כמה הצלחות יפות.
  • אנו עוסקים בצורה פרואקטיבית בארגון בשיתוף ידע על חלקי המערכת השונים, האחריויות שלהם, וה flows העיקריים במערכת. לא עובר כמעט שבוע שאני לא עושה session שכזה, לצוות כלשהו בפיתוח – ואני לא היחידי. עוד פעם ועוד פעם – עד שלכולם כבר יימאס (אנחנו עוד רחוקים משם…).
    שמות פשוטים, מטפורות טובות, וסיפורים קליטים – הם מרכיב עקרי בבניית והפצת הידע.
  • צוות הארכיטקטים לוקח תפקיד קצת יותר ריכוזי מהרגיל (אולי: יותר מהאידאל האישי שלי?!) בהגדרת superflows חדשים במערכת. כן! אנחנו רוצים לעבוד יותר agile ולתת לאנשים יותר ויותר אחריות והשפעה, אבל בנקודת הזמן הזו – תוצאות טובות יותר מושגות כאשר לפחות את ה flows העיקרים – מוגדרים מרכזית ע”י הארכיטקטים.
    כאשר מפתחים עושים שינויים ושיפורים ב flows – זו סיבה לשמחה (אלא אם בכך הם סותרים עקרונות או flows אחרים במערכת).
  • אנו מנסים לקדם בקוד כמה עקרונות:
    • קידום תרבות של הצגת פתרונות – ולא רק בעיות (בכל ה R&D).
    • קוד פשוט להבנה – עדיף יותר על פני קוד קצר או מתוחכם. אם אתם קוראים של הבלוג זמן רב, אתם אולי יודעים שזו הנטייה הטבעית שלי – אבל זה לא הסטנדרט הברור של ריילס (שם עקרונות של קוד קצר ו DRY – מושרשים עמוק בקהילה).
    • יותר כלי monitoring ו supportability עבור הפיתוח. בכתיבה של כל פיצ’ר – לחשוב איזו השקעה תהיה משתלמת כאשר לפיצ’ר הזה תהיה בעיה ב production. כלי supportability יכולים להציג חווית שימוש עלובה למדי – כל עוד הם עוזרים.
    • הכנסה של אוטומציה / בדיקות יחידה / בדיקות-API / בדיקות-אינטגרציה. בכל ארגון שראיתי בעשור האחרון זו הייתה המגמה – אבל אנחנו עכשיו צריכים את זה יותר.

אני מודע לכך שעדיין אין פה Design Patterns הנדסיים משמעותיים (אולי מלבד MSA) – אבל זה מה שעובד, ואנו עושים את מה שעובד – ולא רק מה שמתאים לציפיה מסוימת (ארכיטקטורה = “תרשימים של ריבועים”). סורי! 🙂

זהו… מקווה שנהניתם, ואולי אף השכלתם. כרגיל – אשמח לתגובות.

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