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

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

בואו נכתוב DSL בפייתון

26/05/2018

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

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

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

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

add x 20
add y 50
add z x
add z y
sub z 2
print z

התוכנית כוללת 3 פקודות: add, sub ו print. כל פקודה מקבלת שם של אוגר כפרמטר ראשון וערך כפרמטר שני. הפקודה add מוסיפה את הערך לאוגר, הפקודה sub מחסירה את הערך מהאוגר ו print מדפיסה את ערך האוגר. בסיום תוכנית הדוגמא נקבל את הפלט 68.

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

הקוד עבור המחלקות לפקודות השונות:


class Cmd:
    def __init__(self, args):
        self.args = args

    def get_int_or_value(self, name, mem):
        if name in string.ascii_letters:
            return mem[name]
        else:
            return int(name)

class CmdAdd(Cmd):
    def run(self, mem):
        reg, arg = self.args
        mem[reg] += self.get_int_or_value(arg, mem)

class CmdPrint(Cmd):
    def run(self, mem):
        arg = self.args[0]
        print(self.get_int_or_value(arg, mem))

class CmdSub(Cmd):
    def run(self, mem):
        reg, arg = self.args
        mem[reg] -= self.get_int_or_value(arg, mem)

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

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

COMMANDS = {
        'add': CmdAdd,
        'print': CmdPrint,
        'sub': CmdSub,
        }

ונרצה לייצר מחלקה של Parser שתרוץ על הקלט ותייצר את האוביקטים המתאימים לטיפול בכל שורה:

class MyParser:
    def __init__(self, fname):
        self.fname = fname
        self.memory = defaultdict(int)

    def parse(self, line):
        cmd_name, *args = line.split()
        return COMMANDS[cmd_name](args)

    def run(self):
        with open(self.fname) as f:
            for line in f:
                cmd = self.parse(line.strip())
                cmd.run(self.memory)

p = MyParser('dsldemo.txt')
p.run()

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

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

  2. קל מאוד לשנות את מבנה הקלט באמצעות שינוי הקוד ב MyParser. מוזמנים כתרגיל לנסות לעבור למבנה קלט כזה:

x += 20
y += 50
z += x
z += y
z -= 2
z

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

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

אם זה עובד אל תגעו?

25/05/2018

מתי בפעם האחרונה התקנתם עדכון קושחה ל Router שלכם? או החלפתם Router רק כי הוא היה ישן מדי (אבל עדיין עבד?).

מחקר של קספרסקי שפורסם היום חושף דלת אחורית בנתבים ישנים של D-Link. הנתב נקרא DIR-620 והפירצה די חמורה: היא מאפשרת לכל אחד ברשת להשתלט על הנתב שלכם באמצעות שם משתמש וסיסמא סודיים ששמורים בקושחה של הנתב ולא ניתנים לשינוי דרך הממשק.

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

קישור לפרסום המקורי: https://www.bleepingcomputer.com/news/security/backdoor-account-found-in-d-link-dir-620-routers/

שני שימושים לשתי כוכביות ב Python 3

24/05/2018

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

>>> 2 ** 3
8

הטריק הזה עובד אגב גם ב Ruby, perl ואפילו JavaScript.

אבל השימוש השני אפילו יותר מעניין ולוקח השראה מהבחירה של Shells רבים להשתמש בשתי כוכביות כדי לציין Glob מקונן. לדוגמא ב zsh אפשר לכתוב את השורה הבאה כדי להציג את כל הקבצים בסיומת py בתיקיה הנוכחית ובכל תתי התיקיות:

$ ls **/*.py

גם ב Bash אפשר להפעיל את הפיצ'ר הזה באמצעות פקודת set באופן הבא:

$ set -e globstar
$ ls **/*.py

ופייתון 3 כולל גם הוא תמיכה ב Glob מקונן. התוכנית הבאה תמצא לכם את כל קבצי ה py בתיקיה הנוכחית ובכל תתי התיקיות שלה:

from pathlib import Path

for path in Path('.').glob('**/*.py'):
    print(path)

מכירים עוד שימושים לשתי כוכביות בפייתון? ספרו עליהם בתגובות.

רמאויות של תכנות מרובה תהליכים בפייתון

23/05/2018

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

נתבונן בקוד הבא:

class Counter:
    def __init__(self, total):
        self.count = total

    def take(self, n=1):
        val = self.count
        self.count = val - n
        print(f"Remaining: {self.count}")

יש לכם רעיון איך לגרום למונה לטעות בספירה?

המשך קריאה

אתגרים בפיתוח צד-לקוח ב 2018

22/05/2018

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

המשך קריאה

חכו שניה עם הקוד הגנרי

21/05/2018

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

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

בדיוק בגלל זה אני אוהב קודם לכתוב את הקוד בגירסא הלא גנרית שלו. הנה Person ו Film לצורך הדוגמא:

class Person extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: false };
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  componentDidMount() {
    this.fetchData();
  }

  fetchData() {    
    this.setState({ loading: true });

    const { id } = this.props;
    $.get(`https://swapi.co/api/people/${id}`).then(res => {
      this.setState({
        data: res,
        loading: false,
      })
    });
  }

  renderLoading() {
    return <p>Loading data for id: {this.props.id}</p>
  }

  renderData() {
    const { data } = this.state;
    if (!data) { return false; }

    return (
      <div>
        <p><b>Name:</b> {data.name}</p>
        <p><b>Gender:</b> {data.gender}</p>
      </div>
    )
  }

  render() {
    if (this.state.loading) {
      return this.renderLoading();
    } else {
      return this.renderData();
    }
  }
}

class Film extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: false };
  }

  componentDidUpdate(prevProps, prevState) {
    if (prevProps.id !== this.props.id) {
      this.fetchData();
    }
  }

  componentDidMount() {
    this.fetchData();
  }

  fetchData() {    
    this.setState({ loading: true });

    const { id } = this.props;
    $.get(`https://swapi.co/api/films/${id}`).then(res => {
      this.setState({
        data: res,
        loading: false,
      })
    });
  }

  renderLoading() {
    return <p>Loading data for id: {this.props.id}</p>
  }

  renderData() {
    const { data } = this.state;
    if (!data) { return false; }

    return (
      <div>
        <p><b>Title:</b> {data.title}</p>
        <p><b>Director:</b> {data.director}</p>
      </div>
    )
  }

  render() {
    if (this.state.loading) {
      return this.renderLoading();
    } else {
      return this.renderData();
    }
  }
}

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

מאחר ורוב הקוד המשותף נמצא בפונקציות מחזור החיים הצעד הכי הגיוני הוא להשתמש ב Higer Order Component. כך יראה הקוד של שני הפקדים לאחר העדכון:

@swapiClient('https://swapi.co/api/people')
class Person extends React.Component {
  render() {
    const { data } = this.props;
    if (!data) { return false; }

    return (
      <div>
        <p><b>Name:</b> {data.name}</p>
        <p><b>Gender:</b> {data.gender}</p>
      </div>
    )
  }
}

@swapiClient('https://swapi.co/api/films')
class Film extends React.Component {
  render() {
    const { data } = this.props;
    if (!data) { return false; }

    return (
      <div>
        <p><b>Title:</b> {data.title}</p>
        <p><b>Director:</b> {data.director}</p>
      </div>
    )
  } 
}

ברגע שזיהינו את החלקים המשותפים קל הרבה יותר להוציא החוצה את המשותף למקום אחד ולהשאיר רק את השונה בפקדים עצמם. זה אגב הקוד עבור ה Higher Order Component לקוד המשותף:

function swapiClient(baseUrl) {
  return function(Component) {
    return class SwapiClient extends React.Component {
      constructor(props) {
        super(props);
        this.state = { loading: false };
      }

      componentDidUpdate(prevProps, prevState) {
        if (prevProps.id !== this.props.id) {
          this.fetchData();
        }
      }

      componentDidMount() {
        this.fetchData();
      }

      fetchData() {    
        this.setState({ loading: true });

        const { id } = this.props;
        $.get(`${baseUrl}/${id}`).then(res => {
          this.setState({
            data: res,
            loading: false,
          })
        });
      }

      renderLoading() {
        return <p>Loading data for id: {this.props.id}</p>
      }

      render() {
        const { data } = this.state;

        if (this.state.loading) {
          return this.renderLoading();
        } else {
          return <Component data={data} />
        }
      }
    }
  }
}

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

כוכב נולד

20/05/2018

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

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

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

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

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

מה ההבדל בין מפתח Full Stack למפתח Front End?

19/05/2018

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

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

קוד צד-שרת זה החלק שרץ על השרת (בדרך כלל PHP וגם Java וכו׳), וקוד צד-לקוח זה החלק שרץ על המחשב של הגולשים (HTML / CSS / JavaScript). בעבר היה תפקיד שנקרא מתכנת Web (או וובמסטר) שכתב גם PHP וגם JavaScript.

בשנים האחרונות, פחות או יותר החל מ 2012, עולם ה JavaScript מאוד התקדם ומתכנתים שהתמקצעו בזה הצליחו להגיע להישגים מאוד טובים בפיתוח - ולבנות ממשקים הרבה יותר יפים ומהירים ממה שהיה אפשר בעבר. לכן מתכנתים רבים בחרו לעזוב את ה PHP לגמרי ולהתמקד רק בפיתוח JavaScript/HTML/CSS. היום קוראים להם Front End Developers. בשנים מאז 2012 תפקיד Front End הפך מאוד פופולרי בחברות ובעצם היום אין כמעט מתכנתי Web או וובמאסטרים אלא רק מתכנתי Front End.

לגבי Full Stack זה סיפור אחר אבל קשור- יחד עם העליה בפופולריות של JavaScript מתכנת בשם ריאן דל עשה עבודה שאיפשרה להריץ קוד JavaScript גם בצד השרת במקום PHP. הוא התבסס על מנוע הרצת JS שקיים בדפדפן כרום ונקרא V8. הסביבה שהוא בנה נקראת node.js. עד כמה שאני זוכר ריאן כבר לא מעורב בפיתוח node.js אבל הכלי עדיין מאוד פופולרי ויצר תנועה חדשה של מפתחי JavaScript שחזרו לכתוב את צד השרת אבל הפעם לא ב PHP אלא ב node.js, כלומר נשארו ב JavaScript. מאחר ולהרים סביבת node.js זה יותר מורכב מלהרים סביבת PHP מתכנתים אלו גם למדו על ניהול שרת ובניית בסיס נתונים. לאט לאט נוצרה קבוצה גדולה של מתכנתים חלקם הגיעו מ Front End ובעזרת node.js כתבו גם צד-שרת, וחלקם עשו מהלך הפוך והיו מתכנתי צד-שרת שלמדו Front End אבל המשיכו לכתוב את שתי הטכנולוגיות. היום המונח מפתח Full Stack מתיחס למתכנתים שמבצעים עבודת פיתוח מוצר מקצה לקצה, ולא קשור דוקא ל node.js. מפתחי Full Stack רבים ישתמשו ב Go, Python או Ruby לפתח את קוד צד השרת.

בגדול בדרך כלל מתכנתי Full Stack פחות מקצועיים בפיתוח צד-לקוח ממתכנתי Front-End אבל יש להם אוריינטציה יותר טובה כשמדובר בפיתוח מערכת שלמה.

ואחרי כל זה שווה גם לשאול - מה כדאי ללמוד?

התשובה שלי כמו בהרבה מקרים היא שזה תלוי:

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

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

  3. אם אתם מחפשים לבנות מוצר משלכם אז כמובן שתצטרכו Full Stack (או לעבוד עם חבר שמכיר את עולם ה Back End).

געגועים ל perl

18/05/2018

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

לפני שנגיע למחשב בואו נסכים ש 17% מע"מ ממחיר של 300 ש"ח נותן 51 ש"ח. במספרים זה ייצא 300 כפול 17 חלקי 100, האפסים יורדים ונשארנו עם 3 כפול 17 כלומר 51.

אבל מה שברור לכם לא תמיד ברור למחשב. כך פייתון:

>>> 300 * 0.17
51.00000000000001

ורובי:

2.3.1 :001 > 300 * 0.17
 => 51.00000000000001 

ואפילו JavaScript:

> 300 * 0.17
51.00000000000001

ורק פרל הישנה היתה מספיק חכמה לתת תשובה נכונה:

$ 300 * 0.17
51

הסיבה קשורה לאופן שבו השפות האלה מייצגות מספרים ואפשר לקרוא על הסברים לרוב על התופעה בקישור: http://floating-point-gui.de

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

לאט אבל בטוח

17/05/2018

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

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

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

לאט אבל בטוח עובד, אבל לאט.