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

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

על תבונה ורגישות (של מחשבים)

25/03/2018

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

print(sum(n * n for n in range(100)))
print(sum([n * n for n in range(100)]))

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

ב Ruby שתי השורות האלה אולי נראות דומה אבל השניה תכשיל את כל התוכנית (גם אם יש פונקציה בשם max שהוגדרה לפני כן):

puts max(2, 3)
puts max (2, 3)

וב Bash שתי הפקודות האלה עשויות למצוא קבצים אחרים לגמרי:

find . -name '*.c'
find . -name *.c

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

שתי דרכים לכתוב קוד לשימוש חוזר

24/03/2018

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

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

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

  2. אפשר להתחיל עם מימוש אחד בלי חשיבה מתוחכמת על שימוש חוזר והיררכיית מחלקות. גם המימוש השני והשלישי יהיו פיתוח מאפס עם הרבה copy/paste ורק במימוש הרביעי לחפש את המשותף, לבנות ספריה גנרית ואיתה לכתוב מחדש את שלושת המימושים הראשונים.

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

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

23/03/2018

  1. לחשוב לפני שמתחילים לכתוב.

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

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

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

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

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

  7. ללמוד לרוחב אבל בעיקר לעומק - כל חודשיים יוצאת ספריית JavaScript חדשה ומדליקה ונעלמת באותה המהירות שהופיעה. בזמן שהילדים לומדים את התחביר של עוד Client Side Router אנחנו מעדיפים לכתוב אחד בעצמנו מאפס כדי להבין איך זה בנוי מבפנים. כך קל לנו להיכנס לקוד של כל ספריה חדשה שמגיעה וללמוד אותה הרבה יותר מהר.

הודעות שגיאה

22/03/2018

הקוד הזה בפייתון כותב את הטקסט Hello World לקובץ בשם out.txt:

def run(myfile, text):
    myfile.write(text)

f = open('out.txt', 'w')
run(f, 'Hello World\n')
f.close()

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

import multiprocessing as mp

def run(myfile, text):
    myfile.write(text)

f = open('out.txt', 'w')

p = mp.Process(target=run, args=(f, 'hello world'))
p.start()
p.join()

f.close()

הבעיה? הקובץ הפעם נשאר ריק.

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

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

מה חשוב השם

21/03/2018

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

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

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

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

שורה אחת שעושה את הכל

20/03/2018

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

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

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

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

הכל או כלום

19/03/2018

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

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

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

השוואה בין הטמעת 4 ספקי סליקת אשראי לאתר שלכם

18/03/2018

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

המשך קריאה

כשגוגל טועה

17/03/2018

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

sum('123' - '0')

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

sum(int(x) for x in ['1', '2', '3'])

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

בדוגמא כאן חיפוש בגוגל עבור "python substract string from list" מוצא המון תוצאות לא רלוונטיות (בעיקר על הפונקציה remove), לעומת זאת החיפוש "python convert list to integers" מציג את הפיתרון כבר בתוצאה הראשונה. כשגוגל טועה זה כמעט תמיד בגלל ששאלנו את השאלה הלא נכונה.

ינון קונספט

16/03/2018

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

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

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

import fileinput
from functools import reduce
from itertools import chain

def longer(acc, val):
    if len(acc) > len(val):
        return acc
    else:
        return val

def flatten(listOfLists):
    return chain.from_iterable(listOfLists)


print(
    reduce(
        longer,
        flatten(
            map(
                lambda s: s.split(),
                fileinput.input()
            )
        )
    )
)

ואותה התוכנית בשפת Ruby:

class String
  def longer(other)
    length > other.length ? self : other
  end
end

puts ARGF.flat_map(&:split).reduce(&:longer)

ואותה התוכנית בשפת Elixir:

defmodule My do
  def longer(val, acc) do
    if String.length(acc) > String.length(val) do
      acc
    else
      val
    end
  end
end

IO.stream(:stdio, :line)
|> Enum.flat_map(&String.split/1)
|> Enum.reduce(&My.longer/2)
|> IO.puts

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

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