How to avoid spaghetti code in javascript

25 באוגוסט 2010

תגיות: , ,
תגובה אחת


javascript מאוד פופולרית באתרי web. כל הדפדפנים השכיחים תומכים בjavascript. כמעט כל אתר משתמש בjavascript לבצע פעולות UI בדפדפן. אז מדוע מעטים האנשים שחושבים שהיא שפה נחמדה לתכנות?


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


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



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

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


רגע לפני – javascript = שפה דינמית?


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

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

אומנם היום אני חכם גדול, אבל עד לפני מספר חודשים אפשר להגיד שהייתי סקריפטופוב רציני. אני מתכנת 4 שנים בweb וכתבתי לא מעט ב-JS אבל תמיד ניסיתי למזער את השימוש עד כמה שאפשר. לפני זמן מה קיבלתי עבודה ש"אילצה" אותי לפתח מערכת בjs עם ארכיטקטורה מורכבת ודרישות למודולריות, טיפול נכון בשגיאות (מישהו אמר logging ב-javascript?), ניהול אירועים, עמידה בעומסים ועוד הרבה מטעמים נוספים. בהתחלה נבהלתי כל-כך שסירבתי לקבל את העבודה. אך בסוף התרצתי ותוך כדי עבודה גיליתי שיש לא מעט כלים שהרוב לא מכירים והם יכולים להיות מאוד יעילים בתכנות בJS. ואלו הנושאים שארחיב עליהם במאמרים הבאים:


  • שימוש נכון באובייקטים בקבצי JS (מאמר זה)
  • מספר דרכים ליצירת "מחלקות"
  • הגדרת scoping באפליקציה (ובפרט private members).
  • שימוש ב-Regular Expression.
  • טיפול נכון ומניעה מקדימה של  שגיאות.
  • עבודה עם namespaces.
  • יצירת מנגנון באירועים (events).
  • יכולות מתקדמות בשפה דינמית.

חזרה לנושא – כיצד מייצרים אובייקט


האם אתם מתכנתי web? אם כן אז כנראה כתבתם בשפת js. אני מניח שיש לכם בממוצע 10-40 פונקציות בכל אתר. ומשתנים (var) גלובלים – בטח גם כאלה יש בקוד? אם התשובה היא כן אז יש לי שאלה אליכם – האם כל הפונקציות מוגדרות בעמוד או בקובץ נפרד? האם הקפדתם לקבץ משתנים ופונקציות ליחידות עצמאיות (אובייקטים)? תסתכלו על הקובץ הבא (לקוח מאתר מוכר בארץ) – קובץ זה ממחיש כתיבה שכיחה (ומבלוגנת) של קוד js באתרים.

קובץ לדוגמא עם קוד JS מבולגן

בסוף המאמר הוספתי קובץ שמראה קוד לדוגמא מחולק לאובייקטים.

קיבוץ פונקציות ומשתנים לאובייקטים


הגדרת האובייקט מבוססת על פורמט שנקרא JSON, או בשמו המלא Javascript Object Notation.

הגדרה פשוטה

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

var oUser = { sName : 'eran', nAge : 30, bMarried : true };
// alternative syntax (the same meaning as the above)
var oUser = { 'sName' : 'eran', 'nAge' : 30, 'bMarried' : true };

כאשר:


  • בJSON האובייקט עטוף בסוגריים מסולסלים.
  • כל מאפיין מורכב משם מאפיין וערך כאשר הם מופרדים באמצעות הסימן נקודותיים.
  • כל זוג מאפיינים מופרד באמצעות הסימן פסיק.
  • את שם המאפיין ניתן להגדיר בלי/עם מרכאות – המשמעות זהה.
  • סוג המאפיין נגזר מהערך שהושם לתוכו (לדוגמא bMarried הוא ערך בוליאני ו-nName הוא מחרוזת).
  • כתיבת אובייקט בפורמט json מגדירה את מבנה האובייקט ובsyntax הנ"ל האובייקט מאותחל אוטומטית לתוך המשתנה (בדוגמא למעלה לתוך oUser). לא ניתן ליצור אובייקטים נוספים באמצעות שימוש במילה השמורה new.

דוגמא לעבודה עם האובייקט:

// change user name
oUser.sName = "liron";

הגדרת מערך של אובייקטים

בדוגמא זו נראה כיצד מגדירים אובייקט שהוא מערך של אובייקטים:

var arrColors = [ 'red', 'green', 'yellow' ];
var arrCar = [ {sManufacturer : 'toyota', sModel : 'corolla'}, { sManufacturer : 'subaro', sModel :'impreza'}];

.

כאשר:


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

דוגמא לעבודה עם אחד המערכים:

if (arrCar[0].sManufacturer == 'toyota')
{
// do something
}

הגדרה מורכבת

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

var oUser = {
sName : 'eran',
arrChildren : [],
oStatus : {bIsHappy : true, bIsFriendly : true},
introduce : function()
{
alert(this.sName);
}
};

 


כאשר:



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

דוגמא לשימוש באובייקט:


// add child to array
oUser.arrChildren.push({sName : 'gali'});

// access inner object
oUser.oStatus.bIsHappy = true;

// execute object function
oUser.introduce();

הגדרת אובייקט ברמת האפליקציה


יש 2 דרכים להגדיר את האובייקט ברמת האפליקציה.

דרך 1 – הגדרה עקיפה (implicit)

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

var oUser = { sName : 'eran', nAge : 30, bMarried : true };

דרך 2 – הגדרה ישירה (explicit)

בדרך זו נעבוד עם האובייקט window ונוסיף אליו את האובייקט שיצרנו.

window["oUser"] = { sName : 'eran', nAge : 30, bMarried : true };

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

סיכום


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


קובץ לדוגמא עם קוד JS מחולק נכון (שימוש בכלים שכתובים במאמר זה) 


הערות לסיום


האם יש הבדל בין קוד שנכתב בקובץ js חיצוני לקוד שנכתב בתוך העמוד?


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


כיצד לקרוא לפונקציה A מתוך פונקציה B כששתי הפונקציות שייכות לאובייקט


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


 


var oSearch = {
performSearch : function(sQuery)
{
// alternative 1 – using the reserved word 'this'
this.executeSearch(sQuery,false);
// alternative 2 – calling the application object 'oSearch' (defined in line 1)
oSearch.executeSearch(sQuery,false);
},
executeSearch : function(sQuery, bIsAjax)
{
// handle the execute request
}
}


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


 


ערן.

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *

תגובה אחת

  1. Shlomo26 באוגוסט 2010 ב 8:20

    מאמר נהדר, המשך כך

    הגב