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

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

טיפ קוד ריוויו: התמקדו בדבר אחד

22/02/2022

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

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

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

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

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

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

דברים שהיו קשים ועכשיו קלים ואנחנו

21/02/2022

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

  • תעודות SSL (טירוף)

  • זיהוי תמונה (הבדיחה הזאת למשל כבר לא רלוונטית)

  • להרים שרת בענן

  • פיתוח ממשק משתמש חוצה פלטפורמות

  • העברת קוד כולל כל התלויות ממכונה למכונה (היוש דוקר)

  • הקמת תשתית CI/CD מלאה בחינם (היוש גיטהאב)

  • יישומי ווב בזמן אמת (זוכרים כשהצ'ט בג'ימייל היה נראה כמו הברקה טכנולוגית?)

  • זירו דאונטיים דיפלוימנטס

  • למרכז דברים ב CSS

  • תמיכה בדפדפנים ישנים (כן היה פעם דבר שנקרא IE)

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

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

מה האילוצים שלי

20/02/2022

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

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

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

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

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

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

שכתוב דוגמת תקשורת ב ClojureScript שכתבתי לפני שנתיים

19/02/2022

בפוסט ארבע תבניות לקומפוננטות React מתורגמות ל ClojureScript ו Reagent הראיתי איך לעבוד עם ריאייג'נט ולכתוב קומפוננטות ריאקט ב clojurescript. הדוגמה האחרונה באותו פוסט, וזו שבגללה שמרתי מרחק מריאייג'נט בשנתיים האחרונות היתה דוגמה לתקשורת ajax. היא היתה שונה מקוד שאני רגיל לכתוב בריאקט, ואולי בגללי ואולי בגלל ריאייג'נט עבדה פחות טוב מקוד ג'אווהסקריפט מקביל שהייתי כותב. הקושי העיקרי שלי בתרגום הקוד מ JavaScript לריאייג'נט היתה חוסר התמיכה ב Hooks, ובפרט useEffect שהיה לי מאוד חסר.

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

המשך קריאה

משחק זיכרון ב ClojureScript

18/02/2022

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

המשך קריאה

ארגון מחדש

17/02/2022

בואו נניח שאתם צריכים לכתוב קוד שקורא url-ים של דוקרהאב ומדפיס את שם המחבר ושם הריפו מה url. כלומר אתם תתנו לו את המחרוזת:

https://hub.docker.com/r/bitnami/postgresql

והקוד יחזיר את המילון:

{ 'author': 'bitnami', 'repo': 'postgresql' }

יודעים מה? עזבו, הנה חסכתי לכם את המאמץ, זאת הפונקציה:

import re
import sys

def parse_dockerhub_url(url: str):
    res = re.search(r'https://hub.docker.com/r/([\w%-]+)/([\w%-]+)', url)
    if res:
        author, repo = res.groups()
        return { 'author': author, 'repo': repo }

print(parse_dockerhub_url(sys.argv[1]))

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

https://hub.docker.com/_/ubuntu

הקוד שלי לא מזהה את המבנה כי אני מחפש את ה /r/ לפני שם הריפו, ולכן הם לא מצליחים להזין את הנתיבים שלהם בתוכנה.

מה עושים?

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

import re
import sys

def parse_dockerhub_url(url: str):
    res = re.search(r'https://hub.docker.com/r/([\w%-]+)/([\w%-]+)', url)
    if res:
        author, repo = res.groups()
        return { 'author': author, 'repo': repo }

    res = re.search(r'https://hub.docker.com/_/([\w%-]+)', url)
    if res:
        author, repo = ["Official Image", res.group(1)]
        return { 'author': author, 'repo': repo }



print(parse_dockerhub_url(sys.argv[1]))

זה עובד ואפשר להמשיך לתקלה הבאה.

כן?

רגע.

אומנם תיקנתם את הבאג אבל נראה לי ששברתם את הפונקציה.

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

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

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

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

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

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

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

טיפ קוברנטיס: שימוש בקונטקסט כדי לעבוד עם כמה קלאסטרים

16/02/2022

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

הכלי kubectl משתמש בקובץ קונפיגורציה ברירת מחדל שנמצא בנתיב ~/.kube/config. בתוך אותו קובץ יש הגדרות כמו לדוגמה:

apiVersion: v1
clusters:
- cluster:
    server: https://cimaks-dns-bca70ca9.hcp.westeurope.azmk8s.io:443
  name: cimaks
contexts:
- context:
    cluster: cimaks
    user: clusterUser_Playground_cimaks
  name: cimaks
current-context: cimaks
kind: Config
preferences: {}

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

apiVersion: v1
clusters:
- cluster:
    server: https://146.148.56.200:443
  name: cloud_okteto_com
contexts:
- context:
    cluster: cloud_okteto_com
    extensions:
    - extension: null
      name: okteto
    namespace: ynonp
    user: 6b5b39bc-9c83-4d7b-a345-b03922c7e979
  name: cloud_okteto_com
current-context: cloud_okteto_com
kind: Config
preferences: {}

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

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

$ export KUBECONFIG=~/.kube/config:~/Downloads/okteto-kube.config

ועכשיו הפקודה:

$ kubectl config get-contexts

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

CURRENT   NAME               CLUSTER            AUTHINFO        NAMESPACE
*         cimaks             cimaks             
          cloud_okteto_com   cloud_okteto_com                   ynonp

מההדפסה אני רואה שפקודת ה kubectl הבאה שאכתוב תגיע לקלאסטר cimaks כי הוא מסומן בכוכבית. הפקודה use context בוחרת את הקונטקסט הפעיל ולכן אני יכול לשנות קלאסטר עם:

$ kubectl config use-context cloud_okteto_com

ועכשיו כל פקודת kubectl שאכתוב תפעל על הקלאסטר השני.

הפחד לכתוב גרוע

15/02/2022

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

"לא עדיף לכתוב רק שיש לך משהוא חשוב או מועיל?"

"כאשר חסר בפוסט את הערך המוסף, הנזק רב מהתועלת."

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


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

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

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

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

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

טיפ ג'סט: ג'אסט אין קייס

14/02/2022

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

import cases from 'jest-in-case';

cases('find stuff in arrays', ({haystack, needle, found}) => {
  expect(haystack.includes(needle)).toEqual(found);
}, {
  '5 is in [2, 3, 5]': 
    { haystack: [2, 3, 5], needle: 5, found: true },

  '19 is not in [2, 3, 5]':
    { haystack: [2, 3, 5], needle: 19, found: false },
});

וכשאני מריץ ג'סט הפלט של הקוד יהיה:

PASS  ./demo.test.js
 find stuff in arrays
    ✓ 5 is in [2, 3, 5] (1 ms)
    ✓ 19 is not in [2, 3, 5]

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        0.102 s
Ran all test suites.

מה קרה כאן-

  1. הפקודה cases מקבלת שם של "קבוצת" בדיקות, אחריה פונקציית בדיקה (כמו test רגיל) אבל אותה פונקציית בדיקה הפעם מקבלת אוביקט פרמטרים.

  2. אתם יכולים לבחור כל שם שתרצו למפתחות באוביקט הפרמטרים (מלבד כמה מילים שמורות של jest in case), ולהשתמש בהם בתוך קוד הבדיקה.

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

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

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

לירות לכל הכיוונים

13/02/2022

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

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

זה לא אפקטיבי.

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

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