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

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

מעדכנים אוביקט JSON עם JSONPath

15/05/2024

נניח שיש לכם אוביקט JSON מקונן למשל את זה:

const data = {
  rows: [
    {id: '1', cells: [{text: 'a', state: { active: true }},
                      {text: 'b'}]},
    {id: '2', cells: [{text: 'c'}]}
  ]
}

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

for (let row of data.rows) {
    for (let cell of row.cells) {
    if (!cell.state) { cell.state = {} }
        cell.state.active = false
  }
} 

וזה עובד. זמנית. כי מהר מאוד האוביקט יקבל שורה בלי cells למשל:

const data = {
  rows: [
    {id: '1', cells: [{text: 'a', state: { active: true }},
                      {text: 'b'}]},
    {id: '2', cells: [{text: 'c'}]},
    {id: '3' }
  ]
}

ועכשיו לך תזכור איפה הטעות.

גישה יותר גמישה היא JSONPath. זה spec שמאפשר לחפש מידע באוביקט JSON מקונן או לשנות שדות בתוך אותו אוביקט. הספריה jsonpath מ npm כוללת פונקציות לעבודה עם נתיבים בתוך JSON-ים ואפשר לשלב אותה ביישומי node.js או בדפדפן. הדוגמה שלהם מתוך דף התיעוד היא:

var cities = [
  { name: "London", "population": 8615246 },
  { name: "Berlin", "population": 3517424 },
  { name: "Madrid", "population": 3165235 },
  { name: "Rome",   "population": 2870528 }
];

var jp = require('jsonpath');
var names = jp.query(cities, '$..name');

// [ "London", "Berlin", "Madrid", "Rome" ]

המחרוזת $..name היא נתיב לכל הערכים שמתאימים למפתח בשם name בכל עומק של האוביקט. יש להם גם טבלה בדף התיעוד עם כל הסימנים שאפשר לרשום בנתיב JSON. בחזרה לאוביקט שלנו עם השורות והתאים בעזרת JSONPath אפשר לפתור את הבעיה בצורה יותר פשוטה וגנרית:

function desactivateAll() {
  jsonpath.apply(data, '$.rows[*].cells[*]', (v) => v.state ? v : Object.assign(v, {state: { active: false }}))
  // OR less "safe way":
  // jsonpath.apply(data, '$...active', (v) => false);
  jsonpath.apply(data, '$.rows[*].cells[*].state.active', (v) => false);
}

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

כתבתי גם דוגמה מלאה בקודפן בקישור הזה כדי שיהיה לכם קל לשחק עם הספריה: https://codepen.io/ynonp/pen/eYamWYp

למה כל כך קשה לנו עם אחידות בקוד?

14/05/2024

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

קאפ כתוב בטייפסקריפט ומשתמש בכל הספריות המודרניות שהייתם מצפים למצוא בפרויקט היום כמו Tailwind, React וכמובן Drizzle. יש גירסת דסקטופ עם טאורי וגם קוד האתר זמין בגיטהאב. הקוד עצמו פשוט קל לקריאה לדוגמה הקובץ הזה הוא דף ה FAQ שלהם:

"use client";

const faqContent = [
  {
    title: "What is Cap?",
    answer:
      "Cap is an open source alternative to Loom. It's a video messaging tool that allows you to record, edit and share videos in seconds.",
  },
  {
    title: "How do I use it?",
    answer:
      "Simply download the Cap macOS app (or the Cap web app), and start recording. You can record your screen, your camera, or both at once. After your recording finishes, you will receive your shareable Cap link to share with anyone.",
  },
  {
    title: "Who is it for?",
    answer:
      "Cap is for anyone who wants to record and share videos. It's a great tool for creators, educators, marketers, and anyone who wants to communicate more effectively.",
  },
  {
    title: "How much does it cost?",
    answer:
      "Cap is free to use. However, you can upgrade to Cap Pro for just $9/month and unlock unlimited recordings, unlimited recording length, and much more.",
  },
  {
    title: "What makes you different to Loom?",
    answer:
      "Apart from being open source and privacy focused, Cap is also a lot more lightweight and faster to use. We also focus strongly on design and user experience, and our community is at the heart of everything we do.",
  },
];

export const FaqPage = () => {
  return (
    <div className="wrapper wrapper-sm py-20">
      <div className="text-center page-intro mb-14">
        <h1>FAQ</h1>
      </div>
      <div className="mb-10">
        {faqContent.map((section, index) => {
          return (
            <div key={index} className="max-w-2xl mx-auto my-8">
              <h2 className="text-xl mb-2">{section.title}</h2>
              <p className="text-lg">{section.answer}</p>
            </div>
          );
        })}
      </div>
    </div>
  );
};

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

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

    async createUser(userData) {
      await db.insert(users).values({
        id: nanoId(),
        email: userData.email,
        emailVerified: userData.emailVerified,
        name: userData.name,
        image: userData.image,
        activeSpaceId: "",
      });
      const rows = await db
        .select()
        .from(users)
        .where(eq(users.email, userData.email))
        .limit(1);
      const row = rows[0];
      if (!row) throw new Error("User not found");
      return row;
    },
    async getUser(id) {
      const rows = await db
        .select()
        .from(users)
        .where(eq(users.id, id))
        .limit(1);
      const row = rows[0];
      return row ?? null;
    },

הפונקציה createUser תזרוק שגיאה אם היתה בעיה ביצירה. הפונקציה getUser תחזיר null.

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

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

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

13/05/2024

הי חברות וחברים,

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

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

  2. הוספתי דף תרגילים מסכם לחלק הראשון של הקורס.

  3. הוספתי שיעור על Error Boundaries - בעבר היה שיעור מקיף יותר על Lifecycle Methods של Class Components שכלל גם התיחסות בקטנה ל Error Boundary, אבל האמת שהיום מכל פונקציות ה Lifecycle המנגנון הכי חשוב (וזה שאין לו אלטרנטיבה עם Hooks) הוא טיפול בשגיאות. בקיצור הקלטתי מחדש את השיעור כך שיתמקד במנגנון תפיסת שגיאות Render וארגון אזורי טיפול בשגיאות.

  4. מחקתי בשמחה את השיעור על create-react-app ואני מקווה לא להיתקל שוב בכלי זה.

רוב השינויים הם בחלק הראשון של הקורס שפתוח לצפיה חינם אז אם חיפשתם הזדמנות לקחת צעדים ראשונים בריאקט מוזמנים להעיף מבט: https://www.tocode.co.il/bundles/react

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

ניסוי: מספרים גדולים ב JavaScript

12/05/2024

הקוד הבא מחשב סכום ספרות של מספר:

function sumOfDigits(n) {
  let sum = 0;
  while (n > 0) {
    sum += n % 10;
    n = Math.floor(n / 10);
  }
  return sum;
}

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

const input = document.querySelector('input');
input.addEventListener('input', (e) => {
  const result = sumOfDigits(Number(e.target.value));
  document.querySelector('#result').textContent = result;
});

אפשר לראות אותו בפעולה בקודפן בקישור: https://codepen.io/ynonp/pen/XWwJJmw?editors=1010

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

מה עושים? עוברים לעבוד עם מספרים שלמים גדולים - BigInt. מספר גדול מאותחל דרך הבנאי BigInt או באמצעות הסיומת n כלומר כל אלה הם מספרים גדולים:

const n1 = BigInt(9007199254740991);
const n2 = BigInt(3);
const n3 = 10n;

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

function sumOfDigits(n) {
  let sum = 0n;
  while (n > 0) {
    sum += n % 10n;
    n = n / 10n;
  }
  return sum;
}

const input = document.querySelector('input');
input.addEventListener('input', (e) => {
  const result = sumOfDigits(BigInt(e.target.value));
  document.querySelector('#result').textContent = result;
});

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

https://codepen.io/ynonp/pen/wvbBBBP?editors=1010

להתאמץ פחות

11/05/2024

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

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

מה בעצם זה אומר להשטיח רשימה?

10/05/2024

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

לפייתון אין פונקציית ספריה בשם flatten אבל בתיעוד של itertools אפשר למצוא את המימוש הבא:

def flatten(list_of_lists):
    "Flatten one level of nesting."
    return chain.from_iterable(list_of_lists)

כלומר מבחינתם ההשטחה היא רק ברמה אחת.

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

Range(1, 10).map(i => List(List(i), 1, 2, 3)).flatten

ולקבל את התוצאה שכוללת רשימה פנימית:

val res5: IndexedSeq[List[Int] | Int] = Vector(List(1), 1, 2, 3, List(2), 1, 2, 3, List(3), 1, 2, 3, List(4), 1, 2, 3, List(5), 1, 2, 3, List(6), 1, 2, 3, List(7), 1, 2, 3, List(8), 1, 2, 3, List(9), 1, 2, 3)

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

user=> (->> (range 10) (map (fn [i] [[i] 2 3])) flatten)
(0 2 3 1 2 3 2 2 3 3 2 3 4 2 3 5 2 3 6 2 3 7 2 3 8 2 3 9 2 3)

אליקסיר כמו קלוז'ר לא כוללת רשימות מקוננות בתוצאה:

iex(6)> (1..10) |> Enum.map(fn i -> [[i], 2, 3] end) |> List.flatten
[1, 2, 3, 2, 2, 3, 3, 2, 3, 4, 2, 3, 5, 2, 3, 6, 2, 3, 7, 2, 3, 8, 2, 3, 9, 2,
 3, 10, 2, 3]

ו JavaScript? בהתחלה זה נראה שהיא לוקחת את הגישה של סקאלה ופייתון ומשטיחה רק רמה אחת:

> new Array(10).fill(0).map((_, i) => [[i], 2, 3]).flat()
[
  [ 0 ], 2, 3, [ 1 ], 2, 3,
  [ 2 ], 2, 3, [ 3 ], 2, 3,
  [ 4 ], 2, 3, [ 5 ], 2, 3,
  [ 6 ], 2, 3, [ 7 ], 2, 3,
  [ 8 ], 2, 3, [ 9 ], 2, 3
]

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

> new Array(10).fill(0).map((_, i) => [[i], 2, 3]).flat(Infinity)
[
  0, 2, 3, 1, 2, 3, 2, 2, 3,
  3, 2, 3, 4, 2, 3, 5, 2, 3,
  6, 2, 3, 7, 2, 3, 8, 2, 3,
  9, 2, 3
]

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

קומפוננטות ווב Web Components עובדים ממש טוב

09/05/2024

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

נכון הם לא נותנים את כל מה שריאקט נותנת והם דורשים הרבה עבודה ידנית בעיקר בחיבור קוד לטיפול באירועים, אבל אין לי ספק שאם הם היו קיימים ב 2015 לא היו לנו את כל ה JavaScript Frameworks שאנחנו רואים היום (או מצד שני, אולי רק בזכות ריאקט וחבריו כותבי התקנים הצליחו להגיע להסכמה על Web Components).

בכל מקרה כתבתי דוגמה קטנה של מונה לחיצות ב Web Component בשביל לשחק עם ה API. ה HTML הוא:

<my-counter></my-counter>
<my-counter></my-counter>
<my-counter></my-counter>

<template id="counter">
  <p>I'm a counter. value = <span class="value">0</span>
    <button class="inc">+1</button>
    <button class="dec">-1</button>
  </p>
</template>

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

customElements.define(
  "my-counter",
  class extends HTMLElement {
    connectedCallback() {
      let template = document.getElementById("counter");
      let templateContent = template.content;
      const shadowRoot = this.attachShadow({ mode: "open" });

      shadowRoot.appendChild(templateContent.cloneNode(true));

      let value = 0;
      const valueSpan = shadowRoot.querySelector('.value')
      shadowRoot.querySelector('.inc').addEventListener('click', () => {
        value += 1;
        valueSpan.textContent = value;
      })

      shadowRoot.querySelector('.dec').addEventListener('click', () => {
        value -= 1;
        valueSpan.textContent = value;
      })
    }
  }
);

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

גירסה 19 של ריאקט שתבוא עלינו לטובה באזור יוני או יולי כוללת לפי הסיפורים תמיכה מלאה ב Web Components כך שנוכל לשלב אותם ביישום ריאקט בלי בעיה. יהיה מעניין.

נ.ב. זה הלינק לקודפן עם הקומפוננטת ווב, תרגישו חופשי לשחק ולשבור: https://codepen.io/ynonp/pen/qBGWQEw

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

08/05/2024

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

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

דרן קופ כותב אחרי שנכשל בראיון:

I understand the problems associated with hiring the wrong people as well, which may well be the actual reason we are rightfully stuck with such fearful/timid hiring practices.

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

I’ve also spent hours fixing code that had issues directly stemming from not using the correct data structure/algorithm that I may not have had to if we had been better at screening the employees during the interview process.

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

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

  1. גיוס אנשים שפשוט מדביקים קוד מ Stack Overflow או מנועי AI בלי להבין מה הם כותבים וכתוצאה מכך מייצרים ערימות בוץ שאי אפשר לנקות.

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

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

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

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

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

היום למדתי: למה לשים מרכאות מסביב לפורטים ב docker-compose

07/05/2024

בואו נשחק עם רובי, כי היא קלה ויש תמיכה מובנית ב YAML. זאת התוכנית:

require 'yaml'
require 'pp'

data = YAML.safe_load(<<-END
  apache:
  image: httpd:latest
  container_name: my-apache-app
  ports:
    - 80:80
    - 24:42
  volumes:
    - ./website:/usr/local/apache2/htdocs
END
)

pp data

אפשר לראות אותו לייב ב URL הזה: https://tinyurl.com/mwk97ybd

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

"ports"=>["80:80", 88920],

אני מבין מאיפה הגיע ה 80:80, אבל מה הסיפור עם ה 88920? מה קרה ל 24:42? נו התשובה בכותרת הפוסט. כשקוראים YAML שיש בו שני מספרים נקודותיים ואז עוד שני מספרים המפענח חושב שזה תיאור זמן (שעות ודקות). אז הוא מכפיל את 24 ב 60 ומוסיף 42 וכך מקבל 1482, ואז הופך את זה לשניות ומקבל 88920 שזה המספר שאנחנו רואים כאן.

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

הערך הייחודי

06/05/2024

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

מה הערך שאת מביאה לפרויקט?

מה הופך אותך לבן אדם שהכי מתאים להביא את הערך הזה?

מה האג'נדה שלך?

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