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

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

טיפ קצר: בואו נגדיר קבועים ב Node.JS

09/12/2018

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

נתחיל עם מה שעובד - כל עוד אנחנו בקובץ יחיד אפשר להשתמש ב const כדי לתת שם לערך קבוע:

const MESSAGE_SIZE = 48;

ומכאן ועד סוף הקובץ לא משנה מה תעשו השם MESSAGE_SIZE תמיד יתייחס למספר הקבוע 48. אבל העסק מסתבך מהר מאוד כשאנחנו רוצים לשתף מידע בין מספר קבצים. נניח שהקובץ utils.js מגדיר הפעם את הקבוע ומייצא אותו:

// utils.js
const MESSAGE_SIZE = 48;
exports.MESSAGE_SIZE = MESSAGE_SIZE;

והקובץ app.js מייבא את הקבוע:

const { MESSAGE_SIZE } = require('./utils');
console.log(MESSAGE_SIZE);

זה עבד לא רע והדפיס 48, אבל הולך להישבר די בקלות. נסו לכתוב במקום את הקוד הבא ב app.js:

const utils = require('./utils');
utils.MESSAGE_SIZE = 999;

console.log(utils.MESSAGE_SIZE);

הקוד ידפיס 999 וגם כל קובץ אחר שיטען את הקבוע מ utils יקבל עכשיו את הערך 999. מסתבר שברגע שאנחנו עוברים להשתמש במנגנון ה exports מה שאנחנו בעצם מייצאים הוא אוביקט. המילה const לא מונעת שינויים בשדות של האוביקט וכך כל מי שרוצה יכול לשנות את ה"קבועים" שלנו.

מה אפשר לעשות? אז אומנם const לא תעזור לשמור על שדות של אוביקטים משינויים, אבל Object.freeze דווקא כן. אם נפעיל אותה לפני ה export נוכל לקבל קבועים של ממש.

החליפו את תוכן הקובץ utils.js עם הקוד הבא:

module.exports = Object.freeze({
  MESSAGE_SIZE: 48,
});

ועכשיו בלי לשנות את app אפשר להריץ אותו שוב ולקבל את ערך הקבוע 48. מספר זה לא ישתנה לא משנה מה נכתוב בקבצים שטוענים אותו.

איך להגיב לשאלה שאת כבר מכירה בראיון עבודה?

08/12/2018

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

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

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

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

דוגמא? יאללה בשמחה-

המראיין: מהי בעיית N+1 בעת שליפת נתונים מ DB, ואיך היית מתמודד איתה?

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

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

שבוע אחר כך ועם הקוד הלא נכון

07/12/2018

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

אבל כמובן שזה משנה.

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

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

בלוקים ב Ruby

06/12/2018

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

המשך קריאה

הבעיה עם תכנות מונחה עצמים

05/12/2018

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

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

המשך קריאה

מצאו את ההבדלים

04/12/2018

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

require 'set'

s = Set.new([10, 20, 30])
t = Set.new([30, 40])
puts (s | t).inspect
puts s.merge(t).inspect

בשני המקרים תוצאת ההדפסה היא:

#<Set: {10, 20, 30, 40}>

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

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

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

עכשיו אתם ועדיין ברובי- מה ההבדל בין ארבעת אלה? מה יכול להשתבש? ואיך?

arr = [10, 20, 30]

arr.inject(0) { |acc, val| acc + val }
arr.inject { |acc, val| acc + val }
arr.inject do |acc, val| acc + val; end
arr.inject(0) do |acc, val| acc + val; end

טרנספורמציות של סדרות

03/12/2018

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

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

numbers = (0..Float::INFINITY)

זאת סידרת המספרים 0,1,2,3,4,5,6 וכן הלאה עד אין סוף. אנחנו יודעים שאם נכפיל כל איבר בסידרה ב-2 נקבל את הסידרה 0,2,4,6,8,10,12 כלומר כל המספרים הזוגיים - ואנחנו יכולים להשתמש ב map כדי לעשות את זה ב Ruby:

even = (0..Float::INFINITY).lazy.map {|i| i * 2}

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

puts (0..Float::INFINITY).lazy.map {|i| i * 2}.first(10)

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

boy
girl
helllo
good
boxer

אז המילה helllo עם שגיאת הכתיב כוללת את האות l שלוש פעמים, וזה הכי הרבה שאות מופיעה במילה בכל הרשימה. אם נרצה לגשת לבעיה דרך סדרות נוכל לקחת את רשימת המילים ולהפוך אותה לרשימה של Hash-ים כך שבמקום לשמור מילה נשמור כמה פעמים כל אות מופיעה בה. במילים אחרות נשתמש ב map כדי להפוך את רשימת המילים לרשימת מבני הנתונים הבאה:

["boy", {1=>["b", "o", "y"]}]
["girl", {1=>["g", "i", "r", "l"]}]
["helllo", {1=>["h", "e", "o"], 3=>["l", "l", "l"]}]
["good", {1=>["g", "d"], 2=>["o", "o"]}]
["boxer", {1=>["b", "o", "x", "e", "r"]}]

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

["helllo", {1=>["h", "e", "o"], 3=>["l", "l", "l"]}]
["good", {1=>["g", "d"], 2=>["o", "o"]}]
["boxer", {1=>["b", "o", "x", "e", "r"]}]
["boy", {1=>["b", "o", "y"]}]
["girl", {1=>["g", "i", "r", "l"]}]

בה האיבר הראשון הוא בדיוק המילה שחיפשתי.

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

class String
  def count
    Hash.new(0).tap { |h| self.each_char { |word| h[word] += 1 } }
  end

  def icount
    counts = self.count
    each_char.group_by {|c| counts[c]}
  end
end

words = [
  'boy',
  'girl',
  'helllo',
  'good',
  'boxer',
]

puts words
  .map {|w| [w, w.icount]}
  .sort {|a, b| a[1].keys.max - b[1].keys.max}
  .reverse
  .map {|p| p[0]}
  .first

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

ייצוג סדרות

02/12/2018

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

bookmarks = [
    'https://www.tocode.co.il',
    'https://www.google.com',
]

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

def even(index):
    return index * 2

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

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

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

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

import sys

with open(sys.argv[1]) as f:
    for index, line in enumerate(f):
        sys.stdout.write(f"{index}: {line}")

print()

והנה גירסא נוספת הפעם שמדפיסה רק את השורות שארוכות מ-10 תווים מתוך הקובץ:

import sys

with open(sys.argv[1]) as f:
    for index, line in enumerate(f):
        if len(line) <= 10: continue
        sys.stdout.write(f"{index}: {line}")

print()

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

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

1, 1, 2, 3, 5, 8, 13, 21

אפשר היה לייצג אותה באמצעות פונקציה רגילה שנראית כך:

def fib(n):
    x, y = 0, 1
    for i in range(n):
        x, y = y, x + y

    return x

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

print(fib(8))

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

i = 0
while fib(i) < 200:
    i += 1

print(f"fib({i}) == {fib(i)}")

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

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

def fib():
    x, y = 0, 1
    while True:
        yield x
        x, y = y, x + y

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

כך נראה קוד שמשתמש בסידרה החדשה שלנו כדי להדפיס את המספר השמיני בסידרת פיבונאצ'י:

for index, val in enumerate(fib()):
    if index == 8:
        print(val)
        break

וזה הקוד שיגלה מהו הערך הראשון שגדול מ-200:

for index, val in enumerate(fib()):
    if val > 200:
        print(val)
        break

ואגב המנגנון של הגדרת סדרות קיים בהרבה מאוד שפות תכנות. הנה אותה תוכנית פיבונאצ'י בתרגום לרובי ו JavaScript - נסו למצוא את ההבדלים:

fib = Enumerator.new do |yielder|
  x, y = 0, 1
  loop do
    yielder << x
    x, y = y, x + y
  end  
end

fib.lazy.each_with_index do |val, index|
  if index == 8
    puts val
    break
  end
end

fib.lazy.each_with_index do |val, index|
  if val > 200
    puts val
    break
  end
end
function *enumerate(it, start=0) {
  for(let x of it) {
    yield [start++, x];
  }
}

function *fib() {
  [x, y] = [0, 1];
  while(true) {
    yield x;
    [x, y] = [y, x + y];
  }
}

for ([index, val] of enumerate(fib())) {
  if (index == 8) {
    console.log(val);
    break;
  }
}

for ([index, val] of enumerate(fib())) {
  if (val > 200) {
    console.log(val);
    break;
  }
}

שימו לב איך ב JavaScript גם המבנה של enumerate לא קיים בשפה ואנחנו בונים אותו באותו תחביר בניית סדרות איתו בנינו את סידרת פיבונאצ'י.

מצב פיתוח לעומת מצב ייצור

01/12/2018

המערכת שלכם לא מתנהגת אותו דבר במצב פיתוח ובמצב ייצור. במצב פיתוח יש לנו הודעות שגיאה מפורטות, אפשרות להתחבר בשניה עם Debugger, אולי Live Reload כדי שכל שינוי מיד יופיע על המסך. במצב ייצור (Production) עוברים להציג הודעות שגיאה גנריות ולרשום את השגיאות בלוג, מכווצים את כל הקוד וסוגרים את כל אפשרויות החיבור כדי שירוץ מהר ובצורה מאובטחת.

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

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

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

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

מה השיטה שלכם?

זה לא בשבילי

30/11/2018

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

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

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

האם זה קצב טוב? האם זה משנה?

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

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

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

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

כמה זמן ייקח לך ללמוד לתכנת? כמה שתבחר. והרמה תעלה ככל שתיתן לזה יותר זמן.