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

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

איך להפעיל Selenium עם דפדפן בלי GUI על Github Actions

04/10/2020

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

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

המשך קריאה

העולם לא צריך עוד Pull Request

03/10/2020

אוקטובר מגיע וכל שנה מחדש אירוע Hacktoberfest של Digital Ocean מעורר תגובות דומות: מתחזקים של פרויקטי קוד פתוח לא מבינים למה פתאום הם מקבלים PR-ים מיותרים מאנשים שלא מבינים כלום בפרויקט שלהם.

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

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

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

פרקטיקה, תיאוריה ופאניקה

02/10/2020

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

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

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

איפה כונן D?

01/10/2020

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

כך כשאנחנו עוברים מ Windows ל Linux ברור שנצטרך להתרגל לכתוב ls במקום dir כל פעם שרוצים להציג את רשימת הקבצים. אבל הרבה יותר מוזר יהיה הרגע שנשאל "איך מגיעים לכונן D". ההבנה שכונן הוא בעצם תיקייה ושאנחנו צריכים לעשות פעולת mount כדי שנוכל לגשת לכונן הזה היא יותר ממה שרציתי לדעת.

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

מה בכל זאת כן עובד ב Zoom

30/09/2020

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

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

המשך קריאה

מילים שקשה להגיד

28/09/2020

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

לכל פונקציה ב JavaScript יש מאפיין משם length שמחזיר כמה פרמטרים הפונקציה מצפה לקבל. ב Python קצת יותר קשה לגלות את זה אבל עדיין אפשרי עם חבילת inspect, ב Ruby יש לכל פונקציה מאפיין בשם arity שמחזיר את הערך וב Clojure אנחנו צריכים לקפוץ שמיניות באוויר בשביל למצוא אותו.

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

a = { a: 10, c: 30 }
b = { a: 5, b: 20 }
res = a.merge(b) {|k, v1, v2| v1 + v2 }

# now res is: { a: 15, b: 20, c: 30 }

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

a = { 'a': 10, 'c': 30 }
b = { 'b': 20 }
res = dict(**a, **b)
# now res is: { 'a': 10, 'b': 20, 'c': 30 }

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

ויש עוד מאות כעסים קטנים כאלה בכל מעבר בין שפות, וזה בסדר. שווה לזכור שבכל מעבר כזה בין שפות מאוד קל למצוא חבילות שסוגרות את הפער. גם אם ל JavaScript אין את random.randint של פייתון, אנחנו עדיין יכולים לקבל פונקציונאליות זהה מ lodash; חבילת mergedict של פייתון מאפשרת לשלב בצורה אלגנטית בין מילונים ולקבוע מה לעשות עם ערכים מתנגשים בקלות ממש כמו ב Ruby.

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

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

לעצור את הטובים

27/09/2020

אחת הטכניקות הטפשיות (אך מפתות) של אבטחת מידע היא לבנות מנגנונים שיהיה קל מאוד לעקוף אותם ויעצרו רק את האנשים שאין להם כח להתאמץ יותר מדי. השבוע למדתי במקרה על דרך קלה לזהות גולשים שמשתמשים בסלניום כדי להפעיל בצורה אוטומטית אתרים: מסתבר שסלניום או יותר נכון ה chromedriver שהוא מפעיל מוסיף Cache לכל דף - אני לא לגמרי בטוח מה הוא שומר ב Cache הזה - אבל את ה Cache הוא שומר במפתח עם שם מוזר שמתחיל ב $cdc. לסקרנים שביניכם הנה הקוד שיוצר את ה cache בכרומדרייבר:

function getPageCache(opt_doc, opt_w3c) {
  var doc = opt_doc || document;
  var w3c = opt_w3c || false;
  // |key| is a long random string, unlikely to conflict with anything else.
  var key = '$cdc_asdjflasutopfhvcZLmcfl_';
  if (w3c) {
    if (!(key in doc))
      doc[key] = new CacheWithUUID();
    return doc[key];
  } else {
    if (!(key in doc))
      doc[key] = new Cache();
    return doc[key];
  }
}

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

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

זום

26/09/2020

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

המשך קריאה

חידת פנדס: לאן נעלמו הערכים?

25/09/2020

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

בגלל שכל אחד שילם מחיר קצת שונה היה נוח לחלק את המחירים לקבוצות עם pd.cut וכך הגעתי לקוד הבא:

import pandas as pd

url = 'https://raw.githubusercontent.com/guipsamora/pandas_exercises/master/07_Visualization/Titanic_Desaster/train.csv'
titanic = pd.read_csv(url)

s=pd.cut(titanic['Fare'], bins=[0, 10, 20, 50, 100, 1000], right=True)
data = s.value_counts()

print(data)

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

(0, 10]        321
(20, 50]       216
(10, 20]       179
(50, 100]      107
(100, 1000]     53
Name: Fare, dtype: int64

והנה מאפייני הטיטאניק:

In: titanic['Fare'].describe()
Out:

count    891.000000
mean      32.204208
std       49.693429
min        0.000000
25%        7.910400
50%       14.454200
75%       31.000000
max      512.329200
Name: Fare, dtype: float64

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

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