ru
Feedback
ToCode

ToCode

Открыть в Telegram

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

Больше
1 419
Подписчики
+224 часа
+17 дней
+130 день
Архив постов
ToCode
1 420
# לא מאמין שפספסתי את זה 3 פעמים כבר הפעם הראשונה שקראתי את התיעוד של דוקר היתה לפני מספר שנים. זה היה כשכולם מסביבי דיברו על הכלי ואצל כל לקוח שהגעתי אליו השתמשו בדוקר בשוטף לפיתוח. ידעתי שכבר אי אפשר לדחות את זה ועדיף ללמוד מוקדם מאשר מאוחר. את רוב הדברים הבנתי מהר ובכל זאת רציתי לראות מה עוד אפשר לעשות עם דוקר ובאמת אני זוכר שלמדתי על מנגנון ה Secrets וחשבתי שהוא די מדליק. מאז חזרתי לפחות עוד פעמיים לתיעוד, כל פעם כשהייתי צריך לבנות משהו אחר. פעם אחת בשביל להבין בדיוק איך Image עובד או למה האימג'ים שלי יוצאים כל כך גדולים ופעם נוספת כדי ללמוד על Volumes ואיך לנהל Persistency בתוך docker-compose. אבל רק כשחיפשתי איך לתקשר בין כמה סטאקים של docker-compose הצלחתי לראות את המנגנון של הגדרת External Network והבנתי איך משתמשים בו. נכון, המנגנון הזה היה שם בכל אחת מהפעמים הקודמות שקראתי את אותו תיעוד, ובכל זאת כשלא חיפשתי אותו הוא פשוט חמק לי בין השורות. אני חושב שהסיבה קשורה למוח שלנו. המוח האנושי הרבה יותר טוב בחיפוש תשובה לבעיה מוגדרת מאשר ב"לספוג ידע". הרעיון של ללמוד את הספר הזה בעל פה ואז לדקלם אותו למבחן לא ממש עובד בחיים האמיתיים מאותה סיבה שאי אפשר ללמוד שפה מקריאת מילון. המוח צריך את הקונטקסט כדי להבין מה הוא מחפש ומה הוא קורא. והלימוד? זה לא עוזר שכל הידע כתוב בספר. האתגר בלימוד הוא ליצור לעצמנו משימות שבמהלך המחקר על הפיתרון שלהן אנחנו נגיע שוב ושוב לחפש בתיעוד את החלקים הספציפיים שרלוונטים לאותן משימות. וככל שמשימות הלימוד מכסות חלקים גדולים יותר מהכלי כך יש יותר סיכוי שנוכל ללמוד וגם לזכור את מה שלמדנו.

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

ToCode
1 420
# מה זה Serverless ולמה שיהיה לכם אכפת בניתי היום בחצי שעה מערכת לשמירת לידים מדף נחיתה. אני יודע, זה לא מלהיב במיוחד, יש כאלה 8 בשקל, וממילא בכל מערכת דפי נחיתה שתבחר יש מנגנון שמירת לידים מובנה. ובכל זאת חשבתי שזה יהיה משחק מעניין וגם רציתי להשאיר את האופציות הטכנולוגיות פתוחות. הנה הקוד רק בשביל שיהיה לנו קל לדבר:
const AWS = require("aws-sdk");

const dynamo = new AWS.DynamoDB.DocumentClient();

exports.handler = async (event, context) => {
  let body;
  let statusCode = 200;
  const headers = {
    "Content-Type": "application/json"
  };

  try {
    switch (event.routeKey) {
      case "GET /items":
        body = await dynamo.scan({ TableName: "Leads" }).promise();
        break;
      case "PUT /items":
        let requestJSON = JSON.parse(event.body);
        await dynamo
          .put({
            TableName: "Leads",
            Item: {
              id: requestJSON.email,
              name: requestJSON.name,
              email: requestJSON.email,
            }
          })
          .promise();
        body = `Put item ${requestJSON.email}`;
        break;
      default:
        throw new Error(`Unsupported route: "${event.routeKey}"`);
    }
  } catch (err) {
    statusCode = 400;
    body = err.message;
  } finally {
    body = JSON.stringify(body);
  }

  return {
    statusCode,
    body,
    headers
  };
};

יש פה פחות מ-50 שורות. הלידים נשמרים בתוך טבלה בבסיס נתונים ואפשר למשוך את כל הלידים באמצעות בקשת HTTP GET פשוטה, או פשוט דרך ממשק הניהול של AWS. הנה כמה דברים שהפכו את גישת ה Serverless למדליקה עבור הפרויקט הזה: 1. הכל היה באוויר תוך חצי שעה. בלי Deployment, בלי לבנות Database, בלי להגדיר משתמשים ומה מותר למי ומה אסור. חמישים שורות ולדרך. 2. בזכות ניהול הרשאות מובנה של AWS כל אחד יכול ליצור ליד אבל בשביל לצפות בלידים צריך להיות משתמש מחובר עם הרשאות מתאימות. 3. יש לוג די מפורט של הבקשות הנכנסות מנוהל אוטומטית על ידי AWS. 4. לא צריך לדאוג לסקיילינג. אז נכון אני כנראה לא בונה את הטיק טוק הבא אבל בכל זאת טוב להיות רגועים בגיזרה הזו. 5. לא צריך לכתוב Dockerfile ו docker-compose.yml. שתיים-שלוש לחיצות ב GUI או מספר פקודות מה CLI מספיקות כדי לחבר את כל החוטים. בצד החסרונות שווה לשים לב ש: 1. צריך לדעת לקנפג את ההרשאות בצד של AWS ובטח טעיתי בחלק מהסעיפים שם. 2. צריך לעמוד בפיתוי ולא להדביק את הקוד לתוך הממשק הגרפי אלא ליצור Pipeline מסודר של גיטהאב לבניה ו Deployment. 3. התיעוד של AWS ארוך מדי וכולל המון מידע לא רלוונטי. 4. אין לי מושג כמה זה יעלה בסוף. ועדיין הקסם של גישת ה Serverless הוא משהו שאי אפשר היום להתעלם ממנו. היכרות עם הכלים ויכולת להקים סרביס חדש מהר הם בין המיומנויות החשובות למפתחי Full Stack היום.

ToCode
1 420
# טיפ באש: איך לחפש קובץ JSON עם ערך במפתח מסוים הפקודה jq היא אוצר כשאנחנו צריכים לחפש בתוך קבצי JSON ובטיפ היום אני רוצה להראות לכם שיטה פשוטה לחפש קובץ JSON שמכיל ערך מסוים. ביום רגיל היינו משתמשים ב grep פשוט כדי למצוא את התוכן שאנחנו צריכים וכשזה עובד אז grep הוא גם הבחירה הראשונה שלי. הבעיה שלפעמים בקבצי JSON יש ירידות שורה במקומות לא צפויים, או שאין ירידת שורה בדיוק כשתכננתם שתהיה אחת. הפקודה jq יודעת לפענח קבצי JSON ובצורה אוטומטית לחפש בתוכם את הערכים. בשביל ש jq תוציא ערך מתוך JSON אנחנו צריכים להעביר לה את הנתיב לאותו ערך. עבור קובץ JSON שטוח הנתיב הוא פשוט סימן נקודה ואחריו שם המפתח. לדוגמה אם ה JSON שלי כולל רק מפתח אחד בשם success והערך הוא בוליאני true או false אז הפקודה:
$ jq '.success' file.json
תדפיס את הערך של המפתח success. ומה קורה כשיש לנו ערימה של קבצי JSON שצריך לחפש בהם? מסתבר שבעזרת find ו jq אפשר להגיע רחוק. הפקודה הבאה תחפש ותדפיס את כמות קבצי ה JSON בתיקיה בהם ערך המפתח success הוא true:
find . -name '*.json' -exec jq '"{}: " + (.success|tostring)' {} \;|grep true
מעניין לשים לב: 1. סימן {} יוחלף על ידי find בשם קובץ או תיקיה לכל קובץ שמסתיים בסיומת json 2. פקודת ה + של jq מחברת שני דברים. הראשון הוא שם הקובץ (כפי שמגיע מ find). 3. הסימן השני שהעברתי ל + הוא סימן קו אנכי וזאת הדרך של jq להפעיל פונקציות. בעברית נתרגם את המשפט שם ל"קח את הערך במפתח success והעבר אותו דרך הפונקציה tostring". 4. עד לפה נקבל בכל שורה שם קובץ ואחריו הערך שבמפתח success. את זה אפשר להעביר דרך grep כדי לקבל רק את הקבצים בהם הערך הוא true.

ToCode
1 420
# שלושה חלומות כשאתה מנסה להגשים חלום אחד ונכשל אתה יכול להסתכל אחורה ולזהות (חלק) מהטעויות, ואז לנסות שוב מנקודת פתיחה קצת יותר טובה. כשאתה מנסה להגשים שלושה חלומות במקביל ואף אחד מהם לא מצליח, הלקח הכי סביר שתצליח להוציא מזה הוא שאחד מהם נכשל כי לא השקעת בו מספיק תשומת לב. אני יודע שזה מפתה ללמוד גם Machine Learning, גם אבטחת מידע וגם Full Stack במקביל כי באחד מהם בטוח תהיה עבודה. אבל זה פיתוי ששווה להתנגד אליו. הסיכוי למצוא עבודה כמפתח Full Stack שהקים שלושה פרויקטים גדול משמעותית מהסיכוי למצוא עבודה כמפתח ML שהקים פרויקט קטן, איש אבטחת מידע שהקים פרויקט קטן ומפתח Full Stack שהקים פרויקט קטן, גם אם השלושה האחרונים הם אותו בן אדם.

ToCode
1 420
# שמונה השנים הבאות קוד Front End טיפוסי שכותבת מתכנתת עם שנתיים ניסיון לא יראה שונה בהרבה מקוד מקביל שאותה מתכנתת תכתוב אחרי שיהיו לה עשר שנות ניסיון. נכון, יש מתכנתים שאחרי שנתיים עדיין לא מבינים מה הם עושים, אבל כנראה שהם לא יבינו מה הם עושים גם אחרי עשר שנים. ברוב המקרים שנתיים של ניסיון טוב בעבודה איכותית בכתיבת Front End זה מספיק בשביל להבין מה קורה ולהכיר את רוב הטכניקות שעובדות. אז מה בכל זאת קורה ב-8 השנים הבאות? שני דברים: 1. אחרי שנתיים ניסיון את מכירה את הטכניקות אבל עדיין מתלבטת באיזו טכניקה להשתמש ומתי. את כן מצליחה להגיע אחרי התלבטות להחלטה הנכונה ובגלל זה הקוד כן עובד ונראה טוב, אבל בדרך את תאבדי זמן על לימוד, היזכרות והתלבטות. זה יכול להיות זמן שילך על להיזכר במאפיינים המדויקים של CSS Grid או להתלבט אם להשתמש או לא ב Bootstrap. המשך הלעיסה, ההתלבטויות והחיפוש החוזר בתיעוד כדי למצוא שוב את השם המדויק של המאפיין הזה שאת צריכה לאט לאט מצטברים, עד שבסוף הזמן הזה מתקצר משמעותית. 2. אחרי שנתיים ניסיון את מכירה את הטכניקות המרכזיות, אבל יש עדיין הרבה טכניקות קטנות שעברו מתחת לרדאר. אלה דברים שאי אפשר להיתקל בהם סתם כך באמצע עבודה כי כל פרויקט משתמש בסט מסוים של יכולות ויש המון יכולות טכנולוגיות שהפרויקטים שאת עובדת עליהם פשוט לא יגיעו להשתמש בהן. הדרך היחידה להגיע לטכניקות אלה היא להמשיך לקרוא מאמרים מקצועיים כל הזמן ולאורך זמן. רוב מפתחי ה CSS שאני מכיר לא כותבים אנימציות עם Cubic Bezier, וקריאה של מאמר ולימוד הטכניקה שמופיעה בו יכולה לקחת לך כמה שעות עבודה, מה שאומר שקשה מאוד לקרוא יותר מאחד בשבוע. היכרות מעמיקה עם כל הצדדים של עולם התוכן היא משהו שלוקח זמן לפתח. הדבר החשוב לראות כאן הוא שגם אחרי שנתיים ניסיון, גם כשאת מרגישה שאת מסוגלת לפתור כל בעיה טכנולוגית ולהבין כל פיתרון קיים, וגם כשההתקדמות בלימוד מרגישה הרבה פחות דרמטית מבעבר, עדיין יש צורך להמשיך ללמוד כל יום ולהמשיך לשאול שאלות תוך כדי עבודה, כדי לנצל בצורה טובה גם את שמונה השנים הבאות.

ToCode
1 420
הקומפוננטה האחרונה והארוכה ביותר היא GameArea שאחראית על ציור לוח המשחק וטיפול בלחיצה על משבצות על הלוח:
function GameArea({ board, player, setBoard, setPlayer, active }) {
  function play(row, column) {
    if (!active) {
      return;
    }

    const nextBoard = Game.playerClickedOnSquare(board, player, row, column);
    if (nextBoard !== board) {
      setBoard(nextBoard);
      setPlayer(Game.nextPlayer(player));
    }
  }

  return (
    <div className="container">
      {_.range(3).map((row) =>
        _.range(3).map((column) => (
          <div className="square" onClick={play.bind(null, row, column)}>
            {Game.getCellContents(board, row, column)}
          </div>
        ))
      )}
    </div>
  );
}

בזמן קריאת הקוד שימו לב לקטע הבא מתוך הקומפוננטה הראשית (קטע דומה מופיע גם בקומפוננטת הסטטוס):
      <GameArea
         ... 
        active={!Game.isWinner(board, Game.nextPlayer(player))}
      />
בשביל לבדוק אם יש מנצח אנחנו בודקים האם "השחקן הבא" הוא המנצח. נסו לחשוב - למה "השחקן הבא" ולא "השחקן הנוכחי"? מי השחקן הנוכחי ומי השחקן הבא בכל רגע נתון? והאם יכול להיות מצב ש"השחקן הנוכחי" הוא גם המנצח? לסיום הוספתי גם פונקציה ב game.js כדי לבדוק אם יש מנצח במשחק וזה הקוד שלה:
import produce from "immer";

export function createEmptyBoard() {
  return [
    [" ", " ", " "],
    [" ", " ", " "],
    [" ", " ", " "]
  ];
}

export function getCellContents(board, row, column) {
  return board[row][column];
}

export function playerClickedOnSquare(board, player, row, column) {
  return produce(board, (draft) => {
    if (draft[row][column] === " ") {
      draft[row][column] = player;
    }
  });
}

export function nextPlayer(player = null) {
  switch (player) {
    case "O":
      return "X";
    case "X":
      return "O";
    default:
      return "X";
  }
}

export function isWinner(board, player) {
  for (let i = 0; i < 3; i++) {
    if (
      board[i][0] === board[i][1] &&
      board[i][1] === board[i][2] &&
      board[i][2] === player
    ) {
      return true;
    }
    if (
      board[0][i] === board[1][i] &&
      board[1][i] === board[2][i] &&
      board[2][i] === player
    ) {
      return true;
    }
  }

  if (
    board[0][0] === board[1][1] &&
    board[1][1] === board[2][2] &&
    board[2][2] === player
  ) {
    return true;
  }
  if (
    board[0][2] === board[1][1] &&
    board[1][1] === board[2][0] &&
    board[2][0] === player
  ) {
    return true;
  }
  return false;
}
את הקוד המלא לחלק השני תוכלו למצוא בקישור: https://codesandbox.io/s/dawn-hill-7xqxn?file=/src/App.js. ## רעיונות לשיפור להמשך דרך טובה לוודא שהבנתם קטע קוד מסוים היא להרחיב אותו ולנסות להוסיף פונקציונאליות. הנה כמה רעיונות לשיפור הקוד מהפוסט: 1. החליפו את העברת המשתנים מהקומפוננטה הראשית לילדים ב useReducer כדי לראות את היתרונות והחסרונות בשימוש בכל מנגנון. 2. שימרו נקודות והוסיפו בתחתית המסך לוח שמראה כמה פעמים X ניצח וכמה O. 3. אפשרו לפני תחילת משחק לכל שחקן לבחור לעצמו את השם. 4. הוסיפו אפשרות לשמור את מצב הלוח ל Local Storage. 5. הוסיפו אפשרות למשחק סימולטני על מספר לוחות במקביל. המשחק נגמר רק כשמושג ניצחון בכל הלוחות, והמנצח של המשחק הוא זה שניצח ברוב הלוחות. השמירה ל Local Storage עכשיו צריכה לשמור את מצב כל הלוחות. 6. הוסיפו כפתור Undo שמבטל את המהלך האחרון. 7. כששחקן מנצח הציגו את השורה, העמודה או האלכסון שניצח בצבע מהבהב.

ToCode
1 420
כדי להישאר עם פונקציה נטולת Side Effects אני חייב לוותר על הכתיבה לזיכרון ולהחליף אותה ביצירת ערך חדש לגמרי שלא ישנה את המערך board המקורי. בשביל זה בחרתי להשתמש בספריה immer שיודעת להפוך פעולת כתיבה לזיכרון לפעולת העתקת אוביקטים והחזרת עותק של האוביקט המקורי עם העדכון. יש עוד דרכים לכתוב את הקוד הזה אבל אני חושב ש immer מציעה את הפיתרון הקל ביותר להבנה. קוד הקומפוננטה עצמה לא דורש שום תחכום. הוא משתמש בקוד שכתבתי ב game כדי ליצור לוח משחק חדש ולשמור אותו במשתנה State, לקבל את השחקן הראשון למשתנה State נוסף, לצייר את הלוח וכשלוחצים על משבצת לעדכן את הלוח והשחקן בעזרת הפונקציות ב game. כך נראה הקוד:
import _ from "lodash";
import { useState } from "react";
import * as Game from "./game";

export default function TicTacToe(props) {
  const [board, setBoard] = useState(Game.createEmptyBoard());
  const [player, setPlayer] = useState("X");

  function play(row, column) {
    const nextBoard = Game.playerClickedOnSquare(board, player, row, column);
    if (nextBoard !== board) {
      setBoard(nextBoard);
      setPlayer(Game.nextPlayer(player));
    }
  }

  return (
    <>
      <p>It's player {player} turn</p>

      <div className="container">
        {_.range(3).map((row) =>
          _.range(3).map((column) => (
            <div className="square" onClick={play.bind(null, row, column)}>
              {Game.getCellContents(board, row, column)}
            </div>
          ))
        )}
      </div>
    </>
  );
}

אפשר לראות את החלק הראשון בפעולה בקישור: https://codesandbox.io/s/muddy-sea-4o46f?file=/src/App.js. ## חלק 2: סיום המימוש וחלוקה לקומפוננטות גירסת הקומפוננטה האחת עובדת אבל מתקרבת לתקרת הזכוכית של קומפוננטה. ככל שאוסיף עכשיו עוד לוגיקה לקוד הוא יתחיל להפוך לקשה לתחזוקה וקריאה. בנקודה הזאת אני אוהב לשבור את הקומפוננטה לתתי-קומפוננטות ולפצל את הפונקציונאליות ולפעמים גם חלק מהסטייט בין הקומפוננטות. במקרה שלנו אי אפשר לפצל את הסטייט: כולם צריכים לדעת אם יש מנצח (ולכן צריכים את לוח המשחק) וכולם צריכים לדעת מי השחקן הנוכחי. חלוקה אפשרית לקומפוננטות בתוך אילוצים אלה תהיה לבנות קומפוננטה של Status שמציגה את פרטי השחקן הנוכחי ואם יש מנצח אז את פרטי המנצח וכפתור עם אפשרות להתחיל משחק חדש; וקומפוננטה שניה של לוח המשחק שמציגה את הלוח ומאפשרת לשחקן לשחק. בחירת הקומפוננטות היא אחד הצדדים המעניינים של פיתוח בריאקט וחשוב להישאר גמישים בנושא ולא לפחד לשנות את החלוקה שבחרתם אם אתם רואים שהיא כבר לא עובדת. הקומפוננטה הראשית שלי בחלוקה נראית כך:
export default function TicTacToe(props) {
  const [board, setBoard] = useState(Game.createEmptyBoard());
  const [player, setPlayer] = useState("X");

  function startNewGame() {
    setBoard(Game.createEmptyBoard());
  }

  return (
    <>
      <Status board={board} player={player} startNewGame={startNewGame} />

      <GameArea
        board={board}
        setBoard={setBoard}
        player={player}
        setPlayer={setPlayer}
        active={!Game.isWinner(board, Game.nextPlayer(player))}
      />
    </>
  );
}
התפקיד העיקרי של הקומפוננטה הראשית הוא ליצור את משתני ה State ולהעביר אותם לקומפוננטות פנימיות יותר כדי שיציגו אותם. הרבה פעמים נראה קומפוננטות כאלה משתמשות ב useReducer כדי להעביר פחות משתנים וליצור ממשק פחות מחייב כלפי קומפוננטות הילדים. הקומפוננטה השניה Status היא די פשוטה ונראית כך:
function Status({ board, player, startNewGame }) {
  if (Game.isWinner(board, Game.nextPlayer(player))) {
    return (
      <p>
        Player {Game.nextPlayer(player)} Won!
        <button onClick={startNewGame}>New Game</button>
      </p>
    );
  }

  return <p>It's player {player} turn</p>;
}
וכאן אני חיברתי את המנגנון שמציג מי המנצח עם המנגנון שמציג מי השחקן הנוכחי. זאת בחירה כמובן ואפשר היה לחשב האם יש מנצח בקומפוננטה הראשית ולפי זה לבחור אם להציג קומפוננטת סטטוס אחת לתוך המשחק וקומפוננטת סטטוס אחרת למצב של משחק נגמר.

ToCode
1 420
# בואו נכתוב משחק איקס עיגול ב React עם Hooks בעקבות שאלה של תלמיד שהגיעה אליי למייל נזכרתי שמזמן לא כתבתי מימוש למשחק איקס עיגול בריאקט ובעצם המימוש האחרון שאפשר למצוא אם מחפשים אחורה פה בבלוג משתמש ב Redux. אז נכון פיתוח ב Redux זה תמיד כיף אבל כשמדובר במשחק פשוט כמו איקס עיגול אני חושב שיהיה מעניין לראות גם מימוש ריאקטי שלו רק באמצעות קומפוננטות. ## מילה על מידע גלובאלי בכל פיתוח יישום בריאקט ובמיוחד יישום שמחולק לקומפוננטות אנחנו מחלקים את המידע שהיישום מנהל למידע גלובאלי ומידע פנימי לקומפוננטות. החלוקה היא שרירותית במובן זה שאפשר לכתוב את אותו יישום בחלוקות שונות ומבחינת המשתמש לא יהיה הבדל בין הגירסאות. אבל, החלוקה כן משפיעה על מבנה הקוד ועל כמה קל יהיה לנו לממש פיצ'רים מסוימים בו. צריך להגיד - אין גישה אחת נכונה וחשוב לקבוע איזה מידע יישמר גלובאלית ואיזה מידע מקומית בהתאמה ליישום ולשינויים הצפויים בו. כשהמידע נשמר בתור מידע גלובאלי קל לנו יותר לנהל אותו מכמה קומפוננטות במקביל. כשהמידע נשמר בתור מידע מקומי קל לנו לשכפל את הקומפוננטה ולנהל כמה גירסאות של אותו מידע במקביל. בדוגמה של משחק איקס עיגול "המידע" של המשחק הוא מצב הלוח ומי השחקן הנוכחי. אם המידע הזה יישמר בצורה גלובאלית נוכל יחסית בקלות להוסיף עוד קומפוננטות ככל שנתקדם בפיתוח, למשל כפתור Undo, כפתור שמירת מצב המשחק ל Local Storage, רכיב ששומר נקודות ואלמנטים גלובאליים נוספים. אם המידע נשמר מקומית בקומפוננטה קל לנו לשכפל את הקומפוננטה הזו ולמשל להציע מספר לוחות משחק שפועלים במקביל, וקל להוסיף לקומפוננטה התנהגות באמצעות Custom Hooks למשל כדי לשתף את לוח המשחק עם שרת מרוחק ולאפשר משחק ברשת. אני אוהב להתחיל את הפיתוח בשמירת כל המידע בצורה מקומית בכל קומפוננטה ולאט לאט אם יש צורך מבחינת פיצ'רים להעביר מידע החוצה ולשמור אותו בתור מידע גלובאלי באמצעות Redux, MobX או Context. במימוש בפוסט זה העדפתי לשמור את כל המידע בתור מידע מקומי בתוך הקומפוננטות כי לא היה אף פיצ'ר שהצדיק שמירת מידע גלובאלי. ## חלק 1: מימוש חלקי בתוך קומפוננטה אחת הבסיס של משחק איקס עיגול הוא בסך הכל לוח בגודל 3 על 3 ושחקן נוכחי מתחלף בין X ל O. לחיצה על משבצת מציירת בה את הדמות של השחקן הנוכחי (אלא אם כן המשבצת כבר תפוסה) ומחליפה שחקן. מנצח השחקן הראשון שסיים שורה, עמודה או אלכסון. בשביל להתחיל לכתוב את הקוד מספיק לנו לצייר לוח של 3 על 3, לזכור איזה שחקן תפס איזו משבצת ולאפשר לשחקנים לתפוס משבצות באמצעות לחיצה עליהן. גירסה זו של המשחק לא תזהה עדיין ניצחון אבל כן תתן לנו את הבסיס לפיתוח ותעזור לנו להבין על מה אנחנו מדברים. בעבודה עם ריאקט אני מעדיף להשאיר את קוד הקומפוננטות כמה שיותר נקי ולכן אני מחלק את הקוד ל-2 קבצים: בקובץ game.js אני כותב את הפונקציונאליות עצמה של המשחק, ובקובץ TicTacToe.js אכתוב את הקומפוננטה. מתכנתים רבים רגילים שכשצריך לכתוב לוגיקה של משהו הם יתחילו מכתיבת קוד מונחה עצמים, אבל בריאקט הסיפור קצת יותר מסובך. המידע עצמו שהלוגיקה שלנו צריכה לעבוד עליו עשוי להישמר בתור חלק מהלוגיקה (למשל אם נכתוב את הקוד ב MobX), אבל גם עשוי להישמר בתוך הקומפוננטה באמצעות State. בשביל להשאיר את כל האופציות פתוחות אני מעדיף לכתוב ב game.js פונקציות עזר פשוטות שיקבלו את כל המידע מבחוץ ולא יכללו Side Effects. במקרה של איקס עיגול זה קל ואני יכול להתחיל עם הקוד הבא לקובץ game.js:
import produce from "immer";

export function createEmptyBoard() {
  return [
    [" ", " ", " "],
    [" ", " ", " "],
    [" ", " ", " "]
  ];
}

export function getCellContents(board, row, column) {
  return board[row][column];
}

export function playerClickedOnSquare(board, player, row, column) {
  return produce(board, (draft) => {
    if (draft[row][column] === " ") {
      draft[row][column] = player;
    }
  });
}

export function nextPlayer(player = null) {
  switch (player) {
    case "O":
      return "X";
    case "X":
      return "O";
    default:
      return "X";
  }
}
מתוך 4 פונקציות בקובץ אני חושב שכולן מלבד playerClickedOnSquare הן די פשוטות. הפונקציה playerClickedOnSquare היא היחידה שעושה משהו יצירתי בגלל שהיא היחידה שעשויה היתה לכלול Side Effect. זיכרו שאנחנו רוצים לנהל את כל המידע בצורה חיצונית וחשוב לנו שהפונקציות לא ישנו את המידע שהן מקבלות או מידע חיצוני להן. אבל המשתנה board הוא בדיוק מצביע למערך דו-מימדי. כתיבה אליו תשנה את המערך, שינוי שיצא מגבולות הפונקציה.

ToCode
1 420
# טיפים לכתיבת פרויקט צד הפרויקט Pirate Weather שעלה לאחרונה לאוויר הספיק לקבל כבר מעל 500 לייקים ברדיט. כתב אותו דוקטורנט בשם אלכסנדר ריי והמטרה שלו פשוטה: להחזיר נתוני מזג אוויר באותו פורמט JSON כמו אתר Dark Sky, אחרי שאפל קנו את דארקסקיי וסגרו את השירות. הפרויקט הזה הוא פרויקט צד לפי הגדרה: כתב אותו דוקרטורנט שלכתוב APIs למזג אוויר אינה העבודה המרכזית שלו; עד כמה שהצלחתי להבין הוא כתב את האתר לבד והפרויקט אינו בתשלום אבל מקבל תרומות. הנה כמה דברים שלדעתי אלכסנדר עשה נכון ושווה ללמוד ממנו לפרויקט הצד הבא שלכם: 1. הוא בחר פרויקט שפותר לאנשים אמיתיים בעיה אמיתית: אנשים היו רגילים להשתמש ב Dark Sky API או שהיה להם קוד קיים שקורא JSON-ים מ Dark Sky, דארק סקיי נסגר ועכשיו יהיה להם פיתרון חלופי בלי עלות מעבר. 2. הוא בנה דף בית מאוד מסודר שמסביר בדיוק מה הסיפור של הפרויקט, מה כרגע עובד ואפילו הוסיף אילוסטרציה לארכיטקטורה. 3. הוא השקיע במנגנון רישום ואפשרות לתשלום אפילו שזה פרויקט צד ואפילו שבטח יהיו ממש מעט אנשים שירשמו למסלול בתשלום שלו. עצם זה שמסלול זה קיים נותן תחושה מקצועית יותר לאתר. 4. הוא בנה דף Front End שמשתמש ב API ומראה את התחזית במקום מגוריכם. 5. הוא כתב פוסט מפורט שמסביר טכנית איך בנוי הפרויקט ובאיזה תשתיות הוא משתמש. ההשקעה בכל המסביב (פוסט, דף בית, מנגנון רישום, עמוד Front End) נראית לפעמים מיותרת כשאנחנו כותבים את הפרויקט הגדול שלנו, אבל בתור גולש שנתקל בפרויקט, דווקא ההשקעה בכל הדברים שמסביב היא שנותנת הרגשה שהפרויקט שווה את הזמן שלי, והרגשה זו היא שעוזרת לי לבדוק את הפרויקט ברצינות ולהתרשם ממנו.

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