es
Feedback
ToCode

ToCode

Ir al canal en Telegram

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

Mostrar más
1 420
Suscriptores
Sin datos24 horas
+37 días
-430 días
Archivo de publicaciones
ToCode
1 420
"keys_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/keys{/key_id}",
  "collaborators_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/collaborators{/collaborator}",
  "teams_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/teams",
  "hooks_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/hooks",
  "issue_events_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/issues/events{/number}",
  "events_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/events",
  "assignees_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/assignees{/user}",
  "branches_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/branches{/branch}",
  "tags_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/tags",
  "blobs_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/git/blobs{/sha}",
  "git_tags_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/git/tags{/sha}",
  "git_refs_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/git/refs{/sha}",
  "trees_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/git/trees{/sha}",
  "statuses_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/statuses/{sha}",
  "languages_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/languages",
  "stargazers_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/stargazers",
  "contributors_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/contributors",
  "subscribers_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/subscribers",
  "subscription_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/subscription",
  "commits_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/commits{/sha}",
  "git_commits_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/git/commits{/sha}",
  "comments_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/comments{/number}",
  "issue_comment_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/issues/comments{/number}",
  "contents_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/contents/{+path}",
  "compare_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/compare/{base}...{head}",
  "merges_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/merges",
  "archive_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/{archive_format}{/ref}",
  "downloads_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/downloads",
  "issues_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/issues{/number}",
  "pulls_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/pulls{/number}",
  "milestones_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/milestones{/number}",
  "notifications_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/notifications{?since,all,participating}",
  "labels_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/labels{/name}",
  "releases_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/releases{/id}",
  "deployments_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/deployments",
  "created_at": "2013-12-13T15:39:56Z",
  "updated_at": "2014-09-09T14:50:45Z",
  "pushed_at": "2013-12-13T15:44:15Z",
  "git_url": "git://github.com/ynonp/2013-Advent-Staging.git",
  "ssh_url": "git@github.com:ynonp/2013-Advent-Staging.git",
  "clone_url": "https://github.com/ynonp/2013-Advent-Staging.git",
  "svn_url": "https://github.com/ynonp/2013-Advent-Staging",
  "homepage": null,
  "size": 1777,
  "stargazers_count": 0,
  "watchers_count": 0,
  "language": "Perl",
  "has_issues": false,
  "has_projects": true,
  "has_downloads": true,
  "has_wiki": true,
  "has_pages": false,
  "has_discussions": false,
  "forks_count": 0,
  "mirror_url": null,
  "archived": false,
  "disabled": false,
  "open_issues_count": 0,
  "license": null,

ToCode
1 420
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi | .USD,.EUR'

{
  "code": "USD",
  "symbol": "$",
  "rate": "26,938.2648",
  "description": "United States Dollar",
  "rate_float": 26938.2648
}
{
  "code": "EUR",
  "symbol": "€",
  "rate": "26,241.8029",
  "description": "Euro",
  "rate_float": 26241.8029
}
ושימו לב מה קיבלנו - זה לא אוביקט יחיד, אבל זה גם לא מערך של אוביקטים, אלא שני אוביקטי JSON אחד אחרי השני. בעצם פילטרים ב jq יכולים להחזיר ערך אחד או מספר ערכים, והאופרטור פסיק מייצר פילטר שמחזיר מספר ערכים. עדיין אפשר לקחת את שני האוביקטים האלה לפילטר נוסף כדי לקבל רק את השער:
curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi | .USD,.EUR | .rate'

26,938.4713
26,242.0041
ולפני שניפרד מקוינבייס ארצה להראות פילטר נוסף, הוא פילטר הגישה למפתח באוביקט או במערך המסומן בסוגריים מרובעים. זוכרים את ה JSON איתו התחלנו באתר? זה עם המערך ששלחתי אתכם לנסות למצוא איך לגשת לצבעים שבו? אז אם נשתמש בפילטר:
.colors[1]
נקבל בדיוק את הצבע השני ברשימה. אם נשתמש בפילטר .colors נקבל מערך של הצבעים, ואם נשתמש בפילטר:
.colors[]
נקבל פשוט את שלושת הצבעים בתור שלושה ערכים נפרדים. אופרטור סוגריים מרובעים ניגש למפתח באוביקט או לתא במערך, ואם לא כתבתי אינדקס הוא ייתן את כל התאים במערך או את כל המפתחות באוביקט. אפשר לשלב אותו עם העבודה שעשינוי על coinbase כדי לקבל את כל השערים של הביטקוין:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi | .[] | .rate'
26,920.2192
22,494.3198
26,224.2239
או בכתיב המקוצר:
curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi.[].rate'

26,920.2192
22,494.3198
26,224.2239
וכמובן שזה יהיה יותר שימושי אם יהיה לנו גם את המטבע שמתאים לכל שער:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi.[] | .code,.rate'
USD
26,928.6392
GBP
22,501.3555
EUR
26,232.4262
## עבודה עם מערכים ויצירת אוביקטים דרך גיטהאב זה הזמן להיפרד מקוינבייס לעבור ל API קצת יותר מתוחכם - רשימת המאגרים של משתמש בגיטהאב. אני לוקח את המשתמש שלי בשביל הדוגמה ורשימת המאגרים הציבוריים שלי זמינה בקישור:
$ curl --silent https://api.github.com/users/ynonp/repos
בתור התחלה הייתי רוצה להדביק כאן את ה JSON אבל הוא פשוט ארוך מדי, יש לי יותר מדי מאגרים. במצב כזה אני משתמש ב jq כדי להדפיס רק את המאגר הראשון במערך הקלט:
$ curl --silent https://api.github.com/users/ynonp/repos

{
  "id": 15167222,
  "node_id": "MDEwOlJlcG9zaXRvcnkxNTE2NzIyMg==",
  "name": "2013-Advent-Staging",
  "full_name": "ynonp/2013-Advent-Staging",
  "private": false,
  "owner": {
    "login": "ynonp",
    "id": 128594,
    "node_id": "MDQ6VXNlcjEyODU5NA==",
    "avatar_url": "https://avatars.githubusercontent.com/u/128594?v=4",
    "gravatar_id": "",
    "url": "https://api.github.com/users/ynonp",
    "html_url": "https://github.com/ynonp",
    "followers_url": "https://api.github.com/users/ynonp/followers",
    "following_url": "https://api.github.com/users/ynonp/following{/other_user}",
    "gists_url": "https://api.github.com/users/ynonp/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/ynonp/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/ynonp/subscriptions",
    "organizations_url": "https://api.github.com/users/ynonp/orgs",
    "repos_url": "https://api.github.com/users/ynonp/repos",
    "events_url": "https://api.github.com/users/ynonp/events{/privacy}",
    "received_events_url": "https://api.github.com/users/ynonp/received_events",
    "type": "User",
    "site_admin": false
  },
  "html_url": "https://github.com/ynonp/2013-Advent-Staging",
  "description": "Staging area for Perl Catalyst Advent articles (Winter 2013)",
  "fork": true,
  "url": "https://api.github.com/repos/ynonp/2013-Advent-Staging",
  "forks_url": "https://api.github.com/repos/ynonp/2013-Advent-Staging/forks",

ToCode
1 420
"rate": "26,888.0465",
      "description": "United States Dollar",
      "rate_float": 26888.0465
    },
    "GBP": {
      "code": "GBP",
      "symbol": "£",
      "rate": "22,467.4365",
      "description": "British Pound Sterling",
      "rate_float": 22467.4365
    },
    "EUR": {
      "code": "EUR",
      "symbol": "€",
      "rate": "26,192.8829",
      "description": "Euro",
      "rate_float": 26192.8829
    }
  }
}
רק ההעברה ל jq כבר הדפיסה את ה JSON בצורה קריאה יותר. אגב אם יש לכם JSON מסור ואתם דווקא רוצים לכווץ אותו (כלומר למחוק את כל ירידות השורה והאינדנטציה) תוכלו להשתמש במתג -c. בנוסף המתג -M מדפיס את הפלט ללא צבעים ולכן פקודה כזאת תדפיס לי אוביקט JSON מבולגן שוב:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq | jq -Mc
ואם אנחנו כבר מדברים על המתגים ל jq אז שווה להכיר שהמתג -S גורם ל jq להדפיס את ה JSON עם המפתחות מסודרים לפי סדר מילוני:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -S

{
  "bpi": {
    "EUR": {
      "code": "EUR",
      "description": "Euro",
      "rate": "26,191.6827",
      "rate_float": 26191.6827,
      "symbol": "€"
    },
    "GBP": {
      "code": "GBP",
      "description": "British Pound Sterling",
      "rate": "22,466.4070",
      "rate_float": 22466.407,
      "symbol": "£"
    },
    "USD": {
      "code": "USD",
      "description": "United States Dollar",
      "rate": "26,886.8144",
      "rate_float": 26886.8144,
      "symbol": "$"
    }
  },
  "chartName": "Bitcoin",
  "disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
  "time": {
    "updated": "Sep 19, 2023 08:18:00 UTC",
    "updatedISO": "2023-09-19T08:18:00+00:00",
    "updateduk": "Sep 19, 2023 at 09:18 BST"
  }
}
נתקדם עם ה JSON ובואו נברר כמה דולר שווה ביטקוין היום. בשביל להגיע לערך צריך להתחיל עם המפתח bpi, להמשיך ל USD ושם למצוא את הערך של rate. סך הכל ב jq זה יהיה:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq '.bpi.USD.rate'
"26,889.1029"
ואם אתם לא רוצים את המרכאות סביב הערך תוכלו לבקש מ jq לוותר עליהן באמצעות המתג -r:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi.USD.rate'
26,889.1029
הביטוי .bpi.USD.rate נקרא פילטר, כי הוא מקבל בתור קלט את כל אוביקט ה JSON ומחזיר את הערך שהוא מצא בשדה. ב jq יש שני אופרטורים מרכזיים כדי לחבר פילטרים: אופרטור | ואופרטור ,. האופרטור | מחבר פילטרים בטור, כך שהפלט של פילטר אחד הופך לקלט של הפילטר הבא בתור. האופרטור , מחבר אותם במקביל כך שהקלט נשלח לשני הפילטרים ו jq מחבר את הפלט של שניהם. בואו ננסה את זה. מאחר ואנחנו יודעים לעבוד עם פילטר המפתח אפשר לקחת מפתח יותר קצר לדוגמה רק את האוביקט .bpi:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi'

{
  "USD": {
    "code": "USD",
    "symbol": "$",
    "rate": "26,912.5561",
    "description": "United States Dollar",
    "rate_float": 26912.5561
  },
  "GBP": {
    "code": "GBP",
    "symbol": "£",
    "rate": "22,487.9165",
    "description": "British Pound Sterling",
    "rate_float": 22487.9165
  },
  "EUR": {
    "code": "EUR",
    "symbol": "€",
    "rate": "26,216.7588",
    "description": "Euro",
    "rate_float": 26216.7588
  }
}
עכשיו ניקח את האוביקט הזה ונשלח אותו לפילטר מפתח נוסף:
$ curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq -r '.bpi | .USD'

{
  "code": "USD",
  "symbol": "$",
  "rate": "26,920.8824",
  "description": "United States Dollar",
  "rate_float": 26920.8824
}
וכבר אנחנו מבינים שהכתיב .bpi.USD הוא בעצם קיצור דרך לכתיב חיבור הפילטרים בטור .bpi.USD. אבל מה קורה אם אנחנו רוצים לקבל גם את המידע ביורו? הפעם נשתמש בפסיק:

ToCode
1 420
# פיענוח JSON משורת הפקודה עם jq פורמט JSON הפך לדרך הסטנדרטית בה אנחנו עובדים עם נתונים והכלי jq הוא האולר השוויצרי המושלם בשביל לקרוא ולערוך מידע בפורמט JSON משורת הפקודה. במדריך זה אלמד אתכם איך לעבוד עם jq כדי להוציא בקלות מידע מה JSON-ים שלכם בלי שתצטרכו להפעיל את ה IDE. בשביל שיהיה מעניין אנחנו נלמד על jq דרך עבודה על נתוני JSON אמיתיים: נתחיל עם המרת מטבעות דיגיטליים באתר coinbase, נמשיך לגיטהאב כדי ללמוד על פיענוח מאגרי קוד ונסיים עם עדכון קבצי JSON מקומיים. פוסט זה זמין גם בתור מדריך וידאו למנויי האתר בקישור: https://www.tocode.co.il/boosters/jq ## התקנת jq הכלי jq הוא די סטנדרטי ביוניקס ואם יש לכם לינוקס או מק רוב הסיכויים שהוא כבר הותקן. במידה ולא אפשר למצוא אותו בחבילות הסטנדרטיות של לינוקס לדוגמה:
# Debian / Ubuntu
$ sudo apt-get install jq

# Fedora
$ sudo dnf install jq

# OpenSuse
$ sudo zypper install jq

# Arch
$ sudo pacman -S jq
באתר של jq אפשר למצוא דף מסודר עם חבילות להורדה ואם אתם משתמשי מק תוכלו להתקין אותו עם homebrew עם הפקודה:
$ brew install jq
הכלי כתוב בשפת c, הגירסה העדכנית כרגע היא 1.7 (למרות שבהרבה מקומות עדיין משתמשים ב 1.6). אתם יכולים לוודא שמותקן אצלכם או לוודא אחרי שהתקנתם שהכל עובד באמצעות הפעלת:
$ jq --version
בנוסף אפילו אם אין לכם את jq מותקן על המחשב עדיין תוכלו להשתמש בגירסת האונליין שלו באתר: https://jqplay.org. ## איך זה עובד המטרה של jq היא למשוך מידע מאוביקט JSON ולהחזיר אותו בכל דרך שנרצה, ולכן המבנה הבסיסי בעבודה איתו נקרא filter. פילטר הוא פקודה שמקבלת קלט ומחזירה פלט, ופילטרים אפשר לחבר כך שפלט מפילטר אחד יהפוך לקלט של הפילטר הבא. דוגמה לפילטר פשוט היא הפילטר "נקודה מפתח", שמקבל אוביקט JSON ומחזיר את הערך במפתח. ניכנס לאתר https://jqplay.org/# ונכתוב שם את ה JSON הבא:
{
    "one": 1,
    "two": 2,
    "colors": [
        "red",
        "blue",
        "green"
    ],
    "paths": {
        "jq": "/opt/homebrew/bin/jq",
        "cp": "/bin/cp"
    }
}
עכשיו בתיבה שלמעלה עם הכותרת Filter נכתוב את הפילטר:
.one
ונוכל לראות בתיבת התוצאות את הערך 1. בשביל המשחק נעדכן את הפילטר ל .paths.jq ונראה את הערך בתיבת התוצאות מתעדכן ל "/opt/homebrew/bin/jq". נסו לשחק עם זה ולהגיע בעזרת הפילטר לכל אחד מהערכים ב JSON. אל תדאגו אם לא הצלחתם להגיע לתאים במערך, תכף נראה יחד איך לגשת אליהם. ## פיענוח מידע מ coinbase עכשיו שהבנו מה המטרה של jq בואו ניפרד מ jqplay ונעבור לשורת הפקודה. ה JSON הראשון שנרצה לפענח מגיע מה API של coinbase ואני מושך אותו משורת הפקודה עם curl באופן הבא:
curl --silent https://api.coindesk.com/v1/bpi/currentprice.json

{"time":{"updated":"Sep 19, 2023 08:13:00 UTC","updatedISO":"2023-09-19T08:13:00+00:00","updateduk":"Sep 19, 2023 at 09:13 BST"},"disclaimer":"This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org","chartName":"Bitcoin","bpi":{"USD":{"code":"USD","symbol":"$","rate":"26,884.9920","description":"United States Dollar","rate_float":26884.992},"GBP":{"code":"GBP","symbol":"£","rate":"22,464.8842","description":"British Pound Sterling","rate_float":22464.8842},"EUR":{"code":"EUR","symbol":"€","rate":"26,189.9074","description":"Euro","rate_float":26189.9074}}}%
בעיה ראשונה של ה JSON הזה היא שקשה לקרוא אותו מאחר ואין ירידות שורה ואינדנטציה. נשתמש ב jq כדי לתקן זאת:
curl --silent https://api.coindesk.com/v1/bpi/currentprice.json | jq

{
  "time": {
    "updated": "Sep 19, 2023 08:14:00 UTC",
    "updatedISO": "2023-09-19T08:14:00+00:00",
    "updateduk": "Sep 19, 2023 at 09:14 BST"
  },
  "disclaimer": "This data was produced from the CoinDesk Bitcoin Price Index (USD). Non-USD currency data converted using hourly conversion rate from openexchangerates.org",
  "chartName": "Bitcoin",
  "bpi": {
    "USD": {
      "code": "USD",
      "symbol": "$",

ToCode
1 420
# ספריה במקום הבנה? פרויקט פייתון חדש מציע לפתור עבורנו את הבעיה הקלאסית עם קלוז'רים בפייתון. זה קוד הדוגמה:
a = []
for i in [1, 2]:
    a.append(lambda: i)
for f in a:
    print(f())
הרבה אנשים מופתעים לגלות שהקוד הזה מדפיס פעמיים את המספר 2, במקום את 1 ו-2 כמו שאולי קיווינו. אחרי שרואים למה השאלה הבאה היא איך פותרים את זה. וכן דרך אחת זו הפונקציה מהפרויקט scope_capture. אבל אולי יהיה יותר מעניין להבין שה"בעיה" נגרמה כי לולאה בפייתון לא מייצרת תחום הגדרה למשתנה, וכל מה שאנחנו צריכים זה "לתפוס" את הערך של i במשהו שכן מייצר תחום הגדרה למשתנה, למשל פונקציה. כשאנחנו רואים את זה הצעד הבא הוא לחשוב איך אני מכניס את i להיות משתנה פרטי או פרמטר של פונקציה ואז להיזכר ב partial:
from functools import partial

a = []
for i in [1, 2]:
    a.append(partial(lambda i: i, i))
for f in a:
    print(f())
אז partial יכולה לבנות לי פונקציה חדשה ו"לקבע" את הפרמטר שלה לערך של i ברגע הקריאה ל partial, כלומר בתוך הלולאה. הקוד עובד ומדפיס 1 ו-2 כמו שרצינו, וכמובן יש לו עוד שני יתרונות חשובים: 1. לא צריך להתקין כלום, לא הוספתי תלות חדשה לפרויקט. 2. הקוד משתמש במבנה יותר גנרי - הפונקציה partial. מתכנתים חדשים שיגיעו לקוד אולי ילמדו להשתמש בה גם בהקשרים אחרים. נשווה את זה לקוד של הספריה scope_capture:
import inspect
from types import FunctionType

def _make_cell(value):
    fn = (lambda x: lambda: x)(value)
    return fn.__closure__[0]

def capture(f):
    try:
        frame = inspect.currentframe()
        fake_globals = {}
        fake_globals.update(f.__globals__)
        fake_globals.update(frame.f_back.f_locals)
        captured_cells = []
        if f.__closure__:
            for cell in f.__closure__:
                captured_cells.append(_make_cell(cell.cell_contents))
        call_fn = FunctionType(
            code=f.__code__,
            globals=fake_globals,
            name=f.__name__,
            argdefs=f.__defaults__,
            closure=tuple(captured_cells),
        )
        return call_fn
    finally:
        del frame
אז כן זה יפה איך שהוא בנה פוקנציה חדשה עם קריאה ל FunctionType ועדכן אותה לרוץ עם המשתנים "שנתפסו" אוטומטית. ועדיין התער של אוקהם מנצח פה. להבין את השפה יותר משתלם מאשר לשבור אותה כדי שתתאים למודל המנטלי שלך.

ToCode
1 420
$ rm -rf demo/.git
$ git add demo
$ git commit -m 'add demo'
ביטול ה add עם reset לפני שממשיכים היה חוסך את הכנסת תת הפרויקט ואת הבילבול שבא איתו.

ToCode
1 420
# הצילו גיט - אי אפשר לגשת לקובץ היתרון והחיסרון של עבודה עם ממשק שורת הפקודה של גיט הוא ההודעות האינסופיות שהוא כותב כשמנסים לעשות משהו לא הגיוני. מצד אחד זה אחלה שהוא מסביר הכל, אבל מצד שני קל לדלג על האזהרות הארוכות ולקוות שהכל יסתדר מעצמו. הדוגמה הבאה ממחישה למה חשוב לקרוא את הפידבק של גיט. ## איך נכנסים לבור ניצור פרויקט גיט בתיקיה בשם subproject, ואז ניצור פרויקט גיט חדש בתיקיה אחרת (קראתי לה demo). עכשיו נעתיק את תיקיית demo לתוך תיקיית subproject ונפעיל מתוך תיקיית subproject את הפקודה:
$ git add demo
גיט נלחץ ומדפיס הודעה ארוכה ממנה אני קורא רק את השורה הראשונה:
warning: adding embedded git repository: demo
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> demo
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached demo
hint:
hint: See "git help submodule" for more information.
השורה הראשונה מספיקה בשביל להבין שניסיתי להוסיף פרויקט גיט לתוך פרויקט גיט. נו, מה הבעיה, פשוט נמחק את ההיסטוריה של demo ונתקדם. נפעיל:
$ rm -rf demo/.git
$ git add demo
$ git commit -m 'add demo'
הפעם ב add אין שגיאה והקומיט גם עבר בשלום. את התוצאה דחפתי לריפו בקישור הזה: https://github.com/ynonp/add-subproject-demo. אם תיכנסו לריפו תשימו לב שאתם לא באמת יכולים להיכנס לתיקיית demo או לראות את הקבצים שבתוכה. ## מה שבור בפרויקט נפעיל log כדי לראות מה נשבר:
$ git log -p


commit 6d0d2fd41a8aa93a302d62cea5918b783ac00232 (HEAD -> main, origin/main)
Author: ynonp <ynonperek@gmail.com>
Date:   Mon Sep 18 21:39:37 2023 +0300

    add demo

diff --git a/demo b/demo
new file mode 160000
index 0000000..badb3c5
--- /dev/null
+++ b/demo
@@ -0,0 +1 @@
+Subproject commit badb3c5753a70c02e4e05a62ef9d6fcb65758d6c

commit 64f5679443ac216fb0e94483e679473f6f458294
Author: ynonp <ynonperek@gmail.com>
Date:   Mon Sep 18 21:36:30 2023 +0300

    hello world\

diff --git a/readme.txt b/readme.txt
new file mode 100644
index 0000000..3b18e51
--- /dev/null
+++ b/readme.txt
@@ -0,0 +1 @@
+hello world
ונשים לב באמצע לשורה:
+Subproject commit badb3c5753a70c02e4e05a62ef9d6fcb65758d6c
מה קרה פה? ה git add הראשון אומנם הדפיס אזהרה אבל עדיין ביצע את הפעולה שזה להוסיף פרויקט לתוך פרויקט. ה git add השני ביצע פעולה חדשה שהיתה להוסיף תיקיה וקבצים בתוך פרויקט. עכשיו הקומיט הכניס את שני השינויים ובפרויקט שלי מוגדרת גם תיקיה בשם demo וגם תת-פרויקט בשם demo, כשתת הפרויקט מסתיר את התיקיה ולכן לא ניתן להיכנס אליה. ## איך יוצאים מהבור אחרי שהבנו את הבעיה אפשר לחזור ולקרוא את האזהרה של גיט:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached demo
hint:
hint: See "git help submodule" for more information.
בגלל שתת-הפרויקט demo הסתיר את התיקיה demo (וזה החלק המבלבל פה), אני צריך למחוק אותו לפני שאוכל להוסיף את התיקיה.
$ git rm --cached demo
$ git add demo
$ git commit -m 'add demo'
## מה היה צריך לעשות מראש שווה לזכור ש add באמת עושה דברים, והפעולה ההפוכה שלו היא reset, לכן אם קיבלתי אזהרה מפחידה מגיט אחרי add הדבר הכי קל לעשות הוא:
$ git add demo
warning: adding embedded git repository: demo
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> demo
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached demo
hint:
hint: See "git help submodule" for more information.

$ git reset

ToCode
1 420
ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    3.382    3.382 <string>:1(<module>)
        1    0.000    0.000    3.381    3.381 a.py:16(primes_below_v2)
        1    0.116    0.116    3.381    3.381 a.py:17(<listcomp>)
   999998    3.216    0.000    3.265    0.000 a.py:19(is_prime)
        1    0.000    0.000    3.382    3.382 {built-in method builtins.exec}
   999998    0.049    0.000    0.049    0.000 {built-in method math.sqrt}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
הפעם השיטה שלי לקחה מעל 3 שניות בעוד שהשיטה של ארסטוטנס מצאה את כל המספרים בקצת יותר מחצי שניה. אחד הפיצ'רים שאני אוהב ב cPython הוא היכולת לכתוב את הסטטיסטיקות לקובץ במקום למסך באמצעות הוספת שם קובץ כפרמטר שני לקריאה:
cProfile.run("list(primes_below(1_000_000))", "stats1.profile")
cProfile.run("list(primes_below_v2(1_000_000))", "stats2.profile")
את הקבצים אפשר לשמור על השרת ומדי פעם למשוך אותם ולהשתמש במודול SnakeViz כדי לפתוח את הסטטיסטיקות בצורה ויזואלית אצלנו על המחשב ולהבין למה המערכת שלי עבדה כל כך לאט ואיפה אפשר לשפר.

ToCode
1 420
# הדרך הכי קלה לבדוק ולשפר ביצועים בפייתון המודול cProfile מציע דרך קלה ומדויקת להשוות ביצועים של פונקציות בפייתון ולפעמים גם יוכל ללמד אותנו מה הבעיה ואיך לתקן אותה. בואו ננסה אותו באמצעות חיפוש מספרים ראשוניים. ## איך אני מוצא מספרים ראשוניים דרך קלה לבדוק אם מספר ראשוני בפייתון היא לנסות למצוא במה הוא מתחלק. הנה הקוד:
def is_prime(n: int) -> bool:
    for i in range(2, int(sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True
עכשיו בשביל למצוא את כל המספרים הראשוניים הקטנים ממספר מסוים מספיק להפעיל את הפרדיקט על כל המספרים בטווח ולראות מי מחזיר ערך אמת:
def primes_below_v2(n: int):
    return [p for p in range(2, n) if is_prime(p)]
## איך ארסטוטנס מוצא מספרים ראשוניים הנפה של ארסטוטנס היא שיטה טובה יותר למצוא קבוצות של מספרים ראשוניים. ארסטוטנס שם לב שמספר ראשונים זה מספר שלא מתחלק בשום דבר מלבד באחד ובעצמו, ולכן אם יש לך מספר ראשוני קל מאוד למצוא המון מספרים לא ראשוניים - פשוט תכפול את המספר הראשוני שכבר יש לך במספרים 2, 3, 4, 5 וכך הלאה. אם נתחיל לדוגמה עם 2 שהוא ראשוני אז ברור שכל המספרים הזוגיים אינם ראשוניים. 3 הוא ראשוני ולכן 6, 9, 12, 15 וכל שאר הכפולות של 3 אינם ראשוניים. את הנפה אפשר לקודד בפייתון באופן הבא:

def primes_below(n: int) -> Generator[int, None, None]:
    p = 2
    marked = set()
    while p < n:
        yield p
        marked.add(p)
        for i in range(n // p + 1):
            marked.add(i * p)
        while p in marked:
            p = p + 1
## מי יותר מהיר איך נדע איזה שיטה מהירה יותר? טוב מלבד האינטואיציה הברורה עדיין יהיה מעניין לראות קצת מספרים, והמודול cProfile פה בשביל לעזור. אני מוסיף את הקוד:
import cProfile

cProfile.run("list(primes_below(100))")
cProfile.run("list(primes_below_v2(100))")
ומריץ, והפעם מקבל את הפלט הבא על המסך:
 ~/tmp  python a.py
         250 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
       26    0.000    0.000    0.000    0.000 a.py:5(primes_below)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
      221    0.000    0.000    0.000    0.000 {method 'add' of 'set' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


         201 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 a.py:16(primes_below_v2)
        1    0.000    0.000    0.000    0.000 a.py:17(<listcomp>)
       98    0.000    0.000    0.000    0.000 a.py:19(is_prime)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
       98    0.000    0.000    0.000    0.000 {built-in method math.sqrt}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
זה נחמד - אנחנו רואים כמה זמן לקחה כל אחת משתי הדרכים ואפילו איזה קריאות לפונקציות בוצעו וכמה זמן בילינו בכל אחת מהפונקציות הפנימיות. הבעיה היא שהמספרים עדיין קטנים מדי ואין הבדל בין שתי השיטות. במספרים יותר גדולים הפערים מתגלים:
 ~/tmp  python a.py
         3089206 function calls in 0.652 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.010    0.010    0.652    0.652 <string>:1(<module>)
    78499    0.367    0.000    0.642    0.000 a.py:5(primes_below)
        1    0.000    0.000    0.652    0.652 {built-in method builtins.exec}
  3010704    0.276    0.000    0.276    0.000 {method 'add' of 'set' objects}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}


         2000001 function calls in 3.382 seconds

   Ordered by: standard name

ToCode
1 420
החשיבות של עבודה בסביבה בריאה היא עצומה, כי בשוק התוכנה עבודות הן לא לכל החיים ולא משנה מה תעשו ב-5 שנים הקרובות סיכוי טוב שתצטרכו לחפש עבודה חדשה. סביבת העבודה היא שנותנת לי את הכלים, האמונות והיכולות שימשיכו איתי הלאה לעבודה הבאה. סביבה שמעודדת מתכנתים לקחת אחריות, ללמוד נושאים חדשים, להציע שיפורים ולפתור בעיות בצורה מעמיקה תוציא אתכם מתכנתים טובים יותר ותתן ייתרון תחרותי עצום בחיפוש העבודה הבא.

ToCode
1 420
# סימני אזהרה לדינמיקה רעילה בפרויקט איפה תרצו להיות בעוד שנתיים? איפה אתם חושבים שתהיו? אם אתם עובדים בצוות טוב יש סיכוי טוב שבעוד שנתיים תהיו יותר מקצועיים ממה שאתם היום, יהיו לכם יותר משאבים, יותר קשרים בתעשיה ויותר בעיות שפתרתם ותוכלו ללמוד מהן ולהשתפר לעתיד. אם אתם עובדים בצוות רעיל יש סיכוי טוב שבעוד שנתיים יהיו לכם פחות משאבים, פחות קשרים בתעשיה, ותסבלו מ Tunnel Vision לגבי אפשרויות פיתרון בעיות. עבודה בצוות רעיל היא מזיקה כי לא רק שאנחנו לא משתפרים, אנחנו גם מאמצים פרקטיקות ושיטות עבודה שפשוט ימשכו אותנו למטה בכל פרויקט עתידי ואמונות שיהיה קשה להתנער מהן בהמשך. הבעיה היותר קשה עם צוותים רעילים היא שקשה מאוד לצאת מהם. אנשים שעובדים בסביבה רעילה בטוחים שזאת הדרך היחידה, שצריך להקריב בשביל הפרויקט, שבכל פרויקט יש בעיות ומה אתה עושה עניין, ובגדול ימשיכו לתרץ ולהסביר לעצמם ולאנשים מסביב שבסך הכל המצב לא כזה רע. בעבודה, כמו במסגרות אחרות בחיים, כשאתה עמוק בתוך פרויקט קשה לראות איך דברים היו יכולים לעבוד. אז בדיוק בשביל אלה מכם שתקועים באיזה בוץ כזה, שבטוחים שעכשיו קשה אבל עוד מעט זה יסתדר, שמשוכנעים שבכל פרויקט יש בעיות, הנה רשימה של 4 סימני אזהרה שעשויים לרמוז לכם שכדאי לקחת צעד אחורה- ## יש רק מתכנת אחד שיודע לגעת בחלק מסוים (וקריטי) במערכת. בהרבה פרויקטים האחריות על חלקים בקוד מתחלקת בין מתכנתים שונים בפרויקט וזה טבעי לגמרי, אבל בצוותים טובים יש לנו שיטות לפזר את האחריות ולחלק את הידע והמיומנות הטכנית בין האנשים - אנחנו נשתמש ב Pair Programming, Code Reviews, הרצאות לצוות והאקתונים כדי לשלב אנשים גם בחלקים של המערכת בהם הם לא נוגעים ביום יום. צוותים רעילים ישאירו את האחריות רק על בן אדם אחד ואותם אנשים ישתמשו בכח שלהם כדי להרחיק אנשים אחרים שרוצים לעזור. אם תמיד מתקשרים לאותו בן אדם כשיש בעיה קריטית, אם כל תיקון באג חייב לעבור דרך בן אדם אחד, ובמיוחד אם הבן אדם הזה מאוד זמין גם בשעות לא הגיוניות, יש פה חשד להתנהלות לא בריאה. ## הניהול מושפע מפוליטיקה וממה צריך לספר לבוסים במקום מה יהיה טוב למוצר. בכל פרויקט יש מנהלים שמחליטים מה יהיה הכיוון ועובדים יותר זוטרים שצריכים לממש את ההחלטות. ההבדל הוא שבפרויקט בריא לכל החלטה יש הסבר ומשתמשים במטריקות כדי להבין ולשקף איזה החלטות הביאו לאיזה תוצאות. בפרויקט רעיל אנחנו נראה ישיבות תכופות עם ההנהלה וכל פעם הנחתה אחרת של כיוון שצריך לקחת, בלי לתת לכיוון הקודם הזדמנות ולפעמים אפילו בלי לסיים פיתוח של פיצ'ר שרק לפני שבוע היה התקווה הגדולה של המוצר. מותר לשנות את דעתך, אבל כשהמנהלים לא מסתמכים על נתונים ומופיעים עם דרישות חדשות משום מקום, יש חשד להתנהלות לא בריאה. ## בחירה טכנולוגית המושפעת מטרנדים או פחד לשנות טכנולוגיה שינוי טכנולוגי הוא חלק מהחיים, כי שפות התכנות עצמן והפריימוורקים מתפתחים. אז אם התחלתי פרויקט לפני עשר שנים בגירסה מסוימת של Java ומאז לא שידרגתי את ה Java כי לא היה זמן, זה אומר משהו על סדרי העדיפויות בפרויקט. מצד שני אם כל שלושה חודשים אני משכתב את הפרויקט מאפס בטכנולוגיה אחרת זה גם סימן לסדרי עדיפויות לא נכונים. בתהליך פיתוח בריא אנחנו מתחילים עם בחירה טכנולוגית מסוימת ומשם משדרגים את הכלים והקוד בצעדים קטנים. החשיבות של ריפקטורינג היא שרק דרך כתיבה ושידרוג הפיתרון אנחנו יכולים לשפר אותו, וזו הדרך שלנו ללמוד טכנולוגיה יותר לעומק. כתיבת קוד בלי לעדכן אותו, או כתיבה מאפס כל שלושה חודשים, ישאירו אותי ברמת פיתרון בסיסית ובקוד שכאילו הועתק מאיזה Tutorial או מ Chat GPT. זה לא סוג הניסיון שאתם רוצים לצבור בעבודה. ## רעיונות חדשים נדחים משיקולים לא ענייניים, ופיתוחים חדשים מקבלים כתף קרה או נשלחים לאינסוף סבבי תיקונים לפני שימוזגו לפרויקט. לא כל רעיון הוא טוב אבל בפרויקטים בריאים כולם יכולים להציע רעיונות ויש דרך ידועה שבה רעיונות חדשים נכנסים למערכת. כך אם בניתי סקריפט חדש שאמור לעשות אוטומציה לתהליך שעד עכשיו היה ידני זה בסדר להציע כמה תיקונים לפני שמאשרים להפיץ אותו לכל הצוות, אבל אם כל סקריפט שאני בונה בסופו של דבר נגנז בגלל יותר מדי דרישות לתיקונים אז לאט לאט אני מאבד מוטיבציה ולא רוצה להציע עוד רעיונות חדשים.

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

ToCode
1 420
# זה רק קוד קחו את הקוד הכי יפה, שימו אותו בארון ותחזרו עוד 5 שנים. הוא עדיין יישאר יפה? הוא עדיין יעבוד? יהיה עדיין מי שירצה לתחזק אותו? לא בטוח בכלל. אנחנו משתמשים בקוד כדי לגרום למחשב לעשות דברים ושוכחים שמחשבים משתנים, הדברים שאנחנו צריכים משתנים ואנחנו משתנים - ולכן גם הקוד צריך להשתנות. רוב הזמן אם אף אחד לא נגע בקוד 5 שנים זה לא אומר שהקוד טוב במיוחד אלא בדיוק להיפך, הקוד כל כך גרוע שלאף אחד אין אומץ לגעת בו. כן הוא פותר את הבעיה אבל הוא מפסיד בתחזוקה. כשאנחנו אומרים ש"קוד זה רק קוד" המשמעות היא- 1. לא נקשרנו לקוד שכתבנו. אנחנו נשמח לבנות מחדש מנגנונים כולל מנגנוני ליבה של המערכת אם בניה מחדש כזאת תשפר את עבודה המערכת. 2. בנינו מראש מנגנונים שיאפשרו לשנות את הקוד כשנצטרך. הקוד לא "מתעד את עצמו" אלא יש הערות במקום שמסבירות מה המטרה של הקוד ומה הפונקציות צריכות לעשות. יצרנו תוכניות בדיקה כדי שאפשר יהיה לשנות חלקים בקוד ולוודא שלא שברנו כלום. 3. בנינו מראש את התקשורת לתוך הקוד במהלך העבודה. עבדנו ב Pair Programming כדי לוודא שלפחות עוד בן אדם אחד יכול לקרוא את מה שכתבנו. הצגנו את הבעיות והפיתרונות המרכזיים של הקוד בפורום של הצוות. קיבלנו פידבק מאנשים שלא קשורים למנגנונים הספציפיים עליהם אנחנו עובדים כדי לוודא שכולם יכולים לקרוא אותם ולדבר עליהם. 4. הקפדנו להשתמש בשפה מנותקת לגבי הקוד. זה לא ה"קוד שלי" או "המנוע של בר", זה "ממשק המשתמש", ה"מנוע" או "ה API". ככל שנקפיד להוציא את הכותבים מהקוד ולהפוך אותו לתוצאת עבודה משותפת של כל הצוות כך יהיה קל יותר לשתף אחריות, אשמה וגם לשכתב כשצריך. זה רק קוד. בואו לא ניתן לו להחליף את התקשורת והעבודה המשותפת שלנו.