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

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

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

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.

קוד כניסה (פייתון)

20/04/2018

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

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

import sys
class CodePanel:
    def __init__(self, code):
        self.code = code
        self.guess = Buffer(len(code))

    def check(self):
        if self.guess.get_as_string() == self.code:
            print('welcome')
            sys.exit(0)
        else:
            print('wrong code. try again')
            self.guess = Buffer(self.guess.size)

    def type_char(self, ch):
        self.guess.write(ch)
        if self.guess.is_full():
            self.check()

class Buffer:
    def __init__(self, size):
        self.buf = [''] * size
        self.at = 0
        self.size = size

    def write(self, ch):
        self.buf[self.at] = ch
        self.at += 1

    def is_full(self):
        return self.at == self.size

    def get_as_string(self):
        return ''.join(self.buf)


panel = CodePanel('13579')
code = '123413579'
for ch in code:
    panel.type_char(ch)

בכל פעם שמשתמש לוחץ על כפתור תיקרא הפונקציה CodePanel#type_char ותוסיף את האות לחוצץ עד שהחוצץ מתמלא, ורק אז נבדוק את הקוד ונדפיס הודעה. אפשר לראות שניסיתי להקליד את הרצף 123413579 ונשארתי בחוץ.

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

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

חוצץ מעגלי (Circular Buffer) ישמור את הספרות בהתחלה לפי הסדר ואז כשיתמלא הספרות החדשות ישמרו בתחילת החוצץ במקום הספרות הישנות. עבור אותו רצף מספרים שתיארתי כך יראה החוצץ אחרי כל סיפרה:

1
1, 2
1, 2, 3
1, 2, 3, 4
1, 2, 3, 4, 1
3, 2, 3, 4, 1
3, 5, 7, 9, 1

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

import sys

class CircularBuffer:
    def __init__(self, size):
        self.size = size
        self.buf  = [''] * size
        self.at   = 0

    def write(self, ch):
        self.buf[self.at] = ch
        self.at = (self.at + 1) % self.size

    def get_as_string(self):
        res = []
        for i in range(self.size):
            res.append(self.buf[(self.at + i) % self.size])
        return ''.join(res)

class CodePanel:
    def __init__(self, code):
        self.code = code
        self.guess = CircularBuffer(5)

    def check(self):
        if self.guess.get_as_string() == self.code:
            print(f'welcome: {self.guess.get_as_string()}')
            sys.exit(0)

    def type_char(self, ch):
        self.guess.write(ch)
        self.check()


panel = CodePanel('13579')
code = input()
for ch in code:
    panel.type_char(ch)

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

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

class CircularBuffer:
    def __init__(self, size):
        self.size = size
        self.buf  = deque([''], size)
        self.at   = 0

    def write(self, ch):
        self.buf.append(ch)

    def get_as_string(self):
        return ''.join(list(self.buf))

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

ציד באגים

19/04/2018

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

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

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

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