מושגים בסיסיים בקוברנטיס
שמעתם על קוברנטיס אבל לא בטוחים מה זה? רוצים ללמוד ולא יודעים אפילו איפה להתחיל? הפוסט הזה בשבילכם ואני מקווה שאצליח לעשות קצת סדר באוקיינוס שנקרא קוברנטיס, לפחות בהתחלה שלו.
טיפים קצרים וחדשות למתכנתים
שמעתם על קוברנטיס אבל לא בטוחים מה זה? רוצים ללמוד ולא יודעים אפילו איפה להתחיל? הפוסט הזה בשבילכם ואני מקווה שאצליח לעשות קצת סדר באוקיינוס שנקרא קוברנטיס, לפחות בהתחלה שלו.
מאפיין CSS Sticky הוא הדרך הכי קלה היום להגדיר שפס עליון מהעמוד ימשיך ללוות אותנו כשאנחנו גוללים למטה, ובעצם "יידבק" לראש העמוד. הוא נוח במיוחד כשיש לכם טבלה ואתם רוצים ששורת הכותרת תמשיך ללוות את הגולש גם אם הוא גולל למטה את השורות עצמן. מה שמיוחד ב sticky בניגוד להגדרת overflow: scroll על הטבלה זה שאנחנו לא מייצרים שני פסי גלילה (אחד חיצוני לעמוד והשני פנימי לטבלה) אלא משתמש גולל בפס הגלילה הרגיל החיצוני של העמוד וכששורת הכותרת של הטבלה מגיעה לחלק העליון של העמוד היא פשוט נדבקת לשם.
בקיצור הוספתי את ההגדרות המתאימות ל position: sticky בדיוק כמו בתיעוד וצפיתי באימה כששום דבר לא עבד. הנה הקוד, תחילה ה HTML ואחריו ה CSS (דמיינו את זה עם מאות שורות):
<div class="root">
<table>
<thead>
<tr>
<th>name</th>
<th>hair color</th>
<th>city</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr>
<td>bob</td>
<td>blue</td>
<td>foo</td>
<td>agender</td>
</tr>
<tr>
<td>bob</td>
<td>blue</td>
<td>foo</td>
<td>agender</td>
</tr>
</tbody>
<table>
</div>
.root {
overflow: hidden;
}
thead th {
position: sticky;
top: 0;
background: #d2d2d2;
padding: 2px 5px 2px 0;
}
table {
border-collapse: collapse;
}
ובקודפן:
וקל לראות שגלילה של המסך לא באמת משאירה את שורת הכותרת "דבוקה" לראש העמוד. מה קורה פה?
התשובה מסתתרת במשפט הבא מתוך MDN:
Note that a sticky element "sticks" to its nearest ancestor that has a "scrolling mechanism" (created when overflow is hidden, scroll, auto, or overlay)
או בעברית - בגלל שמעל הטבלה יש לי div עם מאפיין overflow: hidden, למרות של div הזה אין הגבלת גובה והוא לא מייצר גלילה פנימית, ה sticky של הטבלה יהיה דביק ביחס אליו ולא ביחס ל body. במילים אחרות שורת הכותרת של הטבלה אכן נדבקת לחלק העליון, פשוט לחלק העליון של ה div שעוטף אותה, והוא זז למעלה מחוץ לחלק שאנחנו רואים.
הפיתרון? כמו תמיד פשוט כשיודעים. כל פעם שרוצים להשתמש ב position: sticky יש לוודא שאין מעליכם אלמנט עם overflow. הנה הקודפן המתוקן:
אלה שתי שאלות שונות. אל תנסו לחבר ביניהן.
הרבה פעמים אנחנו חושבים על משימה לימודית ומבינים כמה השקעה צריך בשביל לבצע אותה, ומבינים שאין לנו עכשיו את המשאבים והזמן לעשות את זה, ואז דוחים את ההתחלה. נכון, אני אומר לעצמי, אני יכול למצוא שעה בשבוע ללמוד CSS, אבל אני גם יודע ששעה בשבוע לא תספיק בשביל להגיע להישג משמעותי אז עדיף להשקיע את השעה הפנויה שלי בשבוע במשהו אחר.
התוצאה של זה היא דחיה אינסופית של דברים שאנחנו רוצים לעשות כי אף פעם אי אפשר למצוא עשר שעות בשבוע להשקיע בלימוד CSS.
מי שידפדף מספיק אחורה בבלוג פה יגלה שלפני שכתבתי כל יום כתבתי פעמיים בשבוע, ולפני זה כתבתי פעם בשבוע. ההחלטה להתחיל לכתוב בלוג לא היתה מתאפשרת אם מראש הייתי חושב שצריך לכתוב פוסט כל יום. באותה נקודה שרק התחלתי, כתיבה כל יום נראתה לי כמו השקעה מטורפת. פעם בשבוע היה משהו הרבה יותר סביר.
הדעה שלנו לגבי נושאים, חלוקת זמנים והיכולת למצוא זמן לדברים משתנה עם הזמן. ככל שאנחנו עמוק יותר בתוך פרויקט כך קל יותר להמשיך ולמצוא יותר זמן עבורו. התחלה דורשת הרבה יותר אנרגיה מההמשך ושעה בתחילת הפרויקט היא הרבה יותר "קשה" משעה באמצע שלו.
אם יש לכם היום שעה ביום ללמוד CSS זה מהמם. אבל אם אתם רוצים לדעת CSS טוב יותר ויש לכם רק שעה בשבוע בשביל זה - אל תחכו. ככל שתתקדמו עם הלימוד תראו ששעה בשבוע הופכת לשעתיים, שעתיים לארבע ובסוף מגיעים לשעה ביום בקלות. מתחילים היום. את הזמן אפשר למצוא בהמשך.
כשהתחלתי ללמד קורסים הפחד הכי גדול שלי היה לסיים את המצגת לפני הזמן. להספיק את כל החומר של הקורס חמש שעות לפני שהקורס אמור להסתיים, ולהיתקע חמש שעות בלי אף שקף.
ומאחר והמוח האנושי איפה שרואה בעיות מיד ממציא אינסוף פיתרונות, גם המוח שלי החליט שמה שחייבים לעשות הוא לכתוב עוד שקפים. כמה שיותר שקפים. מספיק שקפים ככה שלא משנה כמה מהר אני אדבר בשיעור, כמה מעט שאלות יהיו או כמה מהר כולם יסיימו את התרגילים - תמיד יהיה עוד נושא שאפשר ללמד.
לקח לי זמן להבין שהפיתרון הזה היה יותר גרוע מהבעיה.
כי עכשיו במקום שתלמידים יצאו מהשיעור עם הרגשה טובה שהם הספיקו והבינו הכל, כולם תמיד יצאו עם הרגשה שהם לא הספיקו משהו. לכל אחד היה איזה נושא שהוא מצב במצגת האינסופית שלי שדווקא מאוד עניין אותו, והוא לא הבין למה השיעור נגמר בלי שהספקנו לדבר על הדבר המאוד חשוב הזה.
לימים למדתי שבעיה דומה קיימת גם בתעשיה. מנהלי פיתוח שפוחדים ממתכנתים שיסיימו לפני הזמן (משאב מבוזבז), מרכזים יותר מדי משימות בכל ספרינט ומתעדפים את המשימות, מתוך כוונה ברורה שלא הכל יבוצע, אבל אם כן יהיה יותר זמן עדיף שתהיה לנו תוכנית מאשר שאנשים ישתעממו.
אז בואו נדמיין רגע - מה באמת קורה כשאנשים משתעממים? מה באמת קורה כשאנחנו מרשים לעצמנו לתכנן קצת פחות ממה שאפשר לבצע?
בקורסים עשיתי את הניסוי הזה כמה פעמים והתוצאות הפתיעו לטובה. בין הדברים שקרו:
אנשים שאלו יותר שאלות - פשוט כי הם הרגישו שהאווירה הרגועה יותר מאפשרת את זה (אין שום חומר שהם "אמורים" להספיק)
כשבאמת נשאר זמן פנוי לקראת סוף היום, אנשים העלו נושאים שמעניינים אותם ורצו לשמוע יותר עליהם.
וכן לפעמים גם אנשים הלכו הביתה חצי שעה קודם. לפעמים תלמיד אחד או שניים ניצלו את זה שהכיתה התרוקנה ונשארו לשאול אותי שאלות שרלוונטיות לעבודה הספציפית שלהם.
בעבודה שוטפת של תכנות, אם יש פחות מדי משימות בספרינט אני יכול לדמיין שמתכנתים יכתבו קוד איכותי יותר, שישקיעו יותר זמן בהתמודדות עם תקלות, בתכנות בדיקות נכונות, בכתיבת תיעוד ובפיתוח תשתיות שיאפשרו המשך פיתוח מהיר. אם יש יותר מדי משימות בספרינט, גם אם משימות מסוימות מוגדרות בתיעדוף נמוך, אנחנו נעדיף לסיים כמה שיותר מהמשימות ברשימה גם על חשבון האיכות. ככה המוח האנושי עובד.
נ.ב. אם עשיתם ניסוי כזה בצוות שלכם ונתתם לאנשים יותר חופש פעולה, אשמח לשמוע מה היו התוצאות - מוזמנים לשתף בתגובות פה מתחת לפוסט.
לא מזמן ראיתי את הוידאו של בן עוואד שעושה ראיון עבודה פיקטיבי לדן אברמוב. אם לא ראיתם עדיין ובא לכם לראות את דן אברמוב נאבק עם CSS שווה להיכנס לכאן: https://www.youtube.com/watch?v=XEt09iK8IXs.
אבל הוויכוח שלי עם דן אברמוב לא היה על CSS אלא על נקודה יותר עדינה של אבטחת מידע. באותו ראיון עוואד שואל את אברמוב מה דעתו על dangerouslySetInnerHTML ואברמוב מגיב באי נחת ניכרת. שניהם מסכימים שמשהו ב API הזה מסורבל וההסבר של אברמוב הוא מעניין. נזכיר רק שב API של ריאקט הם רצו להיות מאוד ברורים כשמישהו פותח דלת לפירצת XSS ולכן במקום לקרוא למאפיין שמעדכן את ה innerHTML של אלמנט במשהו פשוט כמו setInnerHTML הם הוסיפו את המילה dangerously. אבל זה לא הכל. כי בשביל להגדיר את ה innerHTML צריך להעביר למאפיין dangerouslySetInnerHTML אוביקט עם מפתח בשם מיוחד __html
, כלומר משהו כזה:
<div dangerouslySetInnerHTML={{ __html: "Hello" }}></div>
הטענה של אברמוב היתה שהבעיה ב API הזה היא שהמילה dangerous היא במקום הלא נכון, ועדיף היה לשים אותה בתור המפתח באוביקט שהמאפיין מקבל, כלומר הוא היה רוצה לראות קריאה כזאת:
<div html={{ __dangerousHTML: "Hello" }}></div>
והסיבה לטענתו היא אבטחה טובה יותר כששני החלקים רחוקים אחד מהשני. לדוגמה נדמיין שבקובץ אחד יש לנו את הפונקציה:
// file utils.js
export function alertHtml() {
return { __html: "<b>alert</b>" };
}
ובקובץ שני הקטע:
import { alertHtml } from './utils.js';
<div dangerouslySetInnerHTML={alertHtml()}></div>
אם חוקרי אבטחה יריצו grep ויחפשו את המילה dangerouslySetInnerHTML - אז בגירסה הנוכחית של הממשק הם יגיעו לחלק השני, אבל לפי דן אברמוב זה לא החלק שחשוב לחקור, כי החלק הראשון הוא זה שכולל את ה HTML המקורי ושם צריך להסתכל ולראות שה HTML אכן בטוח לשימוש.
ואז הבנתי שדן ואני כנראה לא נעבוד יחד. או לפחות לא נסכים על נושאי אבטחת מידע.
אחד הכללים החשובים בכתיבת קוד מאובטח הוא לבדוק את הקלט כמה שיותר קרוב לפעולה המסוכנת, כי כמה שתבדוק יותר רחוק דברים רעים יכולים לקרות בין הפונקציה שבה בדקת לבין המקום שבו משתמשים בקלט כדי לעשות איתו פעולה מסוכנת. לדוגמה:
אם אני צריך למחוק קובץ, אני אוודא ששם הקובץ מתאים לתבנית שאני רוצה למחוק ושהתיקיה מתאימה לתיקיה שבה אני מוחק קבצים בדיוק לפני המחיקה, ולא כשאני מקבל את הפרמטר מהמשתמש.
אם אני צריך לפנות לבסיס הנתונים, אני אעשה Quoting לקלט החיצוני ממש לפני שליחת השאילתה לבסיס הנתונים (רצוי באמצעות שימוש ב Bound Variables).
ואם אני צריך לכתוב HTML למסך, אני אוודא שה HTML הזה נקי כמה שיותר קרוב לפקודה שכותבת אותו למסך, כלומר הפוקוס שלי באמת מתחיל בקטע השני במאפיין dangerouslySetInnerHTML
.
לא מזמן ראיתי חבר מושך מאגר מרוחק ולפני שהפעיל git pull הוא מחק את הענף שאותו הוא רצה למשוך. כששאלתי למה הוא הסביר שהוא יודע שמישהו דחף שינויים עם force
ולכן דברים רעים יקרו אם הוא יפעיל pull רגיל (הוא צודק בזה). בסוף הפוסט אראה פיתרון יותר פשוט ממחיקת הענף, אבל בינתיים בואו נדבר קצת על הדברים הרעים שקורים כשמישהו משנה היסטוריה מרחוק:
מחשבים הם יצורים מאוד ממושמעים. תגיד למחשב לעשות משהו והוא יעשה בדיוק את זה. ובכל זאת בני אדם מבלים חלק ניכר מהחיים שלהם בלמצוא "באגים", כלומר בלהבין למה המחשב לא עשה את מה שאמרנו לו לעשות. איך זה קורה? יש לי שלושה רעיונות-
קוברנטס היא מערכת מדהימה שפותחת למתכנתים אפשרויות Deployment שפעם אפשר היה רק לחלום עליהן. אבל בשביל לעבוד איתה צריך קודם להכיר אותה, וזה יכול לקחת הרבה זמן.
או תסתכלו על פנדס. פנדס היא ספריית פייתון מדהימה שמאפשרת לשלוף מידע מטבלאות בצורה יצירתית הרבה יותר ממה שאקסל מאפשר. אבל בשביל להשתמש בה צריך להכיר אותה, וכדאי להכיר גם את ספריות ציור הגרפים שמלוות אותה. כשמישהו שלא מכיר את הכלים מנסה להשתמש בהם, הוא יגלה שיש עוד דרך ארוכה עד שמגיעים לפרודוקטיביות שכולם מדברים עליה.
כששוקלים אם להשתמש בטכנולוגיה מסוימת זו טעות לשלב את זמן הלימוד בשיקולים. לא כדאי להישאר עם פיתרון לא-אופטימלי, שאולי כולל בעיות אבטחה ואולי נותן תוצאות לא מדויקות, רק בגלל שזה הפיתרון שאתם מכירים היום. ההשקעה בלימוד היא החלק המהנה בעבודה שלנו. אם יש כלי שיכול לעזור לכם להתמודד טוב יותר עם העולם, היום יהיה זמן מצוין להתחיל לקרוא עליו.
קוד טוב הוא כזה שעוזר לנו למצוא באגים מהר, גם אם הבאגים לא נמצאים באותו קוד עצמו. בדוגמה של JavaScript תבנית פופולרית שיכולה לגרום לבעיות מסוג זה היא קבלת אוביקט פרמטרים לפונקציה. נדמיין פונקציה שצריכה להתחבר ל redis, ברירת המחדל שלה תהיה להתחבר ל redis שרץ על המחשב המקומי אבל באוביקט הפרמטרים היא יכולה לקבל מפתח בשם redis ובתוכו כל מיני אופציות לחיבור של redis כמו host ו port. הפונקציה עשויה להיראות כך:
function init(options={}) {
let redisHost = 'localhost', redisPort = 6379;
if (options.redis && options.redis.host) {
redisHost = options.redis.host;
}
if (options.redis && options.redis.port) {
redisPort = options.redis.port;
}
console.log(`Connecting to redis at ${redisHost}:${redisPort}`);
}
init();
init({ redis: { host: 'my-redis' } });
הקוד עובד ומשתמש בערכים הנכונים לכל המצבים, אבל הוא עלול לסבך אותנו אם נשתמש בו לא נכון. נדמיין ששם המכונה מגיע ממשתנה סביבה בשם REDIS_HOST
אבל בקוד טעיתי באיות וכתבתי משהו כמו REDS_HOST
- כלומר הפעלתי:
init({ redis: { host: process.env.REDS_HOST } });
במצב כזה למרות שהעברתי ערך למשתנה redis.host הפונקציה תתעלם מהערך בגלל שהוא undefined ותשתמש בערך ברירת המחדל. שום הודעת שגיאה לא תופיע ולא יהיה לנו שום רמז שכתבנו משתנה בשגיאת כתיב.
קוד טוב יותר לפונקציה הראשונה היה יכול בקלות לזהות שמישהו ניסה להעביר ערך אבל העביר undefined, ולציין את זה באזהרה או שגיאה:
function init(options={}) {
let redisHost = 'localhost', redisPort = 6379;
if (options.redis) {
if ('host' in options.redis) {
if (options.redis.host) {
redisHost = options.redis.host;
} else {
throw 'Error: options.redis.host is undefined';
}
}
if ('port' in options.redis) {
if (options.redis.port) {
redisPort = options.redis.port;
} else {
throw 'options.redis.port is undefined';
}
}
}
console.log(`Connecting to redis at ${redisHost}:${redisPort}`);
}
בכתיבת פונקציה שאחרים הולכים להשתמש בה - שווה לחשוב גם איך אותם אנשים אחרים שישתמשו בפונקציה הולכים לטעות, ולעזור להם למצוא את הטעויות מהר יותר.
בציוץ שעורר סערה לא קטנה בשעתו לורי ווס כתב את הדברים הבאים:
אני, במקרה הכי טוב, מתכנת עם יכולות בינוניות. הלוואי שזה לא היה נכון, הלוואי שהייתי אחד הגאונים, אבל אני לא אז מנסה לעבוד עם מה שיש.
אני מקבל את זה שלעולם לא אמציא אלגוריתם חדש שמשנה את העולם וכנראה אפילו לא פריימוורק חדש שאנשים אוהבים. אני משתמש בדברים האלה - פשוט לא זה שממציא אותם.
מה שכן יש לי זה ניסיון. נו וזה ברור - אם אתה מפשל מספיק פעמים בסוף תלמד איך עושים דברים נכון. אם תכתוב מספיק באגים בסוף תשתפר בלמצוא אותם. זה לא טוב כמו להיות גאון אבל האפקט מצטבר. ניסיון הוא דבר מאוד מועיל.
הדבר השני שאני מצליח לעשות זה לחבר בין רעיונות. אני סופג מידע מכל מיני מקורות, מזהה תבניות ובונה סביב זה הסבר. אפשר להגיד שאם יש משהו שאני מעל-הממוצע בו זה להסביר.
ברור שלהסביר דברים זה לא מרשים כמו להמציא אותם, אבל זה גם לא פופולרי באותה מידה. כישרון להסביר מה קורה זה מה שאני מביא לשולחן.
ובהינתן שזו העיסקה העובדה שאני מתכנת בינוני דווקא עוזרת. אני מצליח לראות דברים מנקודת המבט של המתכנת הממוצע, אני אוהב את מה שממילא פופולרי, אני אף פעם לא רוצה לעשות דברים בדרך הקשה-אבל-חכמה.
וכמובן שניסיון הוא גם דבר מאוד מועיל. אחרי מספיק שנים בתעשיה אתה מתחיל ללמוד להבדיל בין רעיונות טובים שיישארו לאורך זמן לבין אופנות שעוד שבוע יעלמו.
זאת לא מיומנות נוצצת, אני אף פעם לא אהיה מפורסם וזה די מבאס. אבל זאת מיומנות מועילה לי ולאחרים וכך למדתי להיות מרוצה ולהסתפק בהיותי מתכנת בינוני שמצליח רק להסביר דברים.
קראתם? עכשיו בואו נפתח את זה. לפני שנדבר על מי זה לורי ווס ומה הוא עשה, אפילו אם הציוץ הזה היה אנונימי היה קשה להתעלם מהבעיותיות פה. הכותב מתעקש שהוא מתכנת בינוני אבל כל התיאור שלו את עצמו ואת סט המיומנויות שלו הוא ההיפך הגמור מבינוני. בהנחה שווס מתאר את עצמו בצורה אמינה אלה המיומנויות שיש לו שבעיניי הופכות אותו למתכנת מעולה:
הוא לומד מטעויות ומשתמש בניסיון לטובתו.
הוא בעל יכולת אבחנה טובה ומצליח לראות דברים בקוד שאחרים לא רואים.
הוא בעל מיומנות תקשורת טובה ומצליח לתקשר את הדברים שהוא רואה בקוד למתכנתים אחרים.
הוא מסוגל להבין בעיה ולהציע פיתרון שאנשים אחרים יוכלו אחר כך לקרוא ולתחזק אותו.
אלה ארבע תכונות שהייתי שמח למצוא בכל מתכנת שאעבוד איתו. תכונות ששווה להשקיע בהן ותכונות שבכל מקצוע אחר היו מזכות אותו בתואר "בעל מקצוע מעולה".
אם אני צריך לבחור בין רופא שממציא שיטות טיפול חדשות לבין רופא אמין, מנוסה, בעל יכולת תקשורת טובה ויכולת פיתרון בעיות - הייתי בוחר ברופא השני בלי להתבלבל.
אם אני צריך לבחור בין אדריכל שממציא בניינים חדשים לבין אדריכל שמראה Track Record של בניינים אמינים שבנה ומצליח לתקשר לי את כל השיקולים והבחירות בבניה - אני בוחר את השני לבנות כל בניין שאני צריך.
ואצלנו בתכנות? קן תומפסון, הממציא האגדי של יוניקס, מספר בראיונות שאת העבודה על יוניקס הוא עשה אחרי שהמנהלים שלו החליטו לבטל את הפרויקט וחלק גדול ממנה נעשה בלי לספר למנהלים על מה הוא עובד. ברנדון איי, הממציא של JavaScript, התעקש להכניס מאפיינים משפות אזוטריות לתוך השפה וגרם למתכנתים להתבלבל במשך עשרות שנים. טוב שיש מתכנתים גאונים בתעשיה אבל צריך לזכור שרוב הזמן הם לא האנשים שהכי קל לעבוד איתם.
המחשבה כאילו הדרך להיות מתכנת מעולה היא רק להמציא אלגוריתם חדש או לבנות פריימוורקים מאפס היא הרסנית לביטחון העצמי שלכם ולקהילה כולה.
נ.ב. אחרון בגיזרת הקונטקסט. למי שלא מכיר אז הבחור המצייץ הוא Co-Founder של npm (כן מערכת ניהול החבילות ל Node.JS, זאת שמחזיקה את מאגר החבילות הגדול בעולם). הוא מתכנת ווב מאז 1996 ולפני npm הקים חברה בשם awe.sm שנמכרה ל Unified. הוא הרצה ב JS Conf וכנסים נוספים וכותב בלוג טכני מוצלח כבר 20 שנה. יום אחד נצטרך לשאול את עצמנו איך אנשים מעולים מהתעשיה נתקעים עם רגשי נחיתות לא מוצדקים.