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

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

איך לכתוב יותר בדיקות יחידה

15/08/2023

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

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

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

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

נשמע טוב. לא היום.

14/08/2023

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

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

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

תשעים ושלוש אחוז נתח שוק

13/08/2023

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

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

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

  3. בסקר של אקליפס מ 2009, חמשים ושמונה אחוז מהמשיבים השתמשו עדיין ב SVN. ב 2011 גיטהאב עקף בפופולריות את SourceForge ושם התחיל אפקט כדור השלג. ב 2015 גיט עדיין עמד על 69% נתח שוק, מספר שהפך ב 2018 ל 87% כשמייקרוסופט רכשה את גיטהאב והכניסה את גיט גם לארגונים גדולים.

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

מבין מה ניסו לעשות שם

12/08/2023

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

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

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

אני מזהה את התבנית הזאת ומאיזה ספר היא הגיעה.

אני רואה את ה Design Pattern שלא ממומש בדיוק לפי הספר אבל גם ברור למה.

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

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

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

משולש האימה

11/08/2023

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

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

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

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

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

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

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

חמישה דברים שהייתי משנה בקלוז'ר

10/08/2023

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

אני כתבתי פוסט מבוא לקלוז'ר בעבר כאן: https://www.tocode.co.il/blog/2019-11-hello-clojure

וגם העברתי יחד עם חבר וובינר על קלוז'ר שמוקלט כאן: https://www.tocode.co.il/past_workshops/95

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

המשך קריאה

יש לי רעיון מבריק!

09/08/2023

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

״מה הבעיה? רק צריך לרשת מ int ולהוסיף ל __add__ פקודת הדפסה. משהו כזה-

class LoggedAddInt(int):
    def __new__(cls, *args, **kwargs):
        args_without_name = {k: v for k, v in kwargs.items() if k != 'name'}
        return super().__new__(cls, *args, **args_without_name)

    def __init__(self, *args, **kwargs):
        self.name = kwargs.get('name', 'New Value')

    def __add__(self, other):
        res = LoggedAddInt(super().__add__(other), name=self.name)
        print(f"{self.name}: {res}")
        return res


counter = LoggedAddInt(0, name="counter")

def do_something():
    global counter
    counter += 1

do_something()
do_something()
do_something()

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

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

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

class NotifyingCounter:
    def __init__(self):
        self.value = 0

    def inc(self):
        self.value += 1
        print(f"counter: {self.value}")

counter = NotifyingCounter()

def do_something():
    counter.inc()

do_something()
do_something()
do_something()

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

אנשים מתים

08/08/2023

הספר The Art Of Deception היה אחד הטובים והמשפיעים שקראתי. בספר קווין מיטניק מתאר דרך דיאלוגים מפורטים ואמינים איך אנחנו תמיד החוליה החלשה באבטחת מידע. הספר לימד אותי חשדנות בריאה ואת הערך של פרטיות.

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

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

טיפ HTML: גם כפתור הוא קלט

07/08/2023

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

אבל מה עושים אם יש מספר כפתורים שכל אחד מהם צריך לגרום לפעולה אחרת?

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

<form method="POST" action="/news">
  <button  name="like" value="like-value" >Like</button>
  <button  name="dislike" value="dislike-value" >Dislike</button>
</form>

לחיצה על כל אחד מהכפתורים תשלח לשרת בקשת POST לנתיב /news. בקוד השרת אני יכול להסתכל על הפרמטרים שהגיעו כדי להבין איזה כפתור נלחץ: לחיצה על כפתור like תשלח פרמטר בשם like עם הערך like-value. לחיצה על כפתור dislike תשלח פרמטר בשם dislike עם הערך dislike-value.

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

let likes = 0;

router.post('/news', function(req, res, next) {
  console.log(req.body);

  const {like, dislike} = req.body;

  if (like) {
    likes += 1;
  } else if (dislike) {
    likes -= 1;
  }
  res.render('index', { title: 'Express POST /news', likes});
});

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

הפרויקט שלא פורסם

06/08/2023

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

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

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

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