ar
Feedback
ToCode

ToCode

الذهاب إلى القناة على Telegram

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

إظهار المزيد
1 421
المشتركون
لا توجد بيانات24 ساعات
+37 أيام
-430 أيام
أرشيف المشاركات
ToCode
1 421
# שימו לב: אין Merge חלקי ב neo4j הפקודה MERGE ב neo4j יוצרת צמתים חדשים וקשתות חדשות אבל בצורה מעניינת - במקום שנגיד לה מה אנחנו רוצים ליצור אנחנו מתארים לה את מצב העניינים אחרי היצירה. אם הכל כבר קיים merge לא תעשה כלום, ואם החיבור לא קיים היא תיצור אותו. איפה הבעיה? בניגוד לכלים חכמים כמו קוברנטיס שמקבלים מצב סופי ויודעים להבין מה צריך לעשות כדי להגיע מאיפה שאני נמצא למצב שרציתי להגיע, פקודת merge ב neo4j עובדת בשיטת "הכל או כלום". אם החיבור קיים היא לא תעשה כלום, אם החיבור לא קיים היא תיצור אותו, אבל אם חלק מהחיבור קיים אז היא תנסה ליצור חיבור חדש לגמרי שיתאים למה שביקשתי. ניקח דוגמה מבסיס הנתונים של הסרטים מארגז החול של neo4j. בסיס הנתונים מכיל Person בשם Jack Nicholson ו Person נוסף בשם Al Pacino. אני רוצה לחבר בין שניהם בקשר שנקרא FRIEND אז אני מריץ:
MERGE (n:Person{name:"Jack Nicholson"})-[:FRIENDS]-(m:Person{name:"Al Pacino"}) RETURN n LIMIT 25
אבל במקום ליצור את הקשר אני מקבל שגיאה:
Node(17) already exists with label `Person` and property `name` = 'Jack Nicholson'
מסתבר ש MERGE זיהה שאין קשר של חברות בין ג'ק ניקולסון לאל פאצ'ינו, ולכן ניסה ליצור את כל השלישייה - בן אדם בשם ג'ק ניקולסון, בן אדם נוסף בשם אל פאצ'ינו וקשר של חברות ביניהם. בארגז החול מוגדר אינדקס ייחודי על שמות של אנשים ולכן הניסיון ליצור בן אדם נוסף עם שם קיים נכשל. בלי האינדקס המצב היה עוד יותר רע, והייתי מקבל בסיס נתונים עם שני שחקנים באותו שם - מה שיגרום לבאגים בשאילתות בהמשך. הפיתרון, מעבר להבנת החשיבות של אינדקסים, הוא לחלק את ה MERGE לפקודות MATCH כשרוצים להשתמש במידע קיים, כלומר השאילתה תהיה:
MATCH (n:Person{name:"Jack Nicholson"})
MATCH (m:Person{name:"Al Pacino"})
MERGE (n)-[:FRIENDS]-(m) RETURN n LIMIT 25

ToCode
1 421
# היום למדתי: אופרטור += בפייתון נראה אטומי על מעבדי אפל בזמן ריענון מצגת על פייתון וביצוע תהליכים במקביל הרצתי את התוכנית של ה Threads שמראה למה צריך לסנכרן גישה למשתנים למרות ה GIL:
import threading

i = 0

def test():
    global i
    for x in range(100000):
        i += 1

threads = [threading.Thread(target=test) for t in range(10)]
for t in threads:
    t.start()

for t in threads:
    t.join()

print(i)
assert i == 1000000, i
אבל במקום להתרסק ב assert הכל עבר בשלום. לא עזר להריץ שוב. ושוב. ושוב. רק בשביל לראות שלא השתגעתי הרצתי את אותה תוכנית על חלונות ולינוקס וכמובן בשתי המכונות שניסיתי הכל התרסק כצפוי. מה קורה פה? לצערי לא מצאתי כלום בחיפוש רגיל ברשת, וגם Chat GPT לא ידע על מה אני מדבר. בינג הכיר את התופעה וטען שהמעבד של אפל יודע לעבוד בצורה אטומית על ערכים של 64 ביט ונראה לו שזאת הסיבה, אבל אני לא בטוח שאני מאמין לו או רואה את הקשר. כך שנצטרך להישאר עם פוסט פתוח להיום ואם אתם מכירים את ההסבר לתופעה אשמח לשיתוף. כן כדאי לזכור שבהיעדר תיעוד רלוונטי ובהינתן שהקוד נשבר על מערכות הפעלה אחרות לא כדאי להתסמך על עובדה זו בעבודה שלכם עם פייתון על מעבדי אפל ותמיד תזכרו להוסיף נעילות כשכותבים קוד בו מספר תהליכונים מעדכנים את אותם משתנים.

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

ToCode
1 421
בעזרת JavaScript אנחנו מציגים את הדיאלוג בעת לחיצה על הכפתור אבל הפעם אין צורך לסגור אותו. הטופס ייסגר לבד וישלח אירוע close, שקוד הטיפול בו לוקח את ה value של הכפתור שנלחץ ומעתיק אותו למסך מחוץ לדיאלוג:
const showButton = document.getElementById("showDialog");
const favDialog = document.getElementById("favDialog");
const outputBox = document.querySelector("output");
const selectEl = favDialog.querySelector("select");
const confirmBtn = favDialog.querySelector("#confirmBtn");

// "Show the dialog" button opens the <dialog> modally
showButton.addEventListener("click", () => {
  favDialog.showModal();
});

// "Favorite animal" input sets the value of the submit button
selectEl.addEventListener("change", (e) => {
  confirmBtn.value = selectEl.value;
});

// "Cancel" button closes the dialog without submitting because of [formmethod="dialog"], triggering a close event.
favDialog.addEventListener("close", (e) => {
  outputBox.value =
    favDialog.returnValue === "default"
      ? "No return value."
      : `ReturnValue: ${favDialog.returnValue}.`; // Have to check for "default" rather than empty string
});
וזה בקודפן למשחקים: <iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/QWJewzV?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/ynonp/pen/QWJewzV"> Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>) on <a href="https://codepen.io">CodePen</a>. </iframe>

ToCode
1 421
# דיאלוגים מודאליים בדפדפן לפעמים יש לי אמונות והרגלים כל כך עתיקים שאפילו כשיש מסביב רמזים לשינוי אני לא טורח לבדוק אותם. דיאלוגים מודאליים ב HTML? אין דבר כזה. צריך ליצור אלמנט ולהשתמש ב CSS כדי לתת "הרגשה של מודאלי". ככה זה היה תמיד. כן טוב, מה שהיה הוא לא מה שיהיה. כרום 37, אדג' 79, פיירפוקס 98, אופרה 24, ספארי 15.4 ואפילו דפדפני המובייל - כולם תומכים היום תמיכה מובנית וטבעית באלמנט dialog ויודעים להציג דיאלוג מודאלי בלי שום ספריה ובצורה נגישה. בואו נראה איך זה עובד. ## דוגמה 1 - דיאלוג עם כפתור סגירה בשביל ליצור דיאלוג מודאלי אני רק צריך אלמנט מסוג dialog ב HTML:
<p>
  Hello World
  <button id="show">show dialog</button>
</p>
<dialog id="modal">
  <p>Dialog Content</p>
  <button>Close</button>
</dialog>
בשביל להציג או לסגור את הדיאלוג אני משתמש ב JavaScript. הפונקציה showModal מציגה את הדיאלוג בצורה מודאלית, והפונקציה close סוגרת את הדיאלוג:
document.querySelector('#show').addEventListener('click', () => {
  const dialog = document.getElementById("modal");
  dialog.showModal();
})

document.querySelector('#modal button').addEventListener('click', () => {
  const dialog = document.getElementById("modal");
  dialog.close();
});
כשהדיאלוג פתוח אי אפשר ללחוץ על הכפתורים הרגילים שנמצאים על העמוד ובשינוי פוקוס עם טאב אנחנו נשארים בתוך הדיאלוג - ולחיצה על Escape סוגרת אוטומטית את הדיאלוג. קסום. <iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/PoxrOjq?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/ynonp/pen/PoxrOjq"> Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> ## דוגמה 2 - דיאלוג לא מודאלי אם אתם רוצים שהעמוד ימשיך לתפקד תוך כדי שהדיאלוג מוצג אז הדיאלוג שלכם נקרא "דיאלוג לא מודאלי", ותוכלו להשתמש בפונקציה show בשביל להציג את הדיאלוג בצורה כזאת:
document.querySelector('#show').addEventListener('click', () => {
  const dialog = document.getElementById("modal");
  const counter = dialog.querySelector('.counter');
  counter.textContent = Number(counter.textContent) + 1;
  dialog.show();
})

document.querySelector('#modal button').addEventListener('click', () => {
  const dialog = document.getElementById("modal");
  dialog.close();
});
והנה גם הוא באייפריים בשביל משחקים: <iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/ZEmgYmZ?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/ynonp/pen/ZEmgYmZ"> Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> ## דוגמא 3 - דיאלוגים מחזירים ערך ואם הסיפור עד כאן לא היה מספיק משוגע, תשמחו לשמוע שדיאלוגים יכולים גם להציג טפסים ולהחזיר ערך שייצג את הערכים שהמשתמש בחר דרך הטופס. אם בתוך דיאלוג יש טופס, ולטופס הזה יש מאפיין method עם הערך dialog, אז בלחיצה על כפתור של הטופס הדיאלוג אוטומטית ייסגר, ומאפיין returnValue של הדיאלוג יכיל את ה value של הכפתור שנלחץ. בדוגמה הבאה שמתבססת על דוגמה מהתיעוד הדיאלוג כולל טופס עם תיבת בחירה:
<!-- A modal dialog containing a form -->
<dialog id="favDialog">
  <form method="dialog">
    <p>
      <label>
        Favorite animal:
        <select>
          <option value="default">Choose…</option>
          <option>Brine shrimp</option>
          <option>Red panda</option>
          <option>Spider monkey</option>
        </select>
      </label>
    </p>
    <div>
      <button value="cancel">Cancel</button>
      <button id="confirmBtn" value="default">Confirm</button>
    </div>
  </form>
</dialog>
<p>
  <button id="showDialog">Show the dialog</button>
</p>
<output></output>

ToCode
1 421
# כמה פחדים אמיתיים מ WEI משמעות ראשי התיבות WEI היא Web Environment Integrity. זהו API שנתמך כבר בכרום אבל עדיין לא סטנדרטי שמטרתו לוודא בצד השרת שהדפדפן שפונה לקבל את התוכן הוא דפדפן "אותנטי". אפשר לחשוב על WEI בתור Captcha משודרג - משתמש גולש לאתר, האתר דורש מהדפדפן להוכיח שהוא בן אדם שמשתמש בדפדפן "רגיל", הדפדפן פונה לשרת אימות עם ה URL אותו הוא רוצה למשוך, מקבל טוקן ומשתמש בטוקן כדי לחזור לשרת המקורי ולקבל את התוכן. ההבדל המרכזי בין מנגנון זה לבין Captcha הוא שבדרך כלל Captcha חוסם רק מביצוע פעולות באתר, ו WEI יחסום בכל פניה לתוכן. אלה הפחדים המרכזיים של מפתחים והקהילה מ API זה: 1. חסימת מתחרים - אם חברת דפדפנים אחת (אה לא אמרתי? ה API הוצע ונדחף על ידי גוגל) יכולה להחליט אם דפדפן מסוים הוא "אותנטי" כדי לגשת לתוכן, הם יוכלו לחסום דפדפנים מתחרים או להשתמש בכח שלהם כדי לקבוע איזה יכולות דפדפן חייב לממש. 2. אוטומציה - הכנסת תקן WEI תהפוך פרויקטי תחביב וגם פרויקטים יותר גדולים שמתבססים על אוטומציה להרבה יותר קשים. זאת בעצם חסימה מוחלטת של כל אפשרות ל Web Scraping. 3. פרטיות - איך השרת של גוגל ידע אם אתה בן אדם שגולש דרך דפדפן אמיתי? האם נתוני שימוש או נתונים התנהגותיים כלשהם ישודרו מהדפדפן לשרת האימות? האם תהיה לנו שליטה על הנתונים שנשלחים? 4. תוספים לדפדפן - האם תוספים לדפדפן יוכלו למשוך מידע מאתרים? או לחסום מידע מאתרים? איך כל העסק ישפיע על עולם פיתוח התוספים? ברמה האישית אוטומציה של עולם הווב היתה תמיד אחת הסיבות שלי לבחירה בעולם זה. היכולת לבנות כלים קטנים שעוזרים לי בלי לבקש רשות מאף אחד היא בבסיס של החופש שלי היה כל כך חשוב בעת גלישה ברשת. יהיה מאוד חבל להיפרד מחופש זה בגלל שגוגל החליטו שהחופש שלי פוגע להם בהכנסות מפירסומות. כרגע מוזילה עדיין מסרבים לממש את התקן. נמשיך לעקוב ונקווה לטוב.

ToCode
1 421
אתגר אחרון בעבודה עם neo4j הוא בבדיקות ובפרט בהרצה מקבילית של בדיקות. אם הבדיקות שלי מכילות איזשהו קוד שקשור לבסיס נתונים הן צריכות להתחבר לבסיס נתונים אמיתי. אין גירסת In Memory או משהו שיישמר בקובץ בודד כמו sqlite. לכן בשביל להריץ בדיקות במקביל ומהר נצטרך לכתוב את כל הקוד של המערכת כך שיוכל לעבוד בתוך טרנזאקציה למצב "בדיקות", להעביר מהבדיקה טרנזאקציה ובסוף הבדיקה לעשות Rollback. לא סוף העולם אבל היה נחמד אם היתה כבר תשתית שעושה את זה בתור פלאגין ל pytest. בינתיים אופציה יותר קלה שאני בחרתי היתה לאפס את כל ה DB בין בדיקות עם fixture אוטומטי. עובד אבל הרבה יותר לאט. שורה תחתונה ארבעה חודשים לתוך הפרויקט אני עדיין מאוד אוהב את neo4j, אבל גם שמח שהחסרונות מתחילים להופיע. האתגר הגדול הבא יהיה ביצועים, אבל על זה עדיין מוקדם לדבר.

ToCode
1 421
# ארבעה חסרונות מורגשים של neo4j (אחרי 4 חודשים של עבודה) התחלתי לעבוד עם neo4j באפריל בהתרגשות גדולה וגם כתבתי עליו כמה פוסטים באותה תקופה. ארבעה חודשים לתוך הפרויקט החסרונות מתחילים להיות יותר מורגשים, למרות שעדיין מוקדם ורק כשהפרויקט יעלה לפרודקשן אוכל לענות על שאלת המחץ לגבי הביצועים ואם הוא מצליח לסחוב משתמשים אמיתיים. בינתיים קבלו ארבעה חסרונות של neo4j שיכולים ממש להרגיז בזמן פיתוח- ## רק בסיס נתונים אחד ברישיון החופשי מגבלה ראשונה ומאוד מוזרה של הגירסה החופשית של neo4j היא ששרת בסיס נתונים יכול להחזיק רק בסיס נתונים אחד. זה אומר שאם התקנתם neo4j על מחשב הפיתוח שלכם אתם תקועים עם בסיס נתונים אחד לפיתוח. בשביל להריץ בסיס נתונים אחד לפיתוח, אחד ל Staging ואחד לבדיקות יש להריץ 3 עותקים של neo4j, כל אחד בתור דוקר קונטיינר משלו עם מיפוי לפורטים שונים. כשנשאלו לגבי הפיצ'ר התגובה של neo4j היתה- > Presently there are no plans to move the multi-DB feature into community edition. Usually that feature is used by SaaS customers who are Enterprise users. There's good product-market fit there. ## אין דרך קלה לזכור שינויים מחיקה רכה ושמירת שינויים היסטוריים הם מנגנונים קשים למימוש בכל בסיס נתונים, אבל בבסיסי נתונים טבלאיים לפחות יש הרבה כלים קיימים ומימושים שאפשר להיעזר בהם. ג'מים כמו Discard או paper_trail של ריילס מטפלים ברוב מה שצריך מהקופסה כדי לא לאבד מידע. ב neo4j צריך לעשות הכל לבד, ומבנה בסיס הנתונים לפעמים לא עוזר. בפרט neo4j מאפשר לשמור מידע בצמתים או בקשתות שמחברות בין הצמתים. בשביל לשמור היסטוריה לצומת אני יכול "להוציא" את הדברים שנשמרים בצומת (הסטייט) לצמתים נפרדים שיהיו מקושרים אליו, כאשר כל שינוי יווצר צומת State חדש שיהיה מחובר בקשת עם לייבל STATE וכל הסטייטים הקודמים יהיו מחוברים בקשת מסוג OLD_STATE ועל הקשת נוכל לשמור את תאריך השינוי. שמירת היסטוריה של חיבורים תהיה קצת יותר מסובכת כי אי אפשר לחבר "קשת" ל"קשת" או "צומת" ל"קשת", ואז צריך לשחק עם לייבלים של קשתות. אם תצטרכו לממש שמירת היסטוריית שינויים ב neo4j אני ממליץ על ההרצאה הזו שעוברת על מספר טכניקות מרכזיות עם דוגמאות קוד. ## שגיאות כתיב בשאילתות יוצרות משתנים חדשים שפת השאילתות של neo4j מדליקה וגמישה וכל הדברים האלה, אבל יש לה גם חיסרון - היא לא מספיק רגישה לשגיאות כתיב. למשל בבסיס נתונים דמיוני של סרטים שאילתה כזו יכולה להחזיר את כל הסרטים בהם שיחק תום הנקס:
MATCH (tom:Person{name:"Tom Hanks"})-[:ACTED_IN]-(movie:Movie)
RETURN movie;
ואפשר להמשיך ולהחזיר את שמות כל הבמאים של אותם סרטים:
MATCH (tom:Person{name:"Tom Hanks"})-[:ACTED_IN]-(movie:Movie)
MATCH (director:Person)-[:DIRECTED]-(movie)
RETURN director.name;
עכשיו שימו לב למה שמופיע בתוך הסוגריים העגולים: 1. אני יכול לכתוב שם מילה באותיות קטנות (שם של משתנה), אחריה נקודותיים ואז מילה באות ראשונה גדולה (נקראת Label). זה מריץ חיפוש בבסיס הנתונים על כל הצמתים עם הלייבל שהעברתי ושומר את התוצאה במשתנה. 2. אני יכול לכתוב רק שם של משתנה שכבר הופיע בעבר ואז החיפוש יהיה רק על הדברים שכבר "נמצאו" בתוך משתנה זה. זאת הסיבה שבשורה השניה אנחנו מוצאים רק את הבמאים שביימו סרטים של תום הנקס - המשתנה movie מופיע גם בשורה הראשונה וגם בשורה השניה, ומחבר בין התוצאות. ומה היה קורה אם היתה לי שגיאת כתיב בשם המשתנה בשורה השניה? כלומר אם הייתי כותב:
MATCH (tom:Person{name:"Tom Hanks"})-[:ACTED_IN]-(movie:Movie)
MATCH (director:Person)-[:DIRECTED]-(movei)
RETURN director.name;
עכשיו neo4j מתיחס למילה movei (עם שגיאת הכתיב) בתור שם של משתנה חדש, ויחזיר רשימה של כל הבמאים שביימו סרט כלשהו. שם הסרט יישמר במשתנה החדש movei, ותוצאת השאילתה תתעלם לחלוטין מהמשתנה movie שהוגדר בשורה הראשונה. וכן זה חמוד בפוסט אבל בתוכנית אמיתית עם שאילתות ארוכות, שגיאת כתיב כזאת תחזיר תוצאות שלא ציפיתם לקבל ועלולה לגרום לבאגים לא ברורים במקומות אחרים בתוכנית. ## אין תמיכה בבסיס נתונים בזיכרון או גירסה ״קלה״ אחרת

ToCode
1 421
# איך לכתוב יותר בדיקות יחידה בדיקות יחידה הן מגרש החול שלנו בפיתוח תוכנה. בשילוב עם גיט זאת הדרך לחקור רעיונות חדשים - קודם כל עוברים על הקוד בגדול, משנים ממשקים ומימושים ושוברים המון דברים תוך כדי תנועה, אחרי זה מריצים את הבדיקות כדי לסדר מהר את מה שנשבר ותוך כדי התיקון אפשר לראות אם הרעיון החדש משפר את המצב או שעדיף לגנוז אותו. ובכל זאת מפתחים רבים ממשיכים לכתוב קוד בלי בדיקות, משתמשים בבדיקות איטיות שנותנות תשובה רק אחרי כמה שעות או מתיחסים לבדיקות האוטומטיות בתור משהו שעושים ב QA. הסיבה המרכזית לדעתי היא שאנחנו מערבבים בין "כתיבת בדיקה חדשה" ל"כתיבת תשתית בדיקות". ברור שיהיה קשה לכתוב תשתית בדיקות. ברור שהתשתית תצטרך לספק מנגנונים קלים לגישה מהירה לתוך הקוד. ברור שתשתית הבדיקות תישבר לעתים קרובות כשהקוד משתנה ונצטרך לתחזק אותה יחד עם בניית הקוד. תשתית בדיקות היא פיצ'ר והיא דורשת השקעה. מצד שני כתיבה או תחזוקה של בדיקה בתוך התשתית צריכה להיות פשוטה. כשהתשתית טובה אין סיבה שזה יראה אחרת. ניקח דוגמה לבדיקה פשוטה כאן מהאתר:
test 'should subscribe to blog mailing list' do
  visit blog_index_url
  fill_in :email, with: @email
  click_button I18n.t('sign_up_email')

  assert_selector 'p', text: I18n.t('subscriptions.success')
  assert Subscriber.exists?(email: @email)
end
גם אם לא ראיתם קוד רובי בעבר די ברור מה היא עושה רק מהתיאורים ובחירת שמות המשתנים. בעברית הבדיקה נכנסת לדף האינדקס של הבלוג, רושמת איזשהו אימייל בתיבת האימייל, לוחצת על כפתור ההרשמה ולאחר מכן בודקת שהופיעה הודעת הצלחה ברישום ושהמנוי נכנס לבסיס הנתונים. מוקים? יצירת נתוני התחלה מזויפים בבסיס הנתונים? הפעלת האתר? כל אלה הם חלק מהתשתית. ככל שאנחנו מתעקשים על בדיקות קצרות וממוקדות יהיה לנו יותר קל לכתוב את הבדיקה הבאה, ולאורך זמן פשוט נכתוב הרבה יותר בדיקות.

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

ToCode
1 421
# תשעים ושלוש אחוז נתח שוק בסקר המפתחים של סטאק אוברפלו האחרון הם שאלו את המפתחים באיזה תוכנת ניהול גירסאות אנחנו משתמשים. לא תופתעו לשמוע שכמעט 94% מהמשיבים בחרו בגיט, ועוד 4% ענו שלא משתמשים בשום תוכנת ניהול גירסאות. איך תוכנת ניהול גירסאות אחת מצליחה להשתלט כך על העולם? אני לא יודע, אבל אלה כמה סיבות שאני יכול לנחש- 1. כל אחד יכול לפתוח מאגר אצלו ולהתחיל לעבוד (משמעותית יותר קל מ SVN שהיה פופולרי לפני גיט, ועוד עשרות תוכנות ניהול גירסאות מתחרות עם שרת מרכזי). 2. גיטהאב סיפק פלטפורמה מעולה למתכנתי קוד פתוח שהיו צריכים לעבוד עם מתכנתי קוד פתוח אחרים. היכולת למזלג פרויקט, לשלוח Pull Requests והאיחסון החינמי לפרויקטים משכו את המפתחים שעזבו את SourceForge. ואם רצית להצטרף לפרויקט קוד פתוח או פרויקט המאוחסן על גיטהאב היית צריך ללמוד גיט. גיטהאב הפך את גיט לאופנתי. 3. בסקר של אקליפס מ 2009, חמשים ושמונה אחוז מהמשיבים השתמשו עדיין ב SVN. ב 2011 גיטהאב עקף בפופולריות את SourceForge ושם התחיל אפקט כדור השלג. ב 2015 גיט עדיין עמד על 69% נתח שוק, מספר שהפך ב 2018 ל 87% כשמייקרוסופט רכשה את גיטהאב והכניסה את גיט גם לארגונים גדולים. כשאנחנו מסתכלים על כלי כל כך דומיננטי קשה לדמיין אפילו משהו אחר, ובטח משהו אחר שמישהו ירצה להשתמש בו. לחזור ל SVN? מרקוריאל? לא תודה. ובכל זאת גם בשוק שנראה סגור לגמרי יש עדיין פרויקטי צד מעניינים. אחד מהם הוא כלי בקרת תצורה בשם pijul שלוקח גישה שונה מאוד לניהול שינויים כשהדגש שם הוא על ה"שינויים" ולא על מצב הפרויקט בנקודה של קומיט. יצליח? כנראה שלא. אבל גם גיט לא יהיה איתנו לנצח, ובטוח יהיה מעניין לראות איך יראה העולם אחריו.