דפוסי ארכיטקטורה (Architectural Patterns): דפוסי עיצוב, בגדול

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

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

אתן לכם מטאפורה לדפוסי ארכיטקטורה:

דפוסי ארכיטקטורה הם כמו מתכון למרק

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

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

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

הדפוסים עליהם נעבור בפוסט

  • כדור גדול של בוץ (Big Ball of Mud)
  • "הדר המסודר" (Hadar Amesudar)
  • "שכבות עוגה" (Layered Architecture)
  • MVC (קיצור של Model-View-Controller)
  • "דודות מרכלות" (Event-Driven Architecture)
  •  Command-Query Separation (בקיצור: CQRS או CQS) 
  • "גרעין קטן" (MicroKernel) ודרכו נזכיר גם Plug-in Architecture
  • "מיקרו-שירותים" (Micro-Services Architecture, בקיצור: MSA)

דפוסים נפוצים אחרים שלא נעבור עליהם הם, אך שווה להזכיר:

  • צינורות ומסננים (Pipes & Filters) – אותו כיסיתי בפירוט בפוסט משלו.
  • BlackBoard (רלוונטי בעולם של בינה מלאכותית)
  • MVP, MVVM או Presentation-Abstraction-Control (דומים למדי ל MVC)
  • Broker (רלוונטי במערכות מבוזרות)
  • Model-Driven Architecture (מן "רעיון גדול" שלא ממש "תפס") או Domain-Specific Languages (בקיצור DSL – רעיון שהצליח חלקית).
  • וכו'

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

בגדול אזכיר ש:

  • לדפוסים יש ערך רב כשפה משותפת וכמקור השראה.
  • הצמדות לדפוסים "כפי שהוגדרו" – יכולה להיות הרסנית.
  • כדאי ליצור וריאציות שלכם של הדפוסים שיתאימו לכם יותר, או לשלב רעיונות (Mix and Match) מדפוסים שונים.

בואו נצא לדרך!

כדור גדול של בוץ (Big Ball of Mud)

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

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

  • בעיית ההתמצאות (Orientation)
  • בעיית ה Development Scalability
  • יצירת מסגרת מנטלית של סדר וחוקיות במערכת. זו לא בדיחה: כמה כללים פשוטים מה "נכון" ומה "לא נכון" עוזרים לאנשים בפרויקט להרגיש בטוחים יותר במה שהם עושים. אי קיום הכללים הפשוטים הללו / חוסר סדר בסיסי כלשהו בפרויקט – משפיע על אנשים רבים (לא כולם) בצורה שלילית וברורה
    קשה להבין את ההשפעה עד שלא רואים את המעבר של פרויקט ממצב של "אי-סדר", לאפילו מצב של "סדר בסיסי".
את 2 הבעיות הראשונות תיארתי בפירוט בפוסט מדוע אנו זקוקים ל Software Design?

"הדר המסודר"

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

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

  • מחלקות עד 1000 שורות קוד
  • מחלקות מתחת ל 1000 שורות קוד
  • קבועים, enums, או data objects (שהם בד"כ מאוד קטנים)

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

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

נמשיך. הדר הבין שעליו לתקן ולכן מצא חלוקה חדשה: חלוקה "עסקית". אם המערכת היא GMAIL אז נחלק את המערכת ל:

  • דואר נכנס
  • דואר יוצא
  • אנשי קשר
  • קונפיגורציה
  • אחר

החלוקה הזו נשמעת יותר הגיונית, אבל גם בה יש בעיות עקרוניות. למשל: הקבוצה "אחר" יכולה להכיל לא-מעט מחלקות שלא מתקשרות ישירות לפונקציה "עסקית" (persistence, helpers, פקדי UI שבשימוש חוזר, וכו'). יצרנו שם "כדור בינוני של בוץ" בפני עצמו. ההתמצאות עצמה היא בעיקר ברמת האזורים ("שכונות בעיר") אך לא מכסה רזולוציה קטנה יותר ("רחובות").
דבר נוסף הוא שכדי להשיג Developer Scalability (היכולת של הרבה אנשים לעבוד על המערכת במקביל) אנו נרצה להטיל מגבלות על היכולת של החלקים השונים של המערכת לתקשר זה עם זה.

מדוע אנו מעוניינים בסדר וניהול תלויות?

נמשיל את העניין לבעיה פשוטה מאוד: הפרדה של קוד בין 2 מחלקות:

את קוד ה Parser אנו מחלקים ל2 חלקים. מדוע?

  • כי יותר קל לנהל קוד בחתיכות קטנות ופשוטות, מחתיכה גדולה ומורכבת יותר.
  • כי אז ניתן לבצע שינוי באזור אחד, עם סבירות גבוהה* שלא יהיה צורך לשנות אזור אחר. פחות בדיקות רגרסיה, פחות "פחד" לשבור דברים, והיכולת של 2 מפתחים לעבוד במקביל בצורה יותר יעילה.
* כל עוד החלוקה היא אפקטיבית
לא מספיק שחילקנו את ה Parser ל 2 מחלקות, אנו רוצים להטיל מגבלות על התלויות ביניהן:
  • הכמסה (encapsulation) – מגבלה מאוד מקובלת בעולם ה OO, שמפרידה בין חלקים ציבוריים (בהם ניתן ליצור תלות) ופרטיים (בהם לא ניתן ליצור תלות). עצם כך שצמצמנו את התלויות – הגברנו את הסיכוי ששינוי בקוד (באזור הפרטי) לא ידרוש שינויים או ישפיע על מחלקות שתלויות במחלקה זו – שוב: Developer Scalability, תחזוקה קל היותר, ויכולת לבצע שינויים בצורה פשוטה יחסית (באזורים הפרטיים).
  • ניהול תלויות (dependency management) – מגבלה שאומרת שמחלקת ה Document Parser רשאית לקרוא ל Paragraph Parse – אך לא להיפך. צמצמנו כך את התלויות עוד יותר.

אציין שבניהול תלויות המטרה היא לצמצם תלויות, לא רק "לנהל/לעקוב" אחריהן.

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

הכללים המגבילים, מציבים בפנינו שקלול-תמורות מסוים:

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

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

Layered Architecture

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

כללי החלוקה של דפוס השכבות הוא כדלהלן:

  • נפריד את מאגר המחלקות במערכת שלנו לכמה שכבות: שכבות עליונות לטיפול בעניינים ברמת הפשטה גבוהה, כאשר רמת ההפשטה יורדת ככל שיורדים בשכבות.
  • לוגיקה בשכבה גבוהה (n) תהיה מורכבת מהפעלה של לוגיקות (או פונקציות) בשכבה שמתחתיה (n-1). כמו כן, לוגיקה בשכבה גבוהה (n) תעזור להרכיב את הפונקציות בשכבה הגבוהה ממנה (n+1).
  • בד"כ השכבה הגבוהה ביותר היא זו שמתקשרת עם העולם החיצון: משתמש או מערכות אחרות.
  • החלוקה של המחלקות לשכבות היא שלב אחד, ובד"כ יש חלוקות נוספות בתוך השכבה למודולים / תתי-מערכות (sub-systems).
  • בד"כ בכל שכבה יש תת-שכבת API (או "Facade") מנוהלת – בה, ורק בה השכבה העליונה יכולה להשתמש. לא נרצה שכל ה APIs שזמינים בתוך השכבה יהיו זמינים לשכבה מעל – אחרת, איזו משמעות יש להפרדה שהגדרנו?!

בהצצה לתוך השכבה הבודדת, המבנה עשוי להראות דומה למבנה הבא:

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

  • UI – ממשק המשתמש
  • Business Logic – הפונקציה העיקרית שהמערכת ממלאה (ניהול ספקים, רכש, מלאי, וכו')
  • Data Access – בשכבה זו פעם היה הרבה קוד מתוחכם, אך היא הוחלפה חלקית ע"י מערכות ORM – ומה שנשאר בה הם האובייקטים של המערכת שמתארים את הנתונים (Entities, Collections, וכו').
  • Persistency – שכבה זו מתייחסת לבסיס הנתונים (בהנחה שיש כזה, בד"כ רלציוני) – סקריפטים ליצירת הטבלאות (DDL) ולעתים גם קוד שרץ בתוך בסיס הנתונים (stored procedures) [ב].

לחלוקה זו יש וריאציות שונות (הוספת שכבת Service מעל ה Business Logic, או שכבת Presentation Logic מתחת ל UI, וכו') – אבל סה"כ היא מאוד מקובלת.

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

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

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

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

אציין, שבמערכות Enterprise כדוגמת ERP של סאפ – יש ערך ממשי בחלוקה הנ"ך לשכבות: מערכות אלו מבוססות במידה רבה על מידול (modeling) של נתונים, ואז באמת יש פיתוחים ב Business Layer שלא צריכים לעדכן את ה UI (כי הוא נבנה מתוך מודל הנתונים שלא השתנה), ולא צריכים לעדכן כמעט את הטבלאות בבסיס הנתונים. שכבת ה Business Layer היא משמעותית ומשתנה תדיר – ויש ערך רב בהפרדה זו. כמו כן ניתן לשנות את ה UI (כיצד מרנדרים את המודל) – מבלי לשנות את שכבת ה Business Logic בכלל, וכו'.

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

"הב לי עלים ירוקים…"

Model-View-Controller

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

דפוס MVC בא לחלק אפליקציית UI לחלקים מוגדרים היטב – ולהגדיר תלויות ביניהן.
המקור של MVC הוא ב framework של שפת Smalltalk-80 משנת 1980 בשם "Model-View-Controller Editor".

ה Framework הזה היה מיועד לאפליקציות command line (שיא ה UI באותה תקופה), והחלוקה הייתה בין:

  • Model – הנתונים של האפליקציה / מבני נתונים
  • View – תצוגת הפלט, מה לרשום על המסך
  • Control – קבל ה Input מהמשתמש וטיפול בקלט הזה – בעצם ה Business Logic.
MVC "קלאסי" / "המקורי"

כל מחלקה (או פונקציה) נופלת באחד מהאזורים – וחייבת לציית לכליל הנראות. בת'כלס ה Controller רק יוצר את ה View ולא משתמש בו אח"כ, ושניהם מכירים את המודל. בין מחלקה של View למחלקה של Controller יש בד"כ קשר של 1:1 – והם מתארים "מסך" או "תפריט" באפליקציה.

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

אם כולם היו מכירים את MVC ומשתמשים בו באותו האופן – לדפוס זה היה ערך רב יותר. אולם, יש 2 בעיות:

  • MVC המקורי לא כ"כ מתאים לאפליקציות Desktop, Web או כל מה שאינו command line.
  • יש המון וריאציות של MVC: חלקן קיבלו שמות נבדלים (יחסית) כמו MVP, MVVM או PAC – אבל רוב הווריאציות פשוט משתמש בשם "MVC", תוך כדי ביצוע שינויים במודל "המקורי".
התוצאה המידית היא בלבול וחוסר תקשורת –  FUD. אם דיברנו על כך שדפוסים משמשים כשפה משותפת, אזי MVC משמש לרוב כאנטי-שפה-משותפת: מתכנת אחד אומר "Controller" והשני שומע "Kotroller" (כלומר: מה שהוא מכיר כ Controller). שני המתכנתים רבים כמה ימים, אולי שבועות – עד שהם מצליחים להסכים שבעצם השני לא אידיוט/מפגר/לא מבין – שניהם פשוט מכירים שתי וריאציות שונות של MVC.
בפועל, אוותר על הניסיון לתאר ולאפיין את כל הווריאציות התאורטיות של MVC (להזכיר: יש כאלו שהם Client-Side, כאלו שהם MVC צד שרת, כאלו שהם מערבים צד-לקוח וצד-שרת, וכאלו שמתאימים לאפליקציות Desktop או Native Mobile).
אני ממליץ לוותר על ההגדרות, ופשוט להיצמד לספרייה הרלוונטית שבה אתם משתמשים – והכללים שהיא מכתיבה: בסוף, התועלת העיקרית היא מכללים ברורים שכולם מצייתים להם – קצת פחות אם כלל מסוים מנוסח בווריאציה 2א' או בווריאציה 3ג'.
יש ספריות יש רבות הממשות (וריאציה של) דפוס זה: Struts, Play!, Grails, Ruby on Rails, Django, ASP.NET MVC , Angular.js, Backbone.js ועוד….
מבנה של MVC צד-לקוח מודרני טיפוסי אפשרי
העיקרון המשמעותי ביותר, והמוסכם ביותר בקרב ספריות "MVC" מודרניות הוא ההפרדה בין UI (כיצד משהו נראה על המסך) ל Presentation Logic (כיצד להחליט מה יש להציג על המסך). למשל (דוגמה סופר-פשוטה):
במקום לקבוע כלל ש:
  • אדם עם +500 חברים מקבל icon מיוחד.
מחלקים את המימוש ל2 אזורים:
  • Presentation Logic – אדם עם 500+ מתויג כ "אדם פופולרי במיוחד" (What)
  • UI – אדם המתויג כ "אדם פופולרי במיוחד" מקבל icon מיוחד.
הפרדה זו יוצרת מעט overhead על הפיתוח, אך היא מאפשרת להחליף את ה presentation (כלומר: אלמנטים ב UI / כל ה UI) בצורה קלה יחסית, מבלי "לאבד" או לשנות את ה Presentation Logic שהוא יותר יציב, או לחלופין להוסיף presentation נוסף (נאמר: mobile version), מבלי לשכפל את הקוד לגמרי.
החלפת / רענון שכבת ה UI – היא פעולה תדירה מאוד במערכות ווב. ארגונים גדולים עושים זאת כל 2-3 שנים, ואתרים קטנים מבצעים שינויים לפעמים על בסיס חודשי.

סיכום

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

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

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

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

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

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

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

לינקים מעניינים

הדוד בוב בביקורת עוקצנית על MVC (וידאו): "MVC is not an Architecture"

דעה: 5 תחומי-המומחיות ש"עושים" ארכיטקט

מקצוע ארכיטקט התוכנה הוא מקצוע שאינו מוגדר בצורה טובה. רק חשבו על הווריאציות הרבות (או השמות השונים) של התפקיד:
  • Development architect
  • System architect
  • Enterprise Architect
  • Solution architect
  • Application Architect
  • Integration architect
  • Data architect
האם מישהו יכול לומר מה ההבדל בין התפקידים הנ״ל בצורה ברורה?
האם מישהו יכול לתת לאחד התפקידים הנ״ל הגדרה מעשית ויציבה?
חוסר בהירות – הוא כנראה חלק מהתפקיד.


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

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

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




 

מומחיות מספר 1: מומחיות טכנולוגית

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

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

לארכיטקט חשובה יותר הבנה כיצד הדברים עובדים – מאשר היכולת "לקחת מקלדת ולבצע". יש הרבה ידע ו skill שנדרש כדי לבצע (quirks של סביבת העבודה וה build, להכיר היטב את תהליכי העבודה המעשיים – ולא התאורטיים, ידע מעשי של השימוש בכלים קצה לקצה) – skill שיש להשקיע בכדי לשמר.

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

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

מדד מעניין לידע "Architect-like" הוא היחס בין "מה שאני יודע שאיני יודע" (בקיצור: "לא.יודע") לבין "מה שאני לא-יודע שאיני יודע" (בקיצור: "אין.מושג").

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

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

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

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

  • SQL ו noSQL
  • Open Source ו Commercial Software
  • שפות typed (כמו ג'אווה או ++C) ושפות דינאמיות (Python או javaScript)
  • ארכיטקטורת מונחית Data / שירותים וארכיטקטורה של components או Object-Oriented.
  • UI צד-לקוח, UI צד-שרת או Desktop UI [א]
קיים יתרון אמיתי לארכיטקט ש"מאמין" ביכולת לפתור בעיות ב-2 האלטרנטיבות, ארכיטקט שאינו "משוחד" לפתרון מסוג מסוים.
אמנם לא קל להגיע למצב שמכירים, ומאמינים (להכיר בכדי לנגח – זה לא מספיק) שני צדדים ויותר – ובמספר תחומים.
זוהי שאיפה ארוכת-טווח.

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

"הכל זה trade-offs" — משפט של ארכיטקטים.

מומחיות מספר 2: תקשורתיות טכנית

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

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

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

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

סוגי ארכיטקטים שונים, כחומרים מגשרים בין גופים שונים.

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

מומחיות מספר 3: תקשורתיות אנושית והנעת אנשים

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

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

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

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

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

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

אפשר לומר שיכולת הארכיטקט להשפיע מורכבת מ:  יכולות טכניות x יכולות אנושיות.

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

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

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

מומחיות מספר 4: Domain knowledge

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

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

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

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

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

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

על כן, הבנת ה domain – היא שאיפה חשובה לארכיטקט. כלי עבודה ממשי.
ה domain לא קל להבנה: בדר"כ אין לא מומחה יחיד / "גורו" נגיש, אין לו ספרות מסודרת (לפחות בראיית high level) או הדרכות. הדרך ללמוד את ה domain היא ארוכה – וקשה. אבל גם משתלמת.

מומחיות מספר 5: כלים של "ארכיטקטורת תוכנה"

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

  • מערכות של עקרונות הנדסת תוכנה כגון SOLID או GRASP. [כתבתי עליהן קצת בפוסט הזה, ובאופן עקיף גם בפוסט ההוא]
  • UML/sysML – שפות לתיאור פשוט של מערכות (מורכבות).
  • Quality Attributes – כלי להבנת ה non-function requirements של המערכת [כתבתי עליו בפוסט הזה]
  • ATAM – כלי להבחנה בין Trade-offs בהם נעשה שימוש במערכת.
  • "Architecture Styles" ו "Architectural Patterns" (שלעתים עושים יותר נזק מתועלת). הם שימושיים לתקשורת יעילה יותר בין אלו שמכירים אותם.
  • עוד כמה כלים… שאני פשוט לא מאמין בהם: למשל CBAM או Architectural Views אדוקים – שמעולם לא סייעו לי.
  • מתודולוגיה: סקראם, Lean Startup או Continuous Delivery. לא רק עניינם של ארכיטקטים, אבל גם.
  • הרבה Buzz words ותחכומים – להתחבא מאחוריהם.

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

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

קטרוג: "לא צריכים ארכיטקט"

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

"מדוע עדיף שלא יהיה ארכיטקט"

ביקורת לא כ"כ מבוססת או עניינית:

  1. ארכיטקט, כהגדרה, הוא לא מעודכן בטכנולוגיה. כשהוא היה היה מתכנת, COBOL (או ג'אווה – אולי "הקובול הבא") הייתה השפה – ועכשיו הכל אחרת והוא לא מבין שומדבר.
  2. ארכיטקט רק אוהב לצייר ריבועים ולסבך מערכות. הוא מזיק ולא תורם. דיי כבר עם SOA, MDA, EJB או Software Factories!
  3. ארכיטקטים עסוקים בליצר Job Security ולדאוג שכולם יהיו תלויים בהם – ובדרך רק עושים נזק.
לא שאין ארכיטקטים כאלו: פשוט תעמידו אותם במקום או אל תעסיקו אותם. זה לא טיעון רציני כנגד רעיון הארכיטקט ככלל.

ביקורת יותר עניינית:

  1. עצם קיומו של ארכיטקט, פוגע בעצמאות של המתכנתים: אם זה מישהו להישען עליו בפתרונות טכנולוגיים או קבלת החלטות, ואם בהבנת התמונה המלאה של כלל-המערכת.
  2. ארכיטקט שלא ממש חי את הקוד, אפילו אם הוא מבין פחות או יותר את הטכנולוגיה, עלול להוביל את המתכנתים למקומות לא נכונים. רק הבנה מלאה בפרטים – מספיקה כדי לקחת החלטות שמשפיעות על הקוד.
  3. ארכיטקט הוא לא גוף ביצועי (לפחות לא כמו מתכנתים) – ולכן הוא יכול להרשות לעצמו להטיל ביקורת על המבצעים ללא היכר. התוצאה: ניכור ומורל נמוך בקרב המתכנתים – שיוצר יותר נזק מסה"כ התועלת של הארכיטקט.
  4. ארכיטקט, מעצם קיומו או תפקידו, יכול לייצר מתח נוסף בקרב המפתחים ו / או אנשי המוצר (גם QA, Performance וכו'). חבל.
  5. כשהארכיטקט הופך להיות middleman – הוא יוצר שכבה מיותרת ומסרבלת של תהליכי הפיתוח.

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

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

סיכום

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

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

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

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

—-

[א] המקבילה המודרנית של Desktop UI היא native mobile app.

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

ארכיטקטורה: חלוקת המערכת למודולים

תזכורת קצרה:
תהליך הארכיטקטורה מורכב ממספר משימות, אחד מהם הוא "הגדרת הארכיטקטורה". משימות אחרות הן הכשרה, בקרה, מעקב אחר שינויים וכו' – תלוי את מי שואלים.
תהליך הגדרת הארכיטקטורה מורכב מ 4 פעולות עיקריות:
  1. חלוקת המערכת ליחידות קטנות יותר ("מודולים")
  2. ניהול תלויות
  3. הגדרת הפשטות (abstractions) והגדרת API
  4. תיעוד הארכיטקטורה
כמובן שהתהליך לא חייב להיות מנוהל בנוסח "מפל המים" (קרי – כל הגדרת הארכיטקטורה לפני כתיבת הקוד), אלא זה יכול להיות תהליך איטרטיבי שמתרחש תוך כדי כתיבת קוד.
את הפוסט הזה אני רוצה להקדיש לפעולה ראשונה, שאקרא לה בקיצור "חלוקת המערכת למודולים" – אף על פי שהיא יכולה להיות ברמת הפשטה גבוהה (subsystems) או ממש בפרטים (אובייקטים או פונקציות), כפי שתיארתי בפוסט התיאוריה המאוחדת: קוד, תכנון וארכיטקטורה.
חשוב לציין שקשה להפריד בין ארבעת הפעולות, במיוחד בין "חלוקת המערכת למודולים" ו"ניהול תלויות".
לצורך פשטות הפוסט אתעלם בצורה אלגנטית מניהול התלויות – עד לפוסט המתאים בנושא.
חלקים של מערכת. כיצד נקבעה החלוקה?

מדוע לחלק מערכת למודולים?

חלוקת המערכת למודולים מסייעת להתמודד עם מספר בעיות (מתוך הפוסט "מדוע אנו זקוקים ל Software Design"):

  • בעיית ההתמצאות (במידה רבה)
  • בעיית ההקשר / הגבולות הברורים (במידה רבה)
  • בעיית מקביליות הפיתוח
  • בעיות הדומינו (במידה מסוימת)
  • בעיית ה Deployment (במידה מסוימת)
  • שימוש חוזר בקוד (במידה מסוימת)

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

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

דויד לורג' פרנס תיאר זאת כך: "מתכנת חדש לא צריך ללמוד את כל המערכת בכדי לשנות שורת קוד אחת"

אז איך מתחילים?

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

  • לכל מודול תהיה אחריות יחידה
  • כל אחריות תהיה שייכת למודול אחד בלבד.

כלל זה בעצם הוא כלל ה Single Responsibility Principle (בקיצור: SRP), הכלל הראשון במערכת חוקי ה SOLID.

SOLID היא מערכת חוקים שהוגדרה ע"י רוברט ס. מרטין ("דוד בוב") לעיצוב תוכנה מונחה-אובייקטים.
במערכת כמעט מקבילה, מערכת ה GRASP של קרייג לרמן (מקור), העיקרון המקביל הוא High Cohesion (אחידות גבוהה).

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

ביטוי אחר מפורסם של כלל ה SRP ידוע כעקרון ה Separation of Concerns, כלל שאומר ש:
"System elements should have exclusivity and singularity of purpose", קרי – אותו הדבר, במלים גבוהות. זהו התיאור האהוב על ארכיטקטים רבים.

כלל ה SRP הוגדר ע"י דוד בוב בצורה הבאה: "A class should have one, and only one, reason to change". זו לא הדרך הכי פשוטה לבטא את הכלל, בהמשך נסביר את ההיגיון מאחורי הגדרה זו.

בצורה גרפית ניתן לתאר את הכלל בתרשים הבא (מעגל היא אחריות, "איש" הוא מחלקה):

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

מהי אחריות (Responsibility)?

קרייג לרמן הטיב לתאר[א] את מהות האחריות: אחריות היא מומחיות, ידע שרק המחלקה / המודול הספציפי יודע:

  • היכרות אינטימית עם מבנה נתונים מורכב
  • ביצוע חישוב מסוים (למשל חישוב ריבית או זמן נסיעה)
  • היכרות עם פורמט (עבור Parser) או פרוטוקול (למשל HTTP או פרוטוקול אפליקטיבי)
  • היכרות עם Flow (סדר פעולות ב high level) במערכת
שינוי יחיד בדרישות של המערכת מתמפה לעתים קרובות ל"מומחיות" יחידה של המערכת. אם המומחיות מוכמסת (encapsulated) במחלקה יחידה – אזי השינוי יהיה נקודתי.

לכל אחת מהאחריויות אנו רוצים שתהיה רק מחלקה אחת (או מודול אחד) במערכת בעל/ת הידע. ואז:

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

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

יש לי 3 הערות על הגדרה זו:

  • זו לא הגדרה כ"כ ברורה (לדעתי)
  • הגישה של כתיבת מחלקות "שלא ייגעו בהן יותר" קצת השתנתה בשנים האחרונות. נדון בהרחבה בנקודה זו בסקירת "העיקרון הפתוח-סגור" (OCP).
  • נקודה שאני אוהב בהגדרה זו היא ההצמדה של האחריות – להבנה סובייקטיבית שלנו של המערכת ("מה צפויים להיות השינויים"). בכלל ב Design אין "מתמטיקה" שמובילה אותנו לפתרונות טובים. החלטות ה Design מבוססות על הבנת הבעיה / מערכת + הגיון בריא (בשאיפה).
    ישנם גם Guidelines (להלן העקרונות להנדסת תוכנה) המסייעים לנו להגיע להחלטות הנכונות.

שמירה על שני האלמנטים של עקרון ה SRP הוא תנאי מקדים למודולים שיוכלו לשמש במערכות אחרות (להלן Code Reusability).

חזרה לדוגמה לעיל:

אם נתבונן בחלוקה לתתי-המערכות של הדפדפן, נוכל לראות שיש כאן חלוקה ברורה למומחיות:
מודולים 1-3, 5 – התמחות בתחום מסוים ומוגדר היטב
מודול 4 הוא יותר אינטגרטיבי ומשתמש במודולים 1-3,5 לביצוע משימותיו.
מודול 6 מומחה בניהול Flows.
מודול 7 ומודול 4 מחלקים ביניהם את המסך: מודול 7 הוא המסגרת ומודול 4 הוא מה שבאמצע.

מה שמוביל אותנו ל…

עקרון ה Unit Of Work

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

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

שאלה נוספת: כיצד החליטו בוני הדפדפנים לחלק בין ה Shell ל Rendering Engine ל Graphics Module? הרי שלושתם "מומחים לציור על המסך"?

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

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

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

כלומר: קודם יש לדאוג למקביליות הפיתוח ("Unit of Work") ורק לאחר מכן להתמצאות / קביעת גבולות ברורים. לרוב יש התאמה גבוהה (correlation) בין המשימות כך שלא תרגישו שאתם עובדים על דברים שונים.

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

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

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

זו מחשבה מקובלת, אבל בהחלט לא יעילה.

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

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

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

ארכיטקטורות אינטסנט

כדי להקל על המפתחים / ארכיטקטים, נוצרו מספר ״מתכונים מוכנים״ לחלוקת המערכת למודולים + ניהול התלויות.
חלוקה ע״פ שכבות – היא אולי המוכרת מכולן. ארכיטקטורות אינסטנט מוכרות אחרות הן MVC ו Pipes and Filters.

לחלוקה ע"פ שכבות יש מספר וריאציות:

  • n שכבות, כאשר כל שכבה יכולה לפנות רק לשכבה האחת שמתחתיה.
  • n שכבות, כאשר כל שכבה יכולה לפנות לכל שכבה שמתחתיה.
  • Shared Layers, בה יש שכבות "מאונכות" המשותפות לכמה שכבות.
  • 3 שכבות שנקבעו מראש: ממשק משתמש (UI), לוגיקה עסקית (Business Logic) ואחסון מידע (Persistence).
מכיוון שפוסט זה אינו עוסק בניהול תלויות, אדלג על ההבדלים בין 3 הווריאציות הראשונות ואתמקד רק בווריאציה האחרונה. מאחורי חלוקה זו ישנו רעיון דיי מתוחכם: חלוקת המערכת ע"פ טווח השרידות של חלקיה.
ידוע מניסיון של שנים, שהמודל של הבעיה – קרי מיפוי הישויות המעורבות בתהליך ה"עסקי" הוא החלק היציב ביותר במערכת (בהנחה שבוצע מידול מוצלח). הישויות לרוב כמעט ואינן משתנות (אם כי לעתים משתנים פרטים שלהן).
התהליך העסקי נוטה יותר להשתנות. גם בגלל שקשה יותר למדל אותו ב"ניסיון ראשון" וגם בגלל שתהליכים עסקיים משתנים בעקבות מציאות עסקית משתנה. הערה: "תהליך עסקי" הוא מושג כללי שיכול לתאר גם מערכות לא "עסקיות", כגון פייסבוק. למשל: "מעקב אחר חברים" או "מציאת חברים נוספים" הם תהליכים עסקיים. הישויות ("אני", "חבר", "דף פייסבוק עסקי") – נוטים כמעט ולא להשתנות.
ממשק המשתמש הוא החלק המשתנה בתדירות הגבוהה ביותר. באתרים כמו אמזון או ג'ימייל מבצעים שינויים (קטנים) כמעט כל שבוע. במערכות שונות מחליפים את כל חווית המשתמש כל כמה שנים וגרפיקה אפילו בתדירות גבוהה יותר. נראה שבשנים האחרונות הקצב הולך וגובר.
אם נפריד את המערכת שלנו לשלושה חלקים: יציב (אחסון מידע), מעט פחות יציב (לוגיקה עסקית) ולא-יציב (ממשק משתמש) – נוכל לבודד במידה מסוימת את השינויים ואת הסיכונים הכרוכים בהם. שינויים תדירים בממשק המשתמש לא יסכנו את המודל – שכמעט ולא נגע בו.
אל לנו להניח ששימוש בתבנית של "3 השכבות" מבטיחה לנו ארכיטקטורה מוצלחת. כדאי לחשוב על תבניות אלו כ templates בוורד שנראות כמו מכתב מעוצב, אבל אין בהן את התוכן שלנו. ארכיטקטורה גנרית, ללא התאמה למוצר, היא טובה כמו "מרק מוכן משקית": יותר טוב מכלום או ממרק גרוע – אבל אפשר, בלי לעשות קסמים, להשיג הרבה יותר מכך.

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

אנשים נוטים לייחס (בטעות) ל "מודל השכבות" או ל "MVC" (הפופולרית למדי – בימים אלו) סגולות לא-סבירות: "אם אין לנו במערכת MVC – כנראה שאנחנו לא בסדר".

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

כשהצטרפו מאוחר יותר עוד כמה צוותים לפרויקט הם היו מודאגים: "היכן ה MVC? אנחנו יודעים שחייבים MVC כדי לכתוב קוד ניתן-לתחזוקה!"

ניסינו להסביר שיש לנו חלוקה (ותלויות) מותאמים טוב יותר מ MVC [ה], שאנחנו מאוד בעד חלוקה מודולרית במערכת, ובעצם עשינו צעד אחד מעבר – אבל הדאגה בצד השני רק גברה. התוצאה הייתה מבלבלת: הצוותים ניסו להכניס MVC בצורות לא הגיוניות :"כל Service מחולק למודל ו Controller. ה View – ריק" או מיפוי לא נכון של הרכיבים ל MVC והסקת מסקנות לא נכונות לגבי השימוש בהם. במקרה קיצוני יצרו רכיב UI קטנטן שכלל MVC שלם ותקשר בהודעות עם עצמו.

עשו טובה: אל תגיעו למצב הזה.

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

סיכום ביניים

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

בינתיים פורסם: פוסט על העיקרון הפתוח-סגור.

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

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

[ב] עקרון זה, ה Information Hiding, הוא עיקרון שהוגדר ע"י פרנס במאמר פורץ הדרך שלו On the criteria to be used in decomposing systems into modules. העיקרון נשמע אולי טריוויאלי היום אבל בזמנו הייתה עליו לא מעט ביקורת ("למה להחביא, מה אנחנו – קומוניסטים?").

ספר מפורסם למדי, The Mythical Man Month, תקף את הרעיון בגרסתו הראשונה (1975) ובגרסה מעודכנת של הספר שיצאה לאחר 20 שנה, כתב פרדריק ברוקס פרק שמתחיל במשפט: "…Parnas was right, and I was wrong".

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

[ד] ישנה הגדרה שאומרת ש Legacy Code הוא כל קוד שאינו מכוסה היטב בבדיקות יחידה.

[ה] בעיה גדולה של MVC היא שיש כל-כך הרבה פרשנויות שונות שלה. הצהרה על "שימוש ב MVC" ללא הבהרת כללי השימוש המדויקים עלולה לגרום ליותר בלבול וחוסר-סדר מהימנעות מכל הצהרה שהיא בכלל.

מדוע אנו זקוקים ל Software Design?

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

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

אם אתם תוהים על "חישובי עלות-תועלת ב Design" – קראו את הפוסט הקודם שלי בנושא.

להלן סדרת בעיות התוכנה, ש Design יכול למתן:

בעיית ההתמצאות (Orientation)

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

דרכים אלו גוזלות זמן ומהוות waste שנרצה להימנע ממנו.

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

בעיית ההקשר (Context) / הגבולות הברורים (Boundaries)
תת-בעיה של בעיית ההתמצאות.

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

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

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

בעיית "נגעת – שברת" (Software Fragility)

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

האם אפשר לתכן תוכנה אחרת, כך שהסיכוי "לגרום לשבירה" יפחת?

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

בעיית הדומינו (Software Rigidity)

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

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

בעיית ה Development Scalability

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

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

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

לבסוף, אנו רוצים שאינטגרציות יכללו מינימום "התנגשויות" – ומכאן יצטמצמו פעולות ה merge ברמת ה source control. בשביל ה productivity.

בעיית ה Deployment

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

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

לעדכון של מערכת גדולה יש עלות משמעותית: אפשר לעבוד ימים, אם לא שבועות בכדי לעדכן מערכת גדולה ומורכבת.
כאשר יש Persisted Data, שינוי במבני הנתונים יכול לדרוש תהליך של migration: תהליך שיכול לארוך שעות של downtime ולא-תמיד הוא נקי מתקלות.

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

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

בעיית הפריון (Productivity)

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

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

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

בעיית הסיבוכיות

זוהי מטא-בעיה שמסכמת את הבעיות שציינו:

  • בעיית ההתמצאות
  • בעיית ההקשר
  • "נגעת-שברת"
  • בעיית הדומינו
  • בעיית ה Development Scalability
  • בעיית ה Deployment
היא נגרמת מכמה סיבות שגרתיות:
  • גדילת המערכת
  • שינויי דרישות – הנובעים מלמידה טובה יותר של המציאות, לקוחות חדשים או פשוט מציאות משתנה של הלקוחות.
  • טיפול בריבוי תסריטים (טיפול במקרים רבים יותר)
  • בעיות קשות יותר (מקביליות, דרישות ביצועים גבוהות, דרישות זמינות וכו')
  • טיפול ב Legacy ותאימות-לאחור
  • ועוד

סיכום ביניים

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

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

אני לא יכול שלא להזכיר בנושא זה סוג נוסף של "בעיה":

השאיפה לשימוש-חוזר (Reusability)

שימוש-חוזר (Reusability) הוא אחד הדברים שהכי קל "למכור" בצוות / בארגון: "למה לכתוב אותו הקוד פעמיים? בואו נכתוב פעם אחת ונשתמש בו בשני המקומות!"

יש לי הרבה מה לומר בנושא, אך אנסה לקצר בעזרת 2 חוקים בנושא, חוקי ה 3×3 של השימוש-החוזר:

  1. כתיבת רכיב המתאים לשימוש חוזר היא פי 3 יותר קשה מכתיבת רכיב לשימוש יחיד [א].
  2. רק לאחר שרכיב בשימוש ע"י 3 מערכות שונות – הוכחה היכולת שלו להתאים לשימוש חוזר.
אמנם יש מקרים בהם שימוש-חוזר הוא פשוט יותר, אבל לרוב מדובר במצב בו רכיב בודד צריך לטפל בכפליים תסריטים וכפליים מאפייני איכות – מאפיינים ברורים המגדילים את הסיבוכיות שלו. התסריטים ומאפייני האיכות הנוספים לא תמיד צצים בבחינה ראשונית של הנושא, במיוחד אם זו בחינה בגובה "10,000 רגל", שלא נכנסת לפרטים.

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

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

סיכום

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

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

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

[א] אין כמובן דרך למדוד "פי 3 יותר קשה", במיוחד כהכללה. הכוונה היא כמובן לומר: "הרבה יותר קשה".

האם אנו זקוקים ל Software Design?

ביצוע תכנון (Software Design) מתחבר לאנשים רבים ל"עשיית הדבר הנכון".
אם את/אתה אחד מאותם אנשים, עצרו לרגע והסבירו: מדוע זה הדבר הנכון? מה יקרה אם לא נעשה Design?

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

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

מעט ספקות

אנחנו לא באמת יודעים מה היה קורה "אילו היינו עושים…" – אנחנו יכולים רק לדמיין: "אם היינו משקיעים במניית אפל בתחילת 2007 ומוכרים אותה ברבעון הראשון של 2012 היינו עושים 600% רווח!". במקבילה שלנו בעולם התכונה: "אם היינו שומרים את כל ה properties על אובייקט ה xyz זה היה פתרון אחד לשלושת הלקוחות ולא היינו מסתבכים ככה!".

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

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

כל זה השתנה לא מעט בעשור האחרון בעזרת תנועת האג'ייל שעזרה לחדד 2 נקודות:

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

עברו 30-40 שנה. מה השתנה?

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

  • Unit Tests & Automation
  • Agile (ספרינטים קצרים, MVCs)
  • Static Analysis Tools, Modern IDEs
  • מערכת ניהול קוד מבוזרת, כלי build מתקדמים (כמו Gradle [א]) ו Continuous Integration
  • ווב, מחשוב ענן, Continuous Delivery or Deployment

האם כלים אלו:

  1. מעלים את רף מורכבות התוכנה בה תכנון (Design) הוא משתלם?
  2. מייתרים את הצורך ב Design?
  3. הופכים את ה Design למשהו אחר לגמרי?
  4. או בכלל לא משנים?

מה דעתכם?

הכלכלה של ה Design

את הטיעון "הכלכלי" המצדיק השקעה ב Software Design היה מקובל להציג לאורך השנים בדרכים שונות:

שנות ה 60 עד שנות ה 90: "נדל"ן יכול רק לעלות"

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

שנות ה 2000: "הבורסה היא השקעה בטוחה לאורך זמן, עם תשואה צפויה של 6% בשנה"

עם הזמן הוכרה ההבנה שלעתים אי-ביצוע Design אינו מתכון בטוח לכישלון. המודל ה"כלכלי" התאים את עצמו והתמקד בתובנה ש Design משתלם – בטווח הארוך:

מקור: הבלוג של מרטין פאולר

כמה הבחנות חשובות שנוספו הן:

  • הטענה שהשקעה ב Design משתלמת היא תחושה (של רבים ומקצועיים) – אך לא עובדה מוכחת.
  • יש אי-הסכמה בקרב המקצוענים מתי Design משתלם: אחרי שבועות של פיתוח או חודשים רבים.
  • יש אבחנה ברורה בין "good design" ל "mediocre design" – פעולת ה Design לא מבטיחה תוצאות, התוצאות תלויות מאוד באיכות העבודה.
לינק רלוונטי: Design Stamina Hypothesis

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

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

תובנות חדשות שנוספו הן:

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

לינק רלוונטי: ?Is Design Dead

סיכום

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

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

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

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

קוראים קבועים ישימו לב שאני תוקף לאחרונה את כל הנושא, אולם לאט ובזהירות. היעד הבא הוא להתמודד עם 2 השאלות הנ"ל.

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

שאלות, תגובות, מחשבות וכו' – יתקבלו בשמחה!

—-

[א] ב Technology Radar האחרון (מאי 2013) סימנו את Mavan ב "Hold" (תרגם לעברית: "צמצמו השקעות קיימות והימנעו משימוש בו בעתיד"), בעוד Gradle זכה ב "Adopt".