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

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

טיפ Bash - בואו נבטל תיקיות אמצע עם find

15/09/2019

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

קיבלתי עץ תיקיות שכלל הרבה תיקיות אמצע ריקות, דמיינו משהו שנראה כך:

.
└── a
    └── b
        ├── c
        │   └── d
        │       └── e
        │           ├── one.txt
        │           ├── three.txt
        │           └── two.txt
        ├── f
        │   └── g
        │       ├── five.txt
        │       ├── four.txt
        │       └── six.txt
        └── y
            └── seven.txt

ורציתי להיפטר מתיקיות האמצע ולהגיע למבנה כזה:

.
└── a
    ├── c
    │   ├── one.txt
    │   ├── three.txt
    │   └── two.txt
    ├── f
    │   ├── five.txt
    │   ├── four.txt
    │   └── six.txt
    └── y
        └── seven.txt

כלומר ביטול התיקיות b, d, e ו g.

דרך קלה לבטל את תיקיות האמצע תהיה להשתמש ב find:

  1. נמצא את התיקיה העמוקה ביותר שיש להורה שלה רק ילד אחד (כלומר שהיא בן יחיד בעץ התיקיות).

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

  3. נחזור על התהליך עד שנסיים לרוץ על כל עץ התיקיות.

המתגים הרלוונטים של find הם לכן:

  1. המתג -d שיגרום ל find להתחיל מהתיקיה העמוקה ביותר ולרוץ כלפי חוץ.

  2. המתג -type d שיגרום ל find לרוץ רק על תיקיות.

  3. המתג -exec שיאפשר לנו להריץ קוד במהלך החיפוש.

ופקודה ה find שחיברה את הכל נראתה כך:

$ find -d . -type d -exec bash -c '[[ $(ls {}/.. | wc -l | tr -d " ") == 1 ]] && (mv {}/* {}/..; rmdir {})' \;

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

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

מי קורא את הקוד שלך?

14/09/2019

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

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

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

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

  2. כתיבת פרויקט קוד פתוח שלכם.

  3. השתתפות בקורס ושיתוף הפיתרונות עם המרצה והמשתתפים האחרים.

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

  5. מענה על שאלות ב Stack Overflow או פורומים אחרים.

  6. כתיבת פוסט בבלוג (בהנחה שיש קוראים).

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

טיפ פייתון: הגנה על פונקציות מקריאה דינמית

13/09/2019

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

אני מזכיר שהמחלקה שכתבתי אתמול נראתה כך:

class Counters:
    def __init__(self):
        self.counters = defaultdict(int)

    def show(self, reg):
        print(self.counters[reg])

    def inc(self, reg):
        self.counters[reg] += 1

והשורה שהפעילה פונקציה אחת ממנה בצורה דינמית נראתה כך:

        counters.__getattribute__(cmd)(*args)

נראה שלוש דרכים להגנה על הקוד.

המשך קריאה

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

12/09/2019

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

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

> show x
0

> inc x
> show x
1

> inc x
> inc x
> show x 
3

> inc y
> show y
1

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

from collections import defaultdict

counters = defaultdict(int)

while True:
    try:
        next_line = input('> ')
        cmd, arg  = next_line.split()
        if cmd == "show":
            print(counters[arg])
        elif cmd == "inc":
            counters[arg] += 1
    except Exception:
        print("Invalid command. Try again")

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

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

from collections import defaultdict 

class Counters:
    def __init__(self):
        self.counters = defaultdict(int)

    def show(self, reg):
        print(self.counters[reg])

    def inc(self, reg):
        self.counters[reg] += 1

counters = Counters()
while True:
    try:
        next_line = input('> ')
        cmd, *args  = next_line.split()
        counters.__getattribute__(cmd)(*args)
    except AttributeError:
        print("Invalid command, try again")
    except TypeError:
        print("Missing required arguments to command")
    except EOFError:
        print("Bye bye")
        break

במבנה כזה יש מספר יתרונות:

  1. קל להוסיף התנהגות - פשוט מוסיפים עוד פונקציה ל Counters

  2. קל לתת טיפול מתאים לבעיות שונות, בזכות השימוש בסוגי Exceptions שונים.

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

באגים של אחרים

11/09/2019

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

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

{
  "name": "myapp",
  "version": "1.1.0",
  "dependencies": {
    "actioncable": "^5.1.1",
    "babel-core": "6.26.3",
    "babel-eslint": "^10.0.1",
    "babel-loader": "^8.0.5",
    "babel-plugin-react-transform": "^3.0.0",
    "babel-polyfill": "^6.8.0",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-0": "^6.5.0",
    "babel-runtime": "^6.6.1",
    "body-parser": "^1.15.1",
    "classnames": "^2.2.5",
    "create-react-class": "15.6.3",
    "css-loader": "^2.1.1",
    "es5-shim": "^4.5.8",
    "es6-promise": "^4.1.0",
    "eslint-plugin-import": "^2.3.0",
    "eslint-plugin-jsx-a11y": "^6.2.1",
    "exports-loader": "^0.7.0",
    "expose-loader": "^0.7.1",
    "github-api": "^3.0.0",
    "history": "4.9.0",
    "immutable": "^4.0.0-rc.12",
    "imports-loader": "^0.8.0",
    "isomorphic-fetch": "^2.2.1",
    "jquery": "^3.2.1",
    "jquery-ui": "^1.10.5",
    "jquery-ujs": "^1.2.1",
    "loader-utils": "^1.1.0",
    "mirror-creator": "1.1.0",
    "nighthawk": "2.2.0",
    "null-loader": "^1.0.0",
    "prop-types": "^15.7.2",
    "query-string": "^6.5.0",
    "react": "^16.8.6",
    "react-addons-linked-state-mixin": "^15.0.2",
    "react-bootstrap": "^1.0.0-beta.8",
    "react-burger-menu": "^2.0.2",
    "react-dom": "^16.8.6",
    "react-dropzone": "^10.1.4",
    "react-immutable-proptypes": "^2.1.0",
    "react-on-rails": "11.2.2",
    "react-quill": "^1.0.0-rc.2",
    "react-redux": "^7.0.3",
    "react-scroll": "^1.0.16",
    "react-sticky": "^6.0.1",
    "react-transition-group": "^4.0.0",
    "redux": "^4.0.1",
    "redux-batched-subscribe": "^0.1.6",
    "redux-promise": "^0.6.0",
    "redux-thunk": "^2.1.0",
    "router": "^1.1.4",
    "scriptjs": "^2.5.8",
    "strftime": "^0.10.0",
    "underscore": "^1.8.3",
    "url-parse": "^1.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.6.0",
    "@babel/plugin-proposal-class-properties": "^7.5.5",
    "@babel/preset-env": "^7.6.0",
    "@babel/preset-react": "^7.0.0",
    "bootstrap-sass": "^3.3.6",
    "bootstrap-sass-loader": "^1.0.10",
    "eslint": "^5.16.0",
    "eslint-config-airbnb": "17.1.0",
    "eslint-plugin-react": "^7.0.1",
    "esprima-fb": "^15001.1001.0-dev-harmony-fb",
    "express": "^4.13.4",
    "file-loader": "^3.0.1",
    "jade": "^1.11.0",
    "jscs": "^3.0.3",
    "node-sass": "^4.5.3",
    "react-transform-hmr": "^1.0.4",
    "sass-loader": "^7.1.0",
    "speed-measure-webpack-plugin": "^1.3.1",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^4.39.3",
    "webpack-bundle-analyzer": "^3.4.1",
    "webpack-cli": "^3.3.8"
  }
}

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

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

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

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

שברת שילמת

10/09/2019

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

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

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

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

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

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

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

"שברת שילמת" יכול להיות אזהרה, סוג של "אל תעז להתקרב", אבל הוא יכול להיות גם הזמנה לחקור, לשחק וכן גם לשבור:

שברת -> שילמת -> תיקנת -> למדת.

שלוש טעויות שכבר לא תעשו ב Python אחרי שתוסיפו Type Hints

09/09/2019

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

המשך קריאה

בואו נמצא פודקסטים מעניינים עם פייתון

08/09/2019

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

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

המשך קריאה

על ההבדל בין ״לא אופנתי״ ל״לא חשוב״

07/09/2019

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

ובכל זאת מתכנתים כותבים SQL כל יום (למרות שכולם מדברים על NoSQL או על ORM).

ובכל זאת רוב ה APIs שתעבדו מולם עדיין בנויים על REST (למרות שכולם מדברים על GraphQL).

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

ולמרות שרוב קוד ה JavaScript החדש שנכתב היום משתמש בכתיב Object Oriented ובכל היכולות החדשות של השפה, כל מתכנתי ה JavaScript הרציניים שאני מכיר יודעים איך לעבוד עם Prototype, יודעים מה זה bind ו apply וישמחו לספר לכם למה אף פעם לא כדאי להשתמש ב document.write.

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

ללכת עד הקצה

06/09/2019

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

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

$ git checkout -b my-new-feature

ואז לעבוד, לעשות את הקומיטים ובסוף למזג.

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

  1. לנסות לשנות את הקוד לפני שיצרתי את הענף (ואז לראות איך ליצור את הענף עם הקוד שכתבתי).

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

  3. לנסות דרכים אחרות ליצור את הענף (למשל עם switch או עם branch), ולקרוא בתיעוד מה ההבדל בין הדרכים.

  4. לנסות ליצור ענף בשם לא נכון ואז לתקן לו את השם.

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

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