15 טעויות של מפתחים מתחילים

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

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

תמונה: flickr, cc-by, hackNY

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

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

איתחול משתנים

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

אבל למה אין פלט?

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

משפט בקרה הכולל בתנאי פעולה לביצוע

נתבונן בקוד הבא:

int a=7,b=3;

if (a<b || a++>=b)

System.out.println(a);

במקרה זה מדובר באופרטור הלוגי או כך שגם ברגע שהתנאי הראשון אינו נכון עדיין מתבצעת בכל מקרה בדיקה גם של התנאי השני. כתוצאה מכך a יקודם (לפני ההשוואה שלו מול b) והפלט יהיה 8 (עדות לכך ש- a גדל ב- 1).

ומה יהיה הפלט כאן ?

if (a<b && a++<=b)

System.out.println(a);

System.out.println(a);

 

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

המרה (casting)

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

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

int x= (int)Math.random()*10+1;

System.out.println(x);

הפונקציה random ב- java מחזירה מספר עשרוני בין 0 (כולל) ל- 1 (לא כולל).

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

int x= (int)(Math.random()*10)+1;

NaN ו- Infinity

מדובר על שני קבועים כאשר הראשון מייצג תוצאה שאינה מוגדרת (NaN – not a number) והשני מייצג אינסוף. סטודנטים רבים אינם מכירים את הקבועים כיוון שאינם נתקלים בתוכניות המשתמשות בהם באופן ישיר.

מצורף הפלט שיוצג ליד כל פעולת הדפסה למסך:

float x=0.0f,y=0.0f;

System.out.println(x/y); // NaN

float num=-2.0f;

System.out.println(Math.sqrt(num)); // NaN

float m=1.0f,n=0.0f;

System.out.println(m/n); // Infinity

כאשר מדובר במספרים שלמים אזי ניסיון החלוקה ב- 0 יגרום לזריקת חריגה מסוג ArithmeticException:

int a=0,b=0;

System.out.println(a/b); // ArithmeticException

נקודה-פסיק (;) בסוף לולאה

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

for (i=1;i<=10;i++);

System.out.println(i);       הוראה זו לא מתבצעת בתוך הלולאה

כדאי לשים לב כי הפלט לקוד המוצג הוא 11. זהו הערך של i בעת היציאה מן הלולאה.

במידה ומעוניינים לבצע השהייה ניתן להשתמש בפונקציה sleep של מחלקת Thread ולא בלולאה.

לולאה שאינה מתבצעת

בעיה זו נגרמת בשל תנאי ראשוני שאינו נכון. זה נכון עבור הלולאות for ו- while.

* לעיתים הלולאה מתבצעת אך פשוט שכחנו לשבץ בתוכנה את פונקצית הפלט ואז נחשוב בטעות שהלולאה אינה מתבצעת.

דוגמא ללולאה שאינה מתבצעת בגלל תנאי התחלתי שערכו הוא false:

int x=0;

while (x!=0){

      System.out.println(x–);

      …

      …

}

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

לולאה אינסופית

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

for(i=1;i<10;i–)

System.out.println(i);

* הבעיות הגורמות לולאה אינסופית וגם ללולאה שאינה מתבצעת הן רלוונטיות עבור לולאת for כמו גם עבור לולאת while וקורות לא מעט כתוצאה מ-“העתק הדבק” של קוד לולאה אחר בתוכנית ושינוי של חלק מהפרמטרים המרכיבים את הלולאה.

גישה למאפיין או לפונקציה מאובייקט שלא נוצר

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

ב- java ניתן להצהיר על מערך בצורה הבאה:

int arr[];

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

ליצירת המערך יש להשתמש במילה השמורה new:

arr=new int[7];

חריגה מגבולות המערך

בעיה זו נגרמת כתוצאה מניסיון גישה לתא במערך שלא קיים. יש לשים לב כי במערך בגודל SIZE האינדקס האחרון הוא אינו SIZE אלא SIZE-1. חריגה מגבולות המערך תציג בשפת התכנות java את החריגה ArrayIndexOutOfBoundsException.

לדוגמא:

int arr[]=new int [7]       הקצאת המערך

for (i=0;i<=arr.length;i++)      חריגה כאשר מגיעים לאינדקס שלא קיים

arr[i]=i;       

תכנון לא נכון

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

תכנות לא מודולרי

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

סביבות פיתוח/ מהדרים שונים

במוסדות לימוד שונים נלמדות שפות התכנות בסביבות פיתוח שונות עם מהדרים (compiler) שונים. המעבר מסביבה לסביבה יכול לגרום להודעות שגיאה בתוכניות שכבר עבדו. מומלץ לעבוד עם סביבת הפיתוח הנפוצה בתעשייה (במידת האפשר) ובעלת הגרסא העדכנית ביותר.

רגע…איפה הייתי?

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

מה זה הפלט הזה?

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

אבל אני לא זוכר מה עשיתי פה…

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

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

אבל התוכנית עובדת…

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

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

int i=1;

for (;;){

       System.out.println(i);

       if (i==10)

              break;

       i++;

}

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

כתב אורח

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

הגב

15 תגובות על "15 טעויות של מפתחים מתחילים"

avatar
Photo and Image Files
 
 
 
Audio and Video Files
 
 
 
Other File Types
 
 
 
סידור לפי:   חדש | ישן | הכי מדורגים
Doron Sever
Guest

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

Ron Sneh
Guest

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

Doron Sever
Guest

שלילי חזק. קודם מתבצעת ההשוואה ורק אחר כך A גדל ב 1. זה בדיוק ההבדל אם ה "++" בא לפני המשתנה או אחרי המשתנה. לפני המשתנה זה אומר "קודם קדם את המשתנה ואז בצע את הפעולה" ואחרי המשתנה זה אומר "קודם בצע את ההשואה ורק אחך כך תעלה את המשתנה". זה אם כבר טעות של מפתחים מתחילים :)

Doron Sever
Guest

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

הבעייה הנפוצה של מתכנתים מתחילים
Guest
הבעייה הנפוצה של מתכנתים מתחילים

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

החינוך הטכנולוגי בארץ בבעייה

משה סייג
Guest
מספר תיקונים: א. בביטוי (if (a=b a יקודם אחרי ההשוואה שלו מול b לא לפניה ב. בסעיף על “נקודה-פסיק (;) בסוף לולאה” חסרה נקודה-פסיק (;) בסוף הלולאה… בכל מקרה, תמיד עדיף להקיף את הבלוק של הלולאה בסוגריים מסולסלים ולא לסמוך על ה-; ג. ההמרה ()int)Math.random) ממירה מ-double (ש”תופס” 64 סיביות בג’אווה) ל-int (ש”תופס” 32) ולא “מטיפוס בעל מספר סיביות קטן בזיכרון לאחד שתופס יותר מקום.” הבעיה כאן היא המרה ממספר לא שלם (מספר בייצוג נקודה צפה) למספר שלם. ד. לגבי תיעוד: חוסר תיעוד הוא אכן בעייתי, אך גם עודף תיעוד. תיעוד רב, במיוחד כזה שלא באמת מוסיף מידע, יכול אפילו… Read more »
רועי לטקה
Admin

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

אודי
Guest

יש מצב שאם תחליף שפת תיכנות למשהו פחות הזוי, זה יפתור את רוב הבעיות ….
אם אתה מנסה בכוונה לבלבל את המתכנתים האחרים עם שורה לא טריביאלית כמו if (a=b) אל תתפלא שהם עושים טעיות… איזה סיבה יש לא לשים a++ אחרי שאתה מבצע את הבדיקה כדי למנוע בלבול?

SGS
Guest

זה היה מעניין. תודה!
זרקת אותי אחורה כמה שנים טובות לימי התיכון העליזים כשעוד למדתי תכנות… :)

Noa
Guest

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

int a=1, b=3, c=1, sum=0, avg;

sum+=(a+b+c);

avg=sum/3;

System.out.println(avg);

התוצאה שנקבל היא 1, בעוד שהתוצאה הרצויה היא 1.666666666666.

אלון
Guest

כתבה מעולה, אשמח לעוד מאותו הסגנון

Oren Yanay
Guest
הייתי מוסיף את הדברים הבאים: כתיבה של משתנים/אובייקטים בעלי משמעות ולא סתם i למשל אלא indexOfArray, תיעוד מסודר של הפרויקט בצורה מובנית (יש פרויקט נפלא בשם SandCastle), הפרדה לשכבות של מודולים בפרויקט, ניהול זיכרון נכון היכן שצריך, ניצול משאבי מערכת בצורה חכמה יותר, לעבוד נכון עם רמות גישה למשתנים/אובייקטים וכו'בדיקות unit testing תוך כדי פיתוח, אלוגוריתמים כדאי לכתוב קודם בפסאוקוד כדי להבין טוב יותר איך יהיה המימוש. להוריד התלהבות מהוספת מאפיינים נוספים שלא נדרשים לשלב הפיתוח הנוכחי, להיחשף לנושאים חדשים כל הזמן ולא להישאר מקובעים במימושים מיושנים למשל: שליפת נתונים מ-DB בצורה ישנה של sqlconnection, sqlcommand אלא אפשר ואף רצוי… Read more »
דוד סרגוביץ'
Guest

הערה קטנה:
כתבת בחלק של NaN ו- Infinity לגבי שורש של מינוס שזה נחשב NaN כלומר מספר לא מוגדר.
אז בהחלט שאתה צודק שבמצב המתואר יוחזר 'מספר לא מוגדר'!
אבל רציתי להעיר שזה לא אמת מוחלטת כיוון שיש את המושג Imaginary number שהוא התוצאה של שורש – , כמו כן יש Imaginary number (מספר מורכב) שמורכבים ממספר שלם ומספר דמיוני.
ופעם עשינו איזה תרגיל שמקבל מספר שהוא מינוס ומחזיר אובייקט מסוג מספר מורכב שמכיר לא השורש המדומיין שלו.

דוד סרגוביץ'
Guest

יש עוד בעיה שנתקלתי בה המון כשהתחלתי ללמוד תכנות.
ואני ממש מתפלא שלא שמת את זה בכתבה!
השמה במקום השוואה.. (if(a=b ….

Udi Malka
Guest

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

wpDiscuz

תגיות לכתבה: