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

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

טיפ גיט: קומיט זה לא הסוף

13/02/2020

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

כמעט כל המתכנתים שראיתי שעובדים בגיט מרגישים שאחרי commit חייבים לעשות push.

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

שימו לב ל-4 קומיטים לדוגמא:

9a550c6 (HEAD -> master) actually this looks better in a class
e837614 changed some texts
3aa43e8 moved code to function
3785d2d intiial commit

ה diff בין הקומיט הישן ביותר לחדש ביותר נראה כך:

diff --git a/a.rb b/a.rb
index 15aaec7..de05881 100644
--- a/a.rb
+++ b/a.rb
@@ -1 +1,8 @@
-puts "hello world"
+class Greeter
+  def hi
+    puts "Hello World"
+  end
+end
+
+g = Greeter.new
+g.hi

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

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

$ git reset --soft 3785d2d
$ git add .
$ git commit -m 'Changed code to use object oriented syntax'

וקיבלתי לוג הרבה יותר נקי:

6a4e1ad (HEAD -> master) Changed code to use object oriented syntax
3785d2d intiial commit

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

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

נ.ב. רוצים ללמוד יותר לעומק איך לעבוד ב git ולשחק עם קומיטים כאילו היו כדורי ג'אגלינג? יש לי קורס וידאו שתפור בדיוק עליכם. מתחילים כאן: https://www.tocode.co.il/bundles/git/

שלוש סיבות לשלב TypeScript בפרויקט ריאקט הבא שלכם

12/02/2020

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

המשך קריאה

שלבים לפיתרון בעיה

11/02/2020

  1. אני לא מבין מה רוצים ממני

  2. אין לי מושג איך לגשת לזה

  3. יש לי רעיון אבל אין לי מושג איך ליישם אותו

  4. התחלתי ליישם את הרעיון אבל זה לא עבד - לא ברור לי למה

  5. הבנתי למה הרעיון שלי לא עבד. עכשיו שוב אין לי שום רעיון.

  6. ממשיכים את 2-5 בלולאה, לפעמים כמה עשרות פעמים. כל פעם לומדים עוד משהו על הבעיה ועל עוד דרך שלא עובדת.

  7. הצלחתי ליישם פיתרון שעובד!

  8. אה, אבל בעצם יש את מקרה הקצה הזה שלא חשבתי עליו.

  9. יש לי רעיון איך לתקן את מקרה הקצה הבעייתי.

  10. ממשיכים את 8-9 בלולאה, לפעמים כמה עשרות פעמים.

  11. יש לי קוד שעובד!

  12. מה המשימה הבאה?

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

בשביל מה את בונה את זה

10/02/2020

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

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

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

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

ארבעה רעיונות פשוטים לפרויקטים ב Arduino

09/02/2020

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

המשך קריאה

בואו נמצא סדרות של מספרים עוקבים ב Python

08/02/2020

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

arr = [1, 2, 3, 10, 2, 3, 4, 5, 6, 9, 9, 1, 2, 3, 1, 2, 3]

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

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

(0, 3)
(3, 1)
(4, 5)
(9, 1)
(10, 1)
(11, 3)
(14, 3)

כל רצף מזוהה על ידי האינדקס בו מתחיל הרצף (המספר הראשון) ואורך הרצף (המספר השני). כך המערך מתחיל באינדקס 0 ברצף של שלושה מספרים עוקבים - המספרים 1, 2 ו-3 ואחריהם באינדקס 3 יש לנו רצף קצר יותר של מספר יחיד הוא המספר 10.

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

def consecutives(arr):
    start = 0
    i = start
    while i < len(arr) - 1:
        if arr[i] + 1 == arr[i + 1]:
            i += 1
        else:
            yield(start, i - start + 1)
            start = i + 1
            i = start
    yield(start, i- start + 1)

ואחרי שיש לנו את הפונקציה קל למצוא את כל הרצפים עם לולאת for רגילה:

for seq in consecutives(arr):
    print(seq)

או למצוא את הרצף הכי ארוך עם פונקציית max:

print(max(consecutives(arr), key=lambda s: s[1]))

או למצוא את הרצף שסכום האיברים בו הוא הגדול ביותר (שוב עם max):

print(max(consecutives(arr), key=lambda s: sum(arr[s[0]:s[0]+s[1]])))

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

היום למדתי: למה חשוב לכתוב distinct כשסופרים ב SQL

07/02/2020

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

SELECT  courses.*, count(lessons.id) as lessons_count,
    count(course_labels.id) as labels_count
    FROM "courses"
    LEFT OUTER JOIN "course_labels" ON "course_labels"."course_id" = "courses"."id"
    LEFT OUTER JOIN "lessons" ON "lessons"."course_id" = "courses"."id"
    GROUP BY courses.id

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

הבעיה עם השאילתה היא שה join הכפול גורם לשכפול של כל השורות בטבלה השניה - במילים אחרות כך נראית התוצאה כשאני מוריד את ה count וה group by:

sqlite> SELECT courses.id, lessons.id as lessons_count, course_labels.id as labels_count_1 FROM "courses" LEFT OUTER JOIN "course_labels" ON "course_labels"."course_id" = "courses"."id" LEFT OUTER JOIN "lessons" ON "lessons"."course_id" = "courses"."id";
id|lessons_count|labels_count_1
1|1|6
1|2|6
1|3|6
1|5|6
2|4|
3||

ה id של ה course_label היחיד במערכת הבדיקה שלי הוא 6, והוא מופיע 4 פעמים כדי להתאים ל-4 שיעורים שיש בקורס.

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

SELECT 
    courses.*,
    count(distinct lessons.id) as lessons_count,
    count(distinct course_labels.id) as labels_count_1
FROM "courses"
    LEFT OUTER JOIN "course_labels" ON "course_labels"."course_id" = "courses"."id"
    LEFT OUTER JOIN "lessons" ON "lessons"."course_id" = "courses"."id"
GROUP BY courses.id

קוד חכם מדי. קוד טיפש מדי.

06/02/2020

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

Lesson.joins(course: :user).select('lessons.*', 'users.id as user_id').where('user_id = ?', user.id)

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

גישה הרבה יותר מדויקת תיראה כך:

Lesson.where(course: user.courses)

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

word =~ /^[A-Z]/

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

word =~ /^\p{Lu}/

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

גם אם הקוד עובד - כשהוא טיפש מדי, או חכם מדי, שווה להתאמץ ולתקן.

ומה תעשה כשזה נשבר?

05/02/2020

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

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

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

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