MasterScripter

עקרון אחריות יחידה

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

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

בגיל 14, פתחתי את הדפדפן ורשמתי בגוגל:

how to build websites

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

במסגרת המשרד אותו אני מנהל, החלטנו לאחרונה לבצע רענון טכנולוגי, ובמהלך הלמידה נתקלתי בראשי התיבות S.O.L.I.D:

SOLID? סולידי? מה?

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

חמשת העקרונות הם:

  • עקרון אחריות יחידה
  • עקרון יישום פתוח-סגור
  • עקרון ההחלפה של ליסקוב
  • עקרון הפרדה-בין-ממשקים
  • עקרון היפוך תלות

הפוסט הזה הוא אחד מתוך חמישה בסדרת SOLID POSTS: הסבר מעמיק על חמשת העקרונות שתיארתי.

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

נצא לדרך – Single Responsibility, או בעברית:

עקרון אחריות יחידה

צריכה להיות סיבה אחת ויחידה לשינוי מחלקה.

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

המחלקה מכילה שתי פונקציות:

draw מציירת מלבן על המסך
area מחשבת את השטח של המלבן

מכאן ניתן לומר שלמחלקה שני סוגי אחריות:

  • רנדור גראפי (draw)
  • חישוב מתמטי (area)

עכשיו, נתאר מצב שבו שתי אפליקציות עושות שימוש במחלקה Rectangle:

Geometric Calculator

מבצעת חישובים גיאומטריים.
נעזרת במחלקה Rectangle כדי לבצע חישוב שטח מלבן (area).

Geometric Drawer

מדפיסה צורות גיאומטריות.
נעזרת במחלקה Rectangle לחישוב שטח המלבן (area) וציורו (draw)

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

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

ריבוי תפקידים זה רע. אז מה כן?

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

GeometricRectangle

Rectangle

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

  • GeometricRectangle לא תטען את המחלקה הגראפית שמציירת את המלבן
  • שינוי בדרך שבה מלבן מרונדר באפליקציה Geometric Drawer לא ישפיעו על האפליקציה Geometric Calculator

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

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

בוחן פתע: כמה תפקידים יש למחלקה ומהם?

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

למחלקה יש חמישה תפקידים שונים

והנה הם לפניכם:

  1. עדכון פרטי המשתמש (initializeUser, updateField)
  2. עדכון מסד נתונים (updateField, delete)
  3. התמודדות עם שגיאות מסד נתונים (updateField, delete)
  4. בדיקת הרשאות (hasAccessTo)
  5. הדפסת פרטי המשתמש (printUserInfo)

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

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

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

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

לדוגמה, נניח שקיבלנו מהלקוח דרישה למשיכת נתוני משתמש בפורמט JSON דרך כתובת URL.
יש לנו שתי אפשרויות:

  1. להוסיף הסתעפות לפונקציה printUserInfo
  2. ליצור פונקציה נוספת – printUserInfoJSON

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

מהלך היפרדות

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

User

המחלקה אחראית לעדכון פרטי המשתמש

userDB

המחלקה אחראית לעדכון נתוני משתמש במסד הנתונים.
* המחלקה מיישמת את הממשק UserDBLogic ומרחיבה את המחלקה הדמיונית DB

userAccess

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

הגדרת אחריות

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

  • בהירות
  • ניגודיות
  • הגדרות אנרגיה
  • הגדרות תצוגה

איפה זה נגמר?

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

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

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

  1. חיבור (dial, hangUp)
  2. תקשורת (send, recieve)

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

האם הסיבה לשינוי תיתכן במציאות?

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

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

סיכום וטיפ בונוס

רוברט מרטין כתב בסיכום עקרון אחריות יחידה: (תרגום חופשי)

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

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

הבטחתי בונוס, שהוא בעצם טיפ:

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

נתראה בפוסט השני בסדרה: עקרון יישום פתוח סגור!

מקורות

אם הגעת עד לפה, הגיע הזמן להירשם לניוזלטר!

Summary
Article Name
עקרון אחריות יחידה
Description
מאמר הסבר על עקרון אחריות יחידה, אחד מחמשת העקרונות הראשונים של עיצוב תוכנה מונחה-עצמים (S.O.L.I.D), הוא הצעה לתבנית עבודה שמאפשרת תחזוקה גבוהה לצד יכולת הרחבה יעילה.
Author
Publisher Name
MasterScripter
Publisher Logo
אודות 
hello world! אני יונתן, מתעסק בתכנות ופיתוח WEB פלוס מינוס מאז שאני זוכר את עצמי. ב2013 הקמתי את קבוצת סיזן ביחד השותף היקר שלי - שלומי שלומקה זק. ביום מתעסק בפיתוח ועיצוב אתרי אינטרנט ואפליקציות, עיצוב חוויית משתמש, הקמת מיזמים, ועוד..אבל מה שלא ידעתם עליי - זה שבלילה אני באטמן. אל תגלו לאף אחד!

2 תגובות

  1. ronapelbaum

    16 במרץ 2017 - 9:06
    תגובה

    פוסט מצויין!
    מחכה כבר לפוסט הבא!

    שאלה/המלצה – למה שלא תכתוב את הדוגמאות בפסאודו קוד? יחסוך הרבה שורות ויהיה יותר קריא לכולם (אני אישית לא דובר PHP)

השאר/י תגובה

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