מחשבות על coupling ועל dependency injection
אם הייתי צריך לבחור עקרון אחד לכתיבת קוד טוב וללכת איתו, זה היה לכתוב קוד שהוא loosely coupled.
הפוסט הבא מדבר על החשיבות של כתיבת קוד בצימודיות נמוכה ועל איך הזרקת תלויות יכולה לעזור לנו עם הנושא.
מה זה coupling
משמעות המילה coupling בעברית היא צימודיות והכוונה כשמדברים על צימודיות היא על כמה חזקה התלות בין מימושים בקוד.
במילים אחרות - ככל שכמות השינויים והתסבוכות שאכנס אליהם כשאצטרך לעשות שינוי או להוסיף שכבת אבסטרקציה תגדל - כך הצימודיות תהיה גבוהה יותר.
coupled code vs decoupled code
העיפו מבט בקטע הקוד הבא ונסו לחשוב אילו השפעות יכולות להיות לו על פיתוח התוכנה בעתיד
עכשיו דמיינו את המקרה הבא (והניחו שבניית מחשבים היה דבר פשוט שמסתכם בכתיבת מספר מחלקות וחיבורן יחד):
כתבתי את המחלקה Computer, הפצתי אותה למגוון גדול של משתמשים וכולם יכולים ליצור מופע של המחשב שלי ולהינות מיכולות העיבוד המופלאות של Intel.
לאחר כמה חודשים הגיעה לאוזני השמועה שיש מעבד חדש בשם AMD. כעת אני רוצה שכל מי שירצה יוכל להחליף את המעבד שלו ולבחור אם ברצונו להשתמש ב AMD או ב Intel.
אבל יצרתי בעיה.
כשהכנסתי את היצירה של IntelProcessor לתוך היצירה של Computer יצרתי תלות במעבד מסוג IntelProcessor, כעת אנחנו תלויים במימוש ספציפי ואם מחר נרצה שאותו אובייקט יווצר עם מעבד של AMD לא נוכל לעשות זאת ונצטרך לשנות את המחלקה Computer ואת כל השימושים בה על מנת לתמוך בזאת.
Dependency Injection for the rescue
Dependency Injection הוא קונספט מאוד פשוט אך עוצמתי שמטרתו להקל על תלויות בין מחלקות באמצעות הזרקת התלויות שאותה מחלקה צריכה אל תוך פעולת הבנאי (או פעולות אחרות). בדרך זו נוכל בזמן ריצה לבצע בחירה של התלות אותה נרצה ליצור ואותה נעביר אל תוך האובייקט.
דוגמה פשוטה לכך יכולה להיות איתחול Database בסביבת פרודקשן אל מול database בסביבת בדיקות של מוצר מסוים.
על מנת להפר את התלות בין שני האובייקטים הללו, כל שעליי לעשות כרגע זה להזריק את התלות בProcessor מבחוץ ולתלות את עצמי במחלקת Processor גנרית שאינה מדברת על סוג המעבד אלא מכירה רק את הפעולות שיודע לעשות (יודעת מה הוא עושה ולא איך הוא עושה). באופן הבא:
בקטע הקוד שלמעלה נוספה לנו מחלקה אבסטרקטית חדשה בשם Processor (בשפות כמו c# או java יהיה זה interface). מטרת המחלקה הזו היא להוות את הממשק (החוזה) אליו אנחנו מתחייבים, ברמה הגבוהה ביותר ומבלי לדבר בכלל על איך הפונקציה ממומשת. כעת כשניצור את Computer נוכל להחליט עם איזה סוג Processor נרצה אותו.
משפט על Coupling ו Testability
צימודיות גבוהה היא האויב הגדול ביותר של קוד בדיק.
כאשר אנחנו רוצים לבדוק את הקוד שלנו (בעיקר בבדיקות יחידה) אנחנו רוצים פעמים רבות להחליף את התלויות שלנו במחלקות שמתקשרות אל מחוץ לקוד שאנחנו מריצים באובייקטי mock כאלה ואחרים שיחזירו לנו את הערכים הספציפיים אותם אנחנו רוצים לקבל.
אם לדוגמה אני עובד עם מחלקת connection לשרת מסוים בתוך הקוד שלי - בבדיקות היחידה ארצה לבודד את הקוד שלי מהשרת ולהחליף את אובייקט ה connection ואובייקט מזוייף שמחזיר לי בדיוק את מה שאני צריך על מנת לבדוק את המחלקה.
כאשר הקוד tightly coupled, לא אוכל להחליף את אובייקט ה connection בשום אובייקט אחר מכיוון שאני תלוי במימוש קונקרטי שלו ולא באבסטרקציה.
בדיקות יחידה הן קלות ומהירות מאוד. מי שאומר שאין לו זמן לעשות בדיקות יחידה, הבעיה שלו היא כנראה הדיזיין ולא הזמן.
סיכום
אם הייתי צריך לסכם במשפט וחצי, איך כותבים קוד decoupled - הישענו על אבסטרקציות ולא על מימושים קונקרטיים והזריקו את התלויות שלכם מבחוץ ואל תצרו אותן בתוך פונקציות או בתוך הקונסטרקטור. בעולם מושלם, הייתי אומר שמחלקות צריכות להיווצר אך ורק בפונקציית ה factory ולהיות מוזרקות כל הדרך פנימה אל השימוש בהן.
הפוסט מדבר ברובו על דברים שנלקחו מתוך dependency inversion principle.
מדד טוב לכמה הקוד שלנו tightly coupled הוא כמה מחלקות, פעולות ורכיבי קוד נצטרך להזיז ולשנות בעת שינוי הקוד או הוספת רמת אבסטרקציה.
רק אציין שבjavascript לא צריך להזריק תלויות כדי לעשות מוק.
השבמחקגם בפייתון לא צריך
מחקhttps://medium.com/uckey/how-mock-patch-decorator-works-in-python-37acd8b78ae