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

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

לא בכיוון

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 דוגמאות שהשתלבו בול בתוך ההרצאה שהעברתי וכל אחת מהן הציגה בעיה שהיה לה פיתרון מעולה. באופן כללי כשאני כותב קורס או הרצאה הדבר שלוקח הכי הרבה זמן הוא לתכנן את הדוגמאות האלה שיציגו בעיה אחת בצורה ממוקדת, ושהפיתרון שלה יהיה בדיוק מושלם.

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

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

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

הפיתרון הכי טוב שקיבלתי השבוע

08/11/2019

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

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

אני לא מאמין שבזבזתי שעתיים על זה

07/11/2019

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

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

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

וזה ממש חבל שאנחנו מרגישים ככה.

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

ומה עם הים? בינינו, תמיד תהיה עוד עבודה. באמת שאין לאן למהר.

ללמוד איך ללמוד

06/11/2019

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

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

מה כן עובד? הנה הרשימה שלי, תרגישו חופשי להוסיף בתגובות:

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

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

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

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

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

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

האינטרנט הוא מקום מסוכן

05/11/2019

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

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

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

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

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