ניימינג – 10 טיפים שיעזרו לכם לעשות את זה נכון

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

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

Robert c. Martin

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

1. שמות בעלי משמעות

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

2. בלי מידע נלווה על טיפוס

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

3. הבדלים משמעותיים בין שמות

נניח שיש לנו שני משתנים ששומרים נתונים על שני לקוחות: הלקוח הטוב ביותר והלקוח הגרוע ביותר. די ברור שלא נרצה שאחד מהם יהיה שמור במשתנה בשם customer והשני במשתנה בשם cust, שכן ההבדל בין שני שמות המשתנים לא יבטא את ההבדל בין המשמעות שלהם. לכן גם לא נראה לקרוא לאחד מהם customer1 ולשני customer2. בחירה טובה הרבה יותר של השמות תהיה bestCustomer עבור הלקוח הטוב ביותר ו-worstCustomer עבור הלקוח הגרוע ביותר.

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

4. ללא קיצורים וראשי תיבות

שמות צריכים להיות ברורים, ולכן נימנע משימוש בראשי תיבות וקיצורים שבמקרה הטוב מכבידים על הקריאה ובמקרה הרע הופכים אותה לבלתי אפשרית. גם אם נמאס לנו לכתוב fileReader, שימוש ב-fr תגרום לכך שכשנקרא את הקוד לא נוכל להבין משם המשתנה עצמו מה הוא מכיל ונצטרך להקדיש תשומת לב נוספת לקונטקסט. בדומה לכך, למשתנה שמכיל הודעה נקרא בפשטות message, למרות שנראה שיש מתכנתים שחושבים שאותיות ניקוד הן משאב נדיר ומעדיפים msg.
עם זאת, יישום הכלל הזה באופן עיוור יכולה גם היא לגרום לקוד להיות לא קריא. ראשי תיבות מקובלים ומוכרים יהיו עדיפים על המקור שלפעמים לא מוכר כלל. ברור שנשתמש בשם SQL ולא ב-structuredQueryLanguage. גם במקרים קיצוניים פחות נעדיף לכתוב MSE ולא meanSquaredError, או LAX ולא losAngelesInternationalAirport. כאשר נפריד תמונה לערוצי הצבע שלה נוכל להשתמש במוסכמה לפיה ערוצי הצבע הם r, g, b ואין צורך לקרוא להם בפירוש red, green, blue.

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

5. עקביות

נניח שאנו כותבים פונקציה שמאחזרת מהשרת את כל הפגישות של המשתמש. אנחנו יכולים לקרוא לה fetchMeetings או getMeetings, ושתי האפשרויות נראות סבירות באותה המידה. אבל אם החלטנו לקרוא להfetchMeetings, כשנכתוב פונקציה אחרת שמוציאה את אנשי הקשר לא כדאי שנקרא לה getContacts. ההחלטה שעשינו לגבי הבחירה בין fetch לבין get צריכה להיות עקבית, ומי שקורא את הקוד אמור להבין שהחלטנו לקרוא לתשאול השרת fetch, ושיהיה ברור לו שהוא יכול לצפות לקיומן של הפונקציות fetchMeetings ו-fetchContacts. העקביות במתן השמות חשובה במקרה הזה הרבה יותר מעצם ההחלטה על השם, שהיא עניין של טעם.

6. התייחסות להקשר

שם של משתנה או פונקציה עשוי להיות ברור בהקשר אחד אבל מעורפל בהקשר אחר. למשל, במקרה של אובייקט שמחזיק כתובת של משתמש די ברור לקורא ששדה בשם state מחזיק את המדינה, ואם נקרא לו addressState רק נוסיף לסרבול כאשר ההתייחסות אליו מחוץ לאובייקט תהיה address.addressState. לעומת זאת, באובייקט שמחזיק מידע על משתמשים, שדה בשם state יכול לתאר מצב של דברים רבים, ולכן המשמעות שלו מעורפלת והוא לא אומר למי שקורא את הקוד איזה מצב הוא מחזיק. במקרה כזה בו לא ברור מההקשר מה משמעות השם נעדיף שמות מפורשים ונחליף את state בשמות משתנים כמו checkinStatus או batteryState. באופן דומה, משמעות המשתנה daysFromCreation ברורה כאשר המשתנה הוא שדה של האובייקט File, אך בפונקציה דומה שנמצאת מחוץ לאובייקט נעדיף לציין בפירוש daysFromFileCreation.

7. הדגשת ערך בוליאני

נניח שיש לנו משתנה או פונקציה שאומרים לנו האם לקוח מסוים הוא לקוח מועדף. אם נקרא לפונקציה preferredCustomer יהיה אפשר להבין בטעות כי היא מחזירה את שם הלקוח המועדף, או אולי מעדכנת את הסטטוס שלו להיות לקוח מועדף. במקרים כאלו, בהם הערך השמור במשתנה או המוחזר מהפונקציה הוא ערך בוליאני, כדאי שהעובדה הזאת תשתקף בשם. ולכן נעדיף שם כמו isPreferredCustomer.
באופן דומה נוסיף תחיליות נוספות שירמזו על כך שהערך המוחזר הוא בוליאני: שמות הפונקציות isFileOpen ,canJoin ,areReadyToRead הם חד משמעיים וברורים הרבה יותר בזכות התחילית שמבהירה את הערך הבוליאני שלהן.

8. מה עושים, לא איך

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

speed bump

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

9. הימנעות מכפל משמעות

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

10. שמות עצם ופעלים

באופן כללי למחלקות ניתן שם שהוא שם עצם ולפונקציות שם שהוא פועל. למחלקות נקרא User ,File ,Test ,Student ,Profile וכדומה. לפונקציות נקראfetchUser ,runTest ,updateStudent ,deleteProfile וכדומה, אך בפונקציות פשוטות אפשר לוותר על הפועל. למשל, לפונקציה שמחזירה את מספר המשתמשים במערכת נעדיף לקרוא numberOfUsers, ורק במקרה שלצורך החישוב נצטרך לעשות פעולת שאילתה נקרא לה getNumberOfUsers כדי להבהיר את העלות שנלווית לקריאה לפונקציה.

לסיכום

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

מקור תמונות:
Robert C. Martin – Wikipedia

16 תגובות בנושא “ניימינג – 10 טיפים שיעזרו לכם לעשות את זה נכון

הוסיפו את שלכם

  1. כמה הערות:
    1. לגבי הקשר, כשעוסקים במבנים/מחלקות, האובייקט נותן את ההקשר. לכן שדה בשם userStatus הוא מסורבל, אם השימוש יהיה בסופו של דבר user.userStatus. במקרה כזה, user.status יהיה יותר ברור ופחות מסורבל.
    2. שמות ארוכים מדי עלולים לסרבל את הקוד ולהסתיר את הלוגיקה. קשה להבין ביטוי מורכב אם הוא תופס שורה בורך שני מסכים.
    3. יש להתייחס גם לתרבות ולקהילות הפיתוח. לדוגמא, בgo נהוג דוקא לכתוב בקיצורים וראשי תיבות.

    1. 1. נכון מאוד. הוספנו את הנקודה הזאת. תודה!
      2. ברור ששמות ארוכים מדי הן צרה צרורה. כפי שכתבנו, אין תחליף לשכל ישר.
      3. נקודה נכונה, אלא שגם אם ב-go נוהגים לכתוב בקיצורים וראשי תיבות, לטעמנו עדיין זה גורר עומס קוגניטיבי מיותר בקריאת הקוד.

      תודה על הפידבק!

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

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

  3. יפה, יש גם התייחסות בשפות שונות למשתנים גלובלים, גם שימושי.

  4. תודה על הכתבה המועילה!

    הייתי רוצה לציין שתי הערות / תוספות:

    א. נראה שבעברית ניימינג נקרא שִׁיוּם (https://he.wikipedia.org/wiki/%D7%A9%D7%99%D7%95%D7%9D)
    ב. אני זוכר (מהסרטונים, לא מהספר) כלל שימושי ש Uncle Bob הציע: משתנים ב scope מצומצם (נניח: פונקציה) – יכולים להיות קצרים (אפילו ראשי תיבות) – כי קל להבין את משמעותם מההקשר, משתנים ב scope רחב (נניח: שמות פונקציות או משתנים ציבוריים) – עדיף להרבות במילים ולתת להם שם ארוך.

  5. כבר העירו על deleteProfile ו-updateUser שאפשר להשאיר רק את הפועל, אם הפונקציה נמצאת במחלקת Profile או User, בהתאמה, אז נשאר לי רק להעיר על שגיאת כתיב – nofity, התחלפו שם האותיות

נשמח לשמוע מה אתם חושבים על המאמר

ערכת עיצוב: Baskerville 2 של Anders Noren.

למעלה ↑

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

נרשמת בהצלח. בתודה, הגמל.

שגיאה בלתי צפויה, אנא נסה שוב.

camelCase will use the information you provide on this form to be in touch with you and to provide updates and marketing.