1 419
订阅者
无数据24 小时
无数据7 天
-230 天
帖子存档
1 419
# שידרוג תלויות לגירסה החדשה ביותר
פרויקטים רבים היום משתמשים בחבילות חיצוניות ובמיוחד בחבילות חיצוניות בקוד פתוח. בעבודה עם חבילות חיצוניות יש חשיבות לגירסאות ולמספרי הגירסה של אותן חבילות:
1. אם יש תיקון אבטחה או תיקון באג קריטי שלא שובר שום דבר אחר אנחנו רוצים לקבל אותו אלינו למערכת
2. אם יש שיפור שכולל שינוי ממשק או שינויים ששוברים קוד ישן, אנחנו עדיין רוצים לקבל אותו אלינו למערכת אבל בזהירות וכשיש לנו זמן.
מנגנוני ניהול החבילות בשפות השונות מתמודדים עם ההבדל בין שני סוגי השדרוגים באמצעות "נעילת" חבילות. אנחנו נועלים חבילה לגירסה ראשית מסוימת בעת ההתקנה, ואז כל פעם שנריץ פקודת עדכון נקבל רק עדכונים של גירסאות "קטנות", ובשביל לעדכן לגירסה ראשית חדשה יש להיכנס ידנית לקובץ התלויות ולשנות את הערכים בו.
מדי פעם (כן זה נדיר אני יודע) יהיו לנו כמה ימים בין פיצ'רים כדי לשדרג את כל התשתיות של הפרויקט. עבור הימים האלה ובעזרת כלים מיוחדים, תוכלו במכה אחת לשדרג את כל הספריות שיש להן גירסה ראשית חדשה, במכה אחת ולהכניס את עצמכם למערבולת של תיקונים והתאמות לעולם החדש. בואו נראה איך עושים את זה ב Ruby, Python ו Node.JS
## שידרוג חבילות ב package.json עם ncu
הכלי הראשון נקרא ncu שזה קיצור ל npm-check-updates. כלי זה יראה לכם כל תלות של הפרויקט שקיימת לה גירסה חדשה יותר, גם אם השינוי "שובר" את נעילת החבילות של package.json. אחרי התקנת הכלי תוכלו להפעיל:
$ ncu -u
בתיקיה עם קובץ package.json והוא יעדכן את הקובץ לגירסאות החדשות ביותר של כל ספריה.
## שידרוג חבילות ב requirements.txt עם pip-upgrader
הכלי pip-upgrader יעשה את אותו טריק אבל הפעם ב Python עם requirements.txt. אני אוהב להפעיל אותו עם האפשרות --skip-package-installation וכך הוא רק מעדכן את requirements.txt ואני יכול לבחור אחר כך אם להתקין או לו. אחרי התקנת הכלי עם pip פשוט נכנסים לתיקיה שיש בה פרויקט פייתון וקובץ requirements.txt ומפעילים:
$ pip-upgrade --skip-package-installation
בסיום בדיקה מול האינדקס הכלי ייתן לכם רשימה של חבילות הניתנות לשידרוג ויבקש מכם לבחור איזה חבילות אתם רוצים לשדרג (או אפשר לכתוב all כדי לשדרג את כולן).
## שידרוג חבילות ב Gemfile עם bundleup
חבר אחרון הוא רובי ופה הכלי נקרא bundleup, וגם הוא עושה בדיוק מה שכתוב בתיאור - אתם מפעילים אותו בתיקיה עם קובץ Gemfile והוא ישדרג את כל הגירסאות ב Gemfile לגירסה החדשה ביותר של כל חבילה. לאחר התקנת הכלי עם gem נוכל לכתוב:
$ bundleup --update-gemfile
ולקבל Gemfile שמכוון לגירסאות החדשות ביותר של כל חבילה.
שימו לב- באנדלאפ לא מעדכן חבילות שיש עבורן Comment ב Gemfile. זה אומר שאם יש לכם שתי שורות כאלה:
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
אז הג'ם לא יעודכן. לא מצאתי דרך טובה להיפטר מההתנהגות הזאת ולכן בפרויקטים בהם אני משתמש ב bundleup אני מוחק את כל ההערות מה Gemfile לפני הפעלה.
מכירים כלים נוספים טובים יותר או בשפות אחרות? ספרו בתגובות שגם אחרים יוכלו להנות.1 419
# בואו נבנה לבד פונקציית zip ב Python כדי להבין איך זה בנוי
שאלת פייתון קצרה שהגיעה אליי היתה "איך אני עוברת על כמה רשימות במקביל וכל פעם לוקחת איבר מרשימה אחרת?". לפני שארוץ לענות עם zip של פייתון בואו נלך לבנות לבד את הפיתרון כדי להבין את האתגרים.
## ניסיון ראשון: לולאה עם אינדקסים
כיוון אחד יכול להיות לרוץ בלולאה על range לפי גודל הרשימות וכל פעם לקחת איבר מרשימה אחרת. בקוד זה נראה כך:
one = [10, 20, 30]
two = ['a', 'b', 'c']
for i in range(len(one)):
item_one = one[i]
item_two = two[i]
print(item_one, item_two)
בהנחה שהרשימות באותו אורך הקוד עובד. הוא לא משכפל את הרשימות או אלמנטים מתוכן והוא די ברור. הבעיה הבולטת ביותר בגישה זו היא שאין פה אפשרות טובה להשתמש מחדש במנגנון או לשלב את המנגנון הזה עם כלים אחרים של פייתון.
לדוגמה אם הייתי רוצה לקחת שתי רשימות של מספרים ולהחזיר רשימה של סכום כל שני איברים מתאימים בין שתי הרשימות הקוד שלי היה נראה כך:
one = [10, 20, 30]
two = [1, 2, 3]
three = []
for i in range(len(one)):
three.append(one[i] + two[i])
בעיה נוספת קצת יותר עדינה בקוד היא שיחסית קשה להתאים אותו למספר משתנה של רשימות. אם הייתי רוצה להפעיל את אותו מנגנון עם 4 רשימות של מספרים ולחשב את סכומן הייתי צריך ממש לשנות את הקוד שכתבתי.
## הצעד הבא - פונקציה
בשביל להתקדם לכיוון Code Reuse טוב יותר אני יכול לדמיין פונקציה שמקבלת שתי רשימות (או כמה רשימות) ומחזירה רשימה חדשה בה כל איבר הוא Tuple של איברים מתאימים ברשימות המקוריות. בדוגמה האחרונה מהסעיף הקודם הפונקציה תחזיר את הרשימה הבאה:
[(10, 1), (20, 2), (30, 3)]
ואם נעביר לפונקציה יותר רשימות היא תמיד תוכל להחזיר Tuples גדולים יותר.
הנה קוד לדוגמה של פונקציה כזו, בשלב ראשון רק עם שתי רשימות:
def my_zip(list_a, list_b):
result = []
for i in range(len(list_a)):
result.append((list_a[i], list_b[i]))
return result
וכך אפשר להשתמש בפונקציה:
one = [10, 20, 30]
two = [1, 2, 3]
for a, b in my_zip(one, two):
print(a + b)
בשביל להפוך את הפונקציה לכזו שמקבלת כל מספר של רשימות צריך תחביר פייתון קצת יותר מתוחכם והתוצאה עשויה להיראות כך:
def my_zip(*lists):
result = []
length = 0 if len(lists) == 0 else len(lists[0])
for i in range(length):
result.append(tuple([list[i] for list in lists]))
return result
וקוד שמשתמש בה יכול להיראות כך:
one = [10, 20, 30]
two = [1, 2, 3]
three = [1, 1, 1]
for a, b, c in my_zip(one, two, three):
print(a + b + c)
הממשק של הפונקציה עכשיו הרבה יותר הגיוני, אבל עדיין יש בעיה: הפונקציה מייצרת רשימה חדשה, ואם הרשימות המקוריות ארוכות או כוללות אלמנטים שמסובך להעתיק אותם אז הפונקציה תפעל לאט ותתפוס יותר מקום בזיכרון.
## הצעד הבא - Generator
מנגנון ה Generator בפייתון מאפשר לי לשמור על הממשק של משהו שנראה כמו פונקציה-שמחזירה-רשימה אבל מתחת לפני השטח לבנות קוד שלא באמת מייצר רשימה וכך חוסך את ההעתקות. בפועל איברים ברשימת התוצאות יחושבו רק כשנרצה לגשת אליהם בלולאת ה for החיצונית. הקוד נראה כך:
def my_zip(*lists):
length = 0 if len(lists) == 0 else len(lists[0])
for i in range(length):
yield tuple([list[i] for list in lists])
ומשתמשים בו מבחוץ לפי אותו ממשק בדיוק:
one = [10, 20, 30]
two = [1, 2, 3]
three = [1, 1, 1]
for a, b, c in my_zip(one, two, three):
print(a + b + c)
## לסיכום - zip
פונקציית zip של פייתון עושה את כל מה שאני תיארתי כאן ויותר. היא גם יותר מהירה כי היא ממומשת בשפת C כחלק משפת פייתון. את הפרטים עליה תוכלו למצוא בתיעוד בקישור:
https://docs.python.org/3.3/library/functions.html#zip
אני מקווה שהפוסט הזה עזר לכם להבין טוב יותר את הפונקציה ויעזור לכם לזכור אותה בפעם הבאה שתצטרכו לרוץ על מספר רשימות אלמנטים במקביל.1 419
# מוטיבציה
בין הסיבות שאנשים מוצאים כדי ללמוד טכנולוגיה חדשה יש לנו:
1. כדי להתקבל לעבודה.
2. כי במקום העבודה התחילו פרויקט בטכנולוגיה זו.
3. כדי להשתלב בפרויקט בטכנולוגיה זו בעבודה.
4. כי יש ציור של חד-קרן בדף הבית.
5. כי כל החברים שלי מדברים על זה.
6. כי כולם ברדיט מדברים על זה.
7. כי אני חושב שזה יעזור לי להיות מתכנת טוב יותר.
שווה לשים לב שמוטיבציות שונות מעוררות רגשות שונים במהלך הלימוד ויכולות להשפיע על התוצאה. להרבה אנשים יהיה מאוד קל ללמוד טכנולוגיה חדשה אם מעבירים אותם לצוות שעובד בטכנולוגיה זו, ומאוד קשה ללמוד טכנולוגיה חדשה אם רק קראנו עליה ברדיט.
(וזה לא קשור לזמן פנוי. אנשים יכולים לשבת בבית חודשים ולחפש עבודה אבל יתחילו ללמוד ברצינות את הטכנולוגיה רק כשבאמת ייכנסו לעבודה על פרויקט ספציפי בטכנולוגיה זו).
אלה חדשות מעולות - כי זה אומר שיש לנו את היכולת ללמוד טכנולוגיות בצורה טובה ולעומק. עכשיו רק צריך לבנות את הסיפור שייתן לנו את המוטיבציה להתקדם לשם.
1 419
# לא בליגה שלי
אחד הפרדוקסים המעניינים של לימודים קורה כשאני מנסה ללמוד חומר שהוא לא בליגה שלי. לדוגמה היתה לי תלמידה שבקושי הצליחה לכתוב תרגילי FizzBuzz פשוטים אבל סיפרה שלפני שהגיעה אליי היא היתה בקורס בו היא כתבה שרת ווב להעברת אימייל שכלל קוד Front End, קוד Back End והתממשקות עם SMTP.
איך אפשר לכתוב קוד כל כך מתוחכם בקורס אחד, ולא להצליח לכתוב דברים מאוד פשוטים בקורס אחר?
התשובה ברורה לכל מי שכתב קוד לפי מדריך. קוד הוא בסך הכל אוסף של תווים, ואין שום בעיה להסתכל בוידאו של מישהו שמסביר משהו, להעתיק אות אחרי אות את הקוד וקיבלת את התוכנית המתוחכמת שהראו בוידאו. הרבה פעמים אחרי צפיה והקלדה כאלה אנחנו אפילו בטוחים שהבנו הכל ושאנחנו מוכנים להמשיך לכתוב קוד מהנקודה בה הוידאו הפסיק. ואז כשדברים לא עובדים אנחנו בטוחים שפשוט צריך עוד קורס או שהמדריך לא היה מספיק טוב.
אם אתם צריכים אינדיקציה פשוטה לזה שאתם צופים בחומר שמתאים לרמה שלכם אפשר להסתכל על זמן הצפיה לעומת זמן הכתיבה והעיבוד שאחרי. בצפיה בחומר לימודי ברמה שלי, על כל שעה של וידאו יהיו לי בערך 8 שעות עבודה עצמית שאוכל לעשות איתו, לפעמים גם יותר. זה אומר שאם ראיתי וידאו של שעה על PyTest - אז עכשיו יש לי לפחות יום עבודה שלם לבלות עם הספריה ולכתוב טסטים לקוד שלי. הפרדוקס כאן הוא שככל שאני "מבין" יותר מהר את הוידאו ובעצם יש לי פחות שאלות עליו, ככה זה אומר שאני מסתכל על תוכן שהוא לא בליגה שלי ושאני בעצם מבין אותו הרבה פחות טוב ממה שאני חושב (ואנחנו כבר יודעים שבתכנות פערי הבנה מתגלים בסוף. אי אפשר לעבוד על המחשב).
1 419
p1['vy'] = HANDLE_SPEED
if pyxel.btn(pyxel.KEY_W):
p2['vy'] = -HANDLE_SPEED
if pyxel.btn(pyxel.KEY_S):
p2['vy'] = HANDLE_SPEED
לאחר מכן אני בודק אם הכדור פוגע בקיר העליון או התחתון:
if (ball['y'] - BALL_SIZE) <= 0:
ball['vy'] = BALL_SPEED
if (ball['y'] + BALL_SIZE) >= pyxel.height:
ball['vy'] = -BALL_SPEED
השלב הבא הוא הבדיקה אם הכדור פגע בידית של אחד השחקנים. הבדיקה כאן יותר מורכבת אבל עדיין מדובר בבדיקה לוגית שלא תלויה בספריית הציור:
if (p1['y'] < ball['y'] < p1['y'] + HANDLE_HEIGHT) and (p1['x'] < (ball['x'] - BALL_SIZE) < p1['x'] + HANDLE_WIDTH):
ball['vx'] = BALL_SPEED
if (p2['y'] < ball['y'] < p2['y'] + HANDLE_HEIGHT) and (p2['x'] < (ball['x'] + BALL_SIZE) < p2['x'] + HANDLE_WIDTH):
ball['vx'] = -BALL_SPEED
ולסיום אני מזיז את המיקומים של כל הצורות לפי המהירויות שלהן:
for player in [p1, p2]:
if 0 < player['vy'] + player['y'] < (pyxel.width - HANDLE_HEIGHT):
player['y'] += player['vy']
ball['x'] += ball['vx']
ball['y'] += ball['vy']
סך הכל קוד התוכנית המלא נראה כך:
import pyxel
BALL_SIZE = 5
HANDLE_HEIGHT = 24
HANDLE_WIDTH = 5
HANDLE_SPEED = 4
BALL_SPEED = 2
p1 = {'x': 10, 'y': 10, 'vy': 0}
p2 = {'x': 240, 'y': 10, 'vy': 0}
ball = {'x': 113, 'y': 113, 'vx': BALL_SPEED, 'vy': BALL_SPEED}
def update():
if pyxel.btn(pyxel.KEY_UP):
p1['vy'] = -HANDLE_SPEED
if pyxel.btn(pyxel.KEY_DOWN):
p1['vy'] = HANDLE_SPEED
if pyxel.btn(pyxel.KEY_W):
p2['vy'] = -HANDLE_SPEED
if pyxel.btn(pyxel.KEY_S):
p2['vy'] = HANDLE_SPEED
if (ball['y'] - BALL_SIZE) <= 0:
ball['vy'] = BALL_SPEED
if (ball['y'] + BALL_SIZE) >= pyxel.height:
ball['vy'] = -BALL_SPEED
if (p1['y'] < ball['y'] < p1['y'] + HANDLE_HEIGHT) and (p1['x'] < (ball['x'] - BALL_SIZE) < p1['x'] + HANDLE_WIDTH):
ball['vx'] = BALL_SPEED
if (p2['y'] < ball['y'] < p2['y'] + HANDLE_HEIGHT) and (p2['x'] < (ball['x'] + BALL_SIZE) < p2['x'] + HANDLE_WIDTH):
ball['vx'] = -BALL_SPEED
for player in [p1, p2]:
if 0 < player['vy'] + player['y'] < (pyxel.width - HANDLE_HEIGHT):
player['y'] += player['vy']
ball['x'] += ball['vx']
ball['y'] += ball['vy']
def draw():
pyxel.cls(0)
pyxel.circ(ball['x'], ball['y'], BALL_SIZE, 3)
for player in [p1, p2]:
pyxel.rect(player['x'], player['y'], HANDLE_WIDTH, HANDLE_HEIGHT, 5)
pyxel.init(256, 256)
pyxel.run(update, draw)
המשחק ירוץ כל עוד תצליחו לתפוס את הכדור, ואם תפספסו אז הכדור פשוט יברח לכם מהמסך וכך המשחק יסתיים.
לפיקסל יש עוד המון יכולות מעניינות (אהוב עליי במיוחד עורך התמונות המשולב). לפרטים נוספים על הספריה וסקירה של כל היכולות שווה להיכנס לתיעוד שלהם בקישור:
https://github.com/kitao/pyxel1 419
# בואו נכתוב משחק Pong פשוט עם הספריה Pyxel ב Python
ספריית pyxel היא ספריה לפיתוח משחקי רטרו. היא מגיעה עם עורך תמונות מפוקסלות, עורך סאונדים ישנים והכי טוב - המון מגבלות שלא יאפשרו לכם לפתח משחק יותר מדי מתוחכם. וזה מעולה כי בעזרת פיקסל קל וכיף לכתוב משחקים כמו של פעם. בפוסט זה אציג את הכלים הבסיסיים לעבודה עם הספריה באמצעות פיתוח משחק Pong.
## תוכנית פיקסל ראשונה שרק מציגה מסך שחור
התקינו את ספריית pyxel עם pip או מתוך פייצ'ארם ואחרי ההתקנה בשביל לבדוק שהכל עובד כתבו את הקוד הבא והפעילו:
import pyxel
def update():
pass
def draw():
pass
pyxel.init(256, 256)
pyxel.run(update, draw)
אם הכל הותקן כמו שצריך אתם צריכים לקבל חלון חדש שיראה מסך שחור. נשים לב למבנה התוכנית:
1. הפונקציה pyxel.init מאתחלת את הספריה pyxel. אנחנו חייבים להעביר לה את גודל החלון (הגודל המקסימלי הנתמך הוא 256 על 256), ויש עוד די הרבה פרמטרים שאפשר להעביר כאן - אותם תוכלו למצוא בתיעוד.
2. הפונקציה pyxel.run מתחילה את המשחק ומציגה את המסך. הפונקציה צריכה לקבל שתי פונקציות בתור פרמטרים: פונקציה אחת נקראת update והשניה נקראת draw.
לכל אחת מהפונקציות update ו draw תפקיד חשוב משלה:
1. הפונקציה draw אחראית לציור הפריים הבא, וכדאי שתסתיים כמה שיותר מהר.
2. הפונקציה update אחראית לעדכון ערכי המשתנים בין פריימים כדי ש draw תוכל להתמקד בציור.
זה אומר שאם אני רוצה לצייר צורה שתזוז על המסך, בפונקציה draw אכתוב את הקוד שמצייר את הצורה במקום מסוים, ובפונקציה update אכתוב את הקוד שמשנה את המקום לפי כל מיני פרמטרים. תכף נראה את זה בקוד.
## הגדרת המשתנים למשחק פונג
אני רוצה לבנות את משחק פונג הכי פשוט שאני יכול. בתוכניות גדולות יותר נרצה לארגן את הקוד בכתיב מונחה עצמים או לחלק אותו לקבצים, אבל הפעם אנחנו רוצים להתמקד רק בעבודה עם pyxel ולכן אני נשאר עם המבנה הבסיסי איתו התחלתי את הפוסט.
דרך קלה להגדיר את המשתנים למשחק היא להשתמש במבנה הנתונים Dictionary לכל אוביקט במשחק. במחשחק Pong אנחנו צריכים שני שחקנים וכדור קופץ ולכן עליי להגדיר שלושה מילונים. יחד עם כמה קבועים הקוד יראה כך:
BALL_SIZE = 5
HANDLE_HEIGHT = 24
HANDLE_WIDTH = 5
HANDLE_SPEED = 4
BALL_SPEED = 2
p1 = {'x': 10, 'y': 10, 'vy': 0}
p2 = {'x': 240, 'y': 10, 'vy': 0}
ball = {'x': 113, 'y': 113, 'vx': BALL_SPEED, 'vy': BALL_SPEED}
## ציור המסך
הגדרת המילונים והקבועים מספיקים לי כדי לצייר את מה שאני צריך לראות על המסך בפונקציה draw:
1. לכל שחקן אצייר מלבן שיתאים לו לפי הקואורדינטות של השחקן
2. בשביל הכדור אצייר עיגול
ספריית פיקסל עצמה מגדירה פלטה של 16 צבעים כך שלכל צבע מספר בין 0 ל-15. בכל פונקציית ציור אני צריך להעביר את מספר הצבע מתוך הפלטה. הפונקציה pyxel.cls צובעת את כל המסך בצבע רקע לבחירתי מתוך אותה פלטה.
אני משתמש בצבע מספר 0 עבור הרקע, צבע 3 לכדור וצבע 5 לשחקנים ולכן הקוד נראה כך:
def draw():
pyxel.cls(0)
pyxel.circ(ball['x'], ball['y'], BALL_SIZE, 3)
for player in [p1, p2]:
pyxel.rect(player['x'], player['y'], HANDLE_WIDTH, HANDLE_HEIGHT, 5)
רק עד לפה אפשר להריץ כבר את התוכנית ולראות על המסך את שני המלבנים בצדדים עבור השחקנים והכדור באמצע מחכה להתחיל לזוז.
## ניהול תזוזה באמצעות הפונקציה update
הפונקציה update היא המקום בו כל הלוגיקה של המשחק קורית ובמשחקים גדולים נרצה להפריד את הקוד שלה למספר פונקציות עזר. תפקידי הפונקציה update במשחק שלנו הם:
1. בדיקה אם משתמש לוחץ על כפתור תנועה אז לקבוע מהירות לשחקן המתאים.
2. בדיקה האם הכדור פוגע בקיר העליון או התחתון אז לשנות לו את המהירות על ציר y כדי שלא יברח מהמסך.
3. בדיקה האם הכדור פוגע במקל של אחד השחקנים ואז לשנות לו את המהירות על ציר x לצד השני.
4. שינוי המיקום של המלבנים והכדור לפי המהירויות שלהם.
בדוגמאות יותר גדולות היינו גם מוסיפים ניקוד ובודקים אם הכדור עף החוצה מהמסך אז מורידים נקודה לשחקן שפספס את הכדור. נבנה את הפונקציה צעד צעד לפי ארבעת התפקידים.
בשביל לבדוק אם משתמש לוחץ על כפתור מסוים אני משתמש בפונקציה pyxel.btn. אני מעביר לפונקציה מזהה כפתור והיא מחזירה אמת אם הכפתור לחוץ (וכן יש בתיעוד רשימה של כל מזהי הכפתורים, אבל זה די קל לנחש גם בלי להסתכל בתיעוד איזה מזהה אתם צריכים):
if pyxel.btn(pyxel.KEY_UP):
p1['vy'] = -HANDLE_SPEED
if pyxel.btn(pyxel.KEY_DOWN):1 419
# שלושים דקות זוז
הנה משחק קצר שדורש חצי שעה ביום והרבה התמדה אבל מובטח שיהפוך אתכם למתכנתים חדים יותר אחרי כמה שבועות, הוא נקרא 30 דקות זוז (נו בסדר, המצאתי את השם הרגע. אבל את המשחק כן ניסיתי).
אנחנו בוחרים משימה שאפשר לעשות בזמן קצר, נגיד חצי שעה. אולי לכתוב משחק איקס עיגול. פותחים את המחשב ויושבים חצי שעה לכתוב את הקוד. בפעם הראשונה כנראה לא תצליחו לכתוב את המשחק בחצי שעה וזה בסדר. זה לא שההקלדה לוקחת יותר מדי זמן (איקס עיגול זה משחק שאפשר לכתוב בכמה עשרות שורות). התקיעה היא במחשבה.
אחרי חצי שעה אם המשחק לא עובד אפשר לעצור ופה החלק החשוב - למחוק את כל מה שכתבתם.
למחרת מנסים שוב מההתחלה את אותו איקס עיגול ושוב מתחילים מדף חלק. הפעם ההתחלה כבר תרוץ יותר מהר אבל יש סיכוי טוב שעדיין לא תצליחו לסיים לכתוב את כל המשחק בחצי שעה. זה בסדר. שוב מוחקים, ושוב למחרת מתחילים מחדש.
חודש לתוך האתגר ואת האיקס עיגול בטוח הצלחתם לכתוב בחצי שעה, ויש סיכוי לא רע שגם עוד 2-3 תוכניות דומות. כמובן שככל שתצברו מהירות תוכלו לעבור לאתגרים יותר גדולים.
רוב המתכנתים שמגיעים לראיונות עבודה נתקעים על חידות FizzBuzz פשוטות. זה לא בגלל שהם לא יודעים לכתוב קוד, אלא בגלל שהם לא יודעים לכתוב קוד מהר מספיק ולא יודעים להירגע בראיון. תרגיל השלושים דקות זוז עובד כמו תרופת פלא אם גם אתם מוצאים את עצמכם נתקעים בראיונות כשמגיעים לחלק של הקידוד.
1 419
# מתכנתים טובים / קוד טוב
בדרך כלל כשאנחנו חושבים על מתכנתים טובים הדבר הראשון שקופץ לראש הוא אנשים שעומדים בזמנים, אנשים שמוכנים להשקיע מעבר כדי להביא את המוצר ללקוח ברמה המקצועית המתאימה ולפי לוח הזמנים אליו התחייבו, וכן אנשים שגם יישארו כל הלילה במשרד כדי שהדמו של הבוקר יעבור חלק. מתכנתים טובים הם אנשים שאפשר לסמוך עליהם שהמוצר יעבוד לפי ה Spec ובזמן.
ומה עם קוד טוב? קוד טוב הוא קוד מסודר, קוד שלכל דבר יש בו את המקום הטבעי שלו, קוד שקל לקרוא אותו ולהבין אותו, קוד שאתה יודע בדיוק איפה צריך לשנות בשביל לשנות התנהגות מסוימת וכל שינוי משפיע רק על ההתנהגות שרצית. קוד טוב כולל סט של בדיקות יחידה כדי שנדע שלא שברנו כלום ולא כולל כפילויות כי כפל קוד אומר שבשביל לשנות היבט אחד של ההתנהגות אני צריך לבצע את אותו שינוי בכמה מקומות.
מעניין לשים לב שכשאנחנו מגיעים לשפוט קוד לא מעניין אותנו כמה זמן לקח לכתוב אותו. מרגע שהקוד כתוב הוא עומד בפני עצמו, הוא בא לספר לנו סיפור על פונקציונאליות ועל המוצר, אבל גם על המחשבות של המתכנת לגבי שינויים אפשריים בדרישות ומה הסבירות של כל שינוי, ועל ביצועים ואבטחת מידע ויכולת התמודדות עם תקלות ועוד המון המון פרטים קטנים שרק הקוד יודע לספר.
אני חושב שהגיע הזמן להשתלט מחדש על המילה. בואו נכנה "מתכנתים טובים" את האנשים שכותבים קוד טוב. ושהמתכנתים שנשארים כל הלילה במשרד ימצאו לעצמם כינוי חדש.
1 419
# פשוט תראה לי איך עושים את זה
אם יציעו לכם שתי דרכים ללמוד, האחת מהירה אבל הידע שתקבלו יחזיק מעמד בראש שלכם לשבוע הקרוב בלבד (מה שקוראים אצלנו - אחרי הבגרויות תשכחו הכל); והשניה, איטית יותר, תקנה לכם ידע שיישאר אתכם כל החיים. אולי מלבד לימודים לבגרויות, נדמה לי שרובנו נעדיף את הדרך האיטית: ההגיון אומר שאם כבר השקעתי זמן ללמוד משהו, עדיף שהוא יישאר איתי לאורך זמן.
האמת היא שברוב הסיטואציות הלימודיות אנחנו מקבלים בדיוק את הבחירה הזו. אבל בשביל להבין אותה ולהצליח לבחור בדרך שתתאים לנו כדאי לראות קודם איך המוח האנושי עובד.
הזיכרון במוח שלנו בנוי קצת כמו גרף מכוון בקישורים אסוציאטיביים: דבר אחד מוביל לדבר שני, שמוביל לשלישי וכך הלאה. בגלל זה כשלא מצליחים להזיכר במילים של בית בשיר אפשר להתחיל לזמזם את הפיזמון, ואחרי שמצליחים להיזכר במשפט אחד השאר כבר ממשיך מעצמו. או אם ננסה למנות את האותיות מ-ת אחורה ל-א, נגלה שהמוח לגמרי בניגוד להיגיון מתחיל לרוץ על כל האותיות מ-א רק בשביל לגלות מי באה לפני ר.
בהיבט הלימודי זה אומר שכשאני נתקל במידע חדש אני צריך לסווג אותו בזיכרון לפי הדברים שאני כבר יודע שמזכירים לי אותו. אם המידע חדש לגמרי ולא קשור לשום דבר בעולם שלי הוא יפתח פינה נפרדת בראש, אבל אם זה משהו שמתחבר להמון דברים שאני כבר יודע אז הוא יישמר עם הקישורים לדברים האלה. אה וכמעט שכחתי - למוח יש גם סוג של Garbage Collector שיזרוק לארכיון קטעי מידע שאי אפשר להגיע אליהם או שיש אליהם מעט מאוד קישורים נכנסים.
עכשיו בואו נלך לבחירה שלנו לגבי צורת הלימוד וכמה זמן המידע יישמר בראש. נניח שאני לומד ריאקט ואני מנסה לכתוב קומפוננטה שמדפיסה את המספרים מ-1 עד 10 בלולאה. דרך לימוד אחת תהיה לחפש בגוגל "איך עושים לולאות ב JSX". קופי-פייסט מהתשובה ואני אגיע למשהו כזה:
function Numbers(props) {
return (
<ul>
{Array.from({ length: 10 }).map((x, i) => <li>i + 1</li>)}
</ul>
);
}
זה עבד והדפיס לי את המספרים בלולאה אבל ככל הנראה פעם הבאה שאני אצטרך לולאה דומה אני אצטרך שוב לחפש בגוגל או לחזור לחפש בקוד הזה. הוא פשוט יותר מדי שונה מכל הדברים שהכרתי קודם ולכן המוח שלי לא יצר את החיבור (נ.ב. אל תהיו לי מהנודניקים האלה שמסתכלים על קוד כזה בקטע של "נו מה אתה רוצה, זה בסך הכל JavaScript"; אם אתם רוצים לשחק את המשחק הזה לכו למצוא לכם דוגמה אחרת שמאוד שונה מכל מה שאתם מכירים).
לעומת זאת בגישה השניה המשחק יראה אחרת לגמרי. אני מתחיל אותו בחיפוש דברים שאני מכיר: אני יודע שאפשר להציג משתנה ב JSX באמצעות סוגריים מסולסלים, אני יודע לבנות בלולאה מערך עם כל המספרים מ-1 עד 10, אז בואו ננסה לשלב את השניים. הקוד שיצא עשוי להיראות כך:
function Numbers(props) {
const numbers = [];
for (let i=1; i <= 10; i++) {
numbers.push(<li>{i}</li>);
}
return (
<ul>{numbers}</ul>
);
}
וגם זה עבד! אפילו שלא יקבל שום נקודות ב Stack Overflow ואף אחד לא יכתוב שום פוסט שימליץ על השיטה.
העניין שעכשיו אפשר להמשיך, למשל אפשר להראות את הקוד לחבר שימליץ לי לקרוא על הפונקציה map. אני יכול ללמוד מהתיעוד איך היא עובדת ואחרי ש"שמרתי" את map בזיכרון אנסה לארגן מחדש את הקוד באמצעותה:
function Numbers(props) {
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const listItems = numbers.map(x => <li>{x}</li>);
return (
<ul>{listItems}</ul>
);
}
מהקוד הזה אני יכול לחפש איך מייצרים את המערך הראשון בצורה אוטומטית, איך מחברים את שתי השורות הראשונות לשורה אחת ובסוף אני גם אגיע בדיוק לפיתרון שמצאתי בהתחלה בגוגל. ההבדל הוא לא הקוד שנכתב בשתי הגישות אלא איך נראה המוח של שני המתכנתים שפתרו את האתגר.
במקרה של המתכנת הראשון יש לנו רכיב מידע זרוק בצד של המוח שהגיע לשם דרך העתק-הדבק מגוגל. במקרה של המתכנת השני יש לנו מסלול מידע ברור למידע החדש מדברים שמחוברים ברשת חזקה של אסוציאציות. בזכות היסודות החזקים המידע יישאר בראש לאורך זמן ויהיה קל להשתמש בו כבסיס ללמידה עתידית.
היום עם האינטרנט וגוגל למצוא את הידע זה לא החלק הקשה; הרבה יותר מעניין להצליח להטמיע את הידע כך שיהיה לנו קל להשתמש בו כשנצטרך אותו.1 419
# שתי בעיות עם הערכות זמנים
- הי מה קורה? שומעת, תוך כמה זמן נראה לך שהפיצ'ר הזה יהיה מוכן?
- מצאתי כבר משהו בתיעוד שמסביר בדיוק את מה שאנחנו צריכים אז נראה לי שתוך יומיים-שלושה יהיה לנו משהו באוויר
... שלושה ימים מאוחר יותר
- הי, איך הולך עם הפיצ'ר שדיברנו עליו?
- האמת שזה לא הלך בדיוק כמו שתכננתי. הדוגמה בתיעוד לא פתרה בדיוק את הבעיה שלנו והיה צריך הרבה עבודת התאמה. בנוסף הספריה שהם השתמשו בה התנגשה עם חלקים אחרים בקוד שלנו. אני חושב שיש פה לפחות עוד שבוע עבודה.
... שבוע מאוחר יותר
- הי, איך הולך עם הפיצ'ר שדיברנו עליו?
- הלך מעולה בוא אראה לך איך זה עובד
- רגע זה לא בדיוק מה שהייתי צריך... אני רואה שחסרה פה כותרת והכפתור הזה בכלל לא במקום, ורגע איפה כפתור ה Undo?
- לא דיברנו על undo...
- טוב חייבים undo, אחרת המשתמשים יכעסו שנעלם להם המידע
- זה ייקח עוד קצת זמן
מתכנתים גרועים מאוד בלהבין את הפער בין מה שנראה לנו קל לבין כמה עבודה דברים ידרשו בעולם האמיתי. אנשי פרודקט לא תמיד יודעים לראות מה צריך לפני שהכל בנוי כך שפיתוח פיצ'ר אמיתי דורש כמה איטרציות. זה לא סוף העולם אבל שווה להגיע מוכנים ולא לקחת ללב אם משהו לוקח יותר זמן ממה שתכננתם. הרבה פעמים מדובר בתהליך בריא של גדילת פיצ'ר עד שהוא מספיק בשל כדי להגיע למשתמשים.
1 419
# שלושה סוגי כלים
יש כלים שדורשים מיומנות גבוהה ונותנים החזר טוב על ההשקעה. זאת למשל סכין השף שאולי יש לכם במטבח: חיתוך אחד לא זהיר ואתם עלולים לאבד אצבע, אבל בעבודה נכונה הבישול יהיה הרבה יותר מהיר.
יש כלים שעוזרים לנו להשתפר. זה המצוף בחוג שחיה שעוזר לתרגל רק תנועות רגליים, או המתקנים בחדר כושר שכל אחד עוזר לאמן שריר מסוים.
ויש כלים שדורשים מיומנות נמוכה לתפעול ומונעים מאיתנו להתקדם. אלה גלגלי העזר באופניים או המצופים בבריכה. הם פותרים לנו בעיה שאם היינו מספיק מיומנים בכלל לא היתה קיימת.
שימו לב לארגז הכלים שלכם: לטווח הארוך רק הסוג הראשון והשני שווים את הזמן שלנו.
