הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

יש בעיה עם זה

07/05/2018

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

ואז מתישבים על המחשב להתחיל לכתוב... אבל רגע, יש בעיה עם זה.

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

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

שתי סיבות ללמוד

06/05/2018

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

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

זו בחירה שלכם. אני ממליץ על האפשרות השניה.

עשר שאלות לראיונות עבודה על JavaScript ES6

05/05/2018

לכבוד קורס ES6/7/8 החדש קבלו עשר שאלות שהייתי שואל בראיון עבודה על הנושא. מוזמנים להתכונן או לקחת מכאן רעיונות (תלוי באיזה צד של השולחן אתם יושבים):

  1. מה ההבדל בין var, let ו const? מה היה רע ב var? מתי נשתמש ב let ומתי ב const?

  2. מה קורה כשמריצים את הקוד הבא? מדוע? איך תיצרו מערך שאי אפשר להוסיף אליו איברים?

const arr = [10, 20, 30];
arr.push(50);
  1. מהו אוביקט Proxy? כיצד תשתמשו ב Proxy כדי לממש Private Data Members של מחלקה? מה היתרונות והחסרונות של גישה זו בהשוואה לשימוש ב Closures?

  2. הקוד הבא מושך נתוני דמות משירות הרשת swapi.co ולאחר מכן את שמות כל הסרטים בהם הדמות הופיעה:

$.get('https://swapi.co/api/people/1', function(res) {
    for (const url of res.films) {
        $.get(url, function(filmData) {
            console.log(filmData.title);
        });
    }
});

שכתבו את הקוד תוך שימוש ב Promises.

  1. כתבתי מחדש את הקוד משאלה (4) תוך שימוש ב async/await:
async function getFilms() {
    const res = await $.get('https://swapi.co/api/people/1');
    for (const url of res.films) {
        const filmData =await $.get(url);
        console.log(filmData.title);
    }
}

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

  1. מה ההבדל בין WeakMap ל Map? מתי נשתמש ב WeakMap?

  2. כתבו מחלקה בשם BindAll שכל מחלקה שיורשת ממנה מקבלת bind(this) אוטומטי לכל המתודות שלה. דוגמת שימוש:

class Person extends BindAll {
    constructor(name) {
        this.name = name;
    }

    hello() {
        console.log(this.name);
    }
}

const p = new Person('John');
// waits 500 ms and then prints 'John'
setTimeout(p.hello, 500);
  1. כתבו פונקציה בשם bindAll המקבלת מחלקה ומבצעת bind לכל המתודות שלה באופן אוטומטי.

  2. כתבו פונקציה בשם fib כך שהקוד הבא יעבוד וידפיס את המספרים הקטנים מ-100 מסדרת פיבונאצ'י (סדרת פיבונאצי היא סידרת מספרים בה כל איבר שווה לסכום שני האיברים שלפניו):

for (let n of fib()) {
    console.log(n);
    if (n > 100) break;
}
  1. כתבו מחלקה בשם Fib כך שהקוד הבא יעבוד:
const fib10 = new Fib(10);

// iterates through the first 10 numbers of a fibonacci series
for (let n of fib10) {
    console.log(n);
}

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

04/05/2018
git

השבוע דיברתי עם אליעזר במפגש תרגול git ועברנו על בראנצ'ים ועל הפקודה git branch -f שלדעתי עוזרת לעשות קצת סדר בקשר בין בראנצ'ים לקומיטים.

הבלבול מתחיל כבר כשפותחים פרויקט חדש:

$ git init
$ git log
fatal: your current branch 'master' does not have any commits yet

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

$ echo 1 > demo.txt
$ git add .
$ git commit -m 'commit 1'
$ echo 2 > demo.txt
$ git commit -a -m 'commit 2'
$ git log
localhost:project ynonperek$ git log
commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

נראה אחלה עד שחוזרים אחורה:

$ git checkout 85f0794fe33b4718dce3f02b480a119261d64115

$ git log
commit 85f0794fe33b4718dce3f02b480a119261d64115 (HEAD)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

$ git branch
* (HEAD detached at 85f0794)
  master

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

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

בדוגמא שלנו המדבקה HEAD היתה מחוברת לקומיט cd877. אחרי ביצוע checkout הזזנו את המדבקה HEAD לקומיט הישן יותר. המדבקה master אגב עדיין נשארה מחוברת לקומיט החדש. הנה כמה פקודות שעכשיו נראות הרבה יותר הגיוניות (ממשיכים מאותו מצב):

$ git checkout master
$ git branch
* master
$ git log
commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

ואם קומיט לא חי בתוך בראנצ' אפשר להתחיל לעשות דברים מעניינים - כמו לעקוף את master ולבצע קומיט ישירות מתוך קומיט אחר:

$ git checkout cd877
$ echo 3 > demo.txt
$ git commit -a -m 'commit 3'
$ git log
commit ff12c93ea35fce6e1583d877458f848da796a26f (HEAD)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:37:11 2018 +0300

    commit 3

commit cd877d58a9885372b9b6ff93c77dabcff8b58052 (master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

פעולת git checkout הזיזה את HEAD ועכשיו כל הפעולות שלנו מתבצעות על קומיט cd877 ולא "מושכות" את master קדימה. מה שיפה שמאחר ואנחנו חושבים על בראנצ'ים כמו מדבקות קל מאוד להזיז את master לקומיט אחר. הפקודות הבאות מתקנות את המצב:

$ git branch -f master HEAD
$ git checkout master
$ git log
commit ff12c93ea35fce6e1583d877458f848da796a26f (HEAD -> master)
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:37:11 2018 +0300

    commit 3

commit cd877d58a9885372b9b6ff93c77dabcff8b58052
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:51 2018 +0300

    commit 2

commit 85f0794fe33b4718dce3f02b480a119261d64115
Author: ynonp <ynonperek@gmail.com>
Date:   Thu May 3 08:23:40 2018 +0300

    commit 1

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

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

מה להגיד לבוס?

03/05/2018

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

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

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

המשך קריאה

הצצה אחורה בביטוי רגולארי בפייתון

02/05/2018

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

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

טוב אז ברור ש split רגיל של מחרוזת לא יעבוד כאן - split יבלע לי את הנקודותיים ואת הרווח שאחריהן:

text = 'I am :the:      walrus'
text.split(': ')
['I am :the', '     walrus']

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

import re
text = 'I am :the:      walrus'
re.split('(: )', text)
 ['I am :the', ': ', '     walrus']

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

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

import re
text = 'I am :the:      walrus'
re.split('(?<=:) ', text)

# or if you don't need the spaces before the walrus
re.split('(?<=:) +', text)
['I am :the:', 'walrus']

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

  1. הצצה קדימה - Positive Lookahead מסומן באמצעות (?=foo). חיפוש המקום בקלט שאחריו מופיעה המילה foo.

  2. הצצה אחורה - Positive Lookbehind מסומן באמצעות (?<=foo). חיפוש המקום בקלט שלפניו מופיעה המילה foo.

  3. הצצה קדימה שלילית -Negative Lookahead מסומן באמצעות (?!foo). חיפוש מקום בקלט שאחריו לא מופיעה המילה foo.

  4. הצצה אחורה שלילית - Negative Lookbehind מסומן באמצעות (?<!foo) חיפוש המקום בקלט שלפניו לא מופיעה המילה foo.

לדוגמאות נוספות על Lookahead ו Lookbehind שווה להעיף מבט בקישור כאן:

href='http://www.rexegg.com/regex-lookaround

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

איך מרגישה הצלחה

30/04/2018

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

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

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

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

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

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

קריא, נכון או גם וגם?

29/04/2018

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

// prints: new world
console.log(wordsFromList('a brave new world', ['world', 'new']);

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

function wordsFromList(sentence, words) {
    const wordsObject = {};
    words.forEach(word => { wordsObject[word] = true; });

    return sentence.split(' ').filter(word => wordsObject[word]).join(' ');
}

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

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

function wordsFromList(sentence, words) {
    const wordsList = new WordsList(words);
    const breakableSentence = new Sentence(sentence);

    return breakableSentence.filterWords(word => wordsList.includes(word)).toString();
}

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

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

מהר יותר מאופניים

28/04/2018

את יכולה לרוץ מהר יותר מאופניים, אבל לא לאורך זמן.

בשביל באמת לתכנת מהר כדאי יותר:

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

אפשר לרוץ יותר מהר מאופניים (לפחות בחלק מהזמן), אבל הרבה יותר משתלם ללמוד לרכב.

האם אנחנו הולכים לקבל Pattern Match גם ב JavaScript (השראה)

27/04/2018

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

למי שלא מכירים אין קשר לביטויים רגולאריים ומדובר במנגנון switch/case מתוחכם שמסתכל פנימה לתוך אוביקטים. יש בהצעה דוגמא ואם אתם סקרנים לכו לקרוא כאן: https://github.com/tc39/proposal-pattern-matchin

מעניין המשפט משם שההצעה לוקחת השראה מפיצ'ר מקביל שקיים ב Rust, F#, Scala וכמובן אליקסיר.

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

דוגמא יותר אקטואלית היא Generator Functions. אלה כבר קיימים ב JavaScript כולל בגירסא אסינכרונית. כלומר היום אנחנו יכולים לכתוב את הקוד הבא ודפדפנים יריצו אותו:

// noprotect
// Note the * after "function"
let stop = false;

async function* asyncRandomNumbers() {
    // This is a web service that returns a random number
    const url = 'https://www.random.org/decimal-fractions/?num=1&dec=10&col=1&format=plain&rnd=new';

    while (true) {
      const response = await fetch(url);
      const text = await response.text();
      yield Number(text);
    }
  }

  const inp = document.querySelector('input');

  async function example() {
    for await (const number of asyncRandomNumbers()) {
      inp.value = number;
      if (stop) break;
    }
  }

  function startStop() {
      if (stop) {
        stop = false;
        example();
      } else {
        stop = true;
      }
  }

  document.querySelector('button').addEventListener('click', startStop);
  example();

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

  async function example() {
    for await (const number of asyncRandomNumbers()) {
      inp.value = number;
      if (stop) break;
    }
  }

איך לולאת for מצליחה לכתוב למסך בכל איטרציה? ואיך JavaScript מצליח לתפוס את הלחיצה על הכפתור כשהוא "באמצע" לולאת ה for?

ברור שאם אתם מכירים Generator Functions ממקומות אחרים אתם יודעים לענות שהלולאה הזאת בעצם מתורגמת לרצף של callbacks כאשר אחרי כל איטרציה המנוע חוזר ל Main Loop, וזו ממש לא לולאת for קלאסית.

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

נ.ב. שבוע הבא יעלה כאן קורס JavaScript ES6/7/8. שבו אני מלמד על כל הפיצ'רים האלה. יש למה לחכות.