fa
Feedback
ToCode

ToCode

رفتن به کانال در Telegram

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

نمایش بیشتر
1 419
مشترکین
اطلاعاتی وجود ندارد24 ساعت
اطلاعاتی وجود ندارد7 روز
اطلاعاتی وجود ندارد30 روز
آرشیو پست ها
ToCode
1 419
# חידת דוקר: שינוי הרשאות בתיקיה חדשה ניצור 3 קבצים. הראשון הוא Dockerfile:
FROM ubuntu:22.04

WORKDIR /app

COPY . .

RUN chmod +x /app/startup.sh

CMD ["/app/startup.sh"]
השני נקרא startup.sh ומכיל את התוכן הבא:
#!/bin/bash

echo Hello World
והשלישי docker-compose.yml עם התוכן הבא:
version: "3.9"

services:
  app:
    build: .
    volumes:
      - .:/app
אני מפעיל:
$ docker compose build
$ docker compose run
ומקבל את השגיאה:
[+] Running 1/1
 ⠿ Container chmod-in-run-app-1  Recreated                                                                         0.2s
Attaching to chmod-in-run-app-1
Error response from daemon: failed to create shim: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: "/app/startup.sh": permission denied: unknown
מה קרה שם? איך נתקן? ## הסבר ופיתרון קל לראות שהבעיה היא שלקובץ startup.sh אין הרשאת הרצה, אבל מעניין להבין איך זה קרה מאחר וה Dockerfile כולל את השורה:
RUN chmod +x /app/startup.sh
ולתעלומה הפעם יש הסבר פשוט - ה Dockerfile יצר אימג', העתיק את הקובץ startup.sh לתוך האימג' ושינה באימג' את הרשאות ההרצה. אבל אז בא ה docker-compose.yml והחליט למפות את תיקיית העבודה הנוכחית בתור volume ל /app. מיפוי זה דרס את הקובץ startup.sh עם הרשאות ההרצה והסתיר אותו באמצעות הקובץ startup.sh שקיים כבר בתיקיה ושאינו כולל הרשאות הרצה. ואחרי שמבינים את הבעיה הפיתרון הוא ברור - במקום להעתיק את startup.sh לתוך תיקיית /app שם הוא יוסתר, אני מעתיק אותו לתיקיה אחרת בזמן יצירת האימג', כלומר משנה את ה Dockerfile לתוכן הבא:
FROM ubuntu:22.04

WORKDIR /app

COPY . .

COPY ./startup.sh /usr/local/bin/startup.sh

RUN chmod +x /usr/local/bin/startup.sh

CMD ["/usr/local/bin/startup.sh"]
עכשיו רגע רגע יגידו הקוראים הערניים - קלקלת הכל. קודם היתה לך מערכת (לא עובדת, נכון, אבל מערכת) שבה כשאני משנה את startup.sh בתיקיה על המחשב המארח אני לא צריך לבנות מחדש את האימג' כדי לראות את השינוי, ואילו עכשיו חייבים להריץ build מחדש כל פעם שמשנים את startup.sh. אם זאת בעיה גם עבורכם כל מה שצריך זה לפצל את הסקריפט startup.sh לשניים - חלק אחד שלו ישב ב /usr/local/bin, יוגדר בתור ה CMD של האימג', ישנה את ההרשאות ויריץ את /app/startup.sh והחלק השני, הוא /app/startup.sh, והוא זה שיהיה ממופה לתיקיית העבודה במחשב המארח ויתעדכן אוטומטית עם כל שינוי.

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

ToCode
1 419
# הערכה טכנולוגית מתחילה ב Trade Offs כשאנחנו באים לבחור כלי או שיטת עבודה מסוימים הכי קל למצוא את היתרונות המוחלטים של אותו כלי או את החסרונות המוחלטים שלו. בדוגמה פשוטה אם אני מתלבט בין React ל Solid.JS, אז רוב התוצאות בחיפוש בגוגל של ההשוואה יתיחסו לזה שריאקט יותר פופולרי ולכן יש יותר ספריות ומקומות ללמוד מהם; ושסוליד נותן ביצועים טובים יותר והקוד שלו ריאקטיבי יותר. אבל אם אני מגיע לבצע הערכה טכנולוגית של כלי המטרה שלי היא בדרך כלל לא לכתוב פוסט לבלוג אלא להתאים כלי לפרויקט. ובשביל זה הרבה יותר מעניין יהיה למצוא את ה Trade Offs, כלומר את היתרונות והחסרונות הספציפיים של הכלי לפרויקט שלי. מה אני מקבל ומה אני משלם בשביל זה. מה החסרונות של שילוב טייפסקריפט בפרויקט? איך זה ישפיע על קצב הפיתוח? איך זה ישפיע על העבודה שלי עם מתכנתים חיצוניים? איך זה ישפיע על גיוס מתכנתים חדשים לצוות? איך זה ישפיע על ההרגשה של הוותיקים? איך זה ישפיע על האפשרות לשיתוף קוד בין מערכות? במילים אחרות - מה החסרונות של טייפסקריפט בפרויקט הספציפי שלכם? ולמרות שזה נשמע פרדוקסלי, אני הרבה יותר רגוע כשאני רואה לצד היתרונות גם את החסרונות של שיטת העבודה שאני רוצה לבחור. וזה הרבה פעמים לא קל למצוא אותם. אז אם נשים את טייפסקריפט בצד ונחזור לריאקט, הפרסומים על גירסה 18 של ריאקט מספרים בהתלהבות על ה Concurrent Rendering ואיך זה עוזר לשפר ביצועים. לקח לי הרבה זמן להבין ש Render Tearing זאת בעיה אמיתית והיא משפיעה על שיטת העבודה שלי גם אם היום אני לא משתמש ב Concurrent Rendering. לכן אם אתם אחראים על הבחינה הטכנולוגית, חוץ מ POC לכלי החדש שווה לעשות מאמץ מוגבר כדי למצוא כמה שיותר חסרונות מראש. כי הבעיה עם חסרונות היא שהם נוטים להפתיע בדיוק ברגע הלא נכון. נ.ב. הסקירה הזאת על ראסט: https://www.bunniestudios.com/blog/?p=6375 היא דוגמה מעולה לסקירה טכנולוגית שמציגה גם את העלויות ולא רק יתרונות מובהקים או חסרונות מובהקים.

ToCode
1 419
הקובץ השלישי הוא src/App.js (ולא jsx, כי אין יותר jsx) והוא מקבל את התוכן הבא:
import { html } from 'htm/react';
import React from "react";

export default function App() {
  const [count, setCount] = React.useState(0);
  function inc() {
    setCount(c => c + 1);
  }

  return (
    html`<div>
      <p>Welcome To The Future</p>
      <button onClick=${inc}>Value: ${count}. Click To Increase</button>
    </div>
    `
  );
}
הכל כאן ריאקט רגיל לגמרי, חוץ מה JSX שהפך ל Template Strings שמאוד דומים לו. ## הרצה שלושת הקבצים האלה, בלי וובפאק, בלי node.js, בלי בייבל ובלי שום טרנספילציה, רק שלושה קבצים - מספיקים בשביל לקבל יישום ריאקט בדפדפן. אני מפעיל שרת מקומי על פורט 8080 עם הפקודה:
$ npx http-server
נכנס מהדפדפן לשרת מקומי עם הפורט המתאים ומקבל כפתור שמראה כמה פעמים לחצתי עליו. ## מחשבות לעתיד כמה מחשבות והערות אחרי כתיבת הקוד הזה: 1. האתר ga.jspm.io הוא זה שמגיש את קבצי ה JavaScript בפועל והוא מתפקד בתור CDN. זה מאוד מזכיר את החיים בפיתוח ווב לפני עשר שנים, כשבשביל להשתמש בספריה היינו פשוט מדביקים את הלינק אליה ל html. אני מתאר לעצמי שהרבה אנשים יעדיפו עדיין לשמור את כל התלויות האלה אצלם בפרויקט או על cdn בשליטתם, וזה בסדר - ל Import Maps לא אכפת מאיפה הוא מביא את המודולים. 2. יהיה מעניין לראות אם לאורך זמן אפשר יהיה למצוא הצדקה ל webpack ו babel, כשרוב מה שהם עושים אפשר לעשות היום ישירות בדפדפן. נכון - scss זה חמוד ול TypeScript יש יתרונות, אבל המחיר של הכלים האלה מתחיל להיות משמעותי ככל שהאלטרנטיבה הפשוטה יותר יכולה לתת מענה מלא לרוב הצרכים.

ToCode
1 419
# החיים אחרי בייבל את AMD הכרתי באמצעות require.js שהיתה להיט באזור שנת 2012 (מה שנקרא, עובד ב IE6) וכנראה גם קצת קודם. מה ש require עשתה היה לאפשר לכם לכתוב בתוך קובץ JavaScript אחד פקודה שתטען קובץ JavaScript אחר. נכון, לפני require היתה את dojo שעבדה על אותו מנגנון אבל זה כבר למתקדמים. ל Require היה כלי אופטימיזציה שלוקח קבצים שכתובים ב AMD ומאחד אותם יחד לקובץ אחד וככה מצליח לשפר את זמן טעינת העמוד, כי צריך לשלוח פחות קבצים לדפדפן. אחרי זה הגיעו grunt, gulp, webpack ו babel ואנחנו עברנו להשתמש בכתיב ה import/export ולהריץ קוד על קבצי ה JavaScript בתוך שלב האופטימיזציה. הרצת הקוד איפשרה ליצור תחבירים חדשים כמו TypeScript ו JSX, שמתקמפלים ל JavaScript ומספקים חוויה טובה יותר למתכנתים. בגלל שממילא הרצנו וובפאק לא היתה עלות לשימוש בכלים אלה. בסך הכל עוד פלאגין להתקין. נגלגל קדימה ל 2022 והעולם השתנה שוב. היום דפדפנים תומכים תמיכה מלאה ב ES Modules ובטעינת קובץ JavaScript מתוך קובץ אחר. בנוסף תקן HTTP/2 תומך ב Server Push שאומר שאין ייתרון מבחינת ביצועים ל Bundling. וככה אנחנו קמים בוקר אחד ומגלים שזה הזנב שמקשקש בכלב - שה JSX, שמכרו לנו אותו כמו תוספת חביבה שלא עולה כלום, נשאר הסיבה היחידה להמשיך להשתמש בכל הקונסטרוקציה של וובפאק ו babel. והגלגל מתחיל להסתובב שוב. הספריה htm מציעה למי שרוצה לוותר על שלב הקומפילציה דרך החוצה, באמצעות תחביר שנראה מאוד דומה ל JSX אבל עובד בדפדפן. שילוב של htm עם כל שאר החידושים בעולם ה web מאפשר בניה של אפליקציית ריאקט מלאה בלי קומפילציה. בואו נראה איך זה עובד. ## קוד הפרויקט אני כותב קובץ בשם index.html עם התוכן הבא:
<!DOCTYPE html>
<html lang="en">
  <head><title>Hello World</title></head>
  <body>
    <main id="app"></main>
  <script type="importmap">
  {
    "imports": {
      "htm/react": "https://ga.jspm.io/npm:htm@3.1.1/react/index.module.js",
      "react": "https://ga.jspm.io/npm:react@18.1.0/dev.index.js",
      "react-dom": "https://ga.jspm.io/npm:react-dom@18.1.0/dev.index.js"
    },
    "scopes": {
      "https://ga.jspm.io/": {
        "htm": "https://ga.jspm.io/npm:htm@3.1.1/dist/htm.module.js",
        "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js",
        "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.index.js",
        "scheduler/tracing": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.tracing.js"
      }
    }
  }
  </script>
  
  <!-- ES Module Shims: Import maps polyfill for modules browsers without import maps support (all except Chrome 89+) -->
  <script async src="https://ga.jspm.io/npm:es-module-shims@1.5.1/dist/es-module-shims.js" crossorigin="anonymous"></script>

    <script type="module" src="src/main.js"></script>
  </body>
</html>
החלק הראשון אמור להיות לכם מוזר - המנגנון נקרא Import Maps והוא אמור לאפשר בעתיד ניהול תלויות מסודר מתוך ES Modules. כרגע, הבעיה עם תלויות היא שכשאתם כותבים:
import React from 'react';
לדפדפן אין מושג מאיפה להביא את react. ב node או webpack אנחנו משתמשים בקובץ package.json בשביל למפות את השם הזה לקובץ אמיתי, ובתוך דפדפן Import Maps הוא הצעה אחת למימוש כזה מנגנון. אפשר לקרוא עליו כאן: https://github.com/WICG/import-maps הדבר החשוב הוא שכרגע יש פוליפיל שעובד והרבה כלים שיודעים לקחת קובץ package.json ולהפוך אותם ל Import Maps, וגם ממשק גרפי אונלייני לבניית Import Maps שאפשר למצוא בקישור הזה: https://generator.jspm.io/#. לכן החלק של ה Import Maps מקביל ל package.json שיהיה לנו בפרויקט webpack קלאסי. חוץ ממנו יש סקריפט בשם main שמוגדר כמודול והוא נקודת הכניסה ליישום. בקובץ src/main.js אני כותב את התוכן הבא:
import { html } from 'htm/react';
import ReactDOM from 'react-dom';
import App from './App.js';

ReactDOM.render(html`<${App} />`, document.querySelector('#app'));
ופה אנחנו מתחילים לראות את ספריית htm בפעולה. אפשר לראות שהקוד אינו JSX אבל הוא כן מאוד דומה בזכות השימוש ב Template Strings. אגב, חוץ מזה אפשר היה בקלות לטעות ולחשוב שאנחנו בפרויקט וובפאק. כל ה import-ים עובדים רגיל לגמרי.

ToCode
1 419
# מדריך: איך לבדוק פרויקט vite עם vitest לפני כמה חודשים העברתי כאן וובינר על ויט, כלי שורת פקודה לבניית פרויקטי ווב. לויט יש תפריטים יפים כדי ליצור פרויקטים מכל מיני סוגים (ריאקט, ויו, סבלט), אבל הוא גם תומך בספריות אחרות באמצעות קונפיגורציה למשל סוליד. כשהעברתי את הוובינר vitest היה עדיין בתחילת הדרך ולא היתה דרך סטנדרטית לכתוב בדיקות לפרויקטי ויט. זה השתנה לאחרונה ו vitest מספק אינטגרציה טובה עם ויט, תחביר כמעט זהה למה שאנחנו מכירים מ jest ועוד כמה פיצ'רים מעניינים כמו הרצת בדיקות במקביל או כתיבת בדיקות בגוף הקוד. במדריך זה אני אצור אתכם פרויקט react עם vite, אתקין את vitest ואכתוב את הבדיקה הראשונה לפרויקט. מוכנים? הנה זה בא. ## יצירת פרויקט ריאקט עם vite שלב ראשון הוא יצירת פרויקט ריאקט חדש עם ויט. אני מריץ משורת הפקודה:
$ npm create vite@latest my-react-app
בוחר בתפריט את האפשרות react ואחרי זה שוב react ומפעיל את שלושת הפקודות שמופיעות על המסך כדי לוודא שהפרויקט נבנה:
$ cd my-react-app
$ npm install
$ npm run dev
ויט ירים שרת שמאזין לפורט 3000 ויציג לוגו מסתובב של ריאקט יחד עם כפתור מונה לחיצות. כל לחיצה על הכפתור מעלה את המספר ב-1. ## הוספת vitest בשביל לכתוב בדיקות לפרויקט אני מוסיף את ויטסט באמצעות הרצת:
$ npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
בנוסף ל vitest התקנתי גם את jsdom כדי שאוכל לבדוק דברים שקשורים לדפדפן (הבדיקות רצות ב node.js), ואת כל החבילה של react-testing-library ו jest-dom. את ג'סט עצמו אני לא צריך להתקין כי אני לא אשתמש בו. ויטסט משתמש ב chai, אבל מסתבר ש jest-dom יכול להתחבר ל chai והכל יעבוד. אחרי ההתקנה אני ממשיך לעדכון ההגדרות. בקובץ vite.config.js אני מוסיף בלוק test עם התוכן הבא:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  test: {
    environment: "jsdom",
    globals: true,
    setupFiles: 'src/setupTests.js',
  },
})
ביקשתי להריץ את הבדיקות בתוך jsdom ולפני הרצת הבדיקות להריץ קובץ בשם src/setupTests.js. אני יוצר גם את הקובץ הזה וכותב בתוכו:
import '@testing-library/jest-dom'
בשביל לטעון את jest-dom לפני שמריצים את הבדיקות. כמו כן, jest-dom מצפה למצוא את expect בתור משתנה גלובאלי שכבר מוגדר בעמוד ובגלל זה הוספתי את globals: true להגדרות הבדיקות (שיעבוד כמה שיותר דומה למה שאני רגיל מ jest). שינוי הגדרות אחרון הוא בקובץ package.json שם אני מוסיף סקריפט שמריץ את הבדיקות:
  "scripts": {
    "test": "vitest",
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
## כתיבת בדיקה ראשונה זה הכל לגבי ההתקנות ועכשיו אפשר ליצור קובץ App.test.jsx עם הבדיקות הראשונות:
import { screen, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import App from './App';

describe('App', () => {
  it('shows Hello text', () => {
    render(<App />);
    expect(screen.getByText('Hello Vite + React!')).toBeInTheDocument();
  });

  it('increases the value when button is pressed', async () => {
    render(<App />);
    const btn = screen.getByRole('button', { name: /count is/ });
    await userEvent.click(btn);
    expect(btn).toHaveTextContent('count is: 1');
  });
});
וכמו עם ג'סט בשביל להריץ את הבדיקות מספיק להפעיל:
$ npm run test
באופן אוטומטי vitest יעלה ויתחיל להריץ את הבדיקות וימשיך להריץ מחדש את הבדיקות הרלוונטיות כל פעם שתעדכנו את הקוד.

ToCode
1 419
# טיפ גיט: נגמלים מ push ננעלתי מחוץ לבית השבוע. כן, יש לי דלת כזאת שמרגע שסוגרים אותה היא ננעלת, ובלי לשים לב סגרתי את הדלת כשהמפתחות היו בתוך הבית. מהלך בלתי הפיך דומה קורה לי ב git כל פעם אחרי שאני עושה commit - והפעם אני מדבר על push. סיבה אחת להפעיל push מיד אחרי קומיט הוא בגיבויים. הפעלת push דוחפת את המאגר לשרת מרוחק שאולי מתגבה בתדירות גבוהה יותר מהמחשב שלי. אם הנושא הזה חשוב לכם אני מציע לייצר מנגנון גיבויים טוב יותר על תחנת העבודה שלכם, או ליצור מאגר מרוחק שישמש אתכם רק לצורך גיבוי ולא לצורך שיתוף קוד עם אנשים אחרים. אגב - כבר כתבתי בעבר מדריך איך לעבוד עם מספר מאגרים מרוחקים בגיט. סיבה שניה (או יותר נכון אילוץ) להפעיל push מיד אחרי commit היא בשביל להפעיל איזה pipeline שיבדוק את השינוי על שרת מרוחק. גם פה אם אפשר שווה לייצר מאגר אחד עבור ה pipelines והבדיקות, ומאגר שני, אמיתי, לצורך שיתוף קוד עם אנשים אחרים. וכל זה בגלל שהחיסרון בהפעלת push מיד אחרי הקומיטים הוא שמרגע שעשיתי push אני כבר לא יכול לערוך את הקומיטים. וקצת כמו כשננעלים מחוץ לבית, גם פה בדיוק בשניה שאני לוחץ על ה Enter פתאום אני מבין שעדיף היה להפריד את השינוי לשני קומיטים, או ששכחתי להוסיף קובץ בקומיט, או שהיתה לי איזה שגיאת כתיב בקוד או בהודעת הקומיט. רק שמה שלפני שניה היה עניין פשוט של לפתוח את הארון ולהוציא את המפתחות, הופך לעסק הרבה יותר מסובך אחרי ה push, כי עכשיו צריך לרוץ לכל האנשים שבטעות קיבלו את הקומיט הזה ולבקש מהם להתעלם ממנו או להיזהר ב pull הבא כי אני הולך לדרוס את המאגר המרכזי. וטיפ לסיום - צעד בדרך לגמילה מ push יכול להיות לבטל את ה upstream tracking של הענף שאתם עובדים עליו, וכך push בלי שם של ענף לא יעבוד. כלומר על ענף קיים שכבר עוקב אחר ענף מרוחק נפעיל:
$ git branch --unset-upstream
ועכשיו ניסיון לעשות push ייכשל עם ההודעה:
fatal: The current branch mybranch has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin mybranch
אתם עדיין יכולים לעשות push אם אתם משוכנעים שזה רעיון טוב תוך ציון שם הענף בצורה מפורשת, במקרה שלי זה:
$ git push origin mybranch
והיתרון הוא שכבר אי אפשר לכתוב בטעות git push מיד אחרי קומיט ואחרי זה לאכול את הלב כשצריך לתקן שגיאת כתיב.

ToCode
1 419
בתיעוד של פייתון יש דף מידע מעולה על עבודה עם תאריכים שכולל את כל הדוגמאות מכאן והרבה יותר: https://docs.python.org/3/library/datetime.html

ToCode
1 419
# מתי תורי? סקריפט פייתון פשוט לחישוב זמן המתנה הלכתי למשרד הפנים השבוע לחדש דרכון וגיליתי שני דברים - המקומם יותר הוא שצריך לשלם יותר כדי שהפרטים שלי לא יישמרו במאגר הביומטרי (כי הדרכון תקף רק לחמש שנים). ואני הייתי בטוח שאני חוסך להם מקום שם. הפחות מקומם הוא שאפילו אחרי שמזמינים תור חצי שנה מראש, עדיין צריך לחכות איזו שעה עד שנכנסים. בשביל להתמודד עם השני ובשביל לא להשתעמם בשעה הזאת, כתבתי סקריפט פייתון קצר שעוזר להבין כמה זמן המתנה עוד נשאר לך. הוא לא מדויק אבל כן אפשר ללמוד ממנו דבר או שניים לגבי טיפול בזמנים בפייתון, אז אני משתף. ## איך עובדים עם timedelta ו datetime יש מספר מחלקות לעבודה עם זמנים בפייתון. אלה שיעזרו לנו בסקריפט הן datetime, שמייצגת אוביקט תאריך ושעה, ו timedelta שמייצגת הפרש בין שני אוביקטי datetime. הפונקציה now של datetime מחזירה אוביקט שמתאים למה שיש עכשיו. הנה כמה דוגמאות לשימוש במחלקות:
>>> from datetime import datetime, timedelta
>>> datetime.now()
datetime.datetime(2022, 5, 16, 21, 34, 23, 446115)

>>> datetime.now() + timedelta(days=1)
datetime.datetime(2022, 5, 17, 21, 35, 3, 13974)

>>> datetime.now() - timedelta(minutes=10)
datetime.datetime(2022, 5, 16, 21, 25, 18, 937076)
אפשר גם ליצור אוביקט datetime מתאריך ושעה ספציפיים, או לקחת אוביקט קיים ולהחליף חלק מהשדות שלו. לדוגמה הקוד הבא לוקח אוביקט datetime של היום ומחליף רק את השעה והדקות לשעה ארבע אחר הצהריים:
>>> datetime.now().replace(hour=16, minute=0)
datetime.datetime(2022, 5, 16, 16, 0, 17, 369505)
## הנתונים עכשיו שאנחנו יודעים לעבוד עם datetime ו timedelta הגיע הזמן לאסוף קצת נתונים. אם אתם יושבים במשרד הפנים או בדואר אפשר פשוט לרשום בקובץ את מה ששומעים. זאת הרשימה שאני שמעתי:
16:02 484
16:06 501
16:07 504
16:09 508
16:11 510
16:12 513
16:14 511
16:15 516
16:21 521
16:22 523
16:26 478
16:30 524
16:32 525
16:33 530
16:34 532
16:36 527
16:37 531
16:39 534
16:42 542
16:45 541
העמודה הראשונה מייצגת שעה והעמודה השניה את המספר של מי שעכשיו נכנס. אני הייתי 547 ורציתי לדעת כמה זמן עוד נשאר לי לחכות. ועכשיו לסקריפט. ## איך מחשבים בשביל לחשב כמה זמן המתנה עוד נשאר, מספיק להסתכל על כמה שורות אחרונות בקובץ. אנחנו לא רוצים להסתכל על כל הקובץ כי יכול להיות שמישהו נתקע לפני חצי שעה, או שלפני שעה היו פחות פקידים ממה שיש עכשיו. חמש או עשר שורות אחרונות יתנו תוצאה מדויקת. שמתי לב גם שאנשים לא תמיד נכנסים לפי הסדר אבל התעלמתי מזה בסקריפט כי בסוף הגיע תורי. בשביל החישוב יצרתי רשימה, שכל תא ברשימה מכיל אוביקט datetime של שעת הכניסה ומספר שמייצג את המספר שלו. תמיד אפשר להכניס עוד שורות לרשימה כשמקריאים עוד מספר, או לקרוא קובץ שכבר כל השורות נמצאות בו כדי ליצור את הרשימה מאפס. אנחנו כן צריכים לנקות פריטים ישנים אם גודל הרשימה עולה על גודל הדגימה הרצוי. זאת הפונקציה שמטפלת בזה:
def invite(when, who):
    if len(sample) == SAMPLE_SIZE:
        sample.pop()

    sample.insert(0, (when, who))
אחרי שאני בונה את כל הרשימה אני רוצה לבדוק כמה אנשים מחכים לפניי בתור. אני יודע שאנשים לא תמיד נכנסים לפי הסדר אז אני פשוט מחפש בדגימה הנוכחית מי המספר המקסימלי ומחסיר אותו מהמספר שלי:
def people_before_me(my_number):
    last_number = max(x[1] for x in sample)
    return my_number - last_number
האתגר הבא יהיה למצוא מה זמן ההמתנה הממוצע עבור הדגימה שבניתי. בשביל זה אני מחשב את ההפרש בין כל שתי שורות בדגימה - ההפרש מגיע בתור אוביקט timedelta - ואז מחשב את ממוצע ההפרשים:
def average_wait_time():
    wait_times = []
    prev = sample[0]
    for i in sample[1:]:
        wait_times.append(prev[0] - i[0])
        prev = i

    return sum(w.total_seconds() for w in wait_times) / len(wait_times)
הפונקציה total_seconds של timedelta מחזירה את הפרש הזמן הכולל בשניות. לסיום אני קורא את קובץ הקלט ומפענח אותו עם הפונקציות שכתבתי:
with open('myturn.txt') as f:
    for line in f:
        try:
            parse_line(line)
        except Exception:
            print(f"Skipped line: {line}")

print(f"Please wait {average_wait_time() * people_before_me(547)} Seconds")

ToCode
1 419
# זה ייקח רק חמש דקות יש מצבים שאנחנו נתקעים על קוד ויודעים מה שבור בו פשוט לא מצליחים לראות את זה. כתבתי על דוגמה כזאת לפני כמה ימים עם ה div-ים שלא נסגרו לי טוב ב HTML, אבל אנחנו גם מכירים את זה ממצבים ששכחנו לעשות Save, שטעינו באיות של משתנה או ששכחנו להעביר איזה פרמטר לפונקציה שיגרום להכל להסתדר. אלה כולם מצבים שכשמישהי תראה לי מה שבור אני מיד אגיד "יו נכון איך לא שמתי לב" ואמשיך הלאה. ואלה התקלות שהכי כיף לעזור בהן לאנשים. אבל אלה רק חלק קטן מהמצבים. בדרך כלל כשמישהו מחפש תשובה של חמש דקות יש פער ידע עצום שמתחבא מאחוריה, שיהיה קשה לסגור בחמש דקות. אם מישהו ישאל אותי איך לתקן טקסט של הודעת קומיט בגיט, אני יכול לענות git commit --amend, אבל אז אני אצטרך להסביר מה זה מאגר מרוחק, ולמה לא כדאי לעשות את זה אחרי שעשית push, ועל ה Immutability של קומיטים ועוד אינסוף פרטים על איך גיט עובד. תשובה של חמש דקות לא תעזור כאן. או בדוגמה אחרת מ bash הפעם, בשביל להסביר למישהי איך לתקן את הסקריפט הזה:
#!/bin/bash

count=0

ls | while IFS= read -r line;
do
  (( count++ ))
done

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

ToCode
1 419
true
במקום זה, הדרך המקובלת היום לבדוק אם אוביקט מסוים הוא מערך היא הפונקציה Array.isArray שמובנית בשפה ונתמכת בכל מקום:
> const x = { a: 10, length: 20 };
> Array.isArray(x)
false
## טריק 3 - בדיקה אם שדה מסוים שייך לאוביקט או ל Prototype שלו המנגנון המיותר השלישי והאחרון הוא הבדיקה:
if (Object.prototype.hasOwnProperty(k)) {}
הפונקציה hasOwnProperty נועדה להבדיל בין מאפיינים שהם "באמת" באוביקט שלי, לבין מאפיינים שאפשר להגיע אליהם מהאוביקט שלי אבל הם בעצם מוגדרים על אוביקט אחר שהוא במעלה ה Prototype Chain. לדוגמה המאפיין toString זמין על כל אוביקט JavaScript:
> const x = { a: 10 }
> x.toString()
'[object Object]'
אבל לא היינו רוצים לקבל אותו באיטרציה של forEach כי הוא מוגדר ב Object.prototype ולא על האוביקט שעליו אני רץ. הטריק הזה עדיין רלוונטי אם אתם מבצעים לולאת for ... in על אוביקט, אבל מה שהופך אותו למיושן זה שכמעט אין סיבה לביצוע לולאות כאלה. הלולאה שהראיתי בתחילת הפוסט:
for (const [k, v] of Object.entries(x)) {
  handler(v, k);
}
אוטומטית מסננת החוצה את כל המאפיינים שהגיעו לאוביקט דרך ה Prototype שלו ונותנת תוצאה זהה בתחביר יותר נוח. נכון, אנחנו מפסידים בביצועים אבל קשה לי להאמין שהפער יהיה מורגש באיזושהי אפליקציה אמיתית.

ToCode
1 419
# שלושה טריקים של foreach שכבר לא כדאי לעשות החבילה foreach עלתה לכותרות לא מזמן כשצייצן אחד טען שהצליח להשתלט על חשבון ה npm שמנהל אותה. זה הסתבר בתור פייק ניוז אבל עשה הרבה רעש, והזכיר למי שהיה צריך תזכורת כמה שביר כל העסק הזה של חבילות npm. בינתיים ובגלל שהחבילה כוללת בסך הכל פונקציה אחת לא מסובכת, חשבתי לנצל את הבאז כדי ללכת לקרוא את הקוד וללמוד ממנו שלושה טריקים שפעם היו מקובלים ב JavaScript אבל היום כבר לא כדאי להשתמש בהם: ## קודם כל הקוד מטרת החבילה בגדול לאפשר למתכנתים לרוץ בלולאה על אוביקט בלי לדעת אם הוא מערך או אוביקט. אם הוא מערך הלולאה תפעיל פונקציית טיפול ותעביר לה כל פעם אינדקס וערך, ואם הוא אוביקט הלולאה תפעיל את הפונקציה ותעביר לה כל פעם מפתח וערך. ב readme הם נותנים את הדוגמה הבאה:
var each = require('foreach');

each([1,2,3], function (value, key, array) {
    // value === 1, 2, 3
    // key === 0, 1, 2
    // array === [1, 2, 3]
});

each({0:1,1:2,2:3}, function (value, key, object) {
    // value === 1, 2, 3
    // key === 0, 1, 2
    // object === {0:1,1:2,2:3}
});
למרות שאני לא ממש רואה את הערך בכזאת ספריה, הרבה כותבי ספריות אחרים ב npm כן ראו את הערך ובאתר יש 141 ספריות שתלויות ב foreach כאשר הגירסה הקודמת (לפני פרשיית ההשתלטות המזויפת) הגיעה כמעט ל 6 מיליון הורדות סך הכל ב 8 השנים מאז פורסמה. צריך להגיד, הספריה די נטושה ועם JavaScript מודרני אין ממש טעם להשתמש בה. הקוד הבא עובד בכל הדפדפנים ונותן לולאה זהה למערכים ואוביקטים:
const x = [10, 20, 30, 40];

const y = { a: 10, b: 20, c: 30, d: 40 };

function handler(value, key) {
  console.log(`value ${value}; key ${key}`);
}

for (const [k, v] of Object.entries(x)) {
  handler(v, k);
}

console.log('---');

for (const [k, v] of Object.entries(y)) {
  handler(v, k);
}
אבל אנחנו לא פה כדי לחפור בעבר אלא כדי ללמוד ממנו, ולכן ללא דיחוי בואו נראה איזה אוצרות נוכל למצוא בקוד של foreach. זה כל הקוד מתוך המאגר שלהם בגיטהאב:

var hasOwn = Object.prototype.hasOwnProperty;
var toString = Object.prototype.toString;

module.exports = function forEach (obj, fn, ctx) {
    if (toString.call(fn) !== '[object Function]') {
        throw new TypeError('iterator must be a function');
    }
    var l = obj.length;
    if (l === +l) {
        for (var i = 0; i < l; i++) {
            fn.call(ctx, obj[i], i, obj);
        }
    } else {
        for (var k in obj) {
            if (hasOwn.call(obj, k)) {
                fn.call(ctx, obj[k], k, obj);
            }
        }
    }
};
## טריק 1 - בדיקה אם אוביקט הוא פונקציה הטריק הראשון בפונקציה הוא הבדיקה האם הפרמטר השני הוא פונקציה. הם משתמשים בבלוק הבא:
if (toString.call(fn) !== '[object Function]') {
    throw new TypeError('iterator must be a function');
}
מצד אחד אני אוהב שספריות מנסות לעזור לי ולגלות שגיאות, אבל מצד שני ברוב המקרים בדיקות מתוחכמות מדי עלולות לטעות גם הן ואז הכל נהיה יותר מבלבל. במקרה שלנו הבדיקה הפשוטה אם משהו הוא פונקציה היתה צריכה להיות:
if (typeof(fn) !== 'function') {}
או יותר טוב לכתוב את הקוד בתוך try/catch ולזהות TypeError. הסיבה להמרה ל String היא כדי לא לתפוס פונקציות מערכת מסוימות ולהכריח את הקורא להעביר פונקציה שהוא הגדיר לבד. הבעיה עם זה היא שהיום יש הרבה פונקציות אמיתיות שמשתמש מגדיר לבד שיחזירו טקסט אחר ב toString שלהן, לדוגמה פונקציות אסינכרוניות:
> async function h() { }
undefined
> Object.prototype.toString.call(h)
'[object AsyncFunction]'
## טריק 2 - בדיקה אם אוביקט הוא מערך החלק הבא של הקוד צריך להפעיל לולאה אחרת על אוביקט ולולאה אחרת על מערך. הם בודקים מה הם קיבלו באמצעות המאפיין length - אם יש לך length אתה מערך, בלעדיו אתה אוביקט. כאן אנחנו כבר לא יכולים להשתמש ב typeof כי:
> typeof [1,2,3]
'object'
אבל מצד שני המאפיין length הוא גם לא רעיון טוב. בתור התחלה אני יכול להוסיף את המאפיין length לכל אוביקט שלי, ואז הפונקציה each תתיחס אליו בטעות כמו למערך:
> const x = { a: 10, length: 20 };
>  var l = x.length;
> l === +l