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

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

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

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. שבו אני מלמד על כל הפיצ'רים האלה. יש למה לחכות.

איך לעקוף אפליה לפי גיל

26/04/2018

סקוט ג'ונסון העלה השבוע את קורות חיפוש העבודה שלו בחצי קיטור חצי הזדמנות לפרסם מוצר שפיתח. אני חושב שכדאי לכם לקרוא את הסיפור כאן: http://fuzzyblog.io/blog/jobhound/2018/04/24/ten-things-i-learned-from-a-job-hunt-for-a-senior-engineering-role.html

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

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

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

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

  3. התחדשות- כדאי שהטכנולוגיות בקורות החיים שלך יתאימו לטכנולוגיות שעובדים בהן בחברות. בשנים האחרונות יש הרבה פחות חברות שעובדות ב Ruby on Rails. עדיין יש עבודה בתחום והשפה מצוינת והכל (האתר הזה כתוב בריילס), אבל אם היום אני מפרסם קורות חיים שלי למשרת מתכנת ריילס לא יהיו המון הצעות פשוט כי סטארט-אפים עברו ל node.js. הניסיון שלכם בטכנולוגיות ישנות לא נתפס כמשהו בעל ערך.

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

יש לכם עוד רעיונות? אשמח לשמוע בתגובות או במייל.

עיצוב תוכנה בתנועה

25/04/2018

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

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

את המערכת השניה עיצב מתכנת שלא תיכנן על מערכת גדולה וקצת יצא לו בטעות. כל העיצוב היה בנוי על 2-3 מחלקות שנלקחו מאיזה פרויקט Hello World שפשוט יצא מפרופורציה והפך למיליוני שורות קוד כמעט בלי שינוי של מבנה המחלקות. פונקציות באורך מאות שורות קוד היו מחזה נפוץ.

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

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

מיתוסים על מיומנות

24/04/2018

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

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

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

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

ולשאלות- האם אתם יודעים מה רמת המיומנות שלכם? יש לכם מושג מה האחרים בצוות שלכם חושבים על רמת המיומנות שלכם? והאם אתם מרגישים שרמת המיומנות שלכם השתפרה בשנים האחרונות (או התדרדרה)?

3 מטרות

23/04/2018

בואו נדבר על 3 מטרות של קורסים:

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

  2. הקניית ידע - הקורס מלמד איך להשתמש בכלי מסוים.

  3. שיפור מיומנות (טכניקה).

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

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

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

חדש מאתר ToCode: תוכנית המנויים

22/04/2018

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

אל תבלעו Exceptions. זה לא טעים.

21/04/2018

התקנתי Capybara לפני כמה ימים (או יותר נכון ניסיתי להתקין). כל פעם שהפעלתי את המערכת אחרי ההתקנה הופיעה ההודעה הבאה:

System test integration requires Rails >= 5.1 and has a hard dependency
on a webserver and `capybara`, please add capybara to
your Gemfile and configure a webserver
(e.g. `Capybara.server = :webrick`) before attempting to use system tests.

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

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

      included do |other|
        begin
          require 'capybara'
          require 'action_dispatch/system_test_case'
        # rubocop:disable Lint/HandleExceptions
        rescue LoadError
          # rubocop:enable Lint/HandleExceptions
          abort """
            System test integration requires Rails >= 5.1 and has a hard
            dependency on a webserver and `capybara`, please add capybara to
            your Gemfile and configure a webserver (e.g. `Capybara.server =
            :webrick`) before attempting to use system tests.
          """.gsub(/\s+/, ' ').strip
        end

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

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

# from: https://www.python.org/download/releases/2.2.3/descrintro/
class defaultdict(dict):

    def __init__(self, default=None):
        dict.__init__(self)
        self.default = default

    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.default

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

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

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

נ.ב. 2 - אצלי Capybara לא עלה בגלל חוסר תאימות בין ריילס 5.1 ל Capybara 3. ברגע ששינמכתי ל Capybara 2 הכל התחיל לעבוד.

נ.ב. 3 - קפיברה (בנוסף להיותה המכרסם הגדול ביותר) היא ספריית בדיקות אוטומטיות ב Ruby.