על Effective Software Design (או ESD)

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

לא התעלמתי לגמרי. כתבתי מסביבו:

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

מהו תהליך ה"דזיין" (כלומר: Software Design) הזה? תהליך שכל מפתח משתתף בו מספר פעמים בשנה?

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

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

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

ומכיוון שקיים צורך, היכן היועצים? הקורסים? הספרים? האם אני פשוט מפספס את כולם?

אז איך עושים דזיין – בצורה לא יעילה?

אני אפתח בכמה דוגמאות כיצד שאני נתקלתי בהן, כבר יותר מכמה פעמים:
דזיין = הצגת הפתרון
ישיבות דזיין רבות סובבות סביב התיאור של מה שהולכים לעשות: בעיה –> פתרון.
הקהל מודרך בתוך סיפור שאין לו רקע משמעותי לגביו. כמובן גם שאין לו מושג על ההתלבטויות שהיו – אך לא הגיעו לידי מימוש.
הפורמט הזה אולי טוב בכדי לשתף בידע את קהל השומעים, אך הוא איננו יעיל באיתור בעיות משמעותיות ושיפורן.
במקום לדון בבעיות הגדולות, פעמים רבות הזמן של הישיבה מושקע בכיסוי מפורט של הפרטים השונים (והברורים מאליהם) של הדזיין. "כן… בחרנו להשתמש ב Database!", "יש לנו controller ו view על מנת לייצר את ה UI!"
אם מטרת הדיזיין היא לשמוע "הממ… כן.. זה נשמע הגיוני.." – אז זה הפורמט הנכון עבורכם!
דזיין = "חפירה" בסכמת בסיס הנתונים
סכמת בסיס הנתונים היא השתקפות, במידה רבה, של המודל הקונספטואלי, עליו דיברתי בהרחבה בפוסט על DDD.
המודל הקונספטואלי מרכיב בתוכו הנחות משמעותיות על דומיין הבעיה והפתרון שלה –> שזה מצוין!
סכמה של בסיס נתונים (אם אין מודל קונספטואלי) – היא כלי יעיל להעלות על בעיות מהותיות ושיפורים אפשריים.
שתי הבעיות הן:
  • המודל / סכמת בסיס הנתונים היא רק מימד אחד של התוכנה שנכתבת – ואזורים חשובים אחרים כנראה לא זוכים לכיסוי.
  • חוק התכנותיקה קובע: רמת הדיון תותאם לרמת הפרטים המוצגים. כאשר מציגים טיפוסים של עמודות, אינדקסים, ו null / not null – הקהל נוטה לחשוב ברמה הזו ולמצוא שיפורים ברמה המשנית הזו. כן, אולי יהיו כמה שיפורים – אבל הפרטים הגדולים יותר של הדזיין נוטים להיעלם מאחורי הפרטים הקטנים והלא חשובים.
תוכנות wireframe, כמו axure, מכילות סט כלים על מנת לייצר low-fidelity wireframes בהם יש פרטים חסרים / לא שלמים / קווים לא ישרים המחצינים את חוסר הבשלות / שלמות של ה wireframe.
כאשר מציגים למשתמשים wireframe מלוטש ומהוקצע – הפידבקים נוטים להיות על הליטוש: צבעים, מיקום של כפתורים, וטקסטים.
כאשר מציגים למשתמשים low-fidelity wireframe – הפידבקים על המהות, ה flow, והתוכן המוצג – תופסים חלק משמעותי יותר.
דזיין = 90 דקות של מסירות – ואז שריקת סיום

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

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

סיכום ביניים

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

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

אז איך עושים דזיין – בצורה יעילה?

להלן עיקרי הדברים. אציג אותם בתור סוג של "template" למסמך דזיין יעיל.
להזכיר: המסמך הוא לא העיקר – התהליך הוא העיקר. תהליך החשיבה והשיתוף שעוברים האנשים המעורבים.
מה הבעיה העסקית (או הטכנית) שאנו עומדים לפתור?
אם מתמקדים בפתרון, ללא חידוד הבעיה – אזי יש סיכון לאבד את ההזדמנויות ה"שוות" ביותר בדזיין: פתרון אחר לגמרי!
הזדמנות נוספת: קיצוץ אפשרי בפעולות מיותרות. "למה לכתוב קוד multi-threaded? בביזנס אין יותר מ 10-20 בקשות כאלו בדקה!"
לא פחות חשוב בדזיין – הוא להחליט מה לא עושים. את זה אפשר להחליט רק כאשר יש הבנה טובה וצלולה של מה חשוב להשיג.

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

אם מישהו (anyone) מהצוות שיעבוד על הפיצ'ר לא יודע לדקלם, כשמעירים אותו ב 3 בלילה, בשתי שפות שונות (לפחות) – מהי הבעיה שהולכים לפתור…. אז יש פה פוטנציאל פספוס גדול.
אני נוהג לשאול בזמן עבודה על דזיין, מישהו אקראי – שיסביר במשפט או שניים מה בעצם רוצים להשיג כאן. כשהוא לא מבין ומחדדים לו – אני שומע איזה אסימון נופל, ומתגלגל למקום טוב יותר.
הקשר (context)
חשוב מאוד להבין היכן הקוד / מודול / שירות המדובר נמצא ברחבי המערכת הקיימת:
  • מי פונה אליו?
  • למי הוא פונה?
  • באיזה flows גדולים של המערכת הוא מעורב.
זה נשמע טריוויאלי – אבל זה הרבה פעמים זה מידע שחסר, ואף אחד לא מעז לשאול.
תרשים קטן כזה, על הלוח / דף שצולם באייפון / פאוור פויינט – מבאר מאוד את הדברים – ומעמיד אותם על דיוקם.
הוא כמעט תמיד שווה את ההשקעה.
על התרשים שווה להסביר את ה flows החשובים. אחד או כמה. זה יכול להיות בטקסט או בדיאגרמה.
אני אישית מאוד אוהב UML 1.x Collaboration Diagrams.
בגלל שאני רגיל לשרטט אותם – אני גם עושה זאת במהירות.
לפעמים ה flow החשוב – הוא בתוך המודול (או שירות) שלנו.
לפעמים ה flow החשוב – הוא בין המודול שלנו לאחרים.
מה שחשוב הוא לא להציג תרשים בפורמט כזה או אחר.
מה שחשוב הוא "לשים על השולחן" את ה 1 עד 3 flows החשובים ביותר ולראות שהם:
  • פותרים את הבעיה שלשמה אנו עומלים, ובצורה טובה.
  • שהם אכן הדרך הכי פשוטה שאנו מזהים לפתרון הבעיה.
  • שהם משתלבים במערכת הקיימת בצורה יפה, ולא מסבכים דברים. פשטות היא ערך מרכזי.
  • שלא עולים מקרי קצה, אמיתיים או בעייתים – שכדי לשים לב אליהם כבר מעכשיו. עולים? מצוין – נחשוב איך מתמודדים איתם. לפעמים מקרה קצה אחר יהפוך את כל הדזיין על פיו.
בשלב הזה כבר יש לנו (= קבוצת האנשים שעמלה על הדזיין) כבר תמונה טובה של הכיוון אלינו אנחנו הולכים: מהי הבעיה? מהו ההקשר? ומה הפתרון המוצע.
זה השלב לעצור.
Stop!
ועכשיו נרצה להתמקד בדילמות / החלטות הגדולות ביותר שלקחנו כאן. בטח התלבטנו עם עצמנו ו/או עם אחרים על משהו.
מצאנו את דרך א' כלשהי – שנראה שתעבוד. יהיה אפשר לכתוב קוד באופן הזה שירוץ בצורה נכונה.
הנטייה האנושית היא לקבע, להתרגל, ולהסתגל לאותה דרך א' – כי "היא פותרת דברים" או "זה עובד!".

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

הנה דוגמה לאופן לתאר החלטה גדולה של דזיין:

——
החלטה גדולה מספר 1: איך השירות שלי מקבל נתונים שלהם הוא זקוק?
אופציה א': שירות אחר יעדכן אותנו בכל שינוי דרך RabbitMQ
  • יתרונות: פשוט… יעיל ….
  • חסרונות: הרבה הודעות ….
אופציה ב': השירות שלנו יקרא לשירות השני בכל פעם וישלוף את הנתונים.
  • יתרונות: פחות הודעות ברשת. הנתונים יותר up-to-date
  • חסרונות: התלות "מרגישה" לא נכונה.
בגלל xyz בחרנו באופציה א'.
——-
תיאור שכזה הוא בסיס לסיעור מוחות איכותי.
  • סיפקנו לאנשים בדיון מידע עומק מספיק על מנת שיעקבו אחרי ההחלטה שלנו.
  • אנו משקיעים זמן בדברים החשובים ביותר – הרי זו "החלטה גדולה"
חלק גדול מפריצות הדרך בסיעורי מוחות שכאלו קורים כאשר המשתתפים מסכימים עם ההחלטה – אבל לא עם ההנחות!
מישהו אומר: אבל לא לקחתם בחשבון את חסרון x באופציה א'. זו בעיה ממשית כי….
ערעור הנחות היסוד מוביל לעתים לבחירה באופציה השנייה שהועלתה, ו/או ליצירה של אופציה שלישית.
לא תמיד דיון על החלטה יוביל לשינוי – אבל הסבירות להגיע לשיפור ("קפיצת מדרגה קטנה") בסוג כזה של דיון היא גבוהה כמה מונים מפורמט של דזיין בנוסח "הנה חברים – זה הפתרון. לכל שאלה שתעלו – יש לנו תשובה".
אני ממליץ להתעקש על דיון ב 2-3 "החלטות גדולות" עבור בדזיינים משמעותיים.
מה זה "משמעותיים"? נניח, דזיינים שהולכים להשקיע בהם שני חודשי עבודה מלאים של מפתחים, או יותר.
המודל
 
כפי שציינתי – המודל (שלרוב מתבטא כסכמה של בסיס נתונים, רלציוני או לא) הוא כלי יעיל לדיון ושיפור הדזיין.
הדיון במודל יהיה יעיל יותר כאשר:
  • נשמיט שדות לא חשובים. created_at ו updated_at אולי הם שדות רצויים – אבל יש סיכוי טוב שהם לא באמת חשובים לדיון. כלל זה גם טוב אם המודל מאוחסן ברדיס, או MongoDB – למשל.
  • נשמיט טיפוסים של בסיס הנתונים, כדי להימנע מדיונים משניים.
    • הדיון על varchar vs. char vs. text ב postgreSQL הוא מעניין – אבל לא מצדיק את ההשקעה ברוב המוחלט של הפיצ'רים.גם אם מדובר בדיון 1 על 1 – הרשו לעצמכם לדלג על הדיון הזה.
  • נתאר את השימושים העיקריים של ה data, אם מדובר בכמויות / קצבים משמעותיים.
    • דיון קצר על אינדקסים ומבנה הסכמה, לאור השימוש המצופה – כן יכול להיות דיון משמעותי ששווה את הזמן.
    • כמו כן: העובדה שיש לנו מיליון אובייקטים / רשומות שנוספים כל יום – הוא מידע שעלול לערער הנחה ו/או חלק אחר בדזיין של המערכת. דיון על כמויות שימוש במודל עשויות להעלות נקודות שלא היינו חושבים עליהן אחרת.
דוגמה: הצגה פשוטה ויעילה של מודל. מתמקדים בפרטים העקרוניים / משמיטים את הפרטים המשניים.
צ'ק ליסט
לכל ארגון ולכל מערכת – יש את הטעויות הנפוצות שלה. בעיות אבטחה או privacy, חשיבה על האופי המבוזר של המערכת, אי חשיבה על scale, תצורת פרודקשיין, איך לקוחות מסוג מסוים משתמשים בפועל במערכת, וכו' וכו'.
הרכיבו לעצמכם את הצ'קליסט הקצר, היעיל, והכי מחובר לקרקע שאתם יכולים – על מנת לנסות ולתפוס בעיות בזמן תהליך הדזיין. עברו על הצ'ק ליסט לבד או ביחד – מה שעובד הכי טוב.
———–
תהליך דזיין כמו שתואר למעלה אפשר לעשות ביום-יומיים, עם צוות מיומן.
אפשר לעשות ישיבה אחת לא רשמית אחרי כמה שעות לשתף רעיונות, ועוד ישיבה לקראת הסוף – לגבש אותם.
אם כל מפתח ישתתף בדזיין כל ספרינט, הסיכויים להשתפר ו"לשפשף" את התהליך – גדלים.
כמה עצות כלליות:
זדיין הוא לא נוסחה מתמטית
 
ולכן זה לא סוג הפעילות שיעיל לעבוד עליו לבד לאורך זמן.
אם אני יושב עם עצמי לבד בשקט ו"חושב על דזיין" – אחרי שעה בערך אני מפסיק להיות יעיל. אני זקוק להפרייה-הדדית. לדיונים עם אנשים. לספר, לשמוע, להחליף רעיונות.
אני אישית מוצא השקעה של שעה-שעתיים-שלוש בה אני "משרבט" קוד, שלד של flow כפי שאני מתאר לעצמי אותו – כיעילה ל"הזרמת חמצן" לחשיבה.
הלו? – שנות התשעים עברו
תוצר טוב של דזיין הוא לא הפתרון הכי גנרי, ולא הפתרון שמיישם מספר רב ביותר של תבניות עיצוב ™.
רדו מזה! חשבו על פשטות, על תחזוקה, על הרמוניה עם המערכת הקיימת, על עקרונות SOLID או GRASP.
עשו דברים הגיוניים, שעובדים לביזנס שלכם.
דזיין הוא תהליך של קונצנזוס
שזה אומר כן… תהליך חברתי. לא הדבר שהכי טבעי לכל אנשי התוכנה על כדור הארץ.
במהלך הדזיין חשוב להגיע להבנות משותפות, ולפשרות במקומות הפחות משמעותיים.
אם בתהליך לא יצרתם תמונה משותפת לכלל האנשים שיעבדו על הקוד, וגם מעבר לכך – אז… חבל.
אם אתם משקיעים שעות בדיון האם להעביר את שדה ה data ב json ברמה העליונה או רמה משנית – אז …. באמת חבל.
מהנדס תוכנה, חכם ככל שיהיה, שלא יודע להתגמש ולהתמקד בעיקר – עלול להוביל את הדזיין לאזור לא יעיל.
הדזיין לעולם לא נגמר
 
מסמך הדזיין – יעלם לו בדרך כלל בתיקיה בה הוא נשמר. אחרי חודש-חודשיים – הוא כבר כמעט לא רלוונטי.
יש נוסחה שאומרת שההתיישנות של מסמך הדזיין = המרחק הפיסי שלו מהקוד x אורך המסמך.
ולכן, מסמך דזיין שמופיע כשני עמודים של readme.md בתוך ה git repository – הוא זה שהסיכוי שלו להישאר רלוונטי הוא הכי גדול.

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

למה scheduler בצד-שרת – מפחיד אותי? (כתב אורח)

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

איך מתחילה שרשרת פרוצדורלית שכזו? ישנן כמה אפשרויות:
  1. פונקציית ה main והדומות לה – כלומר: קוד שיתחיל להתבצע מיד בעליית האפליקציה.
    אם מדובר בשגרת אתחול של תהליכים אחרים, או עצם ה business logic ש\"יחיה\" לאורך כל חיי האפליקציה.
  2. טיפול ב HTTP request (או פרוטוקול אחר כלשהו) – קריאה מבחוץ, ביצוע משימה כל שהיא, עם או בלי ערך מוחזר.
  3. האזנה לשירות כלשהו ותגובה למידע שמגיע ממנו – לדוגמה: אפליקציה שמאזינה על עסקאות מט\"ח ומבצעת לוגיקה מוגדרת מראש על כל קבלת עסקה שכזו.
  4. שימוש ב scheduler, פנימי או חיצוני לאפליקציה – למשל: cron job שבשעה מסוימת, או ביום מסוים בשבוע או כל פרק זמן מוגדר מראש יתחיל תהליך כלשהו.

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

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

לפני שנסקור את הבעיות האפשריות, בואו נזכר לאילו צרכים אנו לרוב משתמשים ב Scheduler.
הנה תיאור של כמה משימות שהוכנסו ל scheduler שנתקלתי בהן בשנים האחרונות (בחברות שונות ובצוותים שונים):
  1. תחזוקת ה DB (מחיקה של רשומות שפג תוקפן וכד\').
  2. שליחת דוחות יומיים.
  3. פנייה לשירות חיצוני כדי לעדכן מידע סטטי של המערכת פעם ביום.
  4. ביצוע סיכום של מסגרת זמן מסוימת מתוך דאגה לביצועים.
    לדוגמה: להתעורר כל דקה ועל סמך מינימום ומקסימום של שער היורו-דולר לבצע פעולה מסוימת (במקום להגיב לכל שינוי ברמת השנייה ואף פחות מזה).
  5. הפעלת לוגיקה עסקית שקשורה לשעה מסוימת – לדוגמה (מומצאת לחלוטין) כל יום ראשון בשעה 9:00 פג תוקפן של אופציות מסוימות ויש צורך לעדכן זאת במערכת.
אז מה רע כל כך בשימוש ב- scheduler? לכאורה מדובר בכלי חשוב והכרחי שמספק פתרון קל ונוח לבעיות מסוימות.

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

  1. בעיית איזור הזמן – במידה והגדרתם שעה מסוימת ביממה להתחלת התהליך, לאיזה איזור זמן התכוונתם? מן הסתם לזמן המכונה. אבל אולי ה Production שלכם רץ באמזון ב EST לעומת שרתי ה release candidate שנבדקים לוקאלית ב GMT + 2? מה קורה במעבר משעון קיץ לחורף? בקיצור – לא פשוט.
    GMT או BST? – זו השאלה! מקור: fotografiaslubnakielce.wordpress.com

  2. בעיית הגמישות – מישהו חכם פעם אמר \"כשיש לך פטיש כל בעיה נראית לך כמו מסמר\", ולפעמים נדמה שבגלל קלות השימוש ב-scheduler אנו נוטים להשתמש בו למגוון רחב מדי של משימות. בשונה מבקשה מהרשת לקבל webpage, בה מדובר על תהליך מוגדר היטב וסגור שבדרך כלל מסתיים בזמן קצר – אנחנו נוטים להשתמש ב scheduler לבצע משימות שיכולות להיות גם ארוכות ומורכבות.
  3. בעיית concurrency (או לחילופין:latency) – איך המערכת תתנהג כאשר הגיע כבר הזמן לביצוע הבא של ה scheduler והביצוע הקודם עוד לא סיים את עבודתו? במקרה כזה ישנן שתי גישות (המגובות בפ\'יצרים מתאימים בכל מימוש של scheduler שתבחרו):
    1. האחת: לא מתחילים את הפעולה המתוזמנת הבאה עד שהסתיימה הפעולה הקודמת.
      לדוגמה: אין טעם להריץ שוב את תהליך הניקוי של ה DB כאשר התהליך המתוזמן הקודם עוד לא הסתיים.
      גישה זו היא בטוחה יותר, אך לא תמיד היא אפשרית: לפעמים צריך לבצע פעולות מסוימות מיד בתחילת כל דקה-שעה-יום. בנוסף במקרה שהמערכת נכנסת למצב של latency יהיה קשה לפעמים להבין עד כמה המערכת בפיגור וכמה פספסנו.
      אנו עלולים להניח שמכיון שתהליך מסויים מתחיל בשבע ואחר בשמונה – אזי הראשון מן הסתם יסיים קודם. במקרה של מערכת שנכנסת לפיגורים – כל ההנחות הללו קורסות.
    2. הגישה השניה: הפעלת הפעולה המתוזמנת בלי קשר למה קרה לפעולה (או פעולות) מתוזמנת קודמת.
      כאן הסכנה ברורה יותר: במקרה קיצון המערכת תתנפח ותקרוס (בגלל בעיית זיכרון או מיצוי כמות ה – threads הזמינים), אבל גם לא במקרה הקיצוני ביותר, אנחנו נכנסים לבעיית concurrency קלאסית ועכשיו כל התהליך צריך להיות מותאם לביצוע מקבילי (concurrent).
      זהו לא אתגר יוצא דופן, אבל מניסיוני לא תמיד זוכרים זאת בשלב הדיזיין.
  4. בעיית ה\"פספוס\" – תזמונים שעשויים להתפספס
    למשל: \"כל יום בשבע בערב נשלח דוח יומי ללקוחות\", רק שיום אחד הסרבר נכנס למצב של out of memory בשעה 18:58. למרות שזיהינו זאת בזמן והריסטרט עבד חלק – פספסנו את חלון הזמנים של הדו\"ח! מה קורה עכשיו? מי אחראי להשלים את החור הזה? לא טרוויאלי בכלל!
    מה קורה כאשר התהליך המתוזמן נקטע באמצע? מן הסתם עדיף כבר לממש מערכת של משימות שיש לבצע והיא persistent.
  5. בעיית \"איבוד השליטה\" – כמה schedulers אפשר להגדיר למערכת אחת? כמה שרוצים!
    האם יש מקום אחד בו הכל מנוהל? אם לא אז כנראה שויהיה לכם קשה מאד להחליט האם תהליך אחד עלול להשפיע על תהליך אחר במידה וירוצו במקרה במקביל.
    כמה threads הוספנו למערכת בעקבות כל ה schedulers שהגדרנו? שוב יהיה קשה מאד לדעת.
    היבט נוסף שקשור לשליטה על המערכת הוא שתכנון של Scheduler שמתעורר כל שעה לבצע משימה מסוימת, עלול להתבסס על ההנחה שה Scheduler ביצע את אותה המשימה בעבר ויבצע אותה בעתיד. מניסיוני, ההנחות הללו אינן תמיד תקפות (תרגיל חשיבה: איך ניתן לדעת ש scheduler לא הופעל בזמן? איך מנטרים דבר כזה?) ועלולות לגרום לכשלים במקרי קצה שקשה לצפותם מראש.
  6. בעיית ה – Testing – מכיוון שהתזמון מתבסס על \"זמן מכונה אמיתי\", בין אם זו נקודה ספציפית על לוח השנה ובין אם מדובר על טיימר כלשהו – קשה לסמלץ בסביבת הבדיקות תזמון מדויק כמו זה שיהיה בפועל ב production. התוצאה: או ש\"מלכלכים\" את סביבת הבדיקות בכדי לשחזר את ההתנהגות של סביבת הproduction- או שמוותרים על סימלוץ מדויק של סביבת ה-production.
    אני מודה שבעיה זו היא פחות חמורה, ויש לה פתרונות יפים – אך עדיין דרושה כאן קצת אקסטרה מחשבה.
טוב אז מה אני מציע? לא להשתמש ב Schedulers בכלל?

האינסטינקט שלי הוא קודם כל לנסות ולהימנע מ scheduling במידת שאפשר: גם בגלל הבעיות שמניתי לעיל וגם בגלל התחושה הכללית כלפי הכלי הזה – משהו ב-scheduler מרגיש לי לא טבעי לסגנון ה java server side שאני מכיר.

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

  1. במקום להשתמש ב-scheduler כדי לשפר ביצועים גילינו שאין בכלל בעיית ביצועים. למה ניסינו מראש ללכת לכיון של שימוש ב-scheduler? אולי כדי להימנע מהצורך להתחייב על תוצאות ניתוח הביצועים של המערכת. בפועל הקדשנו לנושא עוד מעט מחשבה והגענו לפתרון נקי ובטוח הרבה יותר.
  2. במקום לנקות את ה DB פעם בדקה\\שעה\\יום – לבצע את הניקיון Lazily, רק כשניגשים ל DB. לפעמים זה מספיק טוב וגם חוסך עבודה מיותרת.
לפעמים אין ברירה וצריך להשתמש ב-scheduler. במקרה כזה אני תמיד מעדיף שה scheduler רק יכניס את ה\"משימה\" לתור. רצוי שתור זה יהיה Persistent, ותהליך אחר (יחיד או thread pool) ישלוף משם משימה אחר משימה. כך גם יש תיעוד, וגם חמקנו מרוב הבעיות שצוינו לעיל.
במידה וגם זה לא מספיק טוב וחייבים להשתמש ב scheduler ויהי מה – אז פוסט זה יכול להוות תזכורת לרשימה של דברים שיש לקחת בחשבון ושכדאי להיזהר מהם.

יניב חדד
Software Engineer at Citi TLV Innovation Lab

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

תזכורת קצרה:
תהליך הארכיטקטורה מורכב ממספר משימות, אחד מהם הוא "הגדרת הארכיטקטורה". משימות אחרות הן הכשרה, בקרה, מעקב אחר שינויים וכו' – תלוי את מי שואלים.
תהליך הגדרת הארכיטקטורה מורכב מ 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".