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

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

ואם היום לא בא לי

22/04/2021

אנחנו אוהבים לחשוב על מוטיבציה בתור משהו שלא תלוי בנו: יש דברים ש"בא לי" עליהם, ואחרים ש"לא בא לי לעשות":

בא לי לראות יוטיוב; לא בא לי לקרוא ספר.

בא לי לעשות ספורט; לא בא לי לאכול פיצה.

בא לי לבנות פיצ'ר חדש; לא בא לי לחקור זליגת זיכרון ישנה.

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

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

חוסך עבודה או חוסך הבנה

21/04/2021

זה לא אותו דבר ולשניהם יש מקום:

אני יודע מעט מאוד על איך מערכת הפעלה קוראת קובץ Executable, אבל עדיין מצליח לבנות בקלות קבצי הפעלה ותוכניות באמצעות שפות תכנות מדרגה גבוהה יותר. אני כותב קוד Java => הקומפיילר של Java הופך את הקוד ל Bytecode => המכונה הוירטואלית של Java מתרגמת בזמן אמת את הקוד לשפת מכונה => מערכת ההפעלה מבצעת את הפקודות בשפת המכונה. העבודה ב Java, או בכל שפת תכנות אחרת, חוסכת לי הבנה.

ולמרות שאני יודע די הרבה על Webpack ומבנה פרויקט React, אני עדיין אעדיף להתחיל פרויקט חדש עם create-react-app וכשיגיע הזמן להפעיל eject ולהתחיל לתפעל את העניינים בעצמי. החיסכון בעבודה בהתחלה מאפשר לי לקפוץ מהר יותר לכתיבת הקוד, שזה מה שרציתי לעשות מלכתחילה.

כן חשוב להיות מיושרים וברורים מה אנחנו מנסים לחסוך ולוודא שמשתמשים בכלי הנכון. ספריית next.js היא דרך מעולה לחסוך עבודה בהקמת תשתית ל Server Side Rendering, אבל אם אתם לא מבינים איך SSR עובד יהיה לכם מאוד קשה לפתור בעיות שבוודאות יצוצו. מצד שני להבין איך המעבד עובד לא יהפוך אתכם למתכנתי Clojure טובים יותר, כי שפה פונקציונאלית כמו Clojure מגיעה עם עולם תוכן ועולם מושגים שלה.

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

המקסימום האפשרי

20/04/2021

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

וכך בתור תלמידים למדנו שאם אפשר ללמוד 10 שעות ולקבל 100 אז אין טעם ללמוד 20 שעות (כי ממילא לא תקבל יותר מ 100).

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

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

  1. ככל שתלמדי יותר שפות תכנות, כך יהיה לך קל יותר ללמוד את השפה הבאה.

  2. ככל שתבני יותר משחקים, כך יהיה לך קל יותר לבנות את המשחק הבא.

  3. ככל שתעבירי יותר הרצאות כך תגיעי יותר מוכנה להרצאה הבאה.

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

פירוק כפול ב JavaScript

19/04/2021

מתכנתי JavaScript רבים כבר יודעים לעבוד עם Destructuring כדי לשבור מערך למספר משתנים, לדוגמה הקוד הבא שומר ב x את הערך 10 וב y את הערך 20:

const [x, y] = [10, 20];

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

const [value, setValue] = useState("");

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

function Input(props) {
  const [value, setValue] = props.model;
  return (
    <input
      type="text"
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
}

function App() {
    const model = useState("");
    const [value, setValue] = model;

    return (
        <div>
            <p>Hello {value}</p>
            <Input model={model} />
        </div>
    );
}

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

const model = [value, setValue] = useState("");

והתשובה מורכבת.

הקוד יעבוד (לפחות בחלק מסביבות ההרצה) אבל לא יעשה את מה שרציתם שהוא יעשה. בכתיב כזה המילה const משפיעה רק על המשתנה הראשון model, והמשתנים value ו setValue יהיו משתנים גלובאליים. לכן הקוד יעבוד בסביבות שמאפשרות הגדרת משתנים גלובאליים בלי var, const או let. בכל מקרה אנחנו יודעים שמשתנים גלובאליים ב JavaScript רק מביאים צרות ולכן גם אם הכתיב עובד לא מומלץ להשתמש בו.

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

export default function App() {
  const model = useState(""),
    [value, setValue] = model;

  return (
    <div className="App">
      <p>Hello {value}</p>
      <Input model={model} />
    </div>
  );
}

בגרות באנגלית: חלק שני

18/04/2021

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

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

אם לקחתם את הקוד אתמול ואולי ניסיתם להדפיס חלק מהמילים יכולתם לגלות שהמילים house ו houses נספרות כשתי מילים שונות, וכך גם go, goes ו going. בנוסף אולי שמתם לב ששמות כמו George, Bill ו Jane נספרו בתור מילים.

בואו נתקן את שתי הבעיות בעזרת ספריית nltk.

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

  1. היכולת לבצע Stemming למילה, כלומר לקחת את המילים house ו houses, להבין שזו אותה מילה ולהחזיר את הצורה המקורית של המילה - house.

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

את הספריה מתקינים עם:

pip install nltk

ובתוך התוכנית נשתמש בה עם ה import-ים הבאים:

from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize
import nltk.corpus

נתחיל עם מאגר השמות. הקוד הבא שומר במשתנה all_names קבוצה של כל השמות שמופיעים במאגר של nltk:

names = nltk.corpus.names

all_names = set(names.words('male.txt') + names.words('female.txt'))

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

import nltk
nltk.download('names')

בחזרה לקוד שלנו, החלק השני של התוכנית יהיה להשתמש בפונקציית stem כדי לזהות הטיות של מילה. הקוד הבא ממחיש את השימוש בפונקציה:

# import these modules
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize

ps = PorterStemmer()

# choose some words to be stemmed
words = ["program", "programs", "programer", "programing", "programers"]

for w in words:
    print(w, " : ", ps.stem(w))

התוצאה של כל ההדפסות היא המילה program.

התוכנית שלי אחרי עדכון נראית כך:

import requests, textract, re, os.path
from functools import reduce
from nltk.stem import PorterStemmer
from nltk.tokenize import word_tokenize
import nltk.corpus

names = nltk.corpus.names

all_names = set(names.words('male.txt') + names.words('female.txt'))

index_url = 'https://meyda.education.gov.il/bagmgr/Ajax.ashx?search=1&sheelon=&miktzoa=16&safa=1&pagesize=10&page=1'

r = requests.get(index_url)
urls = [q["question"] for q in r.json()]
total_words = {}
ps = PorterStemmer()

s = requests.Session()
for url in urls:
    m = re.search(r'([^/]+\.pdf)', url)
    if m is None: continue

    filename = m[0]
    if not os.path.isfile(filename):
        r = s.get(url)

        with open(filename, 'wb') as f:
            f.write(r.content)

    text = textract.process(filename).decode('utf8')
    words = [
            ps.stem(w.lower()) for w in re.findall(r'\b[a-zA-Z_]+\b', text)
            if w not in all_names
            ]
    print(f"Test {filename} has {len(words)} different words")
    total_words[filename] = set(words)

all_words = reduce(lambda acc, val: acc | val, total_words.values())
print(f"All tests had a total of {len(all_words)} different words")

אלף מילים

17/04/2021

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

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

תוצאות? מיד אחרי הסקריפט שמחשב את הנתונים...

המשך קריאה

הדרך הכי טובה לאבד מוטיבציה

16/04/2021

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

וזה גרוע גם אם התוצאה חיובית וגם אם שלילית.

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

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

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

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

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

לי זה לקח חודשיים

15/04/2021

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

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

בעקבות פירסום שראיתי לקורס Qt התחלתי לחשוב - החודשיים שהשקעתי באמת היו אפקטיביים, אבל האם היה אפשר לקצר אותם? האם קורס מרוכז היה עוזר לי להגיע לאותה רמה בהשקעה של 5 ימי עבודה בלבד?

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

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

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

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

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

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

אף אחד אף פעם לא ביקש ללמוד על Higher Order Components

14/04/2021

לא נעים להגיד אבל יש בטכנולוגיה פיצ'רים שאנשים רוצים ללמוד עליהם ואחרים שלא ממש. אף אחד לא נכנס לקורס ריאקט ומבקש ללמוד איך עובד Higher Order Component או לקורס JavaScript ומבקש ללמוד איך לממש לבד Class System עם פרוטוטייפים.

מצד שני כשאנשים נכנסים לקורס על בדיקות הם קודם כל רוצים לשמוע איך להשתמש ב Mock Objects, ב Spies ובכל היכולות הכי מתקדמות של ספריית הבדיקות. בקורס ריאקט אנשים ישאלו אותי לעתים קרובות איך עובדים עם Concurrent Mode וכמובן על useContext ו useMemo.

ויש סיבה למשחק הזה.

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

האמת הפוכה לגמרי: כמעט כל קוד בדיקה שראיתי שהשתמש ב Mock Objects כדי "לדרוס" חלקים במערכת היה אפשר לשכתב בקלות לגירסה יותר אמינה בלי אותם Mock Objects. כמעט אף אחד לא צריך את PureComponent או useMemo כי ריאקט עובד טוב מספיק בלעדיו.

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

זה נראה לך בסדר?

13/04/2021

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

זה נראה לך בסדר?

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

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

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

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