es
Feedback
ToCode

ToCode

Ir al canal en Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Mostrar más
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
Sin datos30 días
Archivo de publicaciones
ToCode
1 419
# טיפ גיט: ריבייס אחרי אמנד פקודת git commit --amend היא דרך מאוד נוחה לשנות פרטים של קומיט או הודעת קומיט אחרי שיצרתי אחת בטעות. הבעיה איתה שאם כבר יצרתי ענף שיצא מהקומיט הישן אותו קומיט שחשבתי שמחקתי יחזור כשאני אמזג מחדש את הענף. בעזרת git rebase --onto אפשר לפתור את הסיפור יחסית בקלות ולתקן את הענף. ## הריפו הבעייתי בשביל הדוגמה ניצור ריפו בתיקיה חדשה עם הפקודות:
$ git init .
$ git commit --allow-empty -m c1
$ git commit --allow-empty -m c2
$ git commit --allow-empty -m c3
$ git switch -c dev
$ git commit --allow-empty -m d1
$ git commit --allow-empty -m d2
$ git commit --allow-empty -m d3
$ git commit --allow-empty -m d4
עכשיו נחזור ל main ונעדכן את הודעת הקומיט האחרונה:
$ git switch main
$ git commit --amend -m 'fixed commit message' --allow-empty
והלוג:
$ git log --oneline --graph --all

* cebfcab (HEAD -> main) fixed commit message
| * 784bb2c (dev) d4
| * 4f51f1e d3
| * 595d953 d2
| * cbcb09c d1
| * 707a378 c3
|/
* e09e80f c2
* aa0e4ca c1
## מה יקרה ב merge בואו נניח שלא ייחסתי חשיבות ל amend ואני רוצה למזג חזרה את ענף הפיתוח. אני מפעיל:
$ git merge dev
ולהפתעתי הקומיט לפני התיקון c3 נכנס ללוג ורק מבלבל אותי:
*   72ab395 (HEAD -> main) Merge branch 'dev'
|\
| * 784bb2c (dev) d4
| * 4f51f1e d3
| * 595d953 d2
| * cbcb09c d1
| * 707a378 c3
* | cebfcab fixed commit message
|/
* e09e80f c2
* aa0e4ca c1
## מה יקרה ב rebase נחזיר את המצב לאיך שהיה לפני ה merge עם:
$ git reset cebfcab
וננסה להשתמש ב rebase במקום ב merge. אני מפעיל:
$ git switch dev
$ git rebase main
אחרי הפעלה אני מסתכל שוב בלוג ולהפתעתי אני שוב רואה את c3, למרות שעשיתי לו amend וההודעה היתה צריכה להימחק:
* 1cbdec3 (HEAD -> dev) d4
* 9008326 d3
* c4c2918 d2
* 17683c9 d1
* e69bcf4 c3
* cebfcab (main) fixed commit message
* e09e80f c2
* aa0e4ca c1
## תיקון עם rebase onto נחזיר את המצב לאיך שהיה לפני ה rebase עם:
$ git reset 784bb2c
ועכשיו הגיע הזמן שנשים לב שיש בעיה ונתקן אותה. במקום להשתמש ב rebase רגיל אני מבקש מגיט שיעשה ריבייס רק לקומיטים שקרו אחרי c3 בענף dev וישים אותם על main. זה נראה ככה:
$ git switch dev
$ git rebase --onto main 707a378 dev
$ git log --oneline --graph --all

* c6a7432 (HEAD -> dev) d4
* d6071d0 d3
* 06107ec d2
* 2eb5f05 d1
* cebfcab (main) fixed commit message
* e09e80f c2
* aa0e4ca c1
וככה בדיוק רציתי שההיסטוריה תיראה. הקומיט הבעייתי תוקן והענף שיצא ממנו עודכן להיראות כאילו הוא יצא מהקומיט עם ההודעה הנכונה. אם לא היינו שמים לב ל hash-ים היה אפשר לדמיין שפשוט שיניתי את הודעת הקומיט בלי להשפיע על שום דבר אחר.

ToCode
1 419
# טיפ גיטהאב: איך ליצור Pull Request משורת הפקודה למרות שהרבה פעמים אנחנו מבלבלים בין השניים, גיטהאב אינו גיט וגם מנגנון ה Pull Requests של גיטהאב (כמו גם של אינסוף ספקי איחסון גיט אחרים) אינו חלק מגיט. אבל רק בגלל שמשהו לא חלק מגיט לא אומר שאנחנו חייבים לעבוד בממשק הדפדפן. ספציפית לגיטהאב יש חבילת כלי פיתוח משורת הפקודה, שמאפשרים לבנות סקריפטים לכל פעולה נפוצה שנרצה לעשות. בטיפ של היום אני רוצה ליצור Pull Request בצורה אוטומטית כדי לחסוך לעצמי הקלדה של הטקסט בכותרת ובתוכן ה PR, כי יש לי כבר סקריפט שיודע לייצר את הטקסטים האלה מתוך הקוד. שלב ראשון אני מתקין את חבילת כלי שורת הפקודה של גיטהאב לפי ההוראות בעמוד הזה: https://cli.github.com/manual/installation על מק זה היה באמצעות brew install gh, ועל חלונות מורידים קובץ התקנה מהקישור https://github.com/cli/cli/releases/tag/v2.11.3. יש גם התקנה ללינוקס אבל אני אתן לכם להיכנס לקישור בשביל למצוא את ההוראות. אחרי שיש לנו את הכלי על המחשב אני צריך לחבר אותו לגיטהאב. הפקודה לחיבור היא:
$ gh auth login
אחרי הפעלה ייפתח לכם דפדפן ותצטרכו לאשר שאתם זה אתם ושאתם מסכימים לגשת לגיטהאב משורת הפקודה. ואחרי החיבור נוכל להשתמש בפקודה הבאה כדי ליצור Pull Request משורת הפקודה. בדוגמה שלי אני שולח PR למשתמש ynonp-test ולפרויקט cli-pull-request-demo:
gh pr create -R ynonp-test/cli-pull-request-demo --title "Pull request title" --body "Pull request body"

ToCode
1 419
# רק עוד שעה האם הקרנל של לינוקס היה יותר יציב אם לינוס טורוולדס היה מוותר על שעתיים שינה בלילה, ובמקומן יושב לכתוב עוד קוד? או שמה שהופך את הקרנל ליציב הוא בדיוק שיקול הדעת המקצועי של לינוס, שיקול דעת שעלול להיפגע אם הוא יוותר על שעתיים שינה? והקוד שלכם - האם הוא יהיה יותר טוב ככל שהמפתחים מכירים יותר לעומק את הטכנולוגיה? ככל שהמפתחים ישנים יותר טוב ורגועים יותר? ככל שהשעות שמושקעות בקידוד מושקעות 100% בקידוד? דווקא בגלל שהפרויקט שלכם חשוב, לא כדאי להוסיף היום רק עוד שעה של עבודה. פרויקטים חשובים צריכים מתכנתים עם שיגרת חיים שכוללת גם זמני לימוד, זמן פנאי, זמן למשפחה ולתחביבים ולכל דבר אחר שהופך אתכם למה שאתם.

ToCode
1 419
בהשוואה לפייתון ההבדל המרכזי הוא שבקלוז'ר נוח מאוד לעבוד עם מילון ולשמור את כל המשתנים בתור שדות במילון. המילון הוא הערך היחיד שעובר ב reduce בין איטרציות ופונקציות העדכון מקבלות את הערך הישן משם. בפייתון האילוץ הזה לא קיים ולא היתה לי בעיה להגדיר משתנה לכל אחד מהמדדים שצריך לשמור. לדוגמה החלק השני בפייתון נראה ככה:
pos = 0
depth = 0
aim = 0

with open('day2.txt') as f:
    for line in f:
        op, count = line.split()
        match [op, count]:
            case ["forward", n]:
               pos += int(n)
               depth += (aim * int(n))
            case ["down", n]:
                aim += int(n)
            case ["up", n]:
                aim -= int(n)

print(pos * depth)
זה פיתרון יותר קצר שכולל גם את הלולאה וגם את פונקציית הטיפול, הכל באותו מקום. שני הדברים שהופכים את קוד הפייתון לקליל יותר הם ההמרה למספר (הפונקציה int יותר קצרה מ Integer/parseInt) וה Pattern Matching שבינתיים לא מצאתי מנגנון מקביל לו ב Clojure.

ToCode
1 419
# משחקים עם קלוז'ר: פיתרון AoC 2021 Day 2 אני ממשיך עוד יום בלימודי הקלוז'ר שלנו במטרה לקבל נקודת מבט נוספת על ההבדלים בין תכנות פונקציונאלי לפרוצדורלי, ואיך שפות מעודדות אותנו לבחור כיוון מחשבה מסוים. היום הפיתרון הקצר יותר דווקא היה בפייתון, אבל גם לקלוז'ר היה את הקסם שלו. ## מה אנחנו בונים התרגיל של יום 2 היה פשוט פיענוח הוראות: אנחנו בצוללת ומקבלים דף עם המסלול המתוכנן. המסלול בנוי בתור רצף של שורות בכל שורה יש פקודה כמו forward, down ו up, ומספר שאומר בכמה יחידות לזוז. הצוללת נוסעת בים דו מימדי ויש לה מיקום אופקי ועומק. קלט לדוגמה נראה כך:
forward 5
down 5
forward 8
up 3
down 8
forward 2
בחלק הראשון הפקודה forward X אומרת לזוז X צעדים קדימה, הפקודה down למטה והפקודה up למעלה. המשימה היא לגלות איפה תהיה הצוללת בסוף המסלול בעזרת שני ערכים - עומק ומיקום אופקי, ולהדפיס את מכפלתם. בחלק השני של התרגיל העניינים מסתבכים ומשמעות הפקודות משתנה. אנחנו צריכים לטפל בערך נוסף שנקרא aim. הפקודות אותן פקודות אבל המשמעות הפעם היא: 1. פקודת down מעלה את ה aim 2. פקודת up מורידה את ה aim 3. פקודת forward זזה קדימה וגם מוסיפה את aim כפול הפרמטר שקיבלה לעומק. ## פיתרון בקלוז'ר קלוז'ר היא שפה פונקציונאלית ובתור שכזו הלולאה שאנחנו מריצים כדי להפוך רשימה של הוראות לערך יחיד היא לולאת reduce. כל שלב ב reduce הוא הפעלה של פונקציה שמקבלת את הערך "שאספנו" עד עכשיו ואת הערך החדש, ומחזירה ערך צובר חדש שישמש כקלט לאיטרציה הבאה. בגלל שהתרגיל מחולק לשני חלקים ובכל אחד משמעות הפקודות שונה, אפשר לכתוב פונקציה לטיפול באיטרציה של ה reduce לכל אחד משני החלקים. בחלק הראשון היא תהיה:
(defn part1 [position line]
  (let [[command value-str] (str/split line #"\W")
        value (Integer/parseInt value-str)]
    (case command
      "forward" (update position :horizontal + value)
      "up"      (update position :depth + (* -1 value))
      "down"    (update position :depth + value))))
ובחלק השני היא תהיה:
(defn part2 [position line]
  (let [[command value-str] (str/split line #"\W")
        value (Integer/parseInt value-str)]
    (case command
      "forward" (-> position
                    (update :horizontal + value)
                    (update :depth + (* (:aim position) value)))
      "up"      (update position :aim + (* -1 value))
      "down"    (update position :aim + value))))
הלולאה הראשית שקוראת את הקלט ומריצה את ה reduce היא:
(let [input (->> "input.txt"
                 (slurp)
                 (str/split-lines))
      pos1 (reduce part1 {:horizontal 0 :depth 0} input)
      pos2 (reduce part2 {:horizontal 0 :depth 0 :aim 0} input)]
  (println (apply * ((juxt :horizontal :depth) pos1)))
  (println (apply * ((juxt :horizontal :depth) pos2))))
הפקודה שחוזרת על עצמה הכי הרבה כאן היא update, לדוגמה:
"forward" (update position :horizontal + value)
פקודת update מקבלת מילון, מפתח, פונקציית עדכון ופרמטרים ראשוניים לפונקציית העדכון, ומחזירה מילון חדש אחרי הפעלת פונקציית העדכון על הערך הישן וכתיבת הערך שהחזירה למפתח. בדוגמה שהדבקתי פונקציית העדכון היא פלוס, המפתח הוא :horizontal והפרמטר הקבוע לפלוס הוא הערך שהופיע בשורה. פקודת update תוסיף לפלוס את הפרמטר השני (למעשה זה יהיה הפרמטר הראשון) שהוא הערך שכרגע נמצא במילון במפתח :horizontal, ותכתוב את התוצאה למילון חדש שאותו היא גם תחזיר. המילון החדש הזה יהיה הקלט לאיטרציה הבאה של ה reduce וככה בסוף נקבל מילון שמייצג את המיקום האחרון של הצוללת. פקודה מעניינת נוספת כאן היא פקודת ההדפסה בסוף:
(println (apply * ((juxt :horizontal :depth) pos1)))
בגלל ש pos1 הוא מילון, הפקודה juxt לוקחת ממנו את הערכים של :horizontal ו depth ומחזירה מערך עם שניהם, ואז * צריכה לכפול את שני האיברים במערך. אבל בגלל ש * מצפה לקבל שני פרמטרים ופה היא מקבלת מערך, אנחנו משתמשים ב apply שהוא כמו כוכבית של פייתון - ותפקידו להפוך מערך של שני איברים לשני פרמטרים נפרדים לפונקציה. ## פיתרון ישן בפייתון

ToCode
1 419
הדבר הראשון שקופץ לראש בקריאת הפייתון הוא הניסיון שלי להגיע לפיתרון בלי לחשוב יותר מדי. צריך לזכור מה הערך הקודם שקראתי? נו בסדר ניצור משתנה בשבילו. צריך לספור דברים? נו אין בעיה ניצור עוד משתנה. זו לא גישה רעה אבל היא כן גרמה ל Code Reuse להיות יותר קשה, ובאמת בפייתון היה יותר קל לכתוב כל אחד מהחלקים בנפרד. ## מחשבות להמשך ההשוואה בין פייתון לקלוז'ר לא הוגנת, כי אפשר היה לכתוב קוד פייתון הרבה יותר טוב ממה שכתבתי. ועדיין כשחיפשתי גם ברשת פיתרונות פייתון ראיתי שאני לא היחיד שלקחתי את הגישה של הלולאה. הנה פיתרון נוסף מהרשת בפייתון שלקח אותה גישה:
import sys

prev_depth = int(input())
depth = int(input())
count = 0
while depth:
    if depth > prev_depth:
        count += 1
    prev_depth = depth
    v = sys.stdin.readline()
    if v == '':
        break
    depth = int(v)

print(count)
והנה עוד אחד:
nums = [199, 200, 208, 210, 200, 207, 240, 269, 260, 263]
cnt = 0
for i in range(len(nums) - 1):
    if nums[i] < nums[i+1]:
        cnt += 1
print(f'Ans: {cnt}') # 7
מצד שני הפיתרון הזה בפייתון לוקח גישה דומה הרבה יותר למה שכתבתי בקלוז'ר:
def task1():
    data = load_data()
    x, y = data[:-1], data[1:]
    result = sum(1 for xs, ys in zip(x, y) if ys > xs)
    print(result)
כך שאין שום דבר בשפה עצמה שמכריח גישה מסוימת. בצד של קלוז'ר הקוד שלי היה די טיפוסי ומחיפוש ברשת היו לא מעט פיתרונות עם partition. פיתרון אחד יותר קצר ויתר על כל העסק של חלוקה לזוגות והשתמש בעובדה ש map בקלוז'ר יכול לקבל כמה רשימות:
(defn answer-01 [input]
  (count (filter true? (map < input (rest input)))))
ואז התוכנית כולה הופכת ל:
(ns aoc21-01
  (:require [clojure.string :as str]))

(def input (map parse-long (str/split-lines (slurp "input.txt"))))

(defn answer-01 [input]
  (count (filter true? (map < input (rest input)))))

(def three-sliding-window
  (map + input (next input) (nnext input)))

(defn answer-02 []
  (answer-01 three-sliding-window))

(prn (answer-01 input))
(prn (answer-02))

ToCode
1 419
# משחקים עם קלוז'ר: פיתרון AoC 2021 Day 1 בעקבות המלצה של חבר התחלתי לקרוא על קוטלין, ומאוד רציתי לכתוב כמה פוסטים על התוכניות הראשונות שאני כותב בקוטלין וכמה היא שפה נחמדה. הבעיה שלא היה קליק אז עזבתי את קוטלין לבינתיים, אבל בגלל שכבר הייתי בתוך ה JVM המשכתי לעבוד קצת על קלוז'ר. ומכיוון שהדרך הכי טובה לתרגל שפה היא לפתור כמה תרגילים מ Advent Of Code התחלתי עם פיתרון היום הראשון מ 2021 בקלוז'ר. התוצאה בפוסט לפניכם וגם עם בונוס של השוואה לפייתון כדי להבין את ההבדלים בגישות. ## מה אנחנו בונים את התרגיל המלא אפשר למצוא בקישור כאן: https://adventofcode.com/2021/day/1. הבקיצור ובעברית הוא שיש לנו סידרה של מספרים, למשל:
199
200
208
210
200
207
240
269
260
263
ואנחנו צריכים להבין כמה פעמים המספרים עולים, כלומר כמה מספרים יש בסידרה שהם גדולים יותר מהמספר שהיה לפניהם. במקרה שלנו המספר הוא 7. בחלק השני של התרגיל ובשביל לעשות את המשחק יותר מעניין, אנחנו צריכים לחלק את הסידרה לשלשות, לחשב סכום של כל שלושה מספרים צמודים ואז לספור את מספר העליות בסידרת הסכומים. בדוגמה שלנו זה אומר שהמספר הראשון בסידרת הסכומים הוא 199+200+208 שזה 607, המספר השני בסידרת הסכומים הוא 200+208+210 שזה 618 וכך הלאה. בסידרה הזאת יש 5 מספרים שגדולים יותר מזה שהיה לפניהם. ## פיתרון קלוז'ר האבחנה הראשונה שהיתה לי מהתרגיל הזה היא שיהיה נוח לחלק את הרשימה לזוגות. אם אצליח להפוך את הרשימה לרשימה כזאת:
199, 200
200, 208
208, 210
210, 200
200, 207
207, 240
240, 269
269, 260
260, 263
אז אפשר יהיה להריץ filter פשוט על רשימת הזוגות כדי לסנן רק את אלה שבהם השני גדול מהראשון ולספור כמה זוגות נשארו. וככה הגעתי לפונקציה partition של קלוז'ר שיודעת לעשות בדיוק את זה:
user=> (partition 2 1 [199 200 208 210])
((199 200) (200 208) (208 210))
עכשיו אפשר להשתמש בטריק הזה כדי לספור כמה אלמנטים הם גדולים יותר מזה שהיה לפניהם, כלומר בכמה מהזוגות האיבר השני גדול יותר מהראשון.
(defn count-inc [input]
  (count (filter (partial apply <)
                 (partition 2 1 input))))
ולסיום כל מה שנשאר זה לקרוא את קובץ הקלט, לשבור אותו לשורות, להפוך כל שורה למספר ולהשתמש בפונקציות שכתבתי. בחלק הראשון זה בסך הכל להפעיל את count-inc על הקלט, ובחלק השני קודם אני מחלק את הרשימה לשלשות, אחרי זה סוכם כל שלשה, ואז על רשימת התוצאות מפעיל את count-inc. זה הקוד שנשאר בתוכנית:
(require '[clojure.string :as str])

(let [input (->> "input.txt"
                 (slurp)
                 (str/split-lines)
                 (map read-string)
                 )]

  (println (count-inc input))

  (println (count-inc 
            (map 
              (partial apply +) 
              (partition 3 1 input)))))
סך הכל הקוד לשני החלקים לקח 18 שורות (כולל שורות רווח), שזה ממש אחלה. ## פיתרון ישן שהיה לי בפייתון אחרי שכתבתי את הפיתרון בקלוז'ר נזכרתי שכשהתרגיל פורסם במקור פתרתי אותו בפייתון. מכל מיני סיבות לא השקעתי יותר מדי בפיתרון מתוחכם וביום רגיל לא הייתי מפרסם את הקוד הזה בשום מקום, אבל רק בשביל השוואה אני חושב שזה כן יהיה רעיון נחמד להדביק אותו כאן:
def part1():
    count = 0
    last_value = 0

    with open('day1.txt') as f:
        for line in f:
            if last_value > 0 and int(line) > last_value:
                count += 1

            last_value = int(line)

    print(count)

def part2():
    count = 0
    last_value = []

    with open('day1.txt') as f:
        for line in f:
            if len(last_value) < 3:
                last_value.append(int(line))
                continue

            next_window = last_value[1:] + [int(line)]
            if sum(next_window) > sum(last_value):
                count += 1

            print(f"prev={last_value}; next={next_window}")

            last_value = next_window

    print(count)

ToCode
1 419
# מדריך: בואו נכתוב צ'ט ב Python עם Web Sockets מנגנון Web Sockets הגיע מעולם האינטרנט ומאפשר לדפדפן ולשרת ווב לתקשר בצורה דו-כיוונית. זה מנגנון מרגש כיוון שהוא מהווה את הבסיס להרבה מערכות אינטרנט שאנחנו מכירים היום, במיוחד כל המערכות בהן דברים מתעדכנים על המסך כי מישהו עשה משהו במקום אחר (לדוגמה - מישהו עושה Like בפייסבוק ופתאום אני רואה את זה על המסך שלי, בלי לטעון מחדש את העמוד). שימוש פופולרי ב Web Sockets הוא לבניית צ'ט בין משתמשים, ואפילו כתבתי בעבר מדריך למימוש תקשורת דו כיוונית בין שרת פייתון ללקוח ריאקט. היום ארצה להראות איך לחבר את אותו שרת פייתון ללקוח פייתון, כדי לבנות צ'ט משורת הפקודה בפייתון בלבד. ## פיתוח קוד השרת הספריה python-socketio מספקת מימוש מאוד נוח לעבודה עם Web Sockets בפייתון. ב Web Sockets לקוח יכול לשלוח הודעה לשרת ואז מופעל קוד בצד השרת, או ששרת שולח הודעה ללקוח ואז מופעל קוד בצד הלקוח. לכן בצד השרת אני בסך הכל צריך לכתוב פונקציה שתתאים לכל הודעה שהשרת יכול לקבל, ולעטוף את זה בקצת קוד מנהלה שיפעיל את השרת. במקרה של צ'אט מספיקה לי פונקציה אחת - אני אקרא לה message - שתופעל כשנכנסת הודעה, תדפיס אותה למסך לצרכי debug ואז תשלח אותה לכל הלקוחות המחוברים. הקוד המלא של השרת יהיה:
from aiohttp import web
import socketio

sio = socketio.AsyncServer(cors_allowed_origins='*', aync_mode='aiohttp')
app = web.Application()

sio.attach(app)

@sio.on('message')
async def message(sid, message):
    print("Socket ID: " , sid)
    print(message)
    await sio.emit('message', message, broadcast=True);

if __name__ == '__main__':
    web.run_app(app, port=4000)
ובשביל להפעיל אותו נצטרך להתקין את הספריות socketio ו aiohttp:
$ pip install python-socketio aiohttp
## פיתוח צד לקוח הקוד בצד הלקוח רץ בלולאה, קורא שורה מהמשתמש ושולח אותה לשרת. בדרך הוא יכול לקבל הודעות מהשרת (שלקוחות אחרים שלחו). בגלל העבודה עם שורת הפקודה יותר נוח לי לעבוד בממשק Threads מאשר בממשק asyncio, ולכן אני מתקין את החבילה מבוססת ה Threads עם הפקודה:
$ pip install "python-socketio[client]"
בחבילה זו פונקציה שמטפלת באירוע לא צריכה להיות async, והיא אוטומטית "תתפרץ" לתוכנית כשאירוע יקרה. האירוע היחיד שיכול לקרות הוא קבלת הודעה ולכן הקוד המלא ללקוח נראה כך:
import asyncio
import socketio

sio = socketio.Client()

@sio.event
def message(data):
    print('I received a message!')
    print(data)

sio.connect('http://localhost:4000')
print('my sid is', sio.sid)
while True:
    line = input(":> ")
    if len(line) > 0:
        sio.emit('message', {'text': line})
## הפעלה ושיחה אני שמרתי את השרת בקובץ בשם server.py ואת הלקוח בקובץ בשם client.py, ולכן מפעיל בחלון אחד שרת ובעוד שני חלונות שני לקוחות שונים:
# first window
$ python server.py

# second window
$ python client.py

# third window
$ python client.py
עכשיו אפשר לכתוב שורת טקסט באחד החלונות של הלקוחות ולראות את אותה שורה מופיעה בחלון של הלקוח השני, או אם מזמינים חבר שיתחבר לאותה רשת (ומתקנים את הכתובות) אפשר גם לדבר בין כמה מחשבים שונים.

ToCode
1 419
# טיפ פלקסבוקס: המאפיין width על הילדים דמיינו שניה דף HTML שמחולק לתיבת צד ותוכן מרכזי. נו, אתם מכירים כאלה, משהו כזה:
<div class="main">
  <div class="sidebar" >
    <ul>
      <li>hello world</li>
      <li>hello world</li>
      <li>hello world</li>
    </ul>
    
  </div>
  <div class="content">
    <p>
      Flexbox is really cool if you know how to use it
    </p>
  </div>
</div>
עם הגדרות ה CSS הבאות אפשר לקבוע שהרשימה בתיבת הצד תיקח 300 פיקסלים והתוכן המרכזי יקבל את כל השאר:
.main {
  display: flex;
}

.sidebar {
  width: 300px;
}

.content {
  flex: 1;
}
אבל אל תנסו את זה בבית. המאפיין width של תיבת הצד הוא רק חלק מהסיפור. נכון, ב HTML הדוגמה הוא באמת יקבע את הרוחב, אבל זה רק בגלל שהדפדפן לא מצא סיבה טובה לכווץ אותו קצת. אם נעדכן את ה HTML ונוסיף תמונה רחבה:
<div class="main">
  <div class="sidebar" >
    <ul>
      <li>hello world</li>
      <li>hello world</li>
      <li>hello world</li>      
    </ul>
    
  </div>
  <div class="content">
    <p>
      Flexbox is really cool if you know how to use it
    </p>
    <div>
      <img src="https://placekitten.com/400/300" />
    </div>
  </div>
</div>
ועכשיו נשנה את גודל החלון נוכל לראות שבשלב מסוים הדפדפן מתחיל להילחץ - הוא לא רוצה לצייר פס גלילה אופקי בשביל החתול, ולכן הוא מוותר על חלק מהרוחב של תיבת הצד. הנה קודפן למשחקים: <iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/ExQLEyK?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/ynonp/pen/ExQLEyK"> Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> הסיבה שהוא מרשה לעצמו ככה להשתולל היא שלא הגדרנו ערך למאפיין flex-shrink של תיבת הצד, וערך ברירת המחדל של מאפיין זה הוא 1 - שאומר שמותר לכווץ את האלמנט אם זה חוסך בעיות במקומות אחרים. דרך קשוחה יותר לקבוע רוחב לילד בתוך מיכל פלקס היא לציין את flex-shrink להיות 0 (ועל הדרך גם את flex-grow):
.sidebar {
  width: 300px;
  flex-shrink: 0;
  flex-grow: 0;
}
הגדרה כזאת מכריחה את הדפדפן להשאיר את האלמנט ברוחב 300, גם אם הוא היה מעדיף אחרת. ואם אנחנו כבר פה שווה להזכיר ש width הוא לא הדרך הכי טובה לקבוע גודל של ילד בתוך מיכל פלקס, בגלל שמיכל פלקס יכול גם להיות אנכי. המאפיין flex-basis יקבע את הרוחב או הגובה של האלמנט לפי כיוון המיכל ולכן הקוד הזה עדיף:
.sidebar {
  flex-basis: 300px;
  flex-shrink: 0;
  flex-grow: 0;
}
ולזה יש כבר קיצור דרך פלקסי שבטח פגשתם בכל מיני דוגמאות:
.sidebar {
  flex: 0 0 300px;
}

ToCode
1 419
הי כולם בוקר טוב אני מזכיר אם פספסתם בעוד עשר דקות נתחיל וובינר על OAuth. מוזמנים להיכנס בקישור: https://us06web.zoom.us/j/87293615835?pwd=SkNjaXNRYmVibXcwSjBEdmorRFZIdz09 בתוכנית - הסבר מה זה OAuth ואיזה בעיות זה בא לפתור פיתוח שרת API שמוגן על ידי OAuth פיתוח קלאיינט ריאקט שמאפשר ללקוחות להתחבר ולבצע פעולות ב API

ToCode
1 419
# פיצ'ר קריפ בהבנה פיצ'ר קריפ זו התופעה שקורית כשאני כותב מערכת ואומר "אם כבר יש לי X שווה לכתוב גם Y". הפיצ'ר קריפ מסיח את הדעת מהמטרה העסקית האמיתית של המערכת, ושולח אותנו לבנות דברים שהלקוחות לא רצו רק כי הם נראים בהישג יד. אנחנו יודעים לזהות פיצ'רים שנכנסו בדרך הזאת באמצעות אנאליטיקות - אם יש פיצ'ר שאף אחד לא משתמש בו, כנראה שהוא זחל פנימה בלי הזמנה. גם בלימוד נושא חדש יכול להיות פיצ'ר קריפ, אבל הפעם הרבה יותר קשה לזהות אותו. אם החלטתי ללמוד ריאקט יכול להיות שהמדריך הראשון שאכנס אליו יסביר איך להשתמש ב webpack כדי לבנות פרויקט ריאקט - ועכשיו אני מוצא את עצמי לומד webpack רק בשביל ללכת ללמוד ריאקט. זה פיצ'ר קריפ, כי את רוב העבודה שלי כמתכנת ריאקט אני אעביר בלי לגעת בוובפאק (ואולי אפילו אעבוד על פרויקט שבנוי בלעדיו). במצב כזה הדבר המשתלם לעשות הוא למצוא מדריך אחר שידלג על הוובפאק, או ללמוד רק את המינימום וובפאק שאני צריך כדי להקים פרויקט כדי שאוכל להתמקד במיומנות הליבה עליה אני עובד. אבל הבעיה האמיתית עם הבנה היא בדיוק ההיפך מפיצ'ר קריפ. רוב הפעמים בלימוד נושא חדש אני כל כך מתאמץ להגיע ל"משהו שעובד" שאני מוכן לדלג גם על ידע חיוני, ופוסל אותו בתור פיצ'ר קריפ. וככה אנחנו נפגוש מתכנתי צד שרת שלא יודעים SQL, כי יש ORM שכותב בשבילם את השאילתות. היכרות טובה עם SQL, או נושאי ליבה אחרים שחשובים להצלחה שלנו כמתכנתים אינה פיצ'ר קריפ. היא בדיוק הדבר שהופך אותנו למתכנתים טובים יותר, והיא בדיוק הדבר שמאפשר לנו ללמוד נושאים חדשים מהר יותר. כמעט תמיד שווה ללמוד קצת יותר מהמינימום הנדרש ולהסתכן בפיצ'ר קריפ, במקום להפסיד מיומנות שאחרי זה יהיה מאוד קשה להשלים אותה.