ToCode
Ir al canal en Telegram
1 419
Suscriptores
Sin datos24 horas
Sin datos7 días
+130 días
Archivo de publicaciones
1 419
# שלושה נכסים שאתם יכולים לבנות לבד וכמעט בחינם
למרות שאני לא אוהב את הסגנון, אי אפשר להתווכח עם הפתגם הישן שאומר שעדיף ללמד בן אדם לדוג מאשר לתת לו דג. המיומנות להשיג אוכל מאפשרת לבן אדם להמשיך להשתמש בה ככל שירצה.
באותו אופן אנחנו חושבים על ההבדל בין נכסים לבין כסף. הכסף ממלא צורך מיידי והנכס יכול לעזור לנו להרוויח עוד ועוד כסף לאורך זמן. חלק מהנכסים של העולם המודרני הם קשים להשגה ויקרים: נדל"ן, תואר אקדמי או מותג חזק, כולם יביאו ערך משמעותי לבעלים שלהם וכל אחד מהם דורש השקעה משמעותית בכסף ובזמן לפני שמתחילים לראות תוצאות.
אבל האינטרנט והכלכלה המודרנית יוצרים גם סוג חדש של נכסים, נכסים שדורשים מעט מאוד השקעה כספית ושכל אחד יכול לבנות לגמרי לבד מהבית. הנה שלוש דוגמאות לנכסים כאלה שאולי לא יהפכו אתכם לעשירים אבל בהחלט יעזרו לייצר חיים יותר נוחים:
## קשרים
אחד הביטויים היפים ששמעתי בהקשר הזה הוא Connection Economy שבא להסביר שקשרים עם אנשים אחרים הפכו לנכס כלכלי לכל דבר. בעולם האינטרנט אנחנו יכולים ליצור קשרים מקצועיים בעלי משמעות עם כמות עצומה של אנשים, והרבה פעמים אפילו בלי לפגוש אותם פנים אל פנים. פה בארץ המצב אפילו יותר מוצלח כי המרחקים קטנים ויש הרבה קהילות חזקות שמייצרות מפגשים פיזיים לחברים.
רוב האנשים סביבכם מוקפים כל היום באינסוף פירסומות וכבר נהיינו אטומים לרעש שלהן. לעומת זאת רובנו שמחים לקבל המלצה מחבר על מוצר שהוא אהב או על בעל מקצוע שעזר לו. ספציפית בעולם הפיתוח, בניית פרויקט-צד עם עוד אנשים, מפגש בכנס והעברת הרצאה, או אפילו עזרה בקבוצת פייסבוק יכולים לעזור לאנשים להכיר אתכם מקצועית. בעתיד כשהם יצטרכו בעל מקצוע או יחפשו לגייס עובד אתם תקפצו בראש הרשימה.
הדרך הכי טובה לפתח קשרים היא פשוט לעזור מקצועית לאנשים בכל הזדמנות שיש לכם, ולייצר עוד ועוד הזדמנויות כאלה - אם זה ללכת לכנסים, להצטרף לפיתוח של פרויקט קוד פתוח שקרוב ללבכם, להקים מערכת בהתנדבות שתפתור בעיה לארגון שקרוב ללבכם, לעזור בקבוצות פייסבוק וטלגרם, בקיצור לחפש דרכים לעבודה מקצועית עם מעגלים שונים ורחבים של אנשים.
## מיומנות טכנית
עולם העבודה הופך נישתי ומקצועי יותר ויותר כל שנה והדרישה למפתחים מקצועיים רק גדלה. באותו זמן אין מספיק אנשים שמוכנים להשקיע מספיק בפיתוח המיומנות המקצועית שלהם. כך נוצר מצב שמצד אחד יש המון ג'וניורים שמחפשים עבודה בהייטק ומצד שני יש המון משרות פנויות שחברות לא מצליחות לאייש כי "אין אנשים טובים".
אפשר לדמיין את עובדי ההייטק הפוטנציאלים מסודרים כפירמידה כאשר בתחתית הפירמידה אנחנו מוצאים ג'וניורים ואנשים ללא ניסיון וככל שעולים ברמת המיומנות יש פחות אנשים מתאימים.
השקעה במיומנות באמצעות לימוד קורס, ביצוע פרויקט-צד בטכנולוגיה חדשה, השתתפות בפרויקט קוד-פתוח מרכזי בעולם התוכן שלכם, התמקדות בעולם תוכן ספציפי והיכרות עם כל מה שקורה בו או בכל דרך אחרת תעזור לכם לעלות שלבים בפירמידה. מיומנות טכנית היא נכס, כי היא תאפשר לכם תמיד למצוא עבודה בתנאים שמתאימים לכם.
## בניית פרויקט SaaS מהבית
נכס שלישי שאתם יכולים לבנות לגמרי לבד (אבל לדעתי הוא הקשה ביותר) הוא מערכת תוכנה שאנשים אחרים יהיו מוכנים לשלם עליה. אתם לא צריכים להתחרות בגוגל או בפייסבוק, מספיק לבחור נישה שאף חברת סטארט-אפ או ענקית טכנולוגיה לא מתעניינת בה, לפתור לאנשים בעיה ספציפית ולהציע מחיר אטרקטיבי.
הספר Start Small Stay Small מציע מסלול חשיבה מאוד טכני לפיתוח פרויקט Saas עבור מתכנתים שרוצים להצליח בכוחות עצמם ואני מאוד ממליץ לקרוא אותו אם זה משהו שמדבר אליכם.
באתר Indie Hackers אפשר למצוא אינסוף סיפורים על פרויקטים קטנים שלא מעט מהם מאפשרים למפתחים שלהם לחיות בכבוד רק מההכנסות מאותו פרויקט.
נכס תוכנה שווה היום לא פחות מכל חנות פיזית, אבל הבניה שלו כמעט לא דורשת השקעה כספית וגם אנשים עובדים יכולים להשקיע בנכס כזה בשעות הפנאי.
יש לכם רעיונות לנכסים נוספים שאנחנו יכולים לפתח לבד מהבית? מוזמנים בחום לשתף בתגובות.
1 419
const newTreeData = await loadChildren(treeData, key, children);
setTreeData(newTreeData);
};
return (
<Tree loadData={onLoadData} treeData={treeData} />
);
}
## איפה ללמוד עוד
לעץ של antd יש עוד המון יכולות ואפשר לקרוא עליהן עם דוגמאות בדף התיעוד של העץ כאן:
https://ant.design/components/tree/
והחלק היותר מעניין בדף הוא רשימת הקומפוננטות בצד שמאל. פשוט תלחצו על כל קומפוננטה מהרשימה כדי לראות מה היא עושה ואיך להשתמש בה.1 419
# משחקים עם Antd - קומפוננטת עץ אסינכרונית
ספריית antd היא אחת מספריות ה UI הפופולריות בריאקט ואני לגמרי מבין למה. ה API מאוד פשוט, יש הרבה קומפוננטות שנראות טוב ואפשר להרחיב אותה יחסית בקלות. הנה דוגמה לקומפוננטה של העץ שלהם ולשימוש אסינכרוני בו כדי לייצג מידע שנטען משרת.
## מה אנחנו בונים
הדוגמה של היום תשתמש בקומפוננטת Tree של antd כדי להראות עץ שמציג את כל הסרטים של מלחמת הכוכבים, וכשלוחצים על סרט הוא יטען את שמות כל הדמויות באותו סרט ויציג אותם בתור ילדים בעץ. ככה זה נראה ועובד בקודסנדבוקס:
<iframe src="https://codesandbox.io/embed/fervent-robinson-bse0b?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=light"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="fervent-robinson-bse0b"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"></iframe>
## איך בנוי עץ
בשביל להציג עץ עם קומפוננטת עץ של antd אנחנו צריכים מבנה נתונים שמכיל מערך של אוביקטים, כאשר לכל אוביקט יש מאפיין key ייחודי, מאפיין title אופציונאלי שמכיל את הטקסט של האוביקט ומאפיין children אופציונאלי שמכיל את כל הילדים. אם אתם יודעים שלא יהיו ילדים לצומת מסוים אפשר להוסיף מאפיין isLeaf עם הערך true. האוביקט הבא מייצג עץ לדוגמה:
[
{ key: 'a', title: 'A', children: [] },
{ key: 'b', title: 'B', children: [
{ key: 'bb', title: 'B-1', isLeaf: true },
{ key: 'bc', title: 'B-2', isLeaf: true },
}}
];
בניסוי שלי רציתי להראות עץ של סרטים כך שברמה החיצונית ביותר תהיה רשימה של סרטים ולכל סרט הילדים יהיו הדמויות שמופיעות באותו סרט. בשביל לבנות את העץ אני מתחיל עם רשימת הסרטים שנמצאת בקישור https://swapi.py4e.com/api/films/, ואז מפעיל את הפונקציה הבאה כדי להפוך את הרשימה לעץ:
function initTree(films) {
return films.map((film) => ({
key: `film-${film.episode_id}`,
title: film.title,
children: film.characters.map((url) => ({
key: url,
isLeaf: true,
})),
}));
}
## מה קורה כשמשתמש לוחץ על סרט
עכשיו שיש לנו את הרמה של הסרטים אפשר להמשיך ולראות מה קורה כשמשתמש לוחץ על סרט. התשובה היא שאנחנו צריכים ליצור את כל נתוני העץ מחדש עם העץ המעודכן. בעבודה עם מבני נתונים מורכבים שצריכים להישמר בסטייט אני אוהב להשתמש בספריה immer שנותנת לי לגשת למבנה הנתונים כמו שהייתי כותב JavaScript רגיל והופכת את הקוד לגישה Immutable, כלומר מחזירה מבנה נתונים חדש רק עם השינויים שביצעתי. בעזרת immer כתבתי את שתי הפונקציות הבאות שלוקחות עץ ומפתח של סרט וממלאות את הילדים של הסרט בנתוני הדמויות האמיתיות מאותו הסרט:
async function loadCharacter(node) {
const res = await fetch(node.key);
const data = await res.json();
return { key: node.key, title: data.name, isLeaf: true };
}
async function loadChildren(treeData, filmKey, children) {
return produce(treeData, async (draft) => {
const node = draft.find((n) => n.key === filmKey);
node.children = await Promise.all(children.map(loadCharacter));
});
}
זה עובד כי שמרנו כבר קודם ב children של כל סרט רשימה של ילדים שהמפתח של כל אחד הוא בדיוק ה URL של הדמות. אני משתמש כאן ב Promise.all ולא בלולאה כדי שכל בקשות הרשת יישלחו במקביל ולא אחת אחרי השניה.
החלק האחרון שנשאר בקוד הוא הקומפוננטה עצמה שמדביקה את כל הפונקציות שכתבנו והיא נראית כך:
import React, { useState, useEffect } from 'react';
import produce from "immer"
import { Tree } from 'antd';
import 'antd/dist/antd.css';
export default function Demo() {
const [treeData, setTreeData] = useState(initTreeData);
useEffect(() => {
async function flow() {
const res = await fetch('https://swapi.py4e.com/api/films/');
const data = await res.json();
setTreeData(initTree(data.results));
};
flow();
}, []);
const onLoadData = async ({ key, children }) => {1 419
npm ERR! this command with --force, or --legacy-peer-deps npm ERR! to accept an incorrect (and potentially broken) dependency resolution. npm ERR! npm ERR! See /Users/ynonp/.npm/eresolve-report.txt for a full report. npm ERR! A complete log of this run can be found in: npm ERR! /Users/ynonp/.npm/_logs/2021-12-16T20_16_00_413Z-debug.logרואים מה הבעיה כאן? נכון ש npm install נכשל, אבל הוא גם מציע שני פיתרונות שאת שניהם אנשים נוטים להפעיל כמעט בלי לחשוב. כלומר אחרי שמישהו שרוצה להשתמש בספריה שלכם רואה הודעה כזאת האינסטינקט הראשון הוא לנסות להפעיל:
$ npm install --force
שכמובן עובד עם Warnings, ומתקין את גירסה 16.8.1 של ריאקט בתיקיית node_modules הראשית ולא מתקין כלל את גירסה 17:
$ npm ls react
myapp@1.0.0 /Users/ynonp/tmp/mynpm/myapp
├─┬ mylib@1.0.2
│ └── react@16.8.1 deduped invalid: "^17.0.2" from node_modules/mylib
└── react@16.8.1 invalid: "^17.0.2" from node_modules/mylib
npm ERR! code ELSPROBLEMS
npm ERR! invalid: react@16.8.1 /Users/ynonp/tmp/mynpm/myapp/node_modules/react
npm ERR! A complete log of this run can be found in:
npm ERR! /Users/ynonp/.npm/_logs/2021-12-16T20_18_56_638Z-debug.log
אז נכון אי אפשר להכריח מישהו להשתמש בגירסה שלך של ספריה, אבל כן יש כלים טובים כדי לגלות אם מישהו התקין גירסה שלא המלצת עליה ובגלל זה הקוד לא עובד. הנה מה שחשוב לזכור:
1. הגדירו את הדברים שאתם רוצים שאחרים יתקינו בתור Peer Dependencies.
2. אם קוד לקוח שמשתמש בספריה לא עובד, השתמשו ב npm ls כדי לוודא שהתלויות שרציתם באמת מותקנות כמו שצריך.
3. תקנו את התלויות ואל תשתמשו ב --force, אפילו שזה עובד.1 419
# היום למדתי: אי אפשר להכריח לקוח להתקין ספריות Node.JS (אבל אפשר להתקרב לזה)
באג שבזבז לי יותר מדי זמן ביומיים האחרונים לימד אותי לקח חשוב על npm ובעיקר על מתכנתים אז אני כותב כאן כדי לא לשכוח ובתקווה אולי לחסוך לכם פאדיחות דומות.
הסיפור הוא פשוט - אם אתם כותבים חבילת Node.JS אז אתם יכולים לציין 3 רשימות של תלויות ב package.json שלכם:
1. רשימת dependencies מגדירה רשימה של ספריות שאתם דורשים שהלקוח יתקין ספציפית עבורכם כשהוא מתקין את הספריה שלכם.
2. רשימת peerDependencies מגדירה רשימה של ספריות שאתם דורשים ש"יהיו שם" כשלקוח מתקין את הספריה שלכם, כלומר ספריות שגם אתם משתמשים בהן וגם קוד הלקוח משתמש בהן.
3. רשימת devDependencies מגדירה רשימה של תלויות שרק אתם מתקינים במצב פיתוח.
אז עכשיו בואו נבדוק מה קורה לדוגמה כשאני בונה ספריה שמשתמשת בריאקט 17 וצריכה שהלקוח יתקין את ריאקט 17 כדי לעבוד איתה.
## שימוש ב devDependencies
אם אני מגדיר את ריאקט בתור devDependency באמצעות הבלוק הבא בקובץ package.json בספריה שלי:
"devDependencies": {
"react": "^17.0.2"
}
אז כשאני אעלה את הספריה ל npm ומישהו אחר יפעיל npm install עליה, אותו מישהו אחר לא יקבל את react בכלל. ריאקט היתה בשימוש רק בזמן שפיתחתי את הספריה ולא בזמן השימוש בה.
## שימוש ב dependencies
אם אני מגדיר את ריאקט בתור dependency באמצעות הבלוק הבא בקובץ package.json בספריה שלי:
"dependencies": {
"react": "^17.0.2"
}
אז הלקוח שמתקין את הספריה שלי יקבל בתיקיית node_modules שלו את הספריות הבאות:
node_modules
├── js-tokens
├── loose-envify
├── mylib
├── object-assign
└── react
וזה כבר נראה טוב! כלומר הלקוח קיבל גם את ריאקט וגם את כל הספריות שריאקט תלויה בהן. אבל כדאי לצנן את ההתלהבות. זה עובד רק בגלל שהלקוח תלוי רק בספריה שלי. אם הוא יוסיף תלות בספריה שצריכה גירסה ישנה יותר של ריאקט, או אפילו תלות ספציפית בגירסה ישנה יותר של ריאקט החיים הולכים להסתבך.
נניח שקוד הלקוח שלי כולל את הבלוק הבא בקובץ package.json שלו:
"dependencies": {
"mylib": "file:../mylib/mylib-1.0.1.tgz",
"react": "16.8.1"
}
אז עכשיו בהפעלה של npm install הוא יקבל בתיקיית node_modules את:
node_modules
├── js-tokens
├── loose-envify
├── mylib
├── object-assign
├── prop-types
├── react
├── react-is
└── scheduler
שזה נראה כאילו זה טוב כי הוא התקין את ריאקט אבל האמת שהוא התקין שתי גירסאות של ריאקט:
$ npm ls react
myapp@1.0.0 /Users/ynonp/tmp/mynpm/myapp
├─┬ mylib@1.0.1
│ └── react@17.0.2
└── react@16.8.1
ובאמת בתוך תיקיית node_modules/mylib אני אמצא תיקיית node_modules נוספת שבתוכה יש את ריאקט בגירסה 17.0.2, ובתיקיית node_modules הראשית אני מוצא את ריאקט בגירסה 16.8.1.
לכן הגדרת dependencies לא באמת מאפשרת לי "להכריח" לקוח להתקין ספריה בגירסה מסוימת. אם ספריה אחרת שהוא תלוי בה, או הוא עצמו, מבקשים גירסה אחרת אז זו תקבל עדיפות והגירסה שאני תלוי בה תותקן בצורה פרטית ב node_modules של הספריה שלי.
## שימוש ב peerDependencies
אופציית peerDependencies היתה צריכה להיות הראשונה בניסוי כי לפי התיעוד היא עושה בדיוק את מה שאנחנו מחפשים. היא מגדירה שאני תלוי בספריה אחרת אבל תלות ציבורית, כלומר שאני דורש שהספריה האחרת תהיה בפרויקט בגירסה שאני רוצה.
ובאמת ההתנהגות של peerDependencies היא הפעם ממש מוצלחת. אם יש לי ב package.json של הספריה שלי את הבלוק הבא:
"peerDependencies": {
"react": "^17.0.2"
}
והלקוח שלי מחזיק קובץ package.json עם בלוק כזה:
"dependencies": {
"mylib": "file:../mylib/mylib-1.0.1.tgz",
"react": "16.8.1"
}
אז כשהוא ינסה להתקין הוא יקבל את ההודעה:
$ npm install
npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR!
npm ERR! While resolving: myapp@1.0.0
npm ERR! Found: react@16.8.1
npm ERR! node_modules/react
npm ERR! react@"16.8.1" from the root project
npm ERR!
npm ERR! Could not resolve dependency:
npm ERR! peer react@"^17.0.2" from mylib@1.0.2
npm ERR! node_modules/mylib
npm ERR! mylib@"file:../mylib/mylib-1.0.2.tgz" from the root project
npm ERR!
npm ERR! Fix the upstream dependency conflict, or retry1 419
next_child = next(child for child in doc if '/'+child["key"] == pos+'/'+path[0])
except StopIteration:
doc.append({ "type": "d", 'key': key, 'title': path[-1], 'children': [] })
next_child = doc[-1]
return add_node(next_child["children"], path[1:], key, pos+'/'+path[0])
doc = []
for root, dirs, files in os.walk('.'):
key = root[1:].split('/')[1:]
node = add_node(doc, key, '/'.join(key))
prefix = '/'.join(key) + '/' if len(key) > 0 else ''
node.extend([{ "type": "f", "title": f, "key": f"{prefix}{f}", "children": [] } for i, f in enumerate(files)])
print(json.dumps(doc))
נתחיל מהחלק השני - הפונקציה os.walk:
1. מפעילים את os.walk בתוך for כדי לרוץ על כל הקבצים והתיקיות בצורה רקורביסית בעץ התיקיות.
2. כל כניסה לגוף הלולאה אומרת שנכנסתי לתיקיה חדשה. אני מפעיל את הפונקציה add_node כדי להוסיף צומת חדש בעץ בדיוק במקום שמתאים לאותה תיקיה. פונקציית add_node מחזירה את מערך הילדים של התיקיה שהיא הרגע הוסיפה.
3. אחרי זה אני משתמש במשתנה files שחוזר מאותו איטרטור כדי להוסיף גם את כל הקבצים ולמלא באמצעותם את מערך הילדים.
הפונקציה add_node מטיילת בצורה רקורביסית ב Dictionary לפי המבנה של מילון הקבצים ומוסיפה תיקיה חדשה למקום הנכון. היא משתמשת ב key של כל אוביקט כדי לדעת לאיזה נתיבים לנווט בתוך ה Dictionary.1 419
# עוד דוגמה ל os.walk ב Python
הפונקציה os.walk מספקת דרך קלה לעבור על כל עץ הקבצים והתיקיות החל מנקודת התחלה מסוימת. בדוגמה בפוסט היום אשתמש בה כדי לבנות אוביקט JSON שמתאים לעץ קבצים ותיקיות בפורמט מסוים.
## מה אנחנו בונים
נתון עץ קבצים ותיקיות שנראה כך:
.
├── a
│ ├── b
│ │ ├── 00
│ │ │ └── 11
│ │ │ ├── 22
│ │ │ ├── 33
│ │ │ └── 44
│ │ ├── c
│ │ │ ├── c1.txt
│ │ │ ├── c2.txt
│ │ │ └── d
│ │ └── ff
│ │ └── gg
│ ├── f1.txt
│ └── f2.txt
└── build_json.py
ואני רוצה להפוך אותו לאוביקט JSON שנראה כך:
[
{
"type": "f",
"title": "build_json.py",
"key": "build_json.py",
"children": []
},
{
"type": "d",
"key": "a",
"title": "a",
"children": [
{
"type": "f",
"title": "f1.txt",
"key": "a/f1.txt",
"children": []
},
{
"type": "f",
"title": "f2.txt",
"key": "a/f2.txt",
"children": []
},
{
"type": "d",
"key": "a/b",
"title": "b",
"children": [
{
"type": "d",
"key": "a/b/00",
"title": "00",
"children": [
{
"type": "d",
"key": "a/b/00/11",
"title": "11",
"children": [
{
"type": "d",
"key": "a/b/00/11/33",
"title": "33",
"children": []
},
{
"type": "d",
"key": "a/b/00/11/44",
"title": "44",
"children": []
},
{
"type": "d",
"key": "a/b/00/11/22",
"title": "22",
"children": []
}
]
}
]
},
{
"type": "d",
"key": "a/b/ff",
"title": "ff",
"children": [
{
"type": "d",
"key": "a/b/ff/gg",
"title": "gg",
"children": []
}
]
},
{
"type": "d",
"key": "a/b/c",
"title": "c",
"children": [
{
"type": "f",
"title": "c1.txt",
"key": "a/b/c/c1.txt",
"children": []
},
{
"type": "f",
"title": "c2.txt",
"key": "a/b/c/c2.txt",
"children": []
},
{
"type": "d",
"key": "a/b/c/d",
"title": "d",
"children": []
}
]
}
]
}
]
}
]
כדי שיתאים לקומפוננטה שמציגה עץ בצורה גרפית על המסך. המבנה הולך ככה:
1. האוביקט הראשי הוא מערך של אוביקטים
2. כל אוביקט במערך מכיל 4 שדות: שדה type שיכול להיות d או f כדי לציין אם זה תיקיה או קובץ; שדה key שמכיל את הנתיב המלא לדבר (והוא ייחודי בכל העץ); שדה title שמכיל את שם הדבר ושדה בשם children שמכיל מערך של אוביקטים באותו מבנה בדיוק.
3. אם האוביקט הוא קובץ אז מערך הילדים שלו ריק.
4. אם האוביקט הוא תיקיה אז מערך הילדים שלו מייצג את הקבצים והתיקיות שנמצאים בתוך התיקיה.
מוזמנים לנסות לבנות את המנגנון ואחרי זה להמשיך לקוד הדוגמה שלי.
## התוכנית
עם os.walk אנחנו יכולים לכתוב כל תוכנית שעוברת על עץ הקבצים בצורה פשוטה ובלי ללכת לאיבוד ביער. הפונקציה מחזירה איטרטור שבכל איטרציה בוחר תיקיה אחרת ומחזיר את רשימת הקבצים והתיקיות באותה תיקיה שהוא בחר. האיטרציה היא לפי הסדר האמיתי של הקבצים והתיקיות ולכן נוח להשתמש בה כדי לבנות את אוביקט ה JSON.
זה היה הפיתרון שלי:
import os, json
def add_node(doc, path, key, pos=''):
if len(path) == 0:
return doc
try:1 419
# האומנות של להשיג דברים בלתי אפשריים
יש ימים שאני יכול לראות בבירור את הדרך מפה לשם, אפילו אם "שם" זה מקום מאוד רחוק מאיפה שאני נמצא. אני בטוח שגם לכם יש נקודות הארה כאלה שפתאום רואים במכה אחת סידרה של צעדים שאם רק אצליח בהם אגיע לדבר הזה שנראה כרגע מאוד רחוק ובלתי אפשרי.
ויש ימים שבהם אני לא רואה את הדרך כולה. שהבהירות נעלמת והמרחק נראה בלתי ניתן לגישור. האמת שרוב הימים הם כאלה - ובגלל זה דברים מסובכים נראים הרבה פעמים בלתי אפשריים.
הדרך לצאת מהפרדוקס כשרוצים להשיג דבר בלתי אפשרי היא קודם כל להבין שהדבר הזה הוא כן אפשרי - פשוט מסובך. ולהשיג דברים מסובכים זה הרבה יותר קל מלהשיג דברים בלתי אפשריים. וגם די קל להבין שמשהו הוא אפשרי פשוט מסובך, פשוט מסתכלים על אנשים אחרים שהצליחו אותו או על ההיסטוריה שלכם בה הצלחתם דברים דומים. אני יודע, אנחנו רגילים לחשוב שמשהו הוא אפשרי רק אם אנחנו רואים את הדרך המלאה מכאן לשם. אבל זאת פריווילגיה שאנשים שלומדים לבד לא יכולים לסמוך עליה.
הצעד השני הוא להבין שהדרך להשיג דברים מסובכים היא קשה, ארוכה ומסובכת (נו, אחרת לא היינו צריכים פוסט על זה), ורוב הזמן שנבלה בה יהיה בימים של ערפל, בימים בהם אי אפשר לדמיין אפילו את הדרך מכאן לשם. בימים בהם לא נראה שיש קשר בין המשימה שאתה עושה עכשיו לבין הדבר הבלתי אפשרי שאתה רוצה להשיג.
הדרך ללמוד מיומנות חדשה היא כמו נסיעה על כביש חשוך וערפילי. אפשר לראות כמה מטרים קדימה - זאת המשימה הלימודית הנוכחית שאתה עובד עליה - אבל קשה מאוד לראות איך אותה משימה מביאה בסוף התהליך לתוצאה הרצויה. ובגלל זה כל כך קשה להתמיד במשימה הנוכחית, ובמיוחד אם היא משעממת, קשה, מתסכלת, מכעיסה או יוצרת רגשות שליליים אחרים מכל סוג. וכן משימות קשות נוטות לעשות את זה.
לכן האומנות של להשיג דברים בלתי אפשריים יכולה להסתכם בשתי נקודות:
1. כשיש רגע של בהירות, או אם יש חבר שכבר כתב תוכנית שאתם יכולים לקחת - תבנו לכם תוכנית עבודה מסודרת, מפורטת וכתובה. היא חייבת להיות כתובה כי מהראש היא תימחק לכם מהר מאוד.
2. בכל שאר הזמן תעבדו תמיד על המשימה הבאה בתוכנית עבודה. מותר להחליף תוכנית עבודה רק ברגע של בהירות או אם סיימתם את כל המשימות שבה.
שיעור אחרי שיעור, משימה אחרי משימה וצעד אחרי צעד הם הדרך שלנו להשיג גם את הדברים שנראים היום בלתי אפשריים.
1 419
1. פוד הוא קונטיינר. או לפחות לתקופה הקרובה יהיה לנו נוח מאוד לחשוב עליו בתור קונטיינר. פוד זה משהו שרץ על שרת מתוך אימג', וקוברנטיס יוודא שאותו פוד ימשיך לרוץ כל עוד צריך אותו ואם הוא מתרסק קוברנטס ירים אחד חדש במקומו.
2. דיפלוימנט זה מה שיוצר את הפודים, כלומר זאת התקנה של תוכנה. מרגע ששלחתם Deployment לקוברנטיס הוא (היא?) ינסה ליצור קונטיינרים (פודים) מתוך התיאור שמופיע ב Deployment.
3. סרביס זאת הדרך שלנו להתחבר לפוד. תזכרו שלקוברנטיס יש קלאסטר שלם של שרתים עליו הוא יכול להריץ את הפוד שלכם, וגם אם ישעמם לו או אם אחד השרתים יהיה עמוס קוברנטיס יוכל להזיז את הפוד משרת לשרת. סרביס זה סוג של מיפוי פורטים שאומר שכשמתחברים לפורט מסוים ב IP הציבורי של הקלאסטר אז קוברנטיס צריך לחבר אתכם לפוד המתאים, איפה שהוא לא רץ כרגע.
כל אחד מהשלושה יכול להיות מיוצג על ידי קובץ YAML שמתאר את המאפיינים שלו. ראינו כבר קובץ YAML עבור Deployment, ואם אתם סקרנים אז הנה קובץ YAML פשוט עבור סרביס:
apiVersion: v1
kind: Service
metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: nginx
name: nginx
spec:
type: NodePort
ports:
- name: "80"
port: 80
targetPort: 80
selector:
io.kompose.service: nginx
status:
loadBalancer: {}
הקובץ מתאר סרביס שמתחבר ל Deployment של ה nginx שיצרנו קודם. המפתח להבין את הקובץ הוא הבלוק שמתחיל במילה selector, שקובע לאיזה תוכנית הסרביס הזה צריך לחבר אותנו, ו port שקובע לאיזה פורט צריך להתחבר שם.
# קוברנטיס והענן: התקני קלט ופלט
עד לפה ראינו שקוברנטיס היא (הוא?) מערכת הפעלה לקלאסטרים. ראינו שהתפקיד שלה הוא להפעיל תוכניות, כלומר אימג'ים, ולייצר מהם קונטיינרים ולוודא שאותם קונטיינרים ממשיכים לרוץ ומקבלים את משאבי השרת שהם צריכים.
היבט נוסף בו אנלוגיית מערכת ההפעלה תעזור לנו להבין את קוברנטיס הוא הגישה להתקני קלט ופלט. מערכת הפעלה כזכור לכם מחברת את התוכניות שרצות על המחשב עם החומרה של המחשב באמצעות דרייברים. תוכנית לא כותבת ישירות לדיסק אלא בתיווך מערכת ההפעלה. תוכנית לא קוראת ישירות מהמקלדת אלא בתיווך מערכת ההפעלה.
באותו האופן קוברנטיס מתווך את הגישה של הקונטיינרים למשאבים חיצוניים שנשמרים בענן. לדוגמה:
1. מערכת הקבצים של קונטיינר לא שורדת כשהקונטיינר נסגר. בתיווך קוברנטיס הקונטיינר יכול לקבל Volume שזה מקום על הדיסק שאפשר לכתוב אליו והמידע יישמר גם כשהקונטיינר ייסגר.
2. קונטיינר יכול לקבל מידע סודי באמצעות מנגנון Secrets של קוברנטיס.
3. קונטיינר יכול לקבל משתני סביבה שמועברים אליו מתוך קוברנטיס.
קלאסטרים שונים יכולים לממש את ההתקנים החיצוניים בדרכים שונות: לדוגמה קלאסטר קוברנטיס שרץ על ענן של Azure יכול לחבר Volume לכונן אחסון בענן של אזור, וקלאסטר אחר שרץ על אמזון יחבר את ה Volume לכונן אחסון בענן של אמזון. מבחינת התוכנית שלנו שרצה בתוך הקלאסטר הגישה היא זהה בשני המקרים. אנחנו מקבלים Volume וקוברנטיס אחראי על מיפוי אותו Volume להתקן האיחסון החיצוני.
## איך ממשיכים מכאן
ואם ההקדמה הזאת עשתה לכם חשק להמשיך וללמוד קוברנטיס תשמחו לשמוע שיש אינסוף מדריכים טובים ברשת כולל כאלה שיתנו לכם לתרגל בצורה אינטרקטיבית. אני ממליץ להתחיל מהקישור הזה:
https://kubernetes.io/docs/tutorials/kubernetes-basics/ שכולל סידרה של 6 טוטוריאלס אינטרקטיביים לעבודה עם קוברנטיס והתקנת יישומים באמצעותו.
עמוד התיעוד הראשי של קוברנטס הוא גם מקום לימוד מצוין אם אתם אוהבים לקרוא מגילות של טקסט (נו אל תדאגו יש גם תמונות). קישור:
https://kubernetes.io/docs/concepts/1 419
# מושגים בסיסיים בקוברנטיס
שמעתם על קוברנטיס אבל לא בטוחים מה זה? רוצים ללמוד ולא יודעים אפילו איפה להתחיל? הפוסט הזה בשבילכם ואני מקווה שאצליח לעשות קצת סדר באוקיינוס שנקרא קוברנטיס, לפחות בהתחלה שלו.
## קוברנטיס היא מערכת הפעלה לקלאסטרים
אתם יודעים כבר שלמחשבים יש מערכות הפעלה: יש Windows, Linux ו OS/X; לטלפונים יש את Android ו iOS ועוד כמה וגם מכשירי IOT הרבה פעמים מגיעים עם איזושהי מערכת הפעלה מבוססת לינוקס.
מה שלא כל כך אינטואיטיבי הוא המחשבה שאפשר לייצר מערכת הפעלה גם למשהו שהוא לא מחשב. למשהו גדול יותר ממחשב. במקרה שלנו לאוסף של שרתים. אוסף של שרתים נקרא Cluster וקוברנטיס הוא (היא?) מערכת הפעלה לקלאסטרים.
מערכת הפעלה רגילה של מחשב אחראית על הפעלת תוכניות וניהול וחלוקת משאבי המערכת לתוכניות שרצות. היא גם דואגת שתוכניות לא ידרסו אחת את השניה, ומספקת לתוכניות גישה קלה ואחידה להתקני הקלט פלט של המחשב. כלומר אם יש לי תוכנית שכתובה למערכת הפעלה Windows, אז זה לא משנה לה איזה סוג מקלדת מחוברת למחשב או אפילו אם זו מקלדת אלחוטית. התוכנית מקבלת את הקלט באמצעות התיווך של מערכת ההפעלה ויכולה לעבוד באותה צורה עם כל המקלדות.
מערכת הפעלה של קלאסטר לוקחת קונספט דומה, ומאפשרת לנו לכתוב "תוכניות" שרצות על הקלאסטר. תוכנית שרצה על קלאסטר תהיה בדרך כלל סוג של סרביס שמקבל בקשות ושולח תשובות - כמו Web Application או בסיס נתונים. קוברנטיס תפעיל את היישומים האלה, תדאג לחלק להם את משאבי הקלאסטר - כלומר תחליט מי ירוץ על איזה שרת ואיזה חלק מהשרת הוא יקבל - ותדאג להשאיר אותם בחיים ולהפעיל אותם מחדש אם אחד מהם מתרסק.
וכמו שתוכנית רגילה לא צריכה להכיר את החומרה עצמה עליה היא רצה, כי היא תמיד עובדת בתיווך מערכת ההפעלה, כך תוכנית שרצה בתוך קוברנטיס לא חייבת להכיר את השרתים עצמם עליהם היא רצה, ויכולה להניח שהם מתנהגים בצורה מסוימת - לפי הממשק שהתוכנית מקבלת מקוברנטיס, כלומר ממערכת ההפעלה.
## תוכניות הן אימג'ים, תהליכים הם קונטיינרים
כמו שמערכת הפעלה של מחשבים יודעת לקחת קבצי הפעלה - לדוגמה ב Windows קבצי EXE - ולהפעיל אותם, כלומר ליצור מהם תהליכים על המחשב, כך קוברנטיס יודעת לקחת קבצי הפעלה שהם OCI Images וליצור מהם תהליכים שהם Containers.
אימג' הוא כל מה שאתם מכירים מעבודה עם Docker או Podman. זה מידע בינארי שמתאר אפליקציה מסוימת ואת כל התלויות של אותה אפליקציה. אימג'ים מאוחסנים ב Registries וגם לפני קוברנטיס ידענו להפעיל קונטיינר מתוך אימג' עם דוקר לדוגמה באמצעות הפעלת:
$ docker run hello-world
קוברנטיס בתור מערכת הפעלה יודע (יודעת?) לקחת אימג'ים כמו ה hello-world שלנו ולהריץ אותם על הקלאסטר, כלומר לבנות מהם קונטיינר ולתת לאחת המכונות בקלאסטר להריץ את הקונטיינר הזה. קוברנטיס גם תשים לב אם הקונטיינר מתרסק ותדע להפעיל קונטיינר חדש מאותו אימג'.
במערכת הפעלה רגילה אנחנו משתמשים בתוכנת Installer כדי להתקין את התוכניות שלנו. זה לא נדיר להוריד מהרשת קובץ בסיומת msi, להריץ אותו וכך לקבל תוכנה כזאת או אחרת זמינה לנו על המחשב. בקוברנטיס ההתקנה מתבצעת באמצעות קבצי טקסט בפורמט YAML שמתארים לקלאסטר מה האימג' שאנחנו רוצים להתקין ומה דרישות המערכת שהוא צריך.
לדוגמה הקובץ הבא הוא YAML שמגדיר Deployment (שזו התקנה) של אימג' של שרת הווב nginx:
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: nginx
strategy: {}
template:
metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.25.0 (a70f80cc)
creationTimestamp: null
labels:
io.kompose.service: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
resources: {}
restartPolicy: Always
status: {}
## פודים, דיפלומנטס וסרביסים
שלושה מושגים שכדאי להכיר כבר מתחילת העבודה שלכם עם קוברנטס הם Pod, Deployment ו Service (יש עוד הרבה, אבל את שלושת אלה נצטרך ממש מהשניה הראשונה):1 419
הי הקישור לקריאה מהאתר לא עבד טוב היום זה הקישור
https://www.tocode.co.il/blog/2021-12-css-sticky-overflow
1 419
# היום למדתי: CSS Sticky ו overflow לא הולכים טוב יחד
מאפיין 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;
}
ובקודפן:
<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/qBPqXNQ?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/qBPqXNQ">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>
וקל לראות שגלילה של המסך לא באמת משאירה את שורת הכותרת "דבוקה" לראש העמוד. מה קורה פה?
התשובה מסתתרת במשפט הבא מתוך 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. הנה הקודפן המתוקן:
<iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/gOGLxrO?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true">
See the Pen <a href="https://codepen.io/ynonp/pen/gOGLxrO">
Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>)
on <a href="https://codepen.io">CodePen</a>.
</iframe>1 419
# מתי מתחילים ואיפה מוצאים זמן?
אלה שתי שאלות שונות. אל תנסו לחבר ביניהן.
הרבה פעמים אנחנו חושבים על משימה לימודית ומבינים כמה השקעה צריך בשביל לבצע אותה, ומבינים שאין לנו עכשיו את המשאבים והזמן לעשות את זה, ואז דוחים את ההתחלה. נכון, אני אומר לעצמי, אני יכול למצוא שעה בשבוע ללמוד CSS, אבל אני גם יודע ששעה בשבוע לא תספיק בשביל להגיע להישג משמעותי אז עדיף להשקיע את השעה הפנויה שלי בשבוע במשהו אחר.
התוצאה של זה היא דחיה אינסופית של דברים שאנחנו רוצים לעשות כי אף פעם אי אפשר למצוא עשר שעות בשבוע להשקיע בלימוד CSS.
מי שידפדף מספיק אחורה בבלוג פה יגלה שלפני שכתבתי כל יום כתבתי פעמיים בשבוע, ולפני זה כתבתי פעם בשבוע. ההחלטה להתחיל לכתוב בלוג לא היתה מתאפשרת אם מראש הייתי חושב שצריך לכתוב פוסט כל יום. באותה נקודה שרק התחלתי, כתיבה כל יום נראתה לי כמו השקעה מטורפת. פעם בשבוע היה משהו הרבה יותר סביר.
הדעה שלנו לגבי נושאים, חלוקת זמנים והיכולת למצוא זמן לדברים משתנה עם הזמן. ככל שאנחנו עמוק יותר בתוך פרויקט כך קל יותר להמשיך ולמצוא יותר זמן עבורו. התחלה דורשת הרבה יותר אנרגיה מההמשך ושעה בתחילת הפרויקט היא הרבה יותר "קשה" משעה באמצע שלו.
אם יש לכם היום שעה ביום ללמוד CSS זה מהמם. אבל אם אתם רוצים לדעת CSS טוב יותר ויש לכם רק שעה בשבוע בשביל זה - אל תחכו. ככל שתתקדמו עם הלימוד תראו ששעה בשבוע הופכת לשעתיים, שעתיים לארבע ובסוף מגיעים לשעה ביום בקלות. מתחילים היום. את הזמן אפשר למצוא בהמשך.
¡Ya disponible! Investigación de Telegram 2025 — los principales insights del año 
