שימו לב: בד\"כ כאשר יש ב\"תרגילים לימודיים\" יש סוג של אחיזת עיניים קלה. המרצה \"מאלתר על הלוח\" פתרון מאוד פשוט ומאוד אלגנטי – אבל כנראה שזה לא היה הפתרון אליו הוא היה מגיע בפעם הראשונה. בעצם: הוא פתר את הבעיה כבר קודם לכן, ועשה על הפתרון כמה איטרציות של אופטימיזציה.
התוצאה: מסר יותר חד (\"כך כותבים קוד נקי!\"), בד בבד עם אשליה שאנשים באמת כותבים קוד שכזה \"במכה ראשונה\".
את הפוסט הזה אני כותב בספונטניות, לא בגלל אידאל כלשהו – אלא בעיקר בגלל חוסר-הנכונות להשקיע בהבאת הדוגמה \"לשלמות\". תוך כדי כתיבה אנסה לשתף במחשבות שעוברות לי בראש עד לפרט הקטן – מה שעלול ליצור מעט התפלספות, אך אני מקווה שיכול להביא ערך למתכננים צעירים שיש להם פחות ניסיון משלי.
בואו נצא לדרך.
כיצד מתחילים להגדיר ארכיטקטורה?
מהם המלים שחוזרות על עצמן בתיאור הבעיה? \"מלכה\" ו\"לוח\" (לפחות אצלי):
כבר ביצירת השלד הזה – בעצם לקחתי כבר כמה החלטות \"תכנוניות\" (להזכיר: קוד < תכנון < ארכיטקטורה – אך יש ביניהם דמיון רב).
- אופציה א\': לוח כמטריצה 8×8 של מערך המחזיק אובייקטים.
- יתרונות: מודל פשוט וקל להבנה
- אופציה ב\': ניהול רשימות של הכלים השונים – כאשר כל כלי מחזיק את המיקום שלו על הלוח.
- יתרון: יותר יעיל (טיול על x כלים – שזה מספר קטן מ 64 תאים – בכדי למצוא כלי) – לא משמעותי כאן.
- יתרון: מישהו יצטרך לחשב \"איום\" של כלי על כלי אחר. זה גורר דילמה אחרת (מייד) – שאת התוצאה שלה, אופציה זו \"תקבל\" בצורה טבעית יותר.
- אופציה א\': הלוח הוא זה שמחשב איומים בין הכלים
- יתרונות: אני מעריך שאופציה זו תדרוש פחות שורות קוד.
- אופציה ב\': כל כלי הוא זה שמחשב את האיומים שלו
- יתרונות: יותר OO, משום ש\"הצלחנו למצוא\" מומחיות למחלקות. מלכה מומחית בלהכיר את האיומים שהיא מפיקה, ופרש מומחה בלהכיר את האיומים שהוא מפיק.
תהליך העיצוב והארכיטקטורה הוא תהליך של יצירת סדר. מחלקות לא מקבלות אחריויות משמיים – הן מקבלות אותן מאיתנו. האחריות עוזרת לקבוע גבולות שמשמרים את הסדר. אם אתם קוראים את הבלוג תקופה – אני מניח שרעיונות אלו כבר מוכרים. אני \"טוחן\" אותם פעם נוספת מכיוון שהם חשובים מאוד – ואני נתקל שוב ושוב באנשים שהרעיונות עדיין לא ברורים להם.
- מלכה מכירה את הלוח – כי היא צריכה לבדוק מה יש בתאים מסביבה – (\"()board.content_at\").
- לוח מכיר את המלכות – כי הוא מציב את המלכות.
דילמה: כיצד לבצע את ההדפסה:
- אופציה א\': ה Board אחראי להדפסה
- ייתרון: יש למחלקה את כל המידע הדרוש להדפסה
- חיסרון: עכשיו יהיו למחלקה 2 אחריויות: א. לנהל את ה state של פתרון, ב. לנהל את ההדפסה של ה state הזה. 2 אחריויות היא חריגה מה SRP (קיצור של: Single Responsibility Principle).
- אופציה ב\': ה Board מדפיס את הלוח, ועושה to_s (כלומר: toString ברובי) למלכה – כדי לתת לה את האחריות כיצד להציג את עצמה (למשל: האות \"Q\" או סמיילי)
- ייתרון: פיזור אחריות בין אובייקטים שאחראים למשימה (scalability פיתוחי)
- חיסרון: פיזור אחריויות – אין SRP
- אופציה ג\': ליצור מחלקה נוספת שאחראית על ההדפסה
- ייתרון: הפרדת אחריויות ברורה בקוד
- חיסרון: מה השתגענו – עוד מחלקה? כלומר: יותר קוד לכתוב
מה זה אומר? באופן טבעי כבני אדם, אנו נוטים להעדיף השוואות נוחות.
קל יותר להשאוות בין אופציה A לאופציה -A שדומה לה, אך פחות טובה ממנה ולבחור את A, מאשר להחליט בין A ל B.
לכן, אנו נוטים \"לזנוח\" את B ולהתמקד בהשוואה בין 2 האופציות הנוחות בלבד.
כדי לקבל החלטה טובה יותר, כדאי לזהות את המצב ולבצע את ההחלטה בשני שלבים:
- שלב א\': A מול A-, ולהעלות את אופציה A \"לגמר\".
- שלב ב\': לבחון, מחדש, את אופציה A מול אופציה B.
אנו רוצים לקבל באמת את ההחלטה הטובה ביותר, ולכן הסרתי את אופציה ב\' שנראית לי פחות מאופציה א\'. ולכן:
- אופציה א\': ה Board אחראי להדפסה
- ייתרון: יש את כל המידע הדרוש להדפסה
- חיסרון: עכשיו יהיו לו 2 אחריויות: א. לנהל את ה state של פתרון, ב. לנהל את ההדפסה של ה state הזה. 2 אחריויות היא חריגה מה SRP (קיצור של: Single Responsibility Principle).
- אופציה ב\' (החדשה): ליצור מחלקה נוספת שאחראית על ההדפסה
- ייתרון: הפרדת אחריויות ברורה בקוד
- חיסרון: מה השתגענו – עוד מחלקה? כלומר: יותר קוד לכתוב
| ארכיטקטורה א\' |
- אנו לא בעסקי המושלמות (לפחות לא אני). המטרה היא לייצר ארכיטקטורה \"טובה\" (ככה \"top 5\") ולא \"הטובה ביותר\". למה? כי זה פשוט בלתי-אפשרי לוגית. כל דרישה חדשה למוצר הופכת את הסדר בין כמה הארכיטקטורות הטובות ומציבה ארכיטקטורה אחרת בראש הטבלה כ\"ארכיטקטורה הטובה ביותר\". מכיוון שאי אפשר לחזות את העתיד – אז אי אפשר גם להעריך איזו ארכיטקטורה, מבין הארכיטקטורות \"הטובות\" היא הטובה ביותר. פשוט אי אפשר.
- ארכיטקטורה מתאימים להקשר מסוים: מי הצוות שיעבוד איתה, מי הלקוח, ומה הסיטואציה (לחץ, פרויקט אסטרטגי, וכו\'). לא יהיה נכון להשוות ארכיטקורות שונות כאשר יש להן הקשרים שונים.
למשל: בארכיטקטורה הזו אני עומד להיות המפתח. מה שטוב עבורי, לא בהכרח מספיק טוב לכל אדם אחר. - את הארכיטקטורה ניתן באמת להעריך רק לאחר זמן ממושך. דברו איתי עוד חודשיים – ואספר לכם כמה טובה היא הייתה.
כלומר: מדובר בהימור מחושב.
הנה אלטרנטיבה אחרת (להלן ארכיטקטורה ב\'):
| ארכיטקטורה ב\' |
היתרונות של ארכיטקטורה א\' הם:
- הפרדת אחריויות מקיפה (כל מחלקה אחראית על משהו אחד)
- מוכנות לגדילה* (development scalability).
- מעט מחלקות – מעט קוד -> יותר מהר לפתח. בד\"כ עוד מחלקות דורשות כתיבה של עוד \"Gluing Code\".
- הפרדה בין אחריויות – אם כי פחות מקיפה.
סיכום התהליך
בפוסט הזה, בעצם ביצענו תהליך של הגדרת ארכיטקטורה – אפילו שזה היה מאוד \"בקטן\".
התהליך עצמו הוא דיי פשוט, ומבוסס על כמה עקרונות בסיסיים:
- הידע על הארכיטקטורה איננו קיים – הוא מתגלה. אנו מגלים תוך כדי תכנון ומעט קידוד (\"spike solutions\" ו/או POCs) תובנות חדשות, ומשלבים אותן בארכיטקטורה.
נכון, שאם בנינו מערכות דומות בעבר – אנו יכולים להשתמש בידע שנצבר שם בכדי לקצר תהליכים. - אפשרויות בחירה הן קריטיות לתכנון איכותי. ההרגל נוטה למשוך אותנו ל\"פתרון הראשון שיכול לעבוד\" ולהישאר שם, אבל חשוב לעצור לרגע וליצור עוד אלטרנטיבות ישימות – ואז לבחור מביניהן את הטובה יותר.
כקוריוז: שימו לב שזה אחד הנושאים עליהם מבקר המדינה מעיר שוב ושוב לממשלה: לא הוצגו חלופות (ישימות) בפני חברי הממשלה – ולכן נבחרה החלופה היחידה שהוצגה. אני מניח שהעקרונות לשיטה הם דומים.
- ידע תאורטי מוקדם (SOLID, אחר) מסייע לנו לזהות \"נקודות בעייתיות\" – לבחון אותן, ולתקן אותן (במידת הצורך). יש הרבה מאוד כללים מנחים בעולם התוכנה לגבי תכנון מערכת – חלקם אפילו סותרים.
למשל: בארכיטקטורה א\' הצבנו את כל אחריות ההדפסה על הלוח. בגלל ריבוי הכלים (מלכה, פרש) הוא יאלץ לבצע if (או case) ולהגדיר התנהגויות לכל כלי (המקביל לקריאה ל ()to_s בארכיטקטורה ב\'). מצב כזה נחשב כ bad smell בו מומלץ לעשות refactoring לכיוון State Pattern. מצד שני – המצב שייווצר יפגע ב SRP. פה יש עניין של הבחנה עיקר וטפל – שמבוסס במידה רבה על ניסיון מכיוון שאין דרך מתמטית \"להוכיח\" איזה מצב הוא עדיף. ניסיון הוא בסהכ ה\"מושכל\" ב\"הימור מושכל\", כמובן.
סיכום
לא הספקנו ליצור ארכיטקטורה גדולה.
לא הספקנו לגעת כמעט בקוד רובי (שבשבילו בעצם התחלתי את הפוסט… והדברים התגלגלו).
לא הספקנו להתייחס בכלל לפרש. זו הייתה החלטה מודעת: בהגדרת ארכיטקטורה כדאי לצמצם \"רעשי רקע\". הדרישה לפרש הייתה במודעות ובחשיבה – אך זה הספיק ולכן לא הרגשתי צורך לפרוט אותה לפרטים.
מצד שני:
הספקנו לתאר בצורה דיי פשוטה את עיקרי תהליך הארכיטקטורה.
אני מאמין שהפוסט מספק כמה תובנות משמעותיות, ולכן אני מרגיש נוח לעצור בנקודה זו. אולי אמשיך בעתיד אם ארגיש שהמשך העבודה שלי על התרגיל היא קרקע פוריה להצגת תובנות נוספות.
אולי אפילו הצלחתי קצת להעמיס ולבלבל עם כל ה\"התפלספויות\". אני רק מנחש שהפוסט עשוי להיות לא כל-כך קל לקריאה למהנדסים צעירים.
שיהיה בהצלחה!

