במאמר הקודם קיבלנו הצצה לעולם הפרדיגמות, ועכשיו נלמד על השפעת העבר על ההווה – ואיך זה יכול לשפר את התפיסה שלנו בנושא.
לא משנה באיזו שפה / סגנון תכנותי אנחנו עובדים היום -- הפרדיגמות המוקדמות הן הבסיס לעבודה שלנו.
יהיה קשה לדמיין אפילו - עבודה בלי חלק הפיתוחים שהן הוסיפו.
לפני הפרדיגמה המובנה (Structured), תוכנה הייתה "ערמה של קוד" - באופן המילולי ביותר שניתן לחשוב עליו. התכנה הייתה קרובה יותר לצורת ההבנה של המעבד, מאשר לצורה בה לבני-אדם נוח לנסח דברים.
למשל: המצאת ה"בלוק". בתחילה - לא היה דבר כזה.
למשל: משפטי if היו יכולים באותה התקופה להפעיל רק ביטוי (expression) יחיד. לו היו 4 פעולות שלא היינו רוצים שיפעלו אם x הוא שלילי - היה עלינו לכתוב 4 משפטי if שכל אחד חוזר על הבדיקה "האם x שלילי" ואז מבצע עוד פעולה.
אפשר לעבוד ככה - אם התוכנה קטנה. אני מניח שכולנו נתקלנו פה ושם בעבודה סיזיפית בקוד - שהיא סבירה בקנה מידה מסוים.
נסו לדמיין אותנו עובדים כך היום - עד כמה מסורבל וקשה זה!
דוגמה אחרת היא משפטי ה-GOTO המפורסמים. תוכנה היא לא תמיד רצף סדרתי של פעולות: לפעמים רוצים לקחת דרך אחרת. למעבר יש יכולת לעשות מעבר ולהריץ קוד ממקום אחר בזיכרון, התרגום הישיר של היכולת הזו היא פקודת GOTO (או JMP - אם אתם חושבים באסמבלי). יישום ראשוני של GOTO היה מעין תחליף פרימיטיבי לפונקציות: לך לשורה 300, ואז שורה 306 מחזירה אותנו לשורה 28 ממנה באנו. שימושים נוספים היו טיפול בשגיאות וניקוי זיכרון - מה שבשפות מודרניות כבר זוכה לטיפול ראוי יותר.
בגרסאות הראשונות של GOTO (כך מספרים), הוספת שורה בתחילת התוכנית - אלצה את המתכנת לעבור על כל הקוד ולעדכן את מספרי השורות בכל משפטי ה-GOTO - כי הם פשוט השתנו. רק אח"כ הוסיפו את ה-Labels או מספרי שורות ברמת השפה.
סיזיפי? סיזיפי אך אפשרי? - כך נראתה החלוציות בתחום התוכנה...
העיקרון המרכזי של התכנות המובנה הוא לקבע סדר וכללים ברורים - איך "קופצים" בין שורות בתוך התוכנה. במקום ציון השורה (או Label שמייצג אותה) - יש הפשטות כגון לולאה, בלוקים, רקורסיה, או Sub-routines (שזו ההתחלה של פונקציות. עדיין בלי פרמטרים וערך החזרה ברורים - אלא על בסיס משתנים גלובליים).
הגישה הוכיחה את עצמה דיי מהר כמועילה עד מעולה - והפכה להיות הסטנדרט הבלתי-מעורער בעולם התוכנה.
ארצה להדגיש שני עקרונות שצצו מהגישה הזו ושווים דיון, גם ממרום שנת 2019:
הרעיון אומר כך: כאשר אנו נותנים שם (name binding) למשתנה, פונקציה, קבוע, וכו' - נאפשר להגביל את מרחב הקוד שיהיה חשוף לשם הזה.
בתחילה היה קיים רק המרחב הגלובלי, אך בשל התנגשויות / טעויות שנבעו מכך - החלו להגדיר scopes מצומצמים יותר.
בפשט העיקרון אומר: אם אני לא זקוק למשתנה מחוץ ל scope מסוים (למשל: בלוק) - אזי יש להגדיר אותו בתוך ה-scope, ולא לא להעמיס על שאר הקוד במערכת - בהיכרות אפשרית איתו.
בצורה כוללנית יותר, הרעיון אומר שננסה לצמצם את ההיכרויות במערכת - למינימום האפשרי.
כל אלמנט שאנו מגדירים (משתנה, מחלקה, וכו') - נגדיר ב-scope המצומצם ביותר שאפשר.
למשל: אם יש Enum שזקוקים לו רק במחלקה מסוימת - סגרו אותו ב-scope של המחלקה - ואל תחשפו אותו לשאר העולם.
היום, בעולם ה-IDE המתקדם - המשמעות המיידית של scoping היא מה יציע לנו ה-IDE ב-Auto-complete. אם נגדיר הכל ב-Scope הגלובלי - כל הקלדה תציע לנו את כל מה שיש.
אם נקפיד על הגדרת ישויות ב-scopes מצומצמים ככל הניתן - הצעות ה-Auto-complete יהפכו לממוקדות, והרבה יותר רלוונטיות.
שנים רבות אחרי שהוגדר, העיקרון הזה עדיין לא תמיד מופנם ומקובל. עדיין אנשים מגדירים אלמנטים ב-scopes רחבים מהנדרש מחוסר מודעות, או מתוך מחשבה שזו "הכנה למזגן": אולי מישהו יצטרך את זה בעתיד? "למה לא לחסוך ולשים אותו זמין - כבר עכשיו"
מתלבטים מה עדיף? אני לא.
שווה לציין ש-Scoping הוא צעד ראשון בכיוון של Information hiding ו-Encapsulation. שוב ושוב עלו, לאורך ההיסטוריה - רעיונות בכיוון הזה.
בכדי להגדיר מהו מבנה צפוי ומנוהל של רצף הרצת התוכנה, ולהפסיק "לקפוץ לשורה אחרת בקוד", הגדירו בפרדיגמת התכנות המובנה את "עיקרון היציאה האחידה" (מפונקציה). שם נוסף:"Single Entry, Single Exit". לכל בלוק קוד (=פונקציה) צריך להיות רק מקום אחד להיכנס דרכו - ורק מקום אחד שניתן לצאת ממנו.
העיקרון הזה הוא מובנה בשפות שאנו עובדים איתן - עם כמה חריגות. קשה מאוד להסתדר ב-100% מהמקרים עם Single Exit יחיד.
נסו לרגע לחשוב: האם אתם מצליחים לחשוב היכן בשפה שלכם מפרים את העיקרון?
הנה דוגמאות עיקריות:
• Exceptions שנזרקים - הם לא Single Exit.
o continue או break בלולאה ל Label (בג'אווה, למשל) - הם לא Single Exit.
• אפשר גם לטעון ש-break ו-continue באופן כללי - הם לא Single Exit, לא ברמת הבלוק.
• בשפות פונקציונליות - לעתים אין להם מקבילה.
• coroutines (על עוד לא ממתינים באותו הבלוק לסיומם) - הם גם לא Single Exit. הפעלנו קוד - שרק בונה-עולם יודע מתי בדיוק הוא יסתיים.
• אחרון וחביב: יש גם מי שרואים בקריאת return מתוך גוף הפונקציה - הפרה של עקרון ה-Single Exit. אמנם נקודת היציאה נתונה וקבועה - אך דילגנו על קוד - להגיע אליה. הטענה העיקרית - היא שזה לא צפוי. אני מצפה שהפונקציה תגיע תמיד לשורתה האחרונה.
אין לי פה איזה המלצה או מסר גורף לגבי רעיון ה-Single Exist - אבל אני חושב ששווה להכיר אותו.
אני, אישית, שמח שיש break ו-continue בלולאות. אני מרגיש נוח להשתמש ב-return מתוך גוף של פונקציה (יש לזה מחיר מסוים - אבל האלטרנטיבה פחות טובה לדעתי).
שוב לא הצלחתי לסיים את הפוסט ב-400 מילים.
נושאים בסיסיים, כך אני מאמין, הם אלו שלעתים נכון להרחיב ולהעמיק בהם. הלימוד שלהם עשוי לתרום לנו, כאנשי תוכנה, יותר - מאשר היכרות עם עוד איזה ספריה מגניבה.
התחלנו בפוסט עם הרבה רקע, ונגענו רק בפרדיגמת "התכנות המובנה" - אבל גם ממנה חילצנו כמה תובנות רלוונטיות לתקופתנו. אני בהחלט ארצה להמשיך ולדבר גם על תכנות פרוצדורלי, OO, ותכנות פונקציונלי.
שיהיה לנו בהצלחה!
---
* הרעיון הזה מרגיש מתקדם יותר, ולכן אני מניח שהוא נוסף בשלב מאוחר יותר.
במאמר הקודם קיבלנו הצצה לעולם הפרדיגמות, ועכשיו נלמד על השפעת העבר על ההווה – ואיך זה יכול לשפר את התפיסה שלנו בנושא.
לא משנה באיזו שפה / סגנון תכנותי אנחנו עובדים היום -- הפרדיגמות המוקדמות הן הבסיס לעבודה שלנו.
יהיה קשה לדמיין אפילו - עבודה בלי חלק הפיתוחים שהן הוסיפו.
לפני הפרדיגמה המובנה (Structured), תוכנה הייתה "ערמה של קוד" - באופן המילולי ביותר שניתן לחשוב עליו. התכנה הייתה קרובה יותר לצורת ההבנה של המעבד, מאשר לצורה בה לבני-אדם נוח לנסח דברים.
למשל: המצאת ה"בלוק". בתחילה - לא היה דבר כזה.
למשל: משפטי if היו יכולים באותה התקופה להפעיל רק ביטוי (expression) יחיד. לו היו 4 פעולות שלא היינו רוצים שיפעלו אם x הוא שלילי - היה עלינו לכתוב 4 משפטי if שכל אחד חוזר על הבדיקה "האם x שלילי" ואז מבצע עוד פעולה.
אפשר לעבוד ככה - אם התוכנה קטנה. אני מניח שכולנו נתקלנו פה ושם בעבודה סיזיפית בקוד - שהיא סבירה בקנה מידה מסוים.
נסו לדמיין אותנו עובדים כך היום - עד כמה מסורבל וקשה זה!
דוגמה אחרת היא משפטי ה-GOTO המפורסמים. תוכנה היא לא תמיד רצף סדרתי של פעולות: לפעמים רוצים לקחת דרך אחרת. למעבר יש יכולת לעשות מעבר ולהריץ קוד ממקום אחר בזיכרון, התרגום הישיר של היכולת הזו היא פקודת GOTO (או JMP - אם אתם חושבים באסמבלי). יישום ראשוני של GOTO היה מעין תחליף פרימיטיבי לפונקציות: לך לשורה 300, ואז שורה 306 מחזירה אותנו לשורה 28 ממנה באנו. שימושים נוספים היו טיפול בשגיאות וניקוי זיכרון - מה שבשפות מודרניות כבר זוכה לטיפול ראוי יותר.
בגרסאות הראשונות של GOTO (כך מספרים), הוספת שורה בתחילת התוכנית - אלצה את המתכנת לעבור על כל הקוד ולעדכן את מספרי השורות בכל משפטי ה-GOTO - כי הם פשוט השתנו. רק אח"כ הוסיפו את ה-Labels או מספרי שורות ברמת השפה.
סיזיפי? סיזיפי אך אפשרי? - כך נראתה החלוציות בתחום התוכנה...
העיקרון המרכזי של התכנות המובנה הוא לקבע סדר וכללים ברורים - איך "קופצים" בין שורות בתוך התוכנה. במקום ציון השורה (או Label שמייצג אותה) - יש הפשטות כגון לולאה, בלוקים, רקורסיה, או Sub-routines (שזו ההתחלה של פונקציות. עדיין בלי פרמטרים וערך החזרה ברורים - אלא על בסיס משתנים גלובליים).
הגישה הוכיחה את עצמה דיי מהר כמועילה עד מעולה - והפכה להיות הסטנדרט הבלתי-מעורער בעולם התוכנה.
ארצה להדגיש שני עקרונות שצצו מהגישה הזו ושווים דיון, גם ממרום שנת 2019:
הרעיון אומר כך: כאשר אנו נותנים שם (name binding) למשתנה, פונקציה, קבוע, וכו' - נאפשר להגביל את מרחב הקוד שיהיה חשוף לשם הזה.
בתחילה היה קיים רק המרחב הגלובלי, אך בשל התנגשויות / טעויות שנבעו מכך - החלו להגדיר scopes מצומצמים יותר.
בפשט העיקרון אומר: אם אני לא זקוק למשתנה מחוץ ל scope מסוים (למשל: בלוק) - אזי יש להגדיר אותו בתוך ה-scope, ולא לא להעמיס על שאר הקוד במערכת - בהיכרות אפשרית איתו.
בצורה כוללנית יותר, הרעיון אומר שננסה לצמצם את ההיכרויות במערכת - למינימום האפשרי.
כל אלמנט שאנו מגדירים (משתנה, מחלקה, וכו') - נגדיר ב-scope המצומצם ביותר שאפשר.
למשל: אם יש Enum שזקוקים לו רק במחלקה מסוימת - סגרו אותו ב-scope של המחלקה - ואל תחשפו אותו לשאר העולם.
היום, בעולם ה-IDE המתקדם - המשמעות המיידית של scoping היא מה יציע לנו ה-IDE ב-Auto-complete. אם נגדיר הכל ב-Scope הגלובלי - כל הקלדה תציע לנו את כל מה שיש.
אם נקפיד על הגדרת ישויות ב-scopes מצומצמים ככל הניתן - הצעות ה-Auto-complete יהפכו לממוקדות, והרבה יותר רלוונטיות.
שנים רבות אחרי שהוגדר, העיקרון הזה עדיין לא תמיד מופנם ומקובל. עדיין אנשים מגדירים אלמנטים ב-scopes רחבים מהנדרש מחוסר מודעות, או מתוך מחשבה שזו "הכנה למזגן": אולי מישהו יצטרך את זה בעתיד? "למה לא לחסוך ולשים אותו זמין - כבר עכשיו"
מתלבטים מה עדיף? אני לא.
שווה לציין ש-Scoping הוא צעד ראשון בכיוון של Information hiding ו-Encapsulation. שוב ושוב עלו, לאורך ההיסטוריה - רעיונות בכיוון הזה.
בכדי להגדיר מהו מבנה צפוי ומנוהל של רצף הרצת התוכנה, ולהפסיק "לקפוץ לשורה אחרת בקוד", הגדירו בפרדיגמת התכנות המובנה את "עיקרון היציאה האחידה" (מפונקציה). שם נוסף:"Single Entry, Single Exit". לכל בלוק קוד (=פונקציה) צריך להיות רק מקום אחד להיכנס דרכו - ורק מקום אחד שניתן לצאת ממנו.
העיקרון הזה הוא מובנה בשפות שאנו עובדים איתן - עם כמה חריגות. קשה מאוד להסתדר ב-100% מהמקרים עם Single Exit יחיד.
נסו לרגע לחשוב: האם אתם מצליחים לחשוב היכן בשפה שלכם מפרים את העיקרון?
הנה דוגמאות עיקריות:
• Exceptions שנזרקים - הם לא Single Exit.
o continue או break בלולאה ל Label (בג'אווה, למשל) - הם לא Single Exit.
• אפשר גם לטעון ש-break ו-continue באופן כללי - הם לא Single Exit, לא ברמת הבלוק.
• בשפות פונקציונליות - לעתים אין להם מקבילה.
• coroutines (על עוד לא ממתינים באותו הבלוק לסיומם) - הם גם לא Single Exit. הפעלנו קוד - שרק בונה-עולם יודע מתי בדיוק הוא יסתיים.
• אחרון וחביב: יש גם מי שרואים בקריאת return מתוך גוף הפונקציה - הפרה של עקרון ה-Single Exit. אמנם נקודת היציאה נתונה וקבועה - אך דילגנו על קוד - להגיע אליה. הטענה העיקרית - היא שזה לא צפוי. אני מצפה שהפונקציה תגיע תמיד לשורתה האחרונה.
אין לי פה איזה המלצה או מסר גורף לגבי רעיון ה-Single Exist - אבל אני חושב ששווה להכיר אותו.
אני, אישית, שמח שיש break ו-continue בלולאות. אני מרגיש נוח להשתמש ב-return מתוך גוף של פונקציה (יש לזה מחיר מסוים - אבל האלטרנטיבה פחות טובה לדעתי).
שוב לא הצלחתי לסיים את הפוסט ב-400 מילים.
נושאים בסיסיים, כך אני מאמין, הם אלו שלעתים נכון להרחיב ולהעמיק בהם. הלימוד שלהם עשוי לתרום לנו, כאנשי תוכנה, יותר - מאשר היכרות עם עוד איזה ספריה מגניבה.
התחלנו בפוסט עם הרבה רקע, ונגענו רק בפרדיגמת "התכנות המובנה" - אבל גם ממנה חילצנו כמה תובנות רלוונטיות לתקופתנו. אני בהחלט ארצה להמשיך ולדבר גם על תכנות פרוצדורלי, OO, ותכנות פונקציונלי.
שיהיה לנו בהצלחה!
---
* הרעיון הזה מרגיש מתקדם יותר, ולכן אני מניח שהוא נוסף בשלב מאוחר יותר.
הודעתך לא התקבלה - נסה שוב מאוחר יותר
Oops! Something went wrong while submitting the form