Saturday, July 27, 2013

הערות/תיעוד - Comments

הערות בקוד, זה כלי מאוד מפתה. 

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

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

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

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

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

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

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

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

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

יש רק מספר מקומות בהם לדעתי חובה לרשום הערות:
1) תיעוד של INTERFACES
2) אם צריך להכניס todo לצורך refactor עתידי.


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


    public class Person
    {
        //Default Constructor
        public Person()
        {
        }

        // the Date of birth of the Person
        public DateTime DateOfBirth { get; set; }

        //  the first name of the Person
        public string FirstName { get; set; }

        // the last name of the Person
        public string LastName { get; set; }

        // the address of the Person
        public string Address { get; set; }

        //List of friends
        public List<Person> Friends { get; set; }

        // Returns the age of the Person
        public double Age
        {
            get
            {
                if (DateOfBirth == null ) return 0;
                return (DateTime.Now - DateOfBirth).TotalDays/365;
            }
        }
    }

השאלה היא פשוטה, למה לרשום הערות למשהו שהוא מראש מסביר את עצמו?

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

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

דוגמא נוספת להערות שהיום הם פשוט רעש אלו הערות ה"לוח שינויים".

פעם שלא היה ממש Source Control, למשל בסביבות ישנות של- C או NATURAL/Cobol,

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


הלוג הזה היה נראה משהו כמו:
/* Changes Log
 * =====================================================================
 * RK001 - 2009/7/1 - Bug Fix in Person Name
 * RK002 - 2009/8/1 - Refactor in Person
 * RK003 - 2010/1/1 - added method AddFriend(otherPerson)
 * RK004 - 2010/2/1 - added method UnFriend(perosn);
 * RK005 - 2010/3/1 - some bug fixes
 */

עם כל מיני קישקושים בקוד של מי ביצע (RK001)

ועל כל שינוי היינו צריכים לרוץ לתחילת הקובץ



 אם היום אתם עדיין מתעדים ככה, ותרו. הוא מיותר בסביבת קוד מנוהלת Source Controlled.








Monday, July 15, 2013

ואלו שמות - And those are the names...

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

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

var xmlResposeBuilder = new XMLResponseBuilder();
var xmlRequest = xmlRequestBuilder.Build(someObj);

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

דוגמאות טובות לשמות קלאסים הן:
ResponseParser, IdGenerator, StringInputValidator, NewsFinder
דוגמאות טובות לשמות מתודות הן:
GetName, SetName, IsDefault, Parse, Build, Delete, Add

ושהם קשורות לקלאס שלהם, הכוונה של מה המתודות עושה – מתבהרת.


אז עבדנו לפי הכללים והחלטנו להתחכם,
בעברי הרחוק, כתבתי פונקציה לסביבת unix שהורגת את כל התהליכים ללא אב, תהליכים אלו נקראים לעיתים "זומבים", "יתומים" או "רוחות רפאים" (Zombie/Orphan/Ghost) ומתכנתים בסביבה הזאת יודעים על מה מדובר כאשר השמות הללו עולים לאוויר.
הבעיה שקצת התחכמתי, היה לי קצת משעמם באותו יום ושמעתי אלבום של מטאליקה, תוך כדי עבודה כמובן. קראתי לפונקציה הזאת KillEmAll, ממש נחתה עלי השראה.
אחרי לא מעט זמן, מתכנת אחר היה צריך לתחזק משהו באיזור.
מקריאה ראשונית לא ממש הבין מה הפונקציה הזאת עושה והיה צריך להיכנס אליה.
זה לא מצב תקין, למרות שבזמנו חשבתי שזה מצחיק ונחמד ויכניס טיפה עניין לקוד.
שם טוב לפונקציה היה KillAllOrphanProcesses או KillAllZombieProcesses והוא לא היה צריך לקרוא לי או להיכנס לקוד.... או לקלל אותי בשלב מסוים, הוא לא צחק.


באופן עקרוני, שמות ארוכים מדי לא מומלצים כי הם נוטים לבלבל וליצור חוסר בהירות, ועדיף שמות קצרים ככל האפשר שמתארים את טווח הפעולה של האובייקט או הפונקציה.
לדוגמא, קרה לי מקרה לא מזמן באחד הפרויקטים שהיו כמה אובייקטים משותפים שהקידומת שלהם הייתה בסגנון של BankAccountData ולאחר מכן כל מיני שמות פעולה, כמו Parser, Builder או Mapper. הבעיה שהשמות היו קצת ארוכים אז הציעו לקצר את הקידומת ל-BAD,
כמובן שזה לא עבר, הבעיה עם הקיצורים הללו הם שהם נוטים להיות מובנים למי שכתב אותם, ולאחר מכן שוקעים בתהום של הלא נודע, אנשים פוחדים לשנות וככה נהיים תקועים עם שם לא מוצלח במיוחד, שגם קצת קשה לחפש.



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

Wednesday, July 10, 2013

שמות נכונים למשתנים

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

לדוגמא, מתכנתים רבים אוהבים לקצר שמות ולאגד אותם בראשי תיבות שלעיתים מרמזים לאנשים שונים משמעות שונה.
למשל
String awk;
Strung ls

בפלטפורמות unix למינהם השמות הללו מבטאים שפת סקריפטים ורשימת קבצים, בהתאמה.
אפילו אם נראה ש
awk זה קיצור טוב ל-acknowledge ו-ls אולי נשמע כמו קיצור טוב ל-lowSpecs המנעו מכך, זה יכול להוליך שולל.
או מקרה אחר ואולי אפילו קצת יותר נפוץ שנתקלתי בו לא מעט זה להוסיף את המימוש לשם המשתנה. גם אני חטאתי בזה בעברי אבל מומלץ מאוד להמנע מזה גם לצורך שינויי מימוש עתידיים וגם לצורך בהירות.
למשל, מקרה מאוד נפוץ הוא עם list. אנשים רבים משתמשים בשם המשתנה עם סיומת LIST.
מומלץ להמנע מכך.
לדוגמא, יש לך רשימת חברים.
FriendsList אבל בעתיד אולי זאת לא תיהיה רשימה, אתה תחטא לאמת ולכוונה המקורית של המשורר שפשוט התכוון לחברים, Friends
כעת אתם יודעים קצת על שמות אבל לפעמים אתם גם רוצים לדבר עליהם עם הצוות,
להעביר רעיונות ואולי אפילו לקבל ביקורת בונה, אתם צריכים לתת שמות שניתן לבטא.
למשל שם כמו:
 imgEnc הוא שם פחות טוב מ-ImageEncoder.
רק משום קל יותר לבטא אותו.

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

Wednesday, July 3, 2013

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

חלק מאוד חשוב בקוד ובקוד נקי הם השמות של אבני הבניין שמשמים לבניית התוכנה.
אנחנו משתמשים בשמות לכל דבר, גם בחיים, כדי לסווג דברים, וגם כדי לספר את הסיפור.
קוד תוכנה עם שמות נכונים יכול לספר את הסיפור של התוכנה גם ללא להכנס לתיעוד מעיק.
לכן, כצעד ראשון רצוי להשתמש בשמות שמגלים את המשמעות של האובייקט.
לתת שמות טובים לוקח זמן, לפעמים מתעכבים על הנושא קצת וזה מרגיש שזה "מבזבז" זמן, אבל בעתיד שמישהו, אפילו מי שכתב את הקוד, בא לתחזק את הקוד הרבה מאוד זמן פיענוח נחסך.
לדוגמא,
Int x; // current number of people in the room

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

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

        private List<int> GetThem(int p1, int p2, int p3)
        {
            var theList = new List<int>();

            for (int i = p2; i < p3; i++)
                if (i % p1 == 0)
                    theList.Add(i);

            return theList;
        }
הקוד מעלה הרבה שאלות, וקצב ה-WTF/Minute גבוה, כזכור, זהו המדד היחידי לקוד נקי.
האם אתם מבינים מקריאה ראשונית זריזה מה היא עושה?
מה זה theList? מה היא מכילה?
מה זה p1,2,3 מבלי להכנס לקוד?
מה המשמעות של ההשוואה ל-0?
ומי זאת הפונקציה GetThem בכלל למה זה עושה GET?
עכשיו תקראו את הפונקציה הבאה, כל השינוי היה שינויי שמות שמתאימים ורומזים מה היא עושה





        private List<int> GetAllNumbersDividedByDividerInRange(int divider, int lowerRange, int upperRange)
        {
            const int NO_REMAINDER = 0;
            var numbers = new List<int>();

            for (int number = lowerRange; number < upperRange; number++)
                if (number % divider == NO_REMAINDER)
                    numbers.Add(number);

            return numbers;
        }

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

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