✕ סגור 
צור קשר
תודה על ההתעניינות .

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form

על עיצוב תוכנה – Top-down Programming

תומר כהן
|
קלה
|
Dec 1, 2018
alt="facebook"alt="linkedin"להרשמה לניוזלטר

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

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

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

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

קודם כל, מה זה Top-down programming?

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

לשם כך, נכתוב תוכנית פשוטה שמטרתה לתקשר עם שרת ה-vcenter ולמחוק את כל המכונות אשר נמצאות בתיקייה מסוימת יותר מ-24 שעות. השפה שבה נכתוב תהיה פייתון.
כשאנחנו כותבים את התוכנה שלנו באמצעות Top-down approach אנחנו רוצים להתחיל מהתוכנית הראשית, משכבת האבסטרקציה העליונה ביותר. לכן נתחיל מפונקציית ה-main.

def delete_all_old_machines():
   old_machines = get_all_old_machines()
   delete_machines(old_machines)
if __name__ == '__main__':
 delete_all_old_machines()

ניתן לראות שפונקציית ה-main קוראת ל- delete_all_old_machines , אשר קוראת שתי פעולות בתוכה.

הפעולה מתחלקת ל-get_all_old_machines אשר מחזירה רשימה של כל המכונות שנוצרו לפני יותר מיום, ו-delete_machines אשר מקבלת רשימה של מכונות ומוחקת אותן.

אפשר לשים לב בקלות (באמצעות סביבת הפיתוח) שהפונקציות get_all_old_machines ו-delete_machines אינן ממומשות וזו בעצם הדרך בה נעבוד. עם שיטת Top down נתחיל מכתיבת הקוד ככותרות בשפה העסקית שלנו, ומשם נצלול ונכתוב את המימושים שכבה אחר שכבה - כאשר נרצה לספר את תוכן הפעולה כסיפור בכותרות.

לפיכך, השכבה הבאה בקוד שלנו תצטרך להראות כך:

def get_all_old_machines():
   all_machines = get_all_machines()
   return get_old_machines(all_machines)
def delete_machines(old_machines):
   for machine in old_machines:
       delete_machine(machine)

לאחר שמימשנו את השכבה הזו אנחנו צריכים לממש את התוכן שלה: get_all_old_machines, get_all_machines ו- delete_machines.

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

def get_all_machines():
   folder = vcenter.get_folder(MACHINES_FOLDER)
   return folder.childEntity
def get_old_machines(all_machines):
   return list(filter(lambda machine: was_created_today(machine), all_machines))
def delete_machine(machine):
   vcenter.terminate_vm(machine.name)
   
לפעולות הממומשות במחלקה vcenter לא נכנס כעת, אבל ניתן לראות בקטע הקוד האחרון פונקציית private אחת שאינה ממומשת - was_machine_created_today.

בשלב זה אני ממליץ לעצור ולנסות לחשוב - כיצד תראה הפונקציה הזו בגישת Top-down?

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


def was_created_today(machine: vim.VirtualMachine) -> bool:
   date = _get_date_from_name(machine.name)
   # return some implemetation that checks if date is today
   
def _get_date_from_name(name: str) -> datetime.date:
   # return some implementation that extracts date from name of machine

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

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


הקשר לאוטומציה

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

מה יוצא לנו מ Top-down programming?

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

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

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

לסיכום

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

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

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


מאת: תומר כהן, מומחה לפיתוח תוכנה ואוטומציה

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

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

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

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

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

קודם כל, מה זה Top-down programming?

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

לשם כך, נכתוב תוכנית פשוטה שמטרתה לתקשר עם שרת ה-vcenter ולמחוק את כל המכונות אשר נמצאות בתיקייה מסוימת יותר מ-24 שעות. השפה שבה נכתוב תהיה פייתון.
כשאנחנו כותבים את התוכנה שלנו באמצעות Top-down approach אנחנו רוצים להתחיל מהתוכנית הראשית, משכבת האבסטרקציה העליונה ביותר. לכן נתחיל מפונקציית ה-main.

def delete_all_old_machines():
   old_machines = get_all_old_machines()
   delete_machines(old_machines)
if __name__ == '__main__':
 delete_all_old_machines()

ניתן לראות שפונקציית ה-main קוראת ל- delete_all_old_machines , אשר קוראת שתי פעולות בתוכה.

הפעולה מתחלקת ל-get_all_old_machines אשר מחזירה רשימה של כל המכונות שנוצרו לפני יותר מיום, ו-delete_machines אשר מקבלת רשימה של מכונות ומוחקת אותן.

אפשר לשים לב בקלות (באמצעות סביבת הפיתוח) שהפונקציות get_all_old_machines ו-delete_machines אינן ממומשות וזו בעצם הדרך בה נעבוד. עם שיטת Top down נתחיל מכתיבת הקוד ככותרות בשפה העסקית שלנו, ומשם נצלול ונכתוב את המימושים שכבה אחר שכבה - כאשר נרצה לספר את תוכן הפעולה כסיפור בכותרות.

לפיכך, השכבה הבאה בקוד שלנו תצטרך להראות כך:

def get_all_old_machines():
   all_machines = get_all_machines()
   return get_old_machines(all_machines)
def delete_machines(old_machines):
   for machine in old_machines:
       delete_machine(machine)

לאחר שמימשנו את השכבה הזו אנחנו צריכים לממש את התוכן שלה: get_all_old_machines, get_all_machines ו- delete_machines.

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

def get_all_machines():
   folder = vcenter.get_folder(MACHINES_FOLDER)
   return folder.childEntity
def get_old_machines(all_machines):
   return list(filter(lambda machine: was_created_today(machine), all_machines))
def delete_machine(machine):
   vcenter.terminate_vm(machine.name)
   
לפעולות הממומשות במחלקה vcenter לא נכנס כעת, אבל ניתן לראות בקטע הקוד האחרון פונקציית private אחת שאינה ממומשת - was_machine_created_today.

בשלב זה אני ממליץ לעצור ולנסות לחשוב - כיצד תראה הפונקציה הזו בגישת Top-down?

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


def was_created_today(machine: vim.VirtualMachine) -> bool:
   date = _get_date_from_name(machine.name)
   # return some implemetation that checks if date is today
   
def _get_date_from_name(name: str) -> datetime.date:
   # return some implementation that extracts date from name of machine

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

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


הקשר לאוטומציה

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

מה יוצא לנו מ Top-down programming?

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

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

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

לסיכום

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

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

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


מאת: תומר כהן, מומחה לפיתוח תוכנה ואוטומציה

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

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
בואו נעבוד ביחד
support@israelclouds.com
צרו קשר