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

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

בואו נלמד שפה חדשה עם Python

18/11/2019

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

המשך קריאה

זה עוד רחוק?

17/11/2019

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

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

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

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

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

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

16/11/2019

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

המשך קריאה

איך לשמור מערך בסטייט של React Component

15/11/2019

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

המשך קריאה

לא בכיוון

14/11/2019

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

המשך קריאה

דוגמת ריפקטורינג ב React עם Custom React Hook

13/11/2019

הסיבה המרכזית לדעתי לעבור ל React Hooks היא הפשטות בהוצאת קוד משותף לפונקציה חיצונית וביצוע שימוש חוזר בקוד בין פקדים או אפילו בתוך אותו פקד. כל פונקציה יכולה להפעיל פונקציות Hook ומספיק לכתוב פונקציה שמפעילה Hook-ים אחרים בשביל לקבל Custom Hook. הטרמינולוגיה המסורבלת הזאת באה לתת שם לתופעה מאוד פשוטה שאומרת שכשיש לנו קוד משותף אנחנו יכולים להוציא אותו החוצה לפונקציה.

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

import React from 'react';
import ReactDOM from 'react-dom';
import { useState, useEffect } from 'react';
import _ from 'lodash';
import $ from 'jquery';

const App = () => {
  const [id, setId] = useState(1);
  const [character, setCharacter] = useState({});
  const [filmId, setFilmId] = useState(1);
  const [film, setFilm] = useState({});

  useEffect(function() {
    setFilm({});
    const req = $.getJSON(`https://swapi.co/api/films/${filmId}/`, function(data) {
      setFilm(data);
    });
    return function cancel() {
      req.abort();
    }

  }, [filmId]);

  useEffect(function() {
    setCharacter({});
    const req = $.getJSON(`https://swapi.co/api/people/${id}/`, function(data) {
      setCharacter(data);
    });
    return function cancel() {
      req.abort();
    }
  }, [id]);

  const characterNameText = (character.name == null ?
    '[Loading please wait]' :
    character.name);

  const filmNameText = (film.title == null ?
    '[Loading please wait]' :
    film.title);

  const filmIds = (character.films != null ?
    character.films.map(f => f.match(/(\d+)\/$/)[1]) :
    _.range(1, 8));

  return (
    <div>
      <h1>ID: {id}</h1>
      Character ID:
      <select onChange={(e) => setId(e.target.value)}>
        {_.range(1, 11).map(id => (
          <option value={id}>{id}</option>
        ))}
      </select>
      Film ID :
      <select onChange={(e) => setFilmId(e.target.value)}>
        {filmIds.map(id => (
          <option value={id}>{id}</option>
        ))}
      </select>
      <hr />
      <h2>Character Name: {characterNameText}</h2>
      <h2>Film Name: {filmNameText}</h2>
    </div>
  )
};


const root = document.querySelector('main');
ReactDOM.render(<App />, root);

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

איך מתקנים? כל מה שצריך הוא להוציא את הקוד הכפול לפונקציה:

export function useRemoteData(endpoint) {
  const [id, setId] = useState(1);
  const [data, setData] = useState({});

  useEffect(function() {
    setData({});
    const req = $.getJSON(`https://swapi.co/api/${endpoint}/${id}/`, function(data) {
      setData(data);
    });
    return function cancel() {
      req.abort();
    }

  }, [id]);

  return [id, setId, data];
}

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

const App = () => {
  const [characterId, setCharacterId, character] = useRemoteData('people');
  const [filmId, setFilmId, film] = useRemoteData('films');
  // ...

וכן לריאקט ממש לא אכפת שאתם קוראים מפונקציית הפקד לפונקציה נוספת והיא זו שקוראת בפועל ל useEffect ו useState. למעשה האפשרות הזו להוציא קוד החוצה היא הסיבה המרכזית למעבר ל React Hooks. הפונקציה useRemoteData נקראת Custom Hook וזה כל מה שצריך לדעת בשביל לשתף קוד מבוסס סטייט בין קומפוננטות שונות או לבצע שימוש חוזר בקוד כזה בתוך אותה קומפוננטה.

הערכות זמנים

12/11/2019

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

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

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

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

או מתכנתת שתגיד לעצמה - ממילא נשארה לי פה עוד מלא עבודה אז אולי אנסה לזרוק את הקוד שכתבתי ולפתור את המשימה בשפה אחרת, אולי בתור Micro Service.

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

להתחיל בקטן

11/11/2019

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

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

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

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

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

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

היום למדתי: מאקרו החץ ב Clojure

10/11/2019

באליקסיר יש אופרטור שנקרא Pipe Operator שתפקידו מאוד פשוט, הוא הופך קוד שנראה ככה:

foo(bar(baz(new_function(other_function()))))

לקוד שנראה ככה:

other_function() |> new_function() |> baz() |> bar() |> foo()

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

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

"Elixir rocks" 
|> String.upcase()
|> String.split()

מה שמחזיר את התוצאה:

["ELIXIR", "ROCKS"]

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

%{"blah" => "blah", "vtha" => "blah"}
|> Enum.filter(fn {k, v} -> k != v end)
|> Enum.map(fn {k, v} -> {k, v} end)
|> Map.new
|> IO.inspect

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

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

הנה הדוגמא האחרונה בתרגום לקלוז'ר:

(->>
  {"blah" "blah" "vtha" "blah"}
  (filter (fn [[k v]] (not= k v)))
  (map (fn [[k v]] [v k]))
  (into {})
  (println))

אגב השמות הרשמיים של המאקרו-אים בקלוז'ר הם Thread first macro לקצר ו Thread last macro לארוך. בדף התיעוד הזה תמצאו הסבר יותר מקיף עליהם כמו גם עוד כמה מאקרו-אים לטיפול יותר עדין באותו נושא https://clojure.org/guides/threading_macros.

העולם האמיתי

09/11/2019

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

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

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

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