ru
Feedback
ToCode

ToCode

Открыть в Telegram

טיפים קצרים למתכנתים מאת ינון פרק

Больше
1 419
Подписчики
-124 часа
Нет данных7 дней
-230 день
Архив постов
ToCode
1 419
# טיפ טייפסקריפט: חתימה של פונקציה מתוך Literal Types מנגנון ה Literal Types ואוביקטים בטייפסקריפט הוא חמוד במיוחד כשכותבים בספריה כמו רידאקס, כיוון שהוא מאפשר להגדיר מגבלות על אוביקטים שפונקציה יכולה לקבל. דוגמה קלאסית תהיה הגדרת כמה סוגים של אירועים ופונקציה שיודעת לקבל אוביקט "אירוע" שמתאים לאחד מהם. הקוד יראה בערך כך:
type Event =
  | { type: 'login', payload: { username: string }}
  | { type: 'logout'}
  | { type: 'sendMessage', payload: { to: string, text: string }};

function handle(event: Event) {}
ועכשיו שורה כזאת תתקמפל:
handle({ type: 'login', payload: { username: 'yay' }});
אבל שורה כזאת לא תתקמפל:
handle({ type: 'login', payload: { to: 'yay', text: 'abc' }});
כי אוביקט payload לא מתאים למה שהפונקציה מצפה מאירוע לוגין. מסתבר שאפשר להרחיב את המנגנון ולהשתמש בו גם לבניית פונקציה גנרית - כלומר לכתוב פונקציה שתקבל בתור פרמטר ראשון את ה type של האירוע ופרמטר שני את ה payload, ותבדוק שהכל מתאים. בואו ננסה את זה. ניסיון ראשון עשוי להיראות כך:
type Event =
  | { type: 'login', payload: { username: string }}
  | { type: 'logout'}
  | { type: 'sendMessage', payload: { to: string, text: string }};

function handle(eventType: Event["type"], eventPayload: Event["payload"]) {}
אבל זה לא מתקמפל כי לא לכל האירועים יש payload. אם אני מוסיף payload גם לאירוע הניתוק אני מצליח לקמפל את הקוד:
type Event =
  | { type: 'login', payload: { username: string }}
  | { type: 'logout', payload: undefined }
  | { type: 'sendMessage', payload: { to: string, text: string }};

function handle(eventType: Event["type"], eventPayload?: Event["payload"]) {}
אבל אני לא מקבל את מה שרציתי - טייפסקריפט יקמפל את השורה הזאת למרות שבאירוע login כן צריך להעביר שם משתמש:
handle("login");
בעצם מה שהקוד שלי עשה זה ליצור פונקציה שמקבלת בתור פרמטר ראשון משהו שמופיע ב type של Event, ובתור פרמטר שני משהו שמופיע ב payload, בלי להתאים ביניהם. בשביל ההתאמה בין שני הפרמטרים אני רק צריך להפוך את הפונקציה ל Generic. זה ייתן לי גישה לטיפוסים שבאמת עבר לתוך Event["type"] ואז בעזרתו אני יכול לצמצם את האיחוד:
type Event =
  | { type: 'login', payload: { username: string }}
  | { type: 'logout', payload: undefined }
  | { type: 'sendMessage', payload: { to: string, text: string }};

function handle<E extends Event, EventType extends E["type"]>(
  eventType: EventType,
  eventPayload: Extract<Event, { type: EventType }>["payload"]
) {}

handle("login", { username: "ynon" });
handle("logout", undefined);
handle("sendMessage", { to: "ynon", text: "hi ;)"});
עכשיו שלושת הקריאות מתקמפלות, אבל קריאות שלא מתאימות לחתימה לא יתקמפלו. למשל זה לא יעבור:
handle("login", { to: "me", text: "bye" });
המצב טוב ואנחנו כמעט בסיום אבל עדיין יש דבר אחד שלא יושב טוב בקוד שכתבנו - והוא הצורך לכתוב undefined בתוך הקריאה עם logout. בשביל מה זה טוב? למה אני צריך לעבוד כל פעם שמפעיל את הפונקציה רק בשביל שטייפסקריפט יהיה שמח? עם עוד קצת מאמץ אפשר להיפטר גם מזה ולתקן את הפונקציה כך שהפרמטר השני יהיה אופציונאלי אם לא מוגדר עבורו ערך בטיפוס Event. מה שהייתי רוצה לכתוב זה:
function handle<E extends Event, EventType extends E["type"]>(
  eventType: EventType,
  eventPayload: Extract<Event, { type: EventType }>["payload"] extends undefined
    ? "MAKE EVENT PAYLOAD OPTIONAL"
    : "USE THE VALUE FROM EXTRACT ..."
) {}
וגם זה אפשרי בטייפסקריפט עם קצת וודו, הפעם אני משתמש בסימן שלוש נקודות משמאל למפתח, ומשתמש בערך שהוא מערך של איבר אחד, ואז אני יכול להשתמש בסימן שאלה בתוך אותו מערך. הוספתי גם שם ל Extract בשביל לא לכתוב אותו פעמיים וקיבלתי:
type Event =
  | { type: 'login', payload: { username: string }}
  | { type: 'logout', payload: undefined }
  | { type: 'sendMessage', payload: { to: string, text: string }};

function handle<
  E extends Event,
  EventType extends E["type"],

ToCode
1 419
this.el.style.top = `${this.y - 10}px`;
    }

    remove() {
        document.body.removeChild(this.el);
    }
}


function escaping(shape: Circle) {
    shape.el.addEventListener('click', shape.move.bind(shape));
    return shape;
}

function animate(shape: Circle) {
    shape.el.style.left = "0px";
    shape.el.style.top = "0px";
    
    shape.move = () => {
        shape.x = Math.floor(Math.random() * window.innerWidth);
        shape.y = Math.floor(Math.random() * window.innerHeight);
        anime({
            targets: shape.el,
            translateX: shape.x,
            translateY: shape.y,
            duration: 800
          });
    }
    return shape;
}

const c1 = escaping(new Circle());
const r1 = squarify(escaping(new Circle()));
const c3 = escaping(animate(new Circle()));
מעבר לארכיטקטורה שבה אני "מלביש" תכונות על אוביקטים, במקום כזו שבה אני מגדיר תכונות למחלקות, הופך את הקוד שלי להרבה יותר גמיש כיוון שאפשר כל הזמן ליצור צירופים חדשים בלי ליצור את הצירופים הקודמים שבניתי.

ToCode
1 419
עכשיו אני יכול לעדכן את קוד הריבוע כדי לבטל את האנימציה. אני יכול גם להוציא מחלקה שלישית של "צורה בורחת" עם הקוד המשותף ביניהם, ולעדכן רק את העיגול לזוז עם אנימציה, אבל שני הפיתרונות לא באמת עוזרים לטווח הרחוק. ככל שהמערכת תגדל, יהיו לנו יותר מאפיינים ייחודיים לכל צורה, והיררכיית הירושה שנבנה לא תצליח להכיל אותם. אפשר לדמיין שאחד הלקוחות רוצה שהעיגול ישנה צבע כל פעם שקופץ למקום חדש, או שצורות מסוימות יזוזו כשלוחצים על כפתור מסוים במקלדת ואחרות לא, או שיהיו לנו צורות שבנויות מיותר מ div אחד, ודרישות רבות נוספות. מה שיותר גרוע הוא שהמבנה שיצרנו מחייב שכל עיגול יקבל את כל התכונות של "עיגול" כך שעכשיו כל העיגולים במערכת יתחילו לזוז באנימציה. ככל שנרצה לייצר עיגולים מסוימים עם אנימציה ואחרים בלי, היררכיית הירושה שלנו רק תתנפח עוד יותר ויהיה לנו קשה להבין על איזה צורות משפיע כל עדכון. ## מה אפשר לעשות במקום דרך טובה יותר היא להוסיף את המאפיינים המשותפים בזמן ריצה לאוביקטים במקום למחלקות. בצורה כזאת יש לנו גמישות הרבה יותר גדולה ליצור עיגולים מסוימים שזזים באנימציה ואחרים שלא, ריבועים מסוימים שמשנים צבע כל פעם שקופצים ואחרים שלא, צורות מסוימות שצריכות להיבנות רק עם div אחד ואחרות שצריכות יותר מ div אחד. בשביל "להלביש" התנהגות על צורה כל מה שאני צריך זה פונקציה שמקבלת צורה ומחזירה את הצורה עם ההתנהגות החדשה שלה. אני יכול לאפיין ככה אפילו את ההתנהגות הבסיסית של להיעלם ולהופיע במקום חדש כשלוחצים עליך, וכמובן את האנימציה, שינוי הצבע או כל התנהגות אחרת. אני חוזר לנקודת ההתחלה עם העיגול הבורח, והפעם אצור את הריבוע בדרך אחרת. במקום ליצור מחלקה שיורשת מהעיגול אני כותב פונקציה:
function squarify(shape: EscapingCircle) {
    shape.el.style.borderRadius = "0";
    return shape;
}
בשביל שהקוד יתקמפל צריך לעדכן את מאפייני הגישה של EscapingCircle:
class EscapingCircle {
    public el: HTMLDivElement;
    public x: number = 0;
    public y: number = 0;
    public colors = ['red', 'blue', 'green', 'purple', 'orange', 'pink', 'yellow'];
    ...
ובשביל ליצור ריבוע ועיגול אני יכול להפעיל:
const c1 = new EscapingCircle();
const r1 = squarify(new EscapingCircle());
עכשיו אנחנו יכולים להמשיך באותה דרך לממש גם את ההתנהגויות החדשות הנוספות. ניסיון ראשון שלי לממש את האנימציה יכול להיראות כך:
function animate(shape: EscapingCircle) {
    shape.move = () => {
        shape.x = Math.floor(Math.random() * window.innerWidth);
        shape.y = Math.floor(Math.random() * window.innerHeight);
        anime({
            targets: shape.el,
            translateX: shape.x,
            translateY: shape.y,
            duration: 800
          });
    }
    return shape;
}
אבל זה עדיין לא עובד. במחלקה EscapingCircle הבנאי כבר קושר את האירועים בשורה:
this.el.addEventListener('click', this.move.bind(this));
ולכן כשהפונקציה animate נקראת היא משנה את הפונקציה move של הצורה, אבל זה לא עוזר כי אף אחד לא מפעיל את move. קוד הטיפול באירוע חובר לפני ש animate הופעלה. וכן אני יודע לכתוב קוד שיסיר את החיבור לאירוע click וייצור חיבור חדש, אבל יותר חכם יהיה לחזור לקוד של EscapingCircle ולעדכן אותו כדי שיהיה יותר קל להרחבה. אפשר למשל להוציא את ההתנהגות של "לברוח כשלוחצים עליך" לעוד פונקציה חיצונית:
class Circle {
    public el: HTMLDivElement;
    public x: number = 0;
    public y: number = 0;
    public colors = ['red', 'blue', 'green', 'purple', 'orange', 'pink', 'yellow'];

    constructor() {
        this.el = document.createElement('div');
        this.el.style.width = '20px';
        this.el.style.height = '20px';
        this.el.style.borderRadius = '20px';        
        this.el.style.background = this.colors[Math.floor(Math.random() * this.colors.length)];
        this.el.style.position = 'absolute';
        this.move();

        document.body.appendChild(this.el);
    }

    move = () => {
        this.x = Math.floor(Math.random() * window.innerWidth);
        this.y = Math.floor(Math.random() * window.innerHeight);
        this.el.style.left = `${this.x - 10}px`;

ToCode
1 419
# ירושה? לא בבית ספרנו אחת התבניות הכי פופולריות לשיתוף קוד במערכות תכנות מונחה עצמים נקראת ירושה, אבל למרות הפופולריות שלה ברוב המקרים ירושה היא הפיתרון הלא נכון וגורמת ליותר נזק מתועלת. בואו נראה דוגמה קטנה איך ולמה ירושה נשברת ומה אפשר לעשות במקום. ## נקודת ההתחלה נניח שיש לנו כבר מערכת הכתובת בגישה מונחית עצמים ובתוכה מחלקה עבור כדור-בורח. כדור בורח הוא פשוט עיגול שמופיע על המסך וכל פעם שלוחצים עליו הוא בורח למקום אחר. מימוש לדוגמה בטייפסקריפט יכול להיראות כך:
class EscapingCircle {
    private el: HTMLDivElement;
    private x: number = 0;
    private y: number = 0;
    private colors = ['red', 'blue', 'green', 'purple', 'orange', 'pink', 'yellow'];
    constructor() {
        this.el = document.createElement('div');
        this.el.style.width = '20px';
        this.el.style.height = '20px';
        this.el.style.borderRadius = '20px';        
        this.el.style.background = this.colors[Math.floor(Math.random() * this.colors.length)];
        this.el.style.position = 'absolute';
        this.move();

        this.el.addEventListener('click', this.move.bind(this));

        document.body.appendChild(this.el);
    }

    move() {
        this.x = Math.floor(Math.random() * window.innerWidth);
        this.y = Math.floor(Math.random() * window.innerHeight);
        this.el.style.left = `${this.x - 10}px`;
        this.el.style.top = `${this.y - 10}px`;
    }

    remove() {
        document.body.removeChild(this.el);
    }
}

const c1 = new EscapingCircle();
הכדור הזה נמצא בשימוש בכמה מקומות במערכת וכולם מאוד מרוצים ממנו, עד שאחד הלקוחות מגיע עם בקשה קצת מוזרה - הוא ישמח לקבל "ריבוע בורח", שזה בדיוק כמו עיגול בורח אבל בצורת ריבוע. ## אני יודע! אשתמש בירושה כמו שיודעים כל מתכנת ומתכנתת הקוד הכי טוב הוא קוד שלא צריך לכתוב אותו, וירושה מאפשרת לי לפתור את בעיית הריבוע כמעט בלי לכתוב קוד. זו בלבד סיבה מאוד מפתה להשתמש במנגנון: 1. אני משנה את מאפיין הגישה של this.el מפרטי למוגן. 2. אני יורש מהעיגול הבורח ומוסיף לבנאי שורה שמעדכנת את הסטייל של האלמנט לריבוע. זה הקוד של הריבוע:
class EscapingSquare extends EscapingCircle {
    constructor() {
        super();
        this.el.style.borderRadius = "0";
    }
}
ובמחלקה של העיגול אני צריך לשנות את הגדרת שדה המידע this.el באופן הבא:
class EscapingCircle {
    protected el: HTMLDivElement;
    ...
הכלום קוד שכתבתי עובד, הלקוח מרוצה ואפשר ללכת לים. כן? ## דרישה חדשה אחרי הפיתרון המבריק מגיע הטלפון הבא מהלקוח שמשתמש בעיגול הנעלם. הוא היה רוצה שהעיגול שלו יתחיל לזוז באנימציה. אין בעיה, אני מכיר את הספריה anime.js, אני רק צריך להוסיף אותה לפרויקט עם:
$ npm install --save animejs @types/animejs
ולעדכן את קוד העיגול כדי שיזוז עם אנימציה:
import anime from 'animejs/lib/anime.es.js';

class EscapingCircle {
    protected el: HTMLDivElement;
    private x: number = 0;
    private y: number = 0;
    private colors = ['red', 'blue', 'green', 'purple', 'orange', 'pink', 'yellow'];
    constructor() {
        this.el = document.createElement('div');
        this.el.style.width = '20px';
        this.el.style.height = '20px';
        this.el.style.borderRadius = '20px';        
        this.el.style.background = this.colors[Math.floor(Math.random() * this.colors.length)];
        this.el.style.position = 'absolute';
        this.move();

        this.el.addEventListener('click', this.move.bind(this));

        document.body.appendChild(this.el);
    }

    move() {
        this.x = Math.floor(Math.random() * window.innerWidth);
        this.y = Math.floor(Math.random() * window.innerHeight);
        anime({
            targets: this.el,
            translateX: this.x,
            translateY: this.y,
            duration: 800
          });
        }

    remove() {
        document.body.removeChild(this.el);
    }
}
ראיתם מה קרה שם? אפילו שלא שיניתי שורת קוד בודדת במחלקה של הריבוע הבורח, גם הוא קיבל את האנימציה.

ToCode
1 419
עוד עשר דקות וובינר על פלאסק בייס מוזמנים להצטרף https://us06web.zoom.us/j/81695264435?pwd=ZFZURUE1WmtTSUxRbHZKeHZiTmRyQT09

ToCode
1 419
# מחשבות על פריצה למחשבי פיתוח ככל שנחשפים הפרטים על הפירצה ל lastpass, קל לראות את הדמיון לפריצה ל SolarWinds ושתיהן מזמינות אותנו להתעורר ולהגן טוב יותר על סביבות הפיתוח שלנו. במקרה של SolarWinds האקרים השיגו גישה לסביבת הפיתוח, הוסיפו קוד זדוני למוצר ודרכו פרצו לאינסוף חברות וארגונים ממשלתיים שהסתמכו על אותו מוצר. במקרה של LastPass אנחנו עדיין לא יודעים מה היקף הנזק, וכרגע רק ידוע שהאקרים ישבו במשך 4 ימים ברשת הפנימית בסביבת הפיתוח של החברה. מחשבי פיתוח נתפסים בהרבה מקומות בתור אזור "חופשי", מחשבים שאפשר להתקין עליהם הכל כדי שמפתחים יוכלו לשחק עם טכנולוגיות חדשות. התוצאה שאלה מחשבים שמאוד קשה להגן עליהם. הנה כמה רעיונות שכדאי ליישם עוד היום כדי להתחיל להתמודד עם אתגרי האבטחה שזה יוצר: 1. חתימה על קומיטים - כדאי לדרוש ממפתחים לחתום על קומיטים באמצעות pgp, ולהשתמש במפתח עם סיסמה. כך פורץ שלא יודע את הסיסמה, אפילו אם הצליח להיכנס לרשת ולהשתלט על מחשב פיתוח, לא יוכל להחדיר קוד זדוני ל Source Control. 2. הגבלת גישה למכונות בניה - רצוי לא לאפשר גישה ממחשב פיתוח למכונת בניה, ושהדרך היחידה שמתכנתים יוכלו להכניס קוד חדש היא דרך ה Source Control. 3. הגבלת גישה לסביבות פרודקשן או בדיקות - יש להשתמש רק במפתחות מוגנים בסיסמה כדי לגשת למכונות אחרות. הקלדת סיסמה לפני כל שימוש במפתח מבטיחה שגם אם פורץ השתלט על מחשב פיתוח לא יהיה לו לאן להמשיך משם. 4. שווה להזכיר למפתחים שהמחשבים שלהם אינם בטוחים ושלא כדאי שישמרו סיסמאות, מפתחות גישה או כל מזהה סודי אחר בשום מקום על המחשב. 5. קוד שנכנס ל Source Control לענף שממנו הולכים לבנות גירסה חייב לעבור Code Review על ידי מספר אנשי צוות אחרים. 6. ברמת שירותי ענן שווה להשתמש במצב גלישה פרטית כל פעם שנכנסים לממשקי ניהול מבוססי ווב של שירותי הענן של הפרודקשן ולסגור את הדפדפן כשמסיימים. ככל שנקדים להבין שהמחשב שלנו הוא מטרה, ושסביר להניח שהוא כבר נפרץ כדי להגיע ליעד יותר מעניין, כך נוכל לקחת את הצעדים כדי לעצור את התקיפה לפני שייגרם נזק אמיתי.

ToCode
1 419
# תזכורת לוובינר: פיתוח מערכות ווב עם flask-base אם רוצים לכתוב בינה מלאכותית, אלגוריתמיקה או אפילו סקריפטים לתחזוקת שרת פייתון היא האופציה הראשונה שעולה לראש. אבל בפיתוח ווב פייתון נראית כמו אופציה הרבה פחות מרגשת. לפייתון יש שני Web Frameworks מרכזיים בשם Django ו Flask. דג'נגו נחשב לפיתרון הוליסטי שייתן לכם תשובה לכל אתגר בפיתוח המערכת, אבל יותר קשה ללמוד אותו ויותר קשה להתאים אותו לכל Use Case בגלל שהוא כולל את הכל. ב Django משתמשים גם בתעשיה במספר חברות גדולות כולל אינסטגרם, דיסקאס, איוונט-ברייט ו Prezi. פלאסק הוא פיתרון הרבה יותר קטן שמאוד קל להתקין ולבנות בעזרתו ממשק ווב למערכות פייתון קיימות, אבל כשצריך פיצ'רים מתקדמים כמו שליחת אימייל, ניהול משימות ברקע, חיבור ל APIs חיצוניים או אפילו ניהול משתמשים אנחנו צריכים כבר להתחיל להוסיף לפלאסק ספריות נוספות, ולא תמיד ברור באיזה כלי הכי כדאי להשתמש. השימוש בפלאסק בתעשיה הוא מאוד נרחב, והרבה ממנו כדי לפתח כלים פנימיים או ממשקים פנימיים לכלים קיימים ב Python. בוובינר מחר ערן גולדמן מלכא יציג כלי בשם flask-base שנותן תבנית התחלה מקיפה יותר ליישומי פלאסק מהשלד הבסיסי של פלאסק. התבנית של פלאסק בייס כבר מתאימה לפיתוח מערכות ווב גדולות יותר וכוללת ניהול משתמשים, חיבור ל DB, טפסים, טיפול ב Assets, שליחת מיילים וביצוע משימות ברקע. אם בא לכם ללמוד איך לבנות מערכת ווב בזריזות ובשפה שאתם כבר מכירים ואוהבים, או ללמוד איך לבנות ממשק וובי לכלי פייתון קיים שיש לכם, הוובינר מחר יכול מאוד להתאים. ניפגש בזום בעשר בבוקר, קישור ישיר לכניסה יפורסם בקבוצת טלגרם וגם יישלח במייל לאלה מכם שיירשמו בצירוף תזכורת שעה לפני הפגישה. זה הקישור לפרטים נוספים והרשמה: https://www.tocode.co.il/workshops/120 נתראה בוובינר

ToCode
1 419
# שבע סיבות בגללן אנחנו לא כותבים בדיקות 1. אני לא סומך עליהן. ממילא אחרי כל שינוי אני מריץ את ה flow לבד לראות שהכל עובד. 2. הן לא שוות את ההשקעה. פיתוח עם בדיקות לוקח לי לפעמים פי 4 זמן מאשר פיתוח אותו פיצ'ר בלי הבדיקות, ולפיצ'רים קטנים זה בולט במיוחד. 3. הן מאטות אותי כשאני צריך לשנות קוד. פתאום בדיקות מסוימות לא עובדות ואני לא יודע למה ואם זו בעיה בבדיקות או בקוד האמיתי. 4. כשהבדיקות לא עובדות הודעות השגיאה שלהן מוזרות. כבר קרה לי כמה פעמים שישבתי שעות ואפילו ימים על הודעת שגיאה לא נכונה מתשתית הבדיקות כשבפועל בקוד לא היתה שום בעיה. 5. ספריות הבדיקות עצמן מתיישנות וקרה לי כבר ששידרגתי תלויות, וגירסה חדשה של ספריית הבדיקות דרשה ממני לשנות מאות שורות של קוד בדיקות כי משהו בממשק השתנה. 6. גם כשניסיתי לכתוב בדיקות בפרויקט עדיין היו הרבה באגים. אי אפשר לכסות בבדיקות את כל הדברים שמשתמש עשוי לבצע. 7. יותר כיף לכתוב פיצ'רים חדשים, או לתקן באגים שמפריעים למשתמשים. אם בצוות שלכם יש אנשים שמתנגדים לכתיבת בדיקות שווה לברר למה ולעזור להם להתגבר על המכשול הספציפי שעוצר אותם. כל 7 הסיבות כאן הן דאגות אמיתיות של מתכנתים אמיתיים שניסו לכתוב בדיקות וויתרו על זה. אל תטאטאו אותם מתחת לשטיח. הרבה יותר קל לכתוב בדיקות כשאנחנו יודעים למה לצפות, איזה מכשולים יגיעו ואיך אנשים אחרים מתמודדים איתם.

ToCode
1 419
# לא למבחן ״מה עדיף, לעשות שיעורי בית או להצליח במבחן?״ השאלה הזאת יכולה לנקז את כל הטעויות של מערכת החינוך ובמשפט אחד. כי ברור שהמטרה של בתי ספר וגם אוניברסיטאות היא שתעברו את המבחן; ברור ששיעורי הבית הם שם בשביל לעזור לכם להצליח במבחן, אבל לא כולם "צריכים" אותם, וברור שאף אחד לא יבלבל את המוח לתלמיד שמקבל מאיות בכל המבחנים גם אם הוא לא מכין שיעורי בית. אבל כשמסתכלים על החיים ומה חשוב קל לראות שהמטרה הפוכה: 1. אף אחד לא יודע מראש לאן כדאי להגיע ואיזו רמת מיומנות נצטרך. 2. לאנשים שונים יש מטרות שונות. 3. אנחנו מבלים הרבה יותר זמן ב"שיעורי בית" מאשר ב"מבחנים". 4. ברוב המקרים ל"כישלון" או"הצלחה" ברגע אחד של מבחן אין השפעה דרמטית על החיים שלנו. אם נפתח את זה לדוגמה של בן אדם שרוצה להתקבל לעבודה חדשה, קל לראות איך בוגר מערכת החינוך יסתכל על המשימה - ראיון עבודה הוא ה"מבחן", לפני הראיון כדאי לשבת ולהתכונן כמה ימים (או שבועות) בבית, והצלחה או כישלון בראיון הם בעלי פוטנציאל לשנות דרמטית את החיים שלי, כי אולי אגיע לעבודה יותר מעניינת או עם יותר כסף. או בדוגמה נוספת מתכנתת שמנסה לקבל השקעה לסטארט-אפ שהיא רוצה להקים עשויה להסתכל על הפגישה עם המשקיע כמו מבחן, ולהעביר את הימים או שבועות לפני הפגישה בלי שינה ובתיקוני באגים ב MVP כדי להגיע כמה שיותר מוכנה לדמו. אבל תפיסת עולם שמתמקדת בשיעורי הבית היתה מובילה אותנו להתנהלות אחרת לגמרי, ולדעתי יותר בריאה: המתכנת שלא מרוצה מהעבודה שלו ירוויח אם הוא יתחיל עוד היום ללמוד טכנולוגיות חדשות ולהשקיע יותר זמן בתוך יום העבודה בשילוב אותן טכנולוגיות, אם באמצעות הכנסת פרויקטים חדשים לארגון, באמצעות אירגון הרצאות העשרה, או בכתיבת פרויקטי צד. מתכנתים שכבר לוקחים חלק בפיתוח בטכנולוגיות חדשות או פעילים בפרויקטי קוד פתוח מוצאים יותר בקלות הזדמנויות וגם כשמגיעים לראיון הם כבר יכולים לספר על כל העבודה שהם עשו על פרויקטים דומים בעבר. הצלחה או כישלון בראיון עבודה כמעט לא ישפיעו על החיים של אותו מתכנת, שימשיך לעבוד בטכנולוגיה שהוא בוחר בכל מקום בו הוא נמצא. והמתכנתת שרוצה לבנות סטארט-אפ יכולה לבחור בכיוון של בוטסטרפ ולבנות מוצר שמהיום הראשון אנשים ירצו לשלם עליו. ככל שלחברה יהיו יותר הכנסות השיחה עם המשקיעים מקבלת אופי אחר ולהצלחה או כישלון בגיוס השקעה לא תהיה השפעה דרמטית על עתיד הפרויקט. ההתנהגות והבחירות שלנו מושפעות מתפיסת העולם שלנו כלומר מאיך שאנחנו חושבים שהעולם עובד. כשאנחנו בטוחים ש"כל דבר הוא מבחן" אנחנו נחפש את המבחנים האלה וננווט לתוך דרכים שבהן באמת כל דבר הוא מבחן. יש אנשים שאוהבים את השיטה הזאת ואם היא מתאימה לכם הרווחתם, אבל שווה לזכור שהיא לא היחידה ואם היא לא מתאימה לכם אפשר להתאמץ ולהחליף.

ToCode
1 419
# שיתוף תוכן ב Mobile Web לא מזמן פרסמו ב Smashing רשימה של Web APIs פחות מוכרים, וביניהם גיליתי את האוצר עליו אספר לכם בפוסט הזה. ## מה זה שיתוף תוכן גם אנדרואיד וגם אייפון מכילים תמיכה מובנית בדיאלוג שיתוף - משתמש לוחץ על כפתור באיזושהי אפליקציה, ואז מערכת ההפעלה של הטלפון מראה רשימה של אפליקציות שדרכן אפשר לשתף את התוכן. בדרך כלל זה יהיה "שמירה לפתקים", "יצירת דואר אלקטרוני", "שמירה למסמכים" וכמובן שיתוף דרך אפליקציות המסרים. גם על מחשבי Desktop יש מנגנון דומה, למרות שהוא פחות מוכר והרבה פחות נתמך. ה Web API של שיתוף תוכן פשוט מאפשר לנו להשתמש באותו דיאלוג שיתוף גם מתוך אפליקציית ווב או אתר מותאם. בין הדוגמאות הטובות לשימוש במנגנון אפשר למצוא - שיתוף פוסטים בבלוג (כפתור שייכנס בגירסה החדשה של הבלוג הזה), שיתוף כרטיס מידע, שיתוף איש קשר, שיתוף משימה, שיתוף מסמך ומקרים דומים נוספים. ## איך זה עובד שתי הפונקציות היחידות ש API הן: 1. navigator.canShare() שמחזירה האם הדפדפן תומך בשיתוף המידע מהסוג שרציתם 2. navigator.share() שפותחת את דיאלוג השיתוף קוד? ברור. הנה הגירסה המוטמעת בקודפן: <iframe height="300" style="width: 100%;" scrolling="no" title="Untitled" src="https://codepen.io/ynonp/embed/WNJpaxw?default-tab=html%2Cresult" frameborder="no" loading="lazy" allowtransparency="true" allowfullscreen="true"> See the Pen <a href="https://codepen.io/ynonp/pen/WNJpaxw"> Untitled</a> by Ynon Perek (<a href="https://codepen.io/ynonp">@ynonp</a>) on <a href="https://codepen.io">CodePen</a>. </iframe> והקוד עצמו הוא בסך הכל:
const share = async (shareData) => {
  try {
    await navigator.share(shareData);
  } catch (error) {
    console.error(error);
  }
};

const shareButton = document.querySelector("#share-button");

shareButton.addEventListener("click", () => {
    let shareData = {
      title: "you rock",
      text: "Visit https://www.tocode.co.il to find great programming resources"
    };

    share(shareData);
});
## מגבלות ל API יש מספר מגבלות שכדאי להכיר: 1. אפשר להפעיל את הפונקציה share רק בתגובה לאירוע שיזם המשתמש - לדוגמה לחיצה על כפתור. 2. אפשר לשתף רק מתוך אתרי https. 3. תמיכת דפדפנים יחסית מוגבלת. זה עובד טוב במובייל, כרום על חלונות וספארי במק. האוביקט עצמו שמשתפים כולל 4 סוגי מידע שאפשר לשתף: url, text, title ו files. לא חייבים להעביר את כולם ובדוגמה שלי העברתי רק title ו text.

ToCode
1 419
# טיפ טייפסקריפט: זהירות Tuples טייפסקריפט מספק ממשק נחמד למערך שאנחנו יודעים משהו על המבנה שלו, מה שנקרא Tuple. למשל אם אני רוצה לייצג ריבוע אני יכול להשתמש במערך שהערך הראשון שלו הוא אורך הצלע, אחרי זה שני מספרים לייצג את ה x וה y של הפינה השמאלית עליונה ואז מחרוזת כדי לייצג את הצבע:
const square = [10, 5, 2, 'red'];
או יכול להיות שיש לי רשימה של משתמשים, ולכל משתמש יש מזהה מספרי, כתובת אימייל ושם. אז אני יכול להגדיר טיפוס למשתמש ולהכניס את כולם לרשימה:
type User = [number, string, string];

const users: Array<User> = [
    [0, 'a@gmail.com', 'a'],
    [1, 'b@gmail.com', 'b'],
    [2, 'c@gmail.com', 'c'],
];
וזה נחמד כי אם עכשיו אני אנסה להוסיף משתמש חדש שלא מתאים לתבנית טייפסקריפט יוכל להתעצבן. הקוד הבא למשל לא מתקמפל:
type User = [number, string, string];

const users: Array<User> = [
    [0, 'a@gmail.com', 'a'],
    [1, 'b@gmail.com', 'b'],
    [2, 'c@gmail.com', 'c'],
];

users.push(['d']);
אז אם הכל כל כך טוב למה הכותרת של הפוסט הזה מתחילה במילה "זהירות" אתם שואלים? נו, הבעיה עם Tuples זה שמאוד קל לעבוד על טייפסקריפט. הקוד הזה דווקא כן מתקמפל:
type User = [number, string, string];

const users: Array<User> = [
    [0, 'a@gmail.com', 'a'],
    [1, 'b@gmail.com', 'b'],
    [2, 'c@gmail.com', 'c'],
];

const u: User = [3, 'd@gmail.com', 'd'];
u.splice(0, 2);
// u is now: ['d']
users.push(u);
שווה לשים לב שכשמשתמשים ב Tuples כדאי תמיד להוסיף להם Readonly כדי שמשתמשים לא יוכלו (בכוונה או בטעות) לרמות. ככה זה נראה כשה Tuple לקריאה בלבד:
type User = Readonly<[number, string, string]>;

const users: Array<User> = [
    [0, 'a@gmail.com', 'a'],
    [1, 'b@gmail.com', 'b'],
    [2, 'c@gmail.com', 'c'],
];

const u: User = [3, 'd@gmail.com', 'd'];
u.splice(0, 2);
ועכשיו הקוד שוב לא מתקמפל וטייפסקריפט כותב על השורה האחרונה:
Property 'splice' does not exist on type 'readonly [number, string, string]'. Did you mean 'slice'?

ToCode
1 419
# חיפוש תחילית משותפת האתגר השבועי בפרל תמיד מציע רעיונות נחמדים לתרגולים, אפילו כשלא פותרים אותם בפרל. הנה האתגר השבוע יחד עם פיתרון בשפת Ruby. ## האתגר בהינתן רשימה של שמות קבצים, מצאו את הנתיב העמוק ביותר שמכיל את כולם. לדוגמה עבור רשימת הקלט:
/a/b/c/1/x.pl
/a/b/c/d/e/2/x.pl
/a/b/c/d/3/x.pl
/a/b/c/4/x.pl
/a/b/c/d/5/x.pl
נצטרך להחזיר את הנתיב /a/b/c כי הוא הנתיב הארוך ביותר שעדיין מכיל את כולם. ## פיתרון בשפת רובי הפיתרון לתרגיל הוא הזדמנות לדבר על תחיליות משותפות ומבנה הנתונים Trie. זהו מבנה נתונים מבוסס עץ שמייצג פיצול של אחד הקלטים. אם ניקח לדוגמה את הנתיב הראשון אז הוא ייוצג על ידי העץ:
/a
    /b
        /c
            /1
                /x.pl
כאשר כל רמה באינדנטציה היא צומת, והילדים שלה נשמרים בתוך hash, כשהמפתח הוא שם התיקיה הבאה. נוסיף גם את הנתיב השני לאותו עץ ונקבל:
/a
    /b
        /c
            /1
                /x.pl
            /d
                /e
                    /2
                        /x.pl
שמירת הנתונים בעץ תהפוך את חיפוש הנתיב העמוק ביותר שמכיל את כולם לבעיה ממש קלה - נתחיל לשוטט בעץ מהנתיב הראשי, ומשם כל פעם שיש צומת שיש לו רק ילד אחד נכנס לאותו ילד. קוד? בטח. נתחיל במחלקה Node שהיא הבסיס לעץ:
class Node
  attr_accessor :value, :children
  def initialize(value)
    @value = value
    @children = {}
  end

  def <<(path)
    return if path.length == 0

    next_part = path.shift
    @children[next_part] = Node.new(next_part) unless @children[next_part]
    @children[next_part] << path
  end
end
ועכשיו אפשר להכניס את כל הנתיבים שלנו לעץ:
paths = %W[
    /a/b/c/1/x.pl
    /a/b/c/d/e/2/x.pl
    /a/b/c/d/3/x.pl
    /a/b/c/4/x.pl
    /a/b/c/d/5/x.pl
]

root = Node.new("")
paths.each { |path| root << path.split('/') }
ובסוף לרוץ על העץ ולחפש את הנתיב הכי עמוק שמכיל את כולם:
i = root
prefix = ""
loop do
  prefix += '/' + i.value unless i.value.empty?
  break unless i.children.length == 1
  i = i.children[i.children.keys[0]]
end
puts prefix
רוצים לנסות לפתור בעוד שפות? אולי אפילו בפרל? מוזמנים לפתור ולפרסם בתגובות את הגירסאות שלכם.

ToCode - Статистика и аналитика Telegram-канала @tocodeil