es
Feedback
ToCode

ToCode

Ir al canal en Telegram

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

Mostrar más
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
+130 días
Archivo de publicaciones
ToCode
1 419
// Definitions by: Zlatko Andonovski, Andrew Yang, Chandler Fang and Zac Xu

declare function leftPad(str: string|number, len: number, ch?: string|number): string;

declare namespace leftPad { }

export = leftPad;
קריאה מהירה בקוד מראה לנו שני שינויים משמעותיים מבחינת ביצועים: 1. שינוי באלגוריתם שמקצר משמעותית את הלולאה. 2. תוספת Cache שמקצרת משמעותית את זמן החישוב עבור קלטים קצרים. נתחיל עם ה cache. בנג'מן גרינבאום הוסיף אותו עם הערת הקומיט הבאה: > Cache for very common cases > I estimate that the majority of people who left pad use it for formatting in which case performing any calculations at all is silly. אני לא בטוח מה גודל החיסכון של ה Cache כי הוא באמת משמש רק לאורכים מאוד קטנים, אבל מבחינת קוד זה באמת נותן טאצ' מושקע. השינוי היותר משמעותי הוא זה שמופיע ב PR הזה: https://github.com/camwest/left-pad/pull/5 והוא השינוי באלגוריתם. הנה החלק המרכזי ממנו בלי הערות כדי שיהיה לנו קל לקרוא:
while (true) {
  if (len & 1) pad += ch;

  len >>= 1;

  if (len) {
    ch += ch;
  } else {
    break;
  }
}
// pad `str`!
return pad + str;

מה קורה כאן? אז אם נחשוב על זה רגע האתגר שלנו הוא לבנות מחרוזת באורך len שמורכבת מרווחים בלבד, ואותה להצמיד להתחלה של str. בגירסה הלא יעילה שקראנו קודם הבניה היתה באמצעות לולאה שכל פעם הוסיפה רווח אחד ולכן היתה צריכה len איטרציות. בגירסה של בנג'מן יש לנו אופטימיזציה מעניינית: נשים לב שכל מספר אפשר לייצג באמצעות סכום של חזקות של 2. זה בעצם הייצוג של המספר בבסיס 2. לדוגמה המספר 8 הוא פשוט 2 בחזקת 3, המספר 20 הוא 2 בחזקת 4 ועוד 2 בחזקת 2, והמספר 73 הוא בסך הכל 64 (כלומר 2 בחזקת 6) ועוד 8 (כלומר 2 בחזקת 3) ועוד 1 (שזה 2 בחזקת 0). אם במקום להוסיף כל פעם רווח אחד, נכפיל את מספר הרווחים שאנחנו עובדים איתם נוכל להגיע לסידרת מחרוזות הרווחים הבאה:
" "
"  "
"    "
"        "
"                "
בכל איטרציה אנחנו נוסיף למחרוזת הרווחים את עצמה, וכך נכפיל את אורכה בפעולה אחת. זאת משמעות השורה:
ch += ch;
מקוד התוכנית. האורך שאנחנו צריכים להוסיף כמובן לא כולל את כל מחרוזות הרווחים בסידרה אלא רק את אלה שמתאימות לייצוג הבינארי של len. בשביל למצוא אותן אנחנו מחלקים את len ב-2 בכל איטרציה ולוקחים רק את המחרוזות עבורן תוצאת החלוקה היא אי זוגית. זו משמעות השורה:
if (len & 1) pad += ch;
בואו ננסה את זה עם אורך 20 כדי לראות שהמנגנון עובד: 1. בסיבוב הראשון len הוא 20, ch מכיל רווח בודד ו pad הוא ריק. מחלקים את len ב 2 כדי לקבל 10, הוא גדול מאפס ולכן מכפילים את האורך של ch וממשיכים לאיטרציה הבאה. 2. בסיבוב השני len הוא באורך 10, ch מכיל שני רווחים ו pad עדיין ריק. זיכרו שאנחנו צריכים את 16 ו 4 כדי להגיע ל 20. גם הפעם len הוא זוגי אז מחלקים אותו ב 2 וממשיכים לאיטרציה הבאה. 3. בסיבוב השלישי len הוא באורך 5. זו הפעם הראשונה שהוא אי זוגי ולכן אנחנו לוקחים את ch, שכרגע יש בו 4 רווחים, ומעתיקים אותו ל pad. אנחנו משתמשים במספר 4 שמצאנו כדי לבנות את מחרוזת הרווחים. עכשיו ממשיכים לפי התוכנית - מחלקים את len ל 2 בחלוקת מספרים שלמים ונשארים עם 2, ושוב מכפילים את ch כך שעכשיו יש בו 8 רווחים. 4. בסיבוב הרביעי len הוא באורך 2, כלומר זוגי. אפשר להכפיל את ch ולקבל שם מחרוזת עם 16 רווחים ולהמשיך לאיטרציה הבאה. 5. בסיבוב החמישי len יורד לאורך 1 (כי 2 חלקי 2 נותן 1). זה אי זוגי אז מוסיפים ל pad את הערך הנוכחי של ch כלומר את ה 16 רווחים. קודם היו בו 4 רווחים ולכן עכשיו ב pad יש 20 רווחים. ממשיכים לסיבוב הבא. 6. באיטרציה האחרונה len מתאפס ואנחנו יוצאים מהלולאה בקריאה ל break. סך הכל בשביל לייצר מחרוזת של 20 תווים היינו צריכים רק 5 איטרציות במקום 20 - או במקרה הכללי log2(n) איטרציות. זה חיסכון משמעותי בזמן ריצה אם משתמשים בפונקציה הרבה פעמים. ## וקטנה על ch לפני שניפרד בואו נתעכב רגע על שורת הגדרת ברירת המחדל למשתנה ch:
if (!ch && ch !== 0) ch = ' ';
ב JavaScript היום הייתי מגדיר ערך ברירת מחדל לפרמטר בשורת החתימה של הפונקציה, אבל בגירסאות ישנות יותר של JavaScript זה לא היה אפשרי. עדיין הבדיקה הכפולה נראית חכמה מדי בשביל ניסיון ראשון. שימוש ב git blame מגלה לי ששורה זו נכנסה לקוד בקומיט 0e04eb4d עם הודעת הקומיט המופלאה:

ToCode
1 419
# קריאה מודרכת בקוד של left-pad במרץ 2016 מתכנת בשם Azer Koçulu (לא בטוח שאני יודע לתרגם את השם הזה לעברית) שבר את האינטרנט. הוא מחק מ npm כמה מודולים שפירסם בפרשה שהביאה בפעם הראשונה את מנהלי npm להחזיר מודול שמשתמש מחק. המודול במרכז הפרשה נקרא left-pad ומה שמיוחד בו הוא שאינספור חבילות npm השתמשו בו בתור דרישת קדם. ברגע שמודול זה ירד, אי אפשר היה יותר להתקין אף חבילה שתלויה בו וכך מאות אלפי מתכנתים ברחבי העולם לא הצליחו להפעיל npm install. בפוסט זה ארצה לצלול אתכם לקוד של left-pad, להבין מה היה מיוחד בו ומה השתנה בו לאורך השנים. ## הגירסה שהוסרה אנחנו עובדים עם הריפו https://github.com/left-pad/left-pad וכמו שתשימו לב אם תיכנסו למאגר המודול הוא Deprecated מה שאומר שלא מומלץ להשתמש בו יותר. הוא הוחלף על ידי הפונקציה המובנית String​.prototype​.pad​Start כחלק מ ES 2017, כלומר קצת יותר משנה אחרי הפרשה. טיוטות ההצעה הסתובבו בשטח עוד מ 2015 כך שלא נראה שיש קשר בין הדברים. דפדוף בהיסטוריית הגיט מביא אותנו לקומיט 76979f0a50877c50afd817923acf6f224bba3d36 בו Azer החליט להוריד את המודול מ npm, ולכן זה יהיה הקומיט בו ארצה להתחיל את הקריאה שלי. באותו קומיט קובץ ה readme מתאר את דרך ההתקנה המומלצת למי שרוצה להשתמש במודול:
$ npm install azer/left-pad
הכתיב npm install ואחריו שתי מילים מופרדות בלוכסן מאפשר התקנה של מודולים ישירות מגיטהאב בלי לעבור דרך הריפו המרכזי npm וזה מתועד כאן: https://docs.npmjs.com/cli/v8/commands/npm-install. הקובץ המעניין יותר באותו קומיט הוא כמובן index.js שמכיל את הקוד ל left-pad, כלומר הפונקציה שלוקחת מחרוזת ומוסיפה רווחים בתחילתה עד שמגיעים לאורך הרצוי. זה המימוש:
module.exports = leftpad;

function leftpad (str, len, ch) {
  str = String(str);

  var i = -1;

  if (!ch && ch !== 0) ch = ' ';

  len = len - str.length;

  while (++i < len) {
    str = ch + str;
  }

  return str;
}
האמת? ציפיתי ליותר. הפונקציה פשוט מחשבת את אורך הריפוד (האורך הרצוי פחות אורך המחרוזת), ואז רצה בלולאה כדי לבנות את הריפוד. אישית אם הייתי צריך לממש מנגנון דומה הייתי הולך על join - כלומר משהו כזה:
var pad = new Array(len).join(ch);
אבל האמת שהמימוש עם לולאת ה while יותר מהיר אז אני לגמרי מבין את ההגיון. בכל מקרה אם יש דבר אחד שלמדתי מכל הסיפור של left-pad זה שהרבה יותר נוח להדביק חתיכת קוד שלא אנחנו כתבנו מאשר לממש לבד ולהרגיש רע עם זה שהקוד לא מספיק יפה. ## לפט פד היום דבר אחד טוב שכן יצא מפרשיית left-pad היה הפירסום שהספריה זכתה לה. אחרי ההסרה היא קיבלה Maintainer חדש, בדיקות טובות יותר והכי חשוב מימוש הרבה יותר יעיל. כך נראית הפונקציה היום:
'use strict';
module.exports = leftPad;

var cache = [
  '',
  ' ',
  '  ',
  '   ',
  '    ',
  '     ',
  '      ',
  '       ',
  '        ',
  '         '
];

function leftPad (str, len, ch) {
  // convert `str` to a `string`
  str = str + '';
  // `len` is the `pad`'s length now
  len = len - str.length;
  // doesn't need to pad
  if (len <= 0) return str;
  // `ch` defaults to `' '`
  if (!ch && ch !== 0) ch = ' ';
  // convert `ch` to a `string` cuz it could be a number
  ch = ch + '';
  // cache common use cases
  if (ch === ' ' && len < 10) return cache[len] + str;
  // `pad` starts with an empty string
  var pad = '';
  // loop
  while (true) {
    // add `ch` to `pad` if `len` is odd
    if (len & 1) pad += ch;
    // divide `len` by 2, ditch the remainder
    len >>= 1;
    // "double" the `ch` so this operation count grows logarithmically on `len`
    // each time `ch` is "doubled", the `len` would need to be "doubled" too
    // similar to finding a value in binary search tree, hence O(log(n))
    if (len) ch += ch;
    // `len` is 0, exit the loop
    else break;
  }
  // pad `str`!
  return pad + str;
}
ובנוסף קיבלנו בתיקיה גם קובץ index.d.ts עם הגדרת הטיפוסים ל TypeScript:

ToCode
1 419
# היום למדתי: בחירת טיפוס ממערך ב TypeScript הנה טריק TypeScript מדליק שיעזור לכם להוסיף בדיקת טיפוסים כשיש לכם ביד מערך קבוע ופונקציה שצריכה לקבל ערך מתוך אותו מערך. בדוגמה (מתוך הפוסט שלימד אותי את זה) היה לנו:
const animals = [
    { id: 1, name: 'cat' },
    { id: 2, name: 'dog' },
    { id: 3, name: 'mouse' },
];

type AnimalName = 'cat' | 'dog' | 'mouse'

function getAnimal(name: AnimalName) {
  return animals.find(a => a.name === name)
}
עכשיו אנחנו יכולים להפעיל:
console.log(getAnimal('dog'));
ולקבל את התוצאה בלי בעיה, אבל אם ננסה להפעיל:
console.log(getAnimal('bird'));
נקבל כשלון בזמן הבניה כי אין חיה כזאת במערך. עכשיו זה מעולה אבל השורה הזאת:
type AnimalName = 'cat' | 'dog' | 'mouse'
מרגישה מיותרת - הרי כבר יש לי את המערך, למה אני צריך לחזור על השמות של כל החיות רק בשביל להגדיר טיפוס? מסתבר שגם במייקרוסופט הבינו שהיא מיותרת ול TypeScript יש תחביר ממש פשוט שמאפשר לוותר עליה. הקוד הבא שקול לגמרי לגירסה הראשונה ולא כולל את הכפילות:
const animals = [
    { id: 1, name: 'cat' },
    { id: 2, name: 'dog' },
    { id: 3, name: 'mouse' },
] as const;

type AnimalName = typeof animals[number]['name'];

function getAnimal(name: AnimalName) {
  return animals.find(a => a.name === name)
}

console.log(getAnimal('dog'));
רק אל תשכחו להוסיף אחרי הגדרת המערך את המילים as const אחרת TypeScript יגדיר את AnimalName להיות פשוט string ולא יזרוק שום שגיאה.

ToCode
1 419
# למה צריך להוסיף @latest בהפעלת npx? אם אתם משתמשים ב npx מספיק זמן תוכלו לשים לב שהוא לא תמיד בוחר את הגירסה החדשה ביותר של הכלים להרצה. אצלי על המכונה npx התחיל לאחרונה לקטר על create-react-app:
$ npx create-react-app yoyo
Need to install the following packages:
  create-react-app
Ok to proceed? (y)

You are running `create-react-app` 4.0.3, which is behind the latest release (5.0.0).

We no longer support global installation of Create React App.

Please remove any global installs with one of the following commands:
- npm uninstall -g create-react-app
- yarn global remove create-react-app

The latest instructions for creating a new app can be found here:
https://create-react-app.dev/docs/getting-started/
מיותר לציין שאין לי שום התקנה גלובאלית של create-react-app ולכן שתי ההצעות שלו להסרת create-react-app לא היו רלוונטיות. מה קרה כאן? זה פשוט - גם ל npx יש cache. הוא שומר אותו בתיקיית ~/.npm/_npx. כשהוא שואל אותי אם להתקין את create-react-app ואני מאשר הוא שם לב שלא ביקשתי גירסה מסוימת, אז הולך ל cache, מוצא שם את גירסה 4 ומתקין אותה. דרך קלה לצאת מזה היא לציין את הגירסה, וזו המשמעות של כרוכית latest - כלומר הפעלה כזו:
$ npx create-react-app@latest yoyo
דרך שניה שנותנת פיתרון יותר יציב היא פשוט לרוקן את ה cach של npx. נכון זה אומר שהמחשב שלכם יצטרך לעבוד קצת יותר קשה בפעם הבאה שתפעילו כלים שהיו שמורים שם קודם, אבל אחרי זה שוב יהיו לכם את הגירסאות החדשות ביותר:
$ rm -rf ~/.npm/_npx
$ npx create-react-app yoyo

ToCode
1 419
# קל לשנות הנה רשימה חלקית של דברים שאנחנו בונים מסודרים לפי כמה קל לשנות אותם: 1. מימוש של פונקציה 2. חתימה של פונקציה 3. רשימת פונקציות או מחלקות שמיוצאות ממודול 4. רשימת טבלאות ב DB ועמודות של כל טבלה 5. רשימת API Routes והחלטה על הפרמטרים של כל נתיב 6. פרוטוקולים לתקשורת בין מערכות (API ציבורי) קוד משתנה כל הזמן, עדכונים בבסיס הנתונים הרבה יותר איטיים ומסוכנים, עדכון של API Routes עלול להשפיע על מערכות חיצוניות ולכן דורש סינכרון שינויים בין מספר צוותים, שינוי Public API אומר שהרבה לקוחות שלכם יצטרכו לעדכן את הקוד שלהם וזה סינכרון אפילו יותר מורכב משינוי פנימי בארגון. בבניית מוצר חדש שווה לשים לב ולמקד את ההשקעה במקום הנכון - בחירת API ציבורי בעייתי עלולה לסבך לכם את הקוד של כל המערכת בצורה שיהיה מאוד קשה לתקן בהמשך.

ToCode
1 419
        console.log(`File uploaded successfully. ${data.Location}`);
    });
};

uploadFile(filename);
הסקריפט נועד להרצה משורת הפקודה בצירוף שם קובץ והוא מעלה את הקובץ לדלי שיצרנו. הרצה לדוגמה - בהנחה שיש לכם קובץ בשם cat.jpg בתיקיה תיראה כך:
$ node utils/uploads.js cat.jpg
## איך מקבלים רשימה של כל הקבצים ב Bucket והחלק האחרון במערכת הוא פניה ל S3 כדי לקבל רשימה של כל הקבצים בדלי. ניצור קובץ בשם src/lib/images.js עם התוכן הבא:
const s3 = require('./mys3');
const { BUCKET_NAME } = require('./globals');

var params = {
  Bucket: BUCKET_NAME,
};


export async function listImages() {
  console.log('listing images');
  const data = await s3.listObjectsV2(params).promise();
  const images = data.Contents.map(item => `https://${BUCKET_NAME}.s3-eu-west-1.amazonaws.com/${item.Key}`);
  return images;
}
הפונקציה listImages פונה ל s3, מעבירה את שם הדלי ומקבלת את רשימת כל הקבצים בדלי. הרשימה מגיעה בפורמט כזה:
{
  IsTruncated: false,
  Contents: [
    {
      Key: 'cat.jpg',
      LastModified: 2022-01-19T13:38:18.000Z,
      ETag: '"df71c370626f34e60ae6c1df9b64eb09"',
      Size: 138421,
      StorageClass: 'STANDARD'
    }
  ],
  Name: 'ynonp-s3-photos',
  Prefix: '',
  MaxKeys: 1000,
  CommonPrefixes: [],
  KeyCount: 1
}
אני משתמש ב key של כל קובץ ומצרף אליו את שם הדלי כדי לקבל URL לתמונה. צריך להגיד - הסיפור הזה עובד כל כך פשוט רק בגלל שכל התמונות שלי הן ציבוריות. אם התמונות היו פרטיות הייתי צריך לפנות לפונקציה נוספת כדי לקבל Presigned URL לכל תמונה. האפליקציה שכתבתי היא ב Next.JS ולכן אני יכול לערבב קוד צד שרת וקוד צד לקוח באותו קובץ. במקרה שלי הקובץ נקרא src/pages/index.js וזה התוכן שלו:
import Head from 'next/head'
import styles from '../../styles/Home.module.css'
import { listImages } from '../lib/images';


export default function Home(props) {
  const { images } = props;
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <p>Hello World</p>
        {images.map((imgSource) => (
          <img src={imgSource} />
        ))}
      </main>
    </div>
  )
}

export async function getServerSideProps() {
  const images = await listImages();
  return {
    props: { images },
  };
}
## איך מפעילים אצלכם בשביל להפעיל את המערכת אצלכם תצטרכו לעבור מספר שלבים: 1. שכפלו את המאגר המלא שלי מכאן: https://github.com/ynonp/s3-photos-demo 2. צרו משתמש ב S3 וקבלו את פרטי הגישה 3. צרו משתני סביבה בשם AWS_ID ו AWS_SECRET עם פרטי הגישה שלכם. 4. בחרו שם ל Bucket ורשמו אותו בקובץ src/lib/globals.js במקום השם שאני בחרתי. 5. הפעילו משורת הפקודה את הפקודה שיוצרת את ה Bucket וודאו שהכל עובד. 6. הפעילו משורת הפקודה כמה פעמים את הסקריפט upload עם כמה תמונות כדי להעלות אותן ל Bucket 7. הפעילו את המערכת עם npm run dev וכנסו למסך הראשי ב localhost:3000. אם הכל הלך כמו שצריך תוכלו לראות על המסך את כל התמונות שהעליתם.

ToCode
1 419
# מדריך: איך להעלות קבצים ל AWS S3 מתוך יישום Node.JS בקורס Node.JS פה באתר אני מלמד איך לשמור קבצים בתיקיה מקומית על השרת או בבסיס נתונים של Mongo. בעקבות שאלה של תלמיד חשבתי להוסיף כאן מדריך איך להעלות את הקבצים גם ל AWS S3 למי שמעדיף. ## מה אנחנו בונים בדוגמה במדריך זה אני אבנה מערכת וובית פשוטה להצגת תמונות. המערכת מורכבת מ: 1. סקריפט בשם upload שמעלה קובץ לדלי ב S3. 2. אתר ריאקט שמציג את כל התמונות ששמורות בדלי. ## קטנה על הרשאות מנגנון ההרשאות של AWS הוא מאוד מורכב והוא כנראה הסיבה המרכזית בגללה אנשים מסתבכים עם AWS. בדוגמה שלנו אני יוצר Bucket שמחזיק תמונות. כל התמונות ציבוריות וכל אחד יכול לראות את כל התמונות של כולם. זה הופך את החיים להרבה יותר פשוטים מבחינת הקידוד אבל כמובן שגישה זו לא מתאימה לכל מערכת. אם רוצים לייצר קבצים שיהיה נגישים רק לאנשים מסוימים ולא נגישים לאחרים צריך לשמור את כל הקבצים פרטיים וכל פעם שמשתמש רוצה לגשת לאחד מהם לייצר עבורו לינק מיוחד וספציפי לתמונה. אמזון תומכים בזה עם דבר שנקרא Presigned URL אבל הוא מחוץ לסקופ של מדריך זה. ## איך יוצרים Bucket אני מניח שיש לכם חשבון AWS ושיצרתם אסימון גישה, ושיש לכם ביד את ה id וה secret של אסימון הגישה. לפרטים איך ליצור אסימון גישה ולקבל את פרטי הגישה אפשר להסתכל במדריך הזה: https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html בעבודה עם Node.JS יש לנו חבילה נחמדה בשם aws-sdk שעוזרת ל Node להתחבר ל AWS. אני מתקין את החבילה עם:
$ npm install --save aws-sdk
ויוצר קובץ חדש בפרויקט - אצלי הוא נקרא src/lib/mys3.js עם התוכן הבא:
const AWS = require('aws-sdk');

// Enter copied or downloaded access ID and secret key here
const ID = process.env.AWS_ID;
const SECRET = process.env.AWS_SECRET;

const s3 = new AWS.S3({
    accessKeyId: ID,
    secretAccessKey: SECRET
});

module.exports = s3;
בשביל לא לשמור בקוד את מפתחות הגישה שמרתי אותם בתור משתני סביבה. הקוד עצמו פשוט יוצר אוביקט AWS.S3 חדש ומייצא אותו, כדי שלקבצים אחרים יהיה יותר נוח לעבוד עם s3. באותה תיקיה אני יוצר עוד קובץ בשם src/lib/globals.js עם התוכן הבא:
// The name of the bucket that you have created
exports.BUCKET_NAME = 'ynonp-s3-photos';
שמגדיר את שם הדלי בו אני אשמור את התמונות. עכשיו אנחנו מוכנים לכתוב את הסקריפט הראשון שיוצר את הדלי. אני יוצר תיקיה בשם utils ובתוכה הקובץ utils/create-bucket.js עם התוכן הבא:
const AWS = require('aws-sdk');
const { BUCKET_NAME } = require('../src/lib/globals');
const s3 = require('../src/lib/mys3');

const params = {
    Bucket: BUCKET_NAME,
    CreateBucketConfiguration: {
        // Set your region here
        LocationConstraint: "eu-west-1"
    }
};

s3.createBucket(params, function(err, data) {
    if (err) console.log(err, err.stack);
    else console.log('Bucket Created Successfully', data.Location);
});
את הקובץ אפשר להריץ משורת הפקודה:
$ node utils/create-bucket.js
ומספיק להריץ אותו פעם אחת כדי ליצור את הדלי. שימו לב רק לבחור שם ייחודי לדלי (באמצעות שינוי בקובץ globals.js) לפני שמנסים ליצור. בכל מקרה אם השם שלכם לא מספיק מיוחד אז s3 יצעק עליכם ולא ייצור את הדלי. ## איך מעלים קובץ אחרי שיש לנו דלי נוכל להמשיך לסקריפט השני, גם הוא נועד להרצה משורת הפקודה ולפני השימוש בשרת. ניצור קובץ בשם utils/upload.js עם התוכן הבא:
const fs = require('fs');
const { BUCKET_NAME } = require('../src/lib/globals');
const s3 = require('../src/lib/mys3');

const filename = process.argv[2];
console.log(filename);

const uploadFile = (fileName) => {
    // Read content from the file
    const fileContent = fs.readFileSync(fileName);

    // Setting up S3 upload parameters
    const params = {
      Bucket: BUCKET_NAME,
      Key: 'cat.jpg', // File name you want to save as in S3
      Body: fileContent,
      ACL:'public-read'
    };

    // Uploading files to the bucket
    s3.upload(params, function(err, data) {
        if (err) {
            throw err;
        }

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

ToCode
1 419
# בדיקות מחוץ לקופסה בדיקות אוטומטיות לקוד מתחברות לרוב עם וידוא נכונות של הקוד ותחושת ביטחון למפתחים ולמנהלים שהמערכת עושה מה שהיא צריכה לעשות. זה נכון לפעמים, אבל הרבה פעמים אנחנו כותבים בדיקות מסיבות נוספות ולא פחות חשובות: 1. בדיקות כתיעוד - בדיקה יכולה להראות למתכנתים אחרים איך אמורים להשתמש בפונקציה שלי, לפעמים יותר טוב מתיעוד טקסטואלי. יש גם כלים אוטומטיים כמו doctest בעולם של פייתון שיבנו בדיקות אוטומטית על סמך תיעוד (בפורמט מיוחד) שאנחנו כותבים. 2. בדיקות כדי לחקור קוד חיצוני - קוד בדיקה יכול לעזור לי להריץ בקלות כמה פונקציות מקוד חיצוני - לדוגמה אם אני רוצה ללמוד איך ספריה מסוימת עובדת - במיוחד בסביבות בהן קשה לי לקבל REPL לתוך מערכת עובדת. נכון, אפשר לכתוב סקריפט קטן שיעשה את המחקר, אבל לבדיקה יש ייתרון שהיא תישאר איתי גם בעתיד ותאפשר להמשיך לחקור כשיהיו לי שאלות חדשות. 3. בדיקות כדי לחשוב על ממשק - אנשי TDD מאוד אוהבים את זה. הרעיון שכשאתה מתחיל לבנות ממשק למחלקה או מודול מסוים זה עוזר לנסות לכתוב קוד שמשתמש בממשק הזה כדי להבין עד כמה הוא נוח. תוכנית בדיקה היא מקום מצוין לעשות משחקים כאלה. 4. בדיקות כדי לדווח על באג - כי הרבה יותר קל לקבל דיווח עם תוכנית בדיקה מפורטת שמשחזרת את הבאג מאשר תיאור טקסטואלי בג'ירה. 5. בדיקות כדי לשפר נגישות - ספריות בדיקה כמו testing-library אבל גם סלניום יעבדו טוב יותר אם האתר נגיש, כי ספריית הבדיקה, כמו קורא מסך, צריכה לתקשר עם האלמנטים שעל המסך ורגישה הרבה יותר לקוד מאשר לעיצובים וצבעים. לא פעם קרה לי שהוספתי תווית טקסט או מאפיין aria-label רק בשביל שיהיה לי יותר קל לתפוס אלמנט מסוים בספריית הבדיקה. 6. בדיקות לפני ריפקטורינג - כי אם אני הולך לארגן מחדש קוד שאחראי על פונקציונאליות מסוימת, אני יכול לפני תחילת העבודה לכתוב כמה בדיקות שיעזרו לי להבין שה Refactoring הסתיים בהצלחה. 7. בדיקות כדי להזכיר לעצמי פיצ'ר שרציתי לבנות - כי רעיונות טובים יכולים להגיע גם בזמנים לחוצים יותר כשאי אפשר לממש אותם, אבל תמיד אפשר לכתוב בדיקה קטנה שתזכיר לי על מה לעבוד כששוב יהיה זמן. ההבנה שיש עוד סיבות לכתיבת בדיקות מלבד שמירה על יציבות המערכת עוזרת לנו לתחזק טוב יותר את הבדיקות ולא להרגיש רע כשצריך למחוק בדיקות ישנות. לאורך זמן יחס ידידותי לבדיקות הוא דרך מצוינת לשפר את היציבות של המערכת.

ToCode
1 419
# טכניקות Refactoring לקוד ריאקט ששווה להתאמן עליהן אחד הדברים שהכי כיף לעשות בעבודה עם קוד הוא להזיז דברים ממקום למקום כדי שיהיה יותר קל להמשיך לעבוד על הקוד - מה שנקרא Refactoring. יש טכניקות גנריות שאפשר להפעיל בכל שפת תכנות, כמו לקחת כמה שורות של קוד ולהוציא אותן לפונקציה נפרדת, וטכניקות ספציפיות לשפה או סביבה מסוימת. אלה הטכניקות המרכזיות שאני משתמש בהן בעבודה על קוד ריאקט: 1. להעלות State לקומפוננטה עליונה (ולקבל אותו חזרה כ Props). 2. להוריד State לקומפוננטה פנימית (ואם צריך להעביר לה פונקציות Callback לעדכון כשהסטייט משתנה). 3. להוציא State ל Context. 4. להחזיר State מ Context לקומפוננטה עליונה, ולהעביר אותו למטה בתור Props לקומפוננטות הפנימיות. 5. להוציא חלק מקומפוננטה לקומפוננטה חיצונית חדשה, כדי לפשט קומפוננטות מסובכות. 6. לאחד קומפוננטות פשוטות לקומפוננטה אחת יותר גדולה, כדי לנהל את ה State של כולן במקום אחד. 7. להוציא החוצה לוגיקה + State ל Custom Hook. 8. ליצור קומפוננטה חדשה מקומפוננטה קיימת כדי לקרוא ל Hook בצורה מותנית. 9. להוציא פונקציה שהוגדרה בתוך קומפוננטה לקובץ נפרד, אם היא לא משתמשת במידע הפנימי של הקומפוננטה. 10. להחזיר פונקציה מקובץ נפרד לקומפוננטה כשהיא צריכה גישה ל State, ל Props או למידע אחר ספציפי לקומפוננטה. חלק מהטריקים האלה זמינים בלחיצת כפתור ב IDE וכולם דורשים תרגול. ככל שמתאמנים יותר על להזיז דברים בקוד ככה זה נהיה יותר קל. לא מעט קורה שאני מזיז קטע קוד ממקום למקום ואחרי כמה ימים מחזיר אותו למבנה הקודם - בקוד אנחנו תמיד מחפשים את המבנה הטוב ביותר, והניסוי הוא חלק חשוב מהמשחק.

ToCode
1 419
בפברואר 2017 ג'ון דייויד דלטון החליט להיפטר מכל הנקודות פסיק ב lodash. עכשיו הסיפור של ה semicolons ב JavaScript לא חדש. עוד ב 2012 היה ויכוח ציבורי גדול בנושא והוא נמשך עד היום. לדוגמה בתבנית פרויקט create-react-app חדש שתיצרו תמצאו נקודה פסיק בסוף כל שורה אבל בתבנית פרויקט React שתיצרו עם vite לא תמצאו את הנקודות-פסיקים. לודאש לוקחת את הצד שמתנגד לנקודה פסיק, וזה בסדר גם אם זו לא כוס התה שלי.

ToCode
1 419
    if (hasOwnProperty.call(result, key)) {
      result[key].push(value)
    } else {
      baseAssignValue(result, key, [value])
    }
    return result
  }, {})
}
הרעיון הבסיסי של האלגוריתם ברור: רצים על כל האלמנטים ברשימה עם reduce ומשתמשים באוביקט התוצאה בתור Accumulator. עבור כל אלמנט אנחנו מחשבים מה ה"מפתח" שלו, ואז מעדכנים את ה Accumulator ומוסיפים למפתח המתאים את האלמנט. בגלל שזה reduce צריך להחזיר את ה Accumulator. נכון, מימוש פונקציונאלי טהור אולי היה בכל איטרציה מייצר Accumulator חדש כדי לא לשנות מידע In Place, אבל אנחנו בספריה שצריכה לתת ביצועים טובים ובשפת JavaScript כך שהגישה שלהם של עדכון והחזרת המצביע היא בהחלט הגיונית. כשלוקחים את האלוגריתם לקוד עולות מספר שאלות: 1. הפונקציה reduce מקבלת פונקציה ומפעילה אותה רק עם שני פרמטרים: accumulator ו value. למה בקוד של groupBy מעבירים גם פרמטר שלישי בשם key? 2. למה להשתמש ב hasOwnProperty בתור פונקציה גלובאלית ולא להפעיל אותו מתוך result? כלומר למה לא לכתוב result.hasOwnProperty(key) 3. מה זה baseAssignValue ? למה לא להשתמש פשוט בכתיב הסוגריים המרובעים כדי לכתוב את הערך? ## מאיפה הגיע ה key? הפרמטר השלישי לפונקציה ש reduce מקבלת נקרא במימוש key אבל הוא לא בשימוש. כבר בשורה הראשונה של הפונקציה הוא נדרס עם השורה:
key = iteratee(value)
אז הלכתי ל git בניסיון להבין למה הוא שם והגעתי לגירסה הישנה יותר של הקוד:
var groupBy = createAggregator(function(result, value, key) {
  if (hasOwnProperty.call(result, key)) {
    result[key].push(value);
  } else {
    baseAssignValue(result, key, [value]);
  }
});
עושה רושם שבעבר השתמשו בפונקציה שלהם שנקראת createAggregator שעשתה את רוב העבודה ודאגה להפעיל את הפונקציה iteratee על הערך ולהעביר לנו את המפתח. בשלב מסוים ג'ון דייויד דלטון (היוצר של lodash) החליט לעבור להשתמש ב reduce הרגילה וכנראה ש key נשאר בטעות מהחתימה הישנה. ## מה זה baseAssignValue? תהייה שניה שעלתה לי לגבי המימוש נוגעת לפונקציה baseAssignValue. על פניו בשביל לכתוב מפתח חדש לאוביקט result דמיינתי שאפשר יהיה להשתמש ב:
result[key] = [value];
הנה מה שהם עושים במקום:
function baseAssignValue(object, key, value) {
  if (key == '__proto__') {
    Object.defineProperty(object, key, {
      'configurable': true,
      'enumerable': true,
      'value': value,
      'writable': true
    })
  } else {
    object[key] = value
  }
}
מעניין! מסתבר שב JavaScript אני לא יכול לשנות את השדה __proto__ של אוביקט, ולכן אם במקרה הפונקציה שאחראית על ארגון הפריטים לקבוצות תגלה שהמפתח של פריט מסוים הוא __proto__ במימוש נאיבי היא לא תוכל לכתוב את המפתח הזה. ג'ון דייויד דלטון השתמש ב defineProperty כדי לעקוף את המגבלה וכן לאפשר כתיבה לשדה __proto__. ## ומה לגבי hasOwnProperty? שאלה השלישית לסקירה זו נוגעת ל hasOwnProperty - למה להשתמש ב Object.prototype.hasOwnProperty ולא להפעיל את הפונקציה על אוביקט ה result שיש לנו ביד? אם הגעתם עד פה בקריאה אני מקווה שהתשובה כבר ברורה: גם hasOwnProperty יכול להיות שם של מפתח בו אמור להיות מתויג אחד הערכים, ואם זה המצב אז result.hasOwnProperty יחזיר את רשימת הערכים שמתאימים למפתח זה במקום את הפונקציה Object.prototype.hasOwnProperty. ואגב בחזרה לשורת ההערה שראינו בראש הקובץ, אם היו שואלים אותי הייתי מציע להחליף אותה למשהו שרומז למקרה הזה, לדוגמה:
/** Saving hasOwnProperty in a global variable allows us to call it even if one of the value's key will be the string "hasOwnProperty" */
const hasOwnProperty = Object.prototype.hasOwnProperty
## נ.ב. מה קרה לנקודה פסיק? חדי העין ביניכם אולי הבחינו שבגירסה הישנה של הקוד של groupBy היו סימני ; בסוף כל ביטוי, ובגירסה העדכנית הם נעלמו. חיפוש ב git log מלמד אותנו שזה מכוון:
commit 6cb3460fcefe66cb96e55b82c6febd2153c992cc
Author: John-David Dalton <john.david.dalton@gmail.com>
Date:   Sat Feb 4 23:50:10 2017 -0800

    Remove semicolons.