ToCode
Ir al canal en Telegram
1 419
Suscriptores
-124 horas
-17 días
-130 días
Archivo de publicaciones
1 419
# חידת Strict Mode ב JavaScript
אני אוהב איך ש JavaScript לוקחת רעיונות ממקומות אחרים, שוברת ומעקמת אותם ובסוף מגישה מרק פושר ודלוח. טוב בקיצור די עם האוכל בואו נדבר רגע על קוד.
הקוד הבא מדפיס 9, וזה הגיוני אבל קצת מוזר:
let x = 09;
console.log(x);
מוזר כי ב Python קוד דומה זורק שגיאה:
# Error: SyntaxError: leading zeros in decimal integer literals are not permitted
x = 09
print(x)
ב Ruby הקוד זורק שגיאה:
# SyntaxError ((irb):1: Invalid octal digit)
x = 09
ב Clojure הקוד זורק שגיאה:
;; Invalid number: 09
(let [x 09] (print x))
והאמת שרק Scheme הסכימה לשתף איתי פעולה ולהדפיס את המספר 9 בדומה ל JavaScript:
(display 09)
אבל, ופה JavaScript מתחילה להיות מוזרה, נסו לחשוב מה קורה כשמחליפים את ה-9 ל-10:
let x = 010;
console.log(x);
ולחידה - מה קורה שם? למה זה קורה (רמז בהודעות השגיאה של כל השפות האחרות)? ואיך Strict Mode יכול לעזור לי להיזהר ממצבים כאלה?1 419
# היום למדתי: נקודת עצירה עם תנאי ב Chrome
אני לא מאמין שלקח לי כל כך הרבה שנים (וטעות אחת בלחיצה על העכבר) בשביל לגלות את זה, אבל הגילוי כל כך מלהיב שהוא מצדיק פוסט משלו, גם אם קצר.
עד עכשיו כל פעם שהיה לי קוד שנראה כך:
for (let i=99; i > 0; i--) {
consoe.log(`${i} bottles of beer on the wall`);
}
ורציתי לעצור למשל כש i היה שווה ל-10, הוספתי את התנאי הבא לקוד (באמת!):
for (let i=99; i > 0; i--) {
consoe.log(`${i} bottles of beer on the wall`);
if (i == 10) debugger;
}
והנה, לגמרי במקרה הגעתי היום לשים נקודת עצירה בכרום, אבל במקום ללחוץ על הכפתור השמאלי של העכבר כשהצבעתי על מספר השורה בחלון ה Debugger, נלחץ הכפתור הימני. מסתבר שבמצב כזה כרום פותח תפריט כפתור ימני ואחת האופציות בתפריט נקראת Conditional Breakpoint. לוחצים על הפריט, כותבים את התנאי (i == 10 במקרה שלנו) ו... Voila, לא צריך לכתוב קוד JavaScript בשביל לעצור רק כשתנאי מסוים מתקיים.
אם לא הכרתם נסו את זה. כן, עכשיו, לא צריך לחכות. זה עובד וזה ישנה לכם את החיים.1 419
# טכניקה פשוטה לתוויות צפות ב CSS
אחד הטריקים השימושיים באתרי מובייל הוא להפוך את ה Placeholder של תיבת קלט ל Label של אותה תיבה. במילים אחרות במקום לכתוב תווית Email ואחריה תיבת טקסט עבור אימייל עם placeholder שמייצג קלט אפשרי כמו זה:
<label>
Email:
<input type="email" placeholder="demo@gmail.com" />
</label>
אנחנו רואים תיבות טקסט שנראות כך:
<input type="email" placeholder="Email Address" />
היתרון עבור מעצבים שאוהבים את הטריק הוא שזה חוסך מקום. אתה לא צריך לשים גם תווית טקסט וגם תיבת קלט, ויכול להסתפק רק בתיבת הקלט. מבחינת המשתמשים החיסרון הראשון הוא ש Placeholder כתוב בדרך כלל בגופן בהיר יותר מהגופן הרגיל של תווית טקסט, אבל החיסרון הגדול יותר הוא שה Placeholder נעלם כשמתחילים לכתוב טקסט בתיבה.
פיתרון אפשרי אחד למצב נקרא Floating Labels או תוויות צפות. זה עובד ככה:
1. במצב רגיל אנחנו מסתירים את התווית כדי שמשתמש יראה רק את ה Placeholder בתוך התיבה.
2. כשתיבת הטקסט נמצאת בפוקוס אנחנו עוברים להציג את התווית, אבל מקטינים את התיבה כך שהמקום יחולק בין תווית הטקסט (שנמצאת למעלה) לטקסט עצמו שמשתמש כותב בתוך התיבה. אפשר להשתמש בפלקסבוקס כדי לסדר את שני הרכיבים.
קוד? ברור. הנה ה HTML:
<label>
<input type="text" placeholder="name" />
<span class="label-text">Name</span>
</label>
וזה ה CSS:
label {
position: relative;
height: 50px;
display: flex;
flex-direction: column-reverse;
}
.label-text {
left:-10000px;
position:absolute;
top:auto;
width:1px;
height:1px;
overflow: hidden;
background: orange;
color: white;
}
label input {
font-size: 24px;
height: 100%;
border: 1px solid orange;
box-sizing: border-box;
}
input:focus::placeholder {
color: transparent;
}
input:focus + .label-text {
position: relative;
width: auto;
height: auto;
left: 0;
}
input:focus {
font-size: 18px;
height: auto;
}
שימו לב שבגלל מגבלות של CSS אני צריך לכתוב את ה label-text אחרי ה input (כדי שאוכל להשתמש בסימן הפלוס לעצב את ה label-text).
דבר נוסף ששווה לראות הוא השימוש ב position כדי להסתיר את הטקסט, אבל להשאיר אותו קריא עבור Screen Readers. בצורה כזאת כשמגיע קורא מסך לקרוא את הדף הוא עדיין יוכל להקריא את טקסט התווית (זה פותר לנו בעיה שהיתה בשימוש רגיל ב Placeholder לצורך הגדרת תווית לתיבה). המנגנון שולח את הטקסט אלף פיקסלים שמאלה אבל משאיר אותו בגודל פיקסל אחד, כי קוראי מסך מתעלמים מאלמנטים בגודל 0.
ויש גם קודפן בקישור: https://codepen.io/ynonp/pen/Vwvbrxm, או בהטמעה:
<iframe height="300" style="width: 100%;" scrolling="no" title="Vwvbrxm" src="https://codepen.io/ynonp/embed/Vwvbrxm?height=300&theme-id=20374&default-tab=css,result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
See the Pen <a href='https://codepen.io/ynonp/pen/Vwvbrxm'>Vwvbrxm</a> by Ynon Perek
(<a href='https://codepen.io/ynonp'>@ynonp</a>) on <a href='https://codepen.io'>CodePen</a>.
</iframe>1 419
# איך לבחור מפתחות Cache לאתר דינמי
אומרים שיש רק שתי בעיות קשות במדעי המחשב: איך לבחור שמות לדברים ואיך להחליט מתי צריך לנקות את ה Cache.
השבוע העליתי פיצ'ר של Server Side Rendering שדחף אותי לפתור לפחות לאתר טוקוד את הבעיה השניה ובפוסט היום אנסה לסכם את עיקרי הדברים.
## למה צריך לבחור מפתח Cache
באתר דינמי כל דף באתר נוצר בתגובה להגעה של גולש ספציפי שמגיע. מספר גולשים שמגיעים בדיוק לאותה כתובת לפעמים צריכים לקבל דף שנראה אחרת לגמרי. קחו את ynet לדוגמא: כל הזמן עולות כתבות חדשות, ולכן אי אפשר לשמור את דף הבית של ynet בזיכרון ולשלוח לכולם כל הזמן את אותו דף. כל פעם שגולש מגיע אנחנו צריכים לבדוק ב DB מהן הכתבות החדשות ביותר ולייצר דף עבור אותו הגולש.
כך גם בטוקוד - מאחר והבלוג מתעדכן לעתים קרובות, כל פעם שגולש מגיע לדף הראשי של הבלוג השרת בודק בבסיס הנתונים מה הפוסטים העדכניים ביותר ובונה דף בית חדש עבור אותה בקשה.
המעבר ל Server Sider Rendering אומר שכל בניה של דף הולכת להיות הרבה יותר מסובכת כי צריך להריץ את קוד ריאקט של האתר במלואו בצד השרת. אבל, קל לשים לב שהבלוג אומנם מתעדכן פעם ביום אבל גולשים מגיעים בתדירות הרבה יותר גבוהה, ולכן מספיק להריץ את קוד ריאקט הכבד עבור הגולש הראשון שמגיע בכל יום, לשמור את התוצאה ולשלוח אותה לשאר הגולשים שהגיעו באותו יום.
בשביל לממש את הלוגיקה הזאת בקוד העליתי שרת redis ששומר את כל דפי התוצאות שנשלחו לגולשים - והשאלה הקשה מהכותרת היא איך אפשר לדעת שגולש חדש שמגיע מבקש דף שכבר יש לנו?
מה שבטוח לא יעבוד זה להשתמש בכתובת העמוד. הכתובת
https://www.tocode.co.il/blog שמובילה לבלוג כאן תישאר זהה גם מחר, למרות שתוכן הדף יהיה שונה.
## מפתח עבור דף שמציג אוביקט יחיד מבסיס הנתונים
ה SSR מופעל על כל הדפים באתר. כל הדפים נבנים מתוך מידע ששמור בבסיס נתונים - וכל דף ניבנה מתוך אוביקטים אחרים בבסיס הנתונים.
דפים מסוימים באתר משתמשים בשורה בודדת מטבלא או בשורות בודדות ממספר טבלאות. לדוגמא דף של פוסט בודד באתר משתמש במידע מטבלת הפוסטים. שני המזהים החשובים לכל שורה בהקשר של Cache הם: id ו updated_at. לכן מפתח Cache טוב לנתיב של פוסט יהיה שם הנתיב, blog_show, ואחריו שני השדות id ו updated_at של הפוסט הספציפי שעכשיו מסתכלים עליו. אם במקרה אשנה בבסיס הנתונים פוסט אחרי שיצרתי אותו (לדוגמא בשביל לתקן שגיאת כתיב), שדה updated_at של השורה ישתנה הגולש הבא יקבל מפתח Cache חדש, הדף ייבנה מחדש והגולש יקבל את הדף הנכון. אבל כל עוד שדות ה id ו updated_at לא משתנים, כל גולש נוסף שמגיע יקבל בדיוק את אותו דף שכבר נוצר ושמור ב redis.
## מפתח עבור דף שמציג אוסף אוביקטים
אפשר להרחיב את הפיתרון גם לדפים שתפקידם להציג מספר אוביקטים, כלומר מספר שורות. לדוגמא הדף הראשי של הבלוג מציג 10 פוסטים אחרונים שפורסמו, ולכן אפשר לקחת את כל שדות ה updated_at ו id של כל עשרת הפוסטים, לשרשר את כולם יחד ולקבל מזהה ייחודי של הדף.
ברגע שנוצר פוסט חדש רשימת התוצאות משתנה (כי לפוסט החדש יש id חדש) ולכן ייווצר מחדש עמוד הבלוג הראשי עם הרשימה החדשה. אבל, כל עוד 10 הפוסטים הראשונים לא משתנים, נשלח את אותו דף שכבר שמור ברדיס לכל גולש נוסף שמגיע.
הרכיב האחרון של המנגנון הוא מחיקה אוטומטית של מפתחות Cache ישנים. נשים לב שעם המנגנון שתיארתי, מפתחות Cache מסוימים מפסיקים להיות רלוונטים (למשל דף הבלוג הראשי מאתמול כבר לא ישמש לשליפות עתידיות). לדפים שונים יש אורך חיים שונה, ופה צריך להפעיל היגיון בריא והיכרות עם תדירות העדכונים באתר כדי לדעת לכמה זמן כדאי לשמור כל דף.
הקושי המרכזי ביישום Cache הוא העובדה שצריך לזכור להתיחס לכל השורות שמשפיעות על דף התוצאה בעת יצירת ה Cache Key. בימים הקרובים נגלה אם הצלחתי לזכור את כולם או אם חלק מהדפים יפסיקו להתעדכן ונצטרך להעלות תיקון.1 419
# זיהוי פנים ב Python עם OpenCV
זיהוי פנים בתמונה זו בעיה פתורה ומה שכיף בבעיות פתורות זה שאנחנו יכולים ממש בקלות לשלב את הפיתרון גם ביישומים גדולים יותר שלנו. אם אתם רוצים לדעת לעומק איך עושים את זה תצטרכו לקרוא על אלגוריתמים ותמונות והמון דברים שאני לא מספיק מבין בהם, אבל אם אתם פשוט צריכים למצוא פרצוף בתמונה מספיק להשתמש במנגנון הקיים של OpenCV. הנה הקוד שעושה את זה.
## התקנת OpenCV
התקנת ספריית OpenCV ב Python היא די פשוטה ובסך הכל דורשת הרצת שתי פקודות pip:
python -m pip install numpy opencv-python
בשביל לבדוק שההתקנה עברה חלק אפשר להפעיל את הסקריפט הבא:
import cv2
print(cv2.__version__)
אני קיבלתי את 4.2.0 אבל אני חושב שכל גירסא מעל 4 תספיק.
הדבר השני שנצטרך הוא קובץ המידע עבור ה Classifier. זה בעצם הקובץ שמספר ל OpenCV מה זה פרצוף. הוא הוכן בתהליך של סריקת המון תמונות וסימון ידני של מאפיינים בתוכן. הקובץ מגיע בפורמט xml ואפשר למצוא מספר אפשרויות לקבצי מידע כאלה במאגר של OpenCV עצמו בגיטהאב.
בפוסט זה אשתמש בקובץ haarcascade_frontalface_default.xml שכולל מידע על פנים של אנשים. אפשר להוריד את הקובץ מהקישור הזה.
## הסקריפט
קובץ ה Python שמזהה פרצופים כולל בסך הכל פחות מ-30 שורות כי את כל העבודה כבר עשו בשבילי ב OpenCV. בגדול אנחנו:
1. הופכים את התמונה לשחור-לבן כדי שיהיה ל OpenCV יותר קל לעבוד איתה.
2. מבקשים מ OpenCV למצוא פרצופים בתמונה באמצעות ה Cascader שלנו (זה המידע מקובץ ה xml שלקחנו מהמאגר של OpenCV).
3. מציירים על התמונה המקורית ריבוע מסביב לכל פרצוף ש OpenCV מצא
4. מציגים את התמונה עם הריבועים מסביב לפרצופים.
קוד? ברור:
import sys, pathlib
import cv2
imagePath = sys.argv[1]
cascader_file = str(pathlib.Path(__file__).parent / "haarcascade_frontalface_default.xml")
faceCascade = cv2.CascadeClassifier(cascader_file)
image = cv2.imread(imagePath)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# Detect faces in the image
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags = cv2.CASCADE_SCALE_IMAGE
)
print("Found {0} faces!".format(len(faces)))
# Draw a rectangle around the faces
for (x, y, w, h) in faces:
cv2.rectangle(image, (x, y), (x+w, y+h), (0, 255, 0), 2)
cv2.imshow("Faces found", image)
cv2.waitKey(0)
הקוד מבוסס על פוסט באותו נושא מ Real Python וכולל כמה תיקונים שלי כדי שיעבוד עם גירסא חדשה של פייתון ו OpenCV.
בשביל להפעיל אותו אתם מעבירים בשורת הפקודה שם של קובץ תמונה בתור פרמטר ראשון ונותנים לפייתון לעשות את כל השאר.1 419
# טיפ CSS: המילה matches
אחת היכולות המדליקות של Scss שהרבה זמן היתה חסרה לי ב CSS היא היכולת לארגן Selectors אחד בתוך השני. כלומר במקום לכתוב:
header h1,
header h2,
header h3,
header h4,
header h5,
header h6 {
font-weight: normal;
}
אנחנו יכולים לכתוב פשוט:
header {
h1, h2, h3, h4, h5, h6 { font-weight: normal; }
}
מסתבר שממש בקרוב נוכל להתחיל להשתמש בכתיב שנקרא matches כדי להגיע לאותה תוצאה בדיוק ב CSS:
header :matches(h1, h2) {
font-weight: normal;
}
הבעיה היחידה אגב שבינתיים דפדפנים לא ממש תומכים בכתיב זה. בצד החיובי רובם כן תומכים בדיוק בפיצ'ר הזה בשם אחר - באמצעות מילת הקוד any ותוספת Vendor Prefix. כלומר הקוד הבא כן עובד ברוב הדפדפנים המודרניים היום:
header :-moz-any(h1, h2) {
font-weight: normal;
}
header :-webkit-any(h1, h2) {
font-weight: normal;
}
כך שאם אתם מריצים סקריפט שמוסיף Vendor Prefix בצורה אוטומטית או ב prefixfree אתם כבר יכולים להנות מהכתיב החדש (ורק תזכרו שבקרוב הכתיב של any יהפוך ל matches ותהיו מוכנים להחליף כשיגיע היום).1 419
# חידת ראיונות עבודה בשפת Java
אם יש שפה שאני לא מתגעגע אליה זו Java. היא מצליחה להיות באותו זמן גם מסודרת מדי וגם מבלבלת. השבוע עזרתי לחברה בתרגיל Java קטן. המטרה היתה לכתוב פונקציה שמקבלת מספר ומזהה האם המספר הוא פלינדרום, כלומר אם הוא יוצא אותו דבר כשקוראים אותו מהסוף להתחלה ומההתחלה לסוף. לדוגמא 9009 הוא פלינדרום, וגם 121 הוא כזה, אבל 199 אינו פלינדרום וגם 1920 אינו כזה.
הניסיון הראשון שלה לכתוב את התוכנית כלל שתי טעויות מעניינות. תראו אם אתם מצליחים למצוא אותן ולהסביר לגבי כל טעות מה הבעיה ואיך מתקנים.
public static boolean isPelindrome(int number) {
String reverseNumberString = new StringBuilder(number).reverse().toString();
String numberString = String.valueOf(number);
return reverseNumberString == numberString;
}
מצאתם את הבעיות? ספרו בתגובות מה זה היה.1 419
# איך מגבילים מספר פעולות בדקה
מערכות תוכנה טובות יודעות לנצל בצורה מיטבית את משאבי המחשב עליו הן רצות. אחד המנגנונים שעוזרים לנו לעשות את זה הוא Rate Limit, וזה עובד ככה: בגלל שהמערכת שלנו מריצה מספר תהליכים, אם ניתן לתהליך אחד לרוץ "הכי מהר שרק אפשר" התהליך הזה עלול לתפוס משאבים הנחוצים לתהליכים אחרים במערכת.
הרבה פעמים מנגנון Rate Limit משמש בקוד תקשורת. כשאנחנו כותבים תוכנה שמושכת מידע מ API אנחנו לא רוצים למשוך את המידע "מהר" מדי, כי אז ה API עלול לנעול אותנו בחוץ. כשאנחנו כותבים את ה API אנחנו לא רוצים לאפשר ללקוח בודד למשוך יותר מדי מידע מהר מדי, כי אז לא יישארו לנו משאבים לטפל בלקוחות האחרים.
דרך אחת למימוש Rate Limiter היא שימוש ב Semaphore. כתבתי פעם דוגמא ב Python בקישור כאן. הסמפור מאותחל עם מספר טוקנים וכל בקשה מקבילית לוקחת אסימון, וכך כשנגמרים האסימונים הבקשה הבאה תיתקע עד שאחת אחרת תסיים. זה עובד אבל לא ממש עוזר לנו אם כל בקשות הרשת שלנו חוזרות מאוד מהר, ומה שאנחנו רוצים זה להגביל את מספר הבקשות בדקה.
טכניקה אחרת פשוטה למימוש מגבלת בקשות בדקה אפשר למצוא בקוד של Limiter, שהיא ג'ם של רובי למימוש מגבלה זו. הרעיון שלהם עובד כך:
1. מאתחיל מערך כך שהגודל שלו יהיה זהה למגבלת הבקשות בדקה שאתם צריכים. לדוגמא אם אנחנו רוצים מגבלה של 10 בקשות בדקה ניצור מערך של 10 תאים.
2. בכל תא במערך רושמים את השעה הישנה ביותר שמחשב תומך בה (מיוצגת על ידי המספר 0 - מספר השניות מאז ה 1/1/1970).
3. כל פעם שלקוח מבקש לבצע פעולה אנחנו לוקחים את התא הבא במערך, מוסיפים לו את האינטרוול (כלומר דקה) ובודקים אם התוצאה היא בעתיד. אם כן מחכים עד שהתוצאה תגיע. אם לא מאפשרים את ביצוע הפעולה במיידי.
4. מעדכנים את תוכן התא בזמן הנוכחי, ועוברים לתא הבא בשביל הלקוח הבא (המעבר לתא הבא משתמש במודולו כדי לרוץ על המערך בצורה מעגלית).
כל עוד מספר הבקשות בדקה קטן מהאינטרוול, כשנחבר לתא הבא את האינטרוול נקבל זמן בעבר והכל ירוץ מהר. אבל ברגע שמתחיל עומס המערכת תיעצר עד שתעבור דקה מהבקשה הישנה ביותר ואז תמשיך לרוץ, וכך בכל אינטרוול של דקה לעולם לא יהיו יותר מ n בקשות במקביל, לפי גודל המערך.
הפונקציה המרכזית ברובי מהקוד היא:
def shift
time = nil
@mutex.synchronize do
time = @ring[@head]
sleep_until(time + @interval)
@ring[@head] = Time.now
@head = (@head + 1) % @size
end
time
end
ואת שאר המחלקה תוכלו למצוא בקוד של הג'ם בקישור https://github.com/Shopify/limiter/blob/master/lib/limiter/rate_queue.rb.1 419
# עם האוכל בא התיאבון
הרבה יותר קל לי לכתוב בלוג כל יום מאשר כשכתבתי פוסט פעם בשבוע. אין יום שחסרים לי רעיונות לכתיבה, וברוב הימים יש לי לפחות 3-4 רעיונות לפוסטים בראש מהם אני בוחר את זה שאני הכי אוהב. כשכתבתי בלוג שבועי הסיפור היה אחר לגמרי, ואני ממש זוכר איך כל פעם הייתי מתישב לכתוב ונאבק בנייר הריק בלי שיש לי מושג על מה כדאי לכתוב השבוע.
גם במקרה הכללי - לפחות לגביי שמתי לב - שככל שאני עושה משהו יותר כך אני נהנה ממנו יותר ורוצה להמשיך לעשות אותו. ההבדל בין פעם ביום לפעם או פעמיים בשבוע הוא עצום.
ואותו כלל עובד גם בלימודים ובקורסים: אם אני לוקח התחייבות של ללמוד נושא שעה ביום, 5 ימים בשבוע, זה יהיה הרבה יותר אפקטיבי מאשר ריכוז כל ה-5 שעות ליום אחד מרוכז. והרבה פעמים מפתיע לגלות ששעה ביום אחרי חמישה ימים מסתכמות בהרבה יותר מ-5 שעות.
