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

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

איך ליצור Docker Image בלי Dockerfile

29/04/2021

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

  1. דוקרפייל הוא קובץ בפורמט טקסט, כך שאפשר להתייחס אליו בתור קוד מקור לכל דבר.

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

  3. אפשר לשתף את ה Dockerfile עם חברים כדי שיוכלו בעצמם לבנות את האימג' לפי אותן הוראות.

  4. קל מאוד לתחזק אימג'ים ולשנות אותם כשיש לך ביד את ה Dockerfile שבונה את האימג'.

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

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

זה עובד ככה:

  1. יוצרים קונטיינר חדש ומריצים אותו, למשל עבור אלפין לינוקס אפשר להפעיל:
$ docker run -d --rm alpine sleep infinity
  1. נכנסים לקונטיינר ומתחילים ליצור שם קבצים או לשנות דברים במכונה (שימו לב לשנות את ה Container ID לערך שמופיע אצלכם):
$ docker exect -it 2b090ffbd1fb /bin/sh
  1. בשביל המשחק אני מפעיל בתוך המכונה את הפקודה הבאה כדי ליצור משתמש חדש:
/ # adduser ynon

אחרי שבחרתי סיסמה אני יוצא מהקונטיינר ובמכונה הראשית שלי מפעיל (שוב שימו לב להחליף את ה Container ID למה שמופיע אצלכם):

$ docker commit 2b090ffbd1fb
  1. הקומיט יצר אימג' חדש, וכשנסתכל ב docker images נוכל לראות אותו עדיין ללא שם:
<none>               <none>          3030ab2039c3   30 seconds ago   5.58MB
  1. נשאר רק לתת שם לאימג' כדי שאפשר יהיה לדחוף אותו ל docker hub (ושוב שימו לב לשנות את ה Image ID לזה שהופיע אצלכם, ואת שם האימג' למשהו שמתאים למשתמש דוקרהאב שלכם):
$ docker image tag 3030ab2039c3 ynonp/commit-demo
  1. ובסוף אפשר לדחוף את האימג' החדש לדוקרהאב כדי שכולכם תוכלו להשתמש בו:
$ docker push ynonp/commit-demo
  1. אלה מכם שירצו לשחק עם האימג' החדש שיצרתי צריכים עכשיו רק להריץ:
$ docker run -it --rm ynonp/commit-demo /bin/sh

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

ארבע שנים ניסיון

28/04/2021

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

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

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

שני טיפים על הנושא הזה:

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

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

איך למזג קבצי JSON (או כל קובץ מיוחד אחר) עם git

27/04/2021

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

דוגמה קלה היא קבצי JSON, ובמיוחד הקובץ package.json למתכנתי Front End או מתכנתי Node.JS. כשאנחנו מוסיפים מודול בענף אחד בגיט, ובענף אחר מוסיפים מודול אחר, כמעט תמיד נקבל קונפליקט. הנה דוגמה קטנה של package.json אחרי ניסיון merge, כשענף אחד התקין מודול בשם cowsay וענף אחר התקין מודול בשם mocha:

{
  "name": "driverdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
<<<<<<< HEAD
  "dependencies": {
    "cowsay": "^1.4.0"
=======
  "devDependencies": {
    "mocha": "^8.3.2"
>>>>>>> feature2
  }
}

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

דרייבר מיזוג, או merge driver, הוא פשוט תוכנית ש git יפעיל כשהוא צריך למזג שני קבצים. התוכנית תקבל בתור פרמטרים את שני הקבצים וגם את הקובץ בגירסת ה merge-base שלהם, כלומר הקובץ "המקורי" ממנו שניהם יצאו.

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

דרייבר מיזוג שכבר קיים ועובד טוב לקבצי JSON נקרא git-json-merge. אני ממליץ להסתכל גם על הקוד שלו כדי לראות איך נראה merge driver וכי בסך הכל הוא די פשוט. בשביל להתקין את הדרייבר נפעיל:

npm install --global git-json-merge

ועכשיו נמשיך להגדיר את git-json-merge בתור הדרייבר שימזג קבצי JSON בפרויקט שלנו. קודם כל יש ליצור בתיקיית הפרויקט קובץ בשם .gitattributes ובו התוכן הבא:

*.json merge=json

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

הקובץ השני בסיפור שלנו הוא .git/config שם נוסיף את הבלוק:

[merge "json"]
    name = custom merge driver for json files
    driver = git-json-merge %A %O %B

הפקודה git-json-merge היא התוכנית שהרגע התקנו, ושלושת האותיות אחריה מייצגות את שלושת הגירסאות של הקובץ: %A הקומיט מהענף הנוכחי, %O הקובץ המקורי מ merge-base ו %B הקובץ מהענף השני.

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

{
  "name": "driverdemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "mocha": "^8.3.2"
  },
  "dependencies": {
    "cowsay": "^1.4.0"
  }
}

ואם יש לכם קבצים נוספים בפורמטים יצירתיים שווה להסתכל בקוד של git-json-merge ולכתוב גירסה מותאמת למיזוג הקבצים שלכם.

על ההבדל בין -c ו -s ב su ו runuser

26/04/2021

הפקודות su ו runuser נועדו כדי שנוכל להחליף משתמש או להריץ פקודות בתור משתמש אחר. ב su אנחנו משתמשים כדי לעבור ממשתמש רגיל למשתמש רגיל אחר, וב runuser נשתמש כדי לעבור ממנהל מערכת למשתמש רגיל. בשתי הפקודות האפשרויות -c ו -s משמשות לבחירת התוכנית שצריך להפעיל. בואו נראה את ההבדלים ביניהן.

המשך קריאה

איך לקרוא קובץ ב Bash בלי cat

25/04/2021

בקבוצה [https://t.me/bash_tips] בטלגרם פורסמו לפני כמה ימים מספר אתגרי bash באתר שנקרא cmdchallenge.com. מאחר ויש לי חולשה לאתגרים מסוג זה הלכתי להעיף מבט. התחושה שלי היתה שרוב האתגרים שם לא היו מאוד מעניינים או לא היו מנוסחים מספיק טוב, אבל היה טריק אחד שתפס את העין.

בתרחיש המדובר הגענו למערכת קבצים שנמחקו ממנה כל התוכניות והיה צריך באמצעות Bash Builtins בלבד להציג תוכן של קובץ. האינטואיציה הראשונה שלי היתה להשתמש בפקודת read בלולאה, כמו בשורה הבאה שמדפיסה את כל תוכן הקובץ /etc/shells:

while IFS= read -r; do echo "$REPLY"; done < /etc/shells

אבל מסתבר שיש טכניקה יותר פשוטה ואחרי חיפוש קצר ב man bash מצאתי את הפיסקה הבאה:

The command substitution $(cat file) can be replaced by the equivalent but faster $(< file).

מה שאומר שאפשר לקצר את השורה לגירסה הבאה:

echo "$(< /etc/shells)"

שווה לשים לב למרכאות סביב המשתנה (בשני המקרים). ברוב הקבצים לא תשימו לב להבדל, אבל אם בקובץ יהיו סימנים מיוחדים כמו *, [] או ? הגירסה בלי המרכאות תתיחס לסימן המיוחד בתור תבנית של שם קובץ ותחליף אותו ברשימת כל הקבצים המתאימים לתבנית.

תשתית לשינויים

24/04/2021

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

"תוסיף בבקשה הערות"

"הבדיקה לא מספיק טובה"

"מה קורה במצב ש... ?"

"תמעך את הקומיטים בבקשה שיהיה רק קומיט אחד ב PR"

"תהפוך את הטאבים לרווחים"

"יש לנו API חדש שבדיוק מתאים למה שכתבת, עדיף שתעבור להשתמש בו"

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

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

תשתית של שינויים מורכבת מידע, מכלים ומעיצוב הקוד:

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

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

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

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

איך ולמה להקים Fanout Exchange ב RabbitMQ

23/04/2021

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

המשך קריאה

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

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. ככל שתעבירי יותר הרצאות כך תגיעי יותר מוכנה להרצאה הבאה.

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