ch
Feedback
ToCode

ToCode

前往频道在 Telegram

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

显示更多
1 419
订阅者
-124 小时
无数据7
-230
帖子存档
ToCode
1 419
# קימפול תוכנית Qt ל Web Assembly לפני כמה ימים שוחררה גירסה 6.4 של Qt ובעיניי הפיצ'ר הכי מלהיב שם הוא התמיכה המובנית ב Web Assembly. היום מה שכבר קיים ועובד ב Qt הוא: 1. תמיכה מלאה של Qt Creator ב Web Assembly, כך שאתם יכולים לקחת כל פרויקט Qt שכבר יש לכם (ווידג'טס או QML) ופשוט לקמפל אותו לקובץ wasm. 2. אירגון של כל הקוד שמסביב כך שהבניה מייצרת לכם תיקיית dist עם קובץ HTML ראשי וקבצי JavaScript, כך שכל מה שצריך לעשות זה לטעון את הקובץ HTML והתוכנית רצה בדפדפן, ונראית בדיוק כמו שהיא נראתה כאפליקציית Desktop. 3. מימוש של קוד עוטף סביב מנגנונים של הדפדפן, למשל בשביל לקרוא קבצים, לפתוח בקשות רשת או כל דבר אחר שאפליקציית Desktop היתה עושה. אז עכשיו קריאה לפונקציה QFileDialog::getOpenFileContent בעצם משתמשת ביכולות של הדפדפן כדי להקפיץ למשתמש חלון לבחירת קובץ ולטעון אותו. כרגע החיסרון המרכזי הוא גודל הקבצים. תוכנית Qt פשוטה שבניתי וקימפלתי ל Web Assembly (קוד בהמשך הפוסט) הגיעה ל 19 מגה אחרי קומפילציה. אני מקווה שבגירסאות העתידיות של Qt הם יעבדו על זה ויצליחו לרדת למספר חד ספרתי של מגות. דמו? ברור. זה קוד Qt שבניתי ב Qt Creator. התוכנית היא בסך הכל קובץ אחד main.cpp שבונה מונה לחיצות:
#include <QApplication>
#include <QtWidgets>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    auto clicked = 0;
    auto btn = new QPushButton("Add 1");
    auto result = new QLabel(QString("Button clicked %1 times").arg(clicked));
    w.setLayout(new QVBoxLayout());
    w.layout()->addWidget(new QLabel("Hello World"));
    w.layout()->addWidget(result);

    w.layout()->addWidget(btn);

    QObject::connect(btn, &QPushButton::clicked, [&]() {
        clicked++;
        result->setText(QString("Button clicked %1 times").arg(clicked));
    });

    w.show();
    return a.exec();
}
יש פה חלון, בתוכו שתי תיבות טקסט וכפתור, וכל פעם שלוחצים על הכפתור ערך המשתנה עולה ב 1 והטקסט בתיבה משתנה. המקום על המסך מחולק בין הכפתור ותיבות הטקסט והרכיבים גדלים לפי הכללים של Qt כדי למלא את כל החלון. אחרי קומפילציה מקבלים קובץ wasm ענקי, שני קבצי js וקובץ html. אפשר לראות את כולם בריפו שיצרתי כאן: https://github.com/ynonp/qt-wasm-demo/ ודמו רץ בגיטהאב פייג'ס בקישור הזה: https://ynonp.github.io/qt-wasm-demo/ פשוט תלחצו על הכפתור שבתחתית המסך ותראו את הטקסט בתיבה משתנה. אם גם אתם רוצים לשחק עם הגירסה כל מה שצריך זה להוריד ולהתקין את Qt מהקישור הזה: https://www.qt.io/download-qt-installer ולהתקין את Emscripten מהקישור הזה: https://emscripten.org/docs/getting_started/downloads.html נ.ב. מבחינת רישיון ממה שאני מבין בשביל לבנות אפליקציות מסחריות יש לקנות רישיון מסחרי בגלל שהקומפילציה ל Web Assembly היא סטטית. עושה רושם שהם עובדים על אפשרות לקומפילציה דינמית מה שיאפשר להשתמש ברישיון ה LGPL ולבנות גם יישומים מסחריים בקוד סגור ב Web Assembly.

ToCode
1 419
let sol2 = part1(values_for_part2.iter());
dbg!(sol2);
## הפונקציה part1 נמשיך לקריאת פונקציית העזר:
fn part1<'a>(it: impl Iterator<Item=&'a u32>) -> u32 {
    let mut increases = 0;
    let mut previous_line = f64::INFINITY;

    for value in it {
        if f64::from(*value) > previous_line {
            increases += 1;
        }
        previous_line = f64::from(*value);
    }

    return increases;
}
החתימה של הפונקציה היא החלק הכי קשה כאן - הפונקציה מקבלת איטרטור ל &u32, כלומר איטרטור שבו מישהו אחר צריך לשמור על הערכים. זאת הסיבה שלא יכולתי להעביר לפונקציה ישירות את התוצאה של map - בהגדרת הפונקציה כתבתי במפורש שאני לא רוצה לקחת בעלות על הערכים ואני רק קורא אותם. זאת המשמעות של סימן ה &. מבחינת הקוד המילה mut לפני שם משתנה אומרת שאני הולך לשנות אותו במהלך הריצה (ברירת המחדל היא שמשתנים מקבלים ערך רק פעם אחת - כלומר const). ובלולאה ה for אני בודק אם הערך הנוכחי גדול מהערך הקודם ואם כן מעלה את המונה. ההמרה ל Float נדרשת כי הערך הראשוני של previous_line היה אינסוף (כדי שהאיבר הראשון לא יהיה גדול ממנו), ואינסוף בראסט הוא מטיפוס float. ## לאן להמשיך מכאן אם התוכנית עשתה לכם חשק ללמוד עוד ראסט אני מאוד ממליץ על הספר החינמי הרשמי שלהם כאן: https://doc.rust-lang.org/stable/book/ או על המדריך Rust By Example בקישור הזה: https://doc.rust-lang.org/stable/rust-by-example/

ToCode
1 419
הפונקציה main בקוד מחולקת גם היא למספר חלקים: 1. פיענוח פרמטרים שעברו משורת הפקודה. 2. קריאת קובץ הקלט. 3. המרת הקלט למספרים. 4. הפעלת פונקציית העזר כדי לספור כמה שורות גדולות יותר מאלה שלפניהן. 5. המרת הקלט לקבוצות וסכימת המספרים בכל קבוצה. 6. קריאה נוספת לפונקציית העזר כדי לחשב את החלק השני. בחלק הראשון אני לוקח את הפרמטר הראשון משורת הפקודה לתוך משתנה בשם file_path:
let args: Vec<String> = env::args().collect();
let file_path = &args[1];
הפקודה collect לוקחת משהו שאפשר לרוץ עליו (איטרטור) והופכת אותו למבנה נתונים. בשורה הראשונה לוקחים את כל הארגומנטים שהתוכנית קיבלה ושומרים אותם במבנה נתונים מסוג וקטור. שורה שניה לוקחת את התא הראשון בוקטור ושומרת אותו במשתנה בשם file_path. הסימן המיוחד & מסמן לנו ולראסט שאנחנו לא רוצים להשתלט על הערך אלא רק "לשאול" אותו, כלומר לקחת Reference אליו. הקומפיילר ידאג שכל עוד יש Reference פעיל לערך מסוים אז קוד אחר לא ישנה או ימחק את הערך הזה. נמשיך לקריאת הקובץ ופה אנחנו רואים איך ראסט עוזר לנו לכתוב תוכניות בטוחות יותר By Design:
let contents = match fs::read_to_string(file_path) {
    Ok(c) => c,
    Err(error) => panic!("Problem opening the file: {} - {:?}", file_path, error),
};
הפקודה fs::read_to_string לוקחת שם קובץ וקוראת את הקובץ למחרוזת. אבל קריאה של קובץ זה משהו שיכול להיכשל - ולכן הפקודה מחזירה משהו שנקרא Result. ל Result יש שתי אפשרויות, או שהוא תקין ואז יש לנו את המחרוזת, או שהוא מייצג שגיאה. הפקודה match מאפשרת לנו ולקומפיילר לטפל בכמה אפשרויות ומוודאת שטיפלנו בכולן, והפקודה panic זורקת שגיאה ומרסקת את התוכנית. אם אני מנסה לוותר על קוד הטיפול בשגיאות ולכתוב משהו כזה:
let contents = fs::read_to_string(file_path);
let lines = contents.split("\n");
התוכנית לא מתקמפלת כי ל Result אין פונקציה בשם split. קיצור דרך שכן אפשר לכתוב ויעבוד הוא:
let contents = fs::read_to_string(file_path).expect("Error");
let lines = contents.split("\n");
וזה ייתן בדיוק את אותה התנהגות. עכשיו שיש לנו את הערכים וגם פיצלנו אותם לשורות הגיע הזמן להפוך את השורות לוקטור של מספרים. בשביל זה אני ממשיך לשורה:
let values: Vec<u32> = lines
    .filter_map(|v| v.trim().parse::<u32>().ok())
    .collect();
ופה אנחנו שוב רואים את ראסט ומנגנוני ניהול השגיאות שלה. הפקודה filter_map מפעילה את הבלוק שהיא קיבלה על כל האיברים באיטרטור, ותסנן מהם החוצה את כל הריקים. הפקודה ok לוקחת Result ומחזירה את הערך רק אם ה Result תקין, אחרת מחזירה ערך ריק. לכן הבלוק שלנו ימחק את הרווחים מתחילה ומסוף השורה, יפענח את המחרוזת למספר, יסנן החוצה את כל מה שלא הצליח לפענח ומייצר מכל העסק וקטור. אפשר לראות שאני מציין טיפוסים רק איפה שאני צריך אותם - אני צריך להגיד ל collect איזה סוג מבנה נתונים לייצר, ואני צריך להגיד ל parse לאיזה טיפוס לנסות לפענח. הטיפוס u32 הוא Unsigned Integer באורך 32 ביטים. עכשיו שיש לי וקטור עם כל הערכים אני יכול להעביר אותו לפונקציית העזר שלי שסופרת כמה ערכים גדולים יותר מאלה שלפניהם. הפונקציה מצפה לקבל איטרטור ולכן הקוד:
let sol1 = part1(values.iter());
dbg!(sol1);
הפקודה dbg! פשוט מדפיסה את הערך שקיבלה לצרכי debug. בשביל החלק השני של החלוקה לקבוצות הקוד קצת יותר יצירתי אבל לא יותר מדי. המתודה zip של איטרטור לוקחת איטרטור אחר ומחזירה זוג של ערכים מכל אחד מהאיטרטורים. הפקודה skip מדלגת על איבר באיטרטור ולכן אני יוצר 3 איטרטורים כל אחד מתחיל מאיבר אחד קדימה:
let it1 = values.iter();
let it2 = values.iter().skip(1);
let it3 = values.iter().skip(2);
מחבר את שלושתם עם zip על zip ומשתמש ב map כדי לסכום את האיברים בכל שלשה:
let values_for_part2: Vec<u32> = it1.zip(it2).zip(it3).map(|((a, b), c)| a + b + c).collect();
את התוצאה אני שומר לוקטור חדש, וזאת נקודה של ראסט שיכולה להרגיז מתחילים כמוני, כי תמיד צריך לשים לב מי שומר על הערכים. תכף נמשיך לקרוא את פונקציית העזר ונראה למה היא לא מוכנה לקבל ערכים במתנה והיא ממש חייבת שמישהו אחר ״יחזיק״ את הערכים כדי שלא יימחקו. לסיום נעביר איטרטור על הוקטור לפונקציית העזר ולהדפיס את התוצאה של החלק השני:

ToCode
1 419
# שלום עולם חלוד שפת התכנות Rust מקבלת הרבה תשומת לב חיובית ברשת ומסיבה טובה. לאחרונה התחלתי לשחק איתה ואני רוצה לשתף פה תוכנית ראשונה שכתבתי עם כמה דברים שאהבתי בשפה. יש סיכוי לא רע שחלק מהדברים בפוסט הזה לא יצאו מדויקים כי בכל זאת אני עם ראסט רק כמה ימים אז איפה שאני מפספס או שאפשר היה לכתוב קוד פשוט יותר מוזמנים לתקן אותי בתגובות. ## קצת על ראסט ראסט פותחה במוזילה על ידי Graydon Hoare והופיעה לראשונה ביולי 2010. גריידון עבד במוזילה בזמן הפיתוח והעיצוב של השפה, וגירסה יציבה ראשונה יצאה ב 2014. היום משתמשים בראסט בחברות מובילות בתעשיה כולל אמזון, דיסקורד, דרופבוקס, פייסבוק, גוגל ומייקרוסופט. החל מ 2021 הפיתוח של ראסט מנוהל וממומן דרך ה Rust Foundation שהוקם כשיתוף פעולה בין AWS, Huawei, Google, Microsoft ו Mozilla. השפה עצמה נועדה לפיתוח יישומי מערכת. היא מהירה כמו C++, מתקמפלת, כוללת מנגנון ניהול חבילות ותלויות מובנה בשם cargo. היא בטוחה יותר מ C++ והכתיבה בראסט מהנה בהרבה. ראסט כוללת מערכת טיפוסים נוקשה כך שלכל דבר יש טיפוס, ומשלבת את זה עם Type Inference כך שאנחנו כמעט לא צריכים לכתוב הגדרות לטיפוסים. היא מתקמפלת לכל הפלטפורמות וכוללת מודל ניהול זיכרון חדשני (שעדיין לא הבנתי אותו עד הסוף) שמאפשר לקומפיילר לדאוג לניקוי הזיכרון בלי להזדקק ל Garbage Collector או Reference Count. ## התוכנית - תרגיל ראשון של AoC 2021 בשביל להבין את ראסט קצת מעבר ל Tutorail הראשון הלכתי לממש את התרגיל הראשון מ Advent Of Code האחרון. עם הזמן אולי אממש גם את הבאים. בתרגיל אנחנו צריכים לקרוא קובץ טקסט שמכיל מספרים ולהגיד כמה מהשורות בקובץ מכילות מספר שגדול יותר מהמספר שהיה קודם. לדוגמה אם הקובץ מכיל את:
199
200
208
210
200
207
240
269
260
263
אז אפשר לראות ש 7 שורות הן גדולות יותר מהשורה שלפניהן. בחלק השני של התרגיל יש לאסוף את השורות לקבוצות של 3 ולסכום את הערכים בכל קבוצה. האיור הבא ממחיש את זה כשכל אות מייצגת קבוצה:
199  A      
200  A B    
208  A B C  
210    B C D
200  E   C D
207  E F   D
240  E F G  
269    F G H
260      G H
263        H
סכומי הקבוצות הם:
A: 607 (N/A - no previous sum)
B: 618 (increased)
C: 618 (no change)
D: 617 (decreased)
E: 647 (increased)
F: 716 (increased)
G: 769 (increased)
H: 792 (increased)
ובחלוקה כזאת אני רואה ש 5 קבוצות מייצרות סכום גדול יותר ממה שהיה בשורה הקודמת. עכשיו בואו נלך לראות איך ראסט מצליח לחשב את אותם מספרים. ## פיתרון ב Rust הקוד בראסט מספיק ארוך בשביל ללמד אותנו דבר או שניים על השפה והסגנון שלה, אבל עדיין מספיק קצר כדי שאפשר יהיה לסכם אותו בפוסט אחד. אני מדביק כאן קודם את התוכנית המלאה ואחרי זה ממשיך לפרק אותה חלק חלק:
use std::env;
use std::fs;

fn part1<'a>(it: impl Iterator<Item=&'a u32>) -> u32 {
    let mut increases = 0;
    let mut previous_line = f64::INFINITY;

    for value in it {
        if f64::from(*value) > previous_line {
            increases += 1;
        }
        previous_line = f64::from(*value);
    }

    return increases;
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let file_path = &args[1];

    let contents = match fs::read_to_string(file_path) {
        Ok(c) => c,
        Err(error) => panic!("Problem opening the file: {} - {:?}", file_path, error),
    };

    let lines = contents.split("\n");

    let values: Vec<u32> = lines.filter_map(|v| v.trim().parse::<u32>().ok()).collect();
    
    let it1 = values.iter();
    let it2 = values.iter().skip(1);
    let it3 = values.iter().skip(2);
    let values_for_part2: Vec<u32> = it1.zip(it2).zip(it3).map(|((a, b), c)| a + b + c).collect();
    
    let sol1 = part1(values.iter());
    let sol2 = part1(values_for_part2.iter());

    dbg!(sol1);
    dbg!(sol2);
}
## מבנה כללי של הקובץ הקובץ כולל שלושה חלקים: 1. בתחילת הקובץ פקודות use טוענות משתנים ממרחבי שמות אחרים. אני מתאר לעצמי שגם חבילות חיצוניות נטען בעתיד דרך use, אבל בתוכנית הזאת לא השתמשתי בחבילות חיצוניות. 2. פקודות fn מגדירות פונקציות, ובקובץ קיימות שתי פונקציות: הפונקציה part1 והפונקציה main. השם main הוא השם של הפונקציה הראשית בתוכנית שמופעלת כשהתוכנית מתחילה. ## הפונקציה main

ToCode
1 419
# קצת כמו כלב רק עם כנפיים לכלב אין כנפיים. אם היינו צריכים כלב שגם יכול לעוף הכנפיים היו רק חלק קטן מהבעיה - המוח היה צריך להשתנות, מבנה השרירים היה שונה ובטח עוד אינסוף מאפיינים קטנים בגוף של הכלב. כשמישהו אומר לכם שפיצ'ר מסוים הוא בדיוק כמו X רק עם תוספת, צריך לשים לב שהתוצאה עשויה להיות מאוד שונה מהאינטואיציה שלכם לגבי X. אנגולר1 שיחקה עם האינטואיציה שלנו לגבי ביצועים, ולכן רוב תוכניות אנגולר1 שנכתבו על ידי אנשים שלא מספיק הכירו אנגולר הכילו בעיות ביצועים. ובדוגמת הקוד של היום ביליתי קצת יותר מדי זמן לדבג ביטוי עם תנאי בטייפסקריפט, משהו שאפשר לנקות אותו לצורה הזאת:
type Upsert<FullType, T> = T extends { id: number }
    ? Partial<FullType>
    : Omit<FullType, "id">;
מאוד רציתי לקחת טיפוס כזה:
type MyStuff = {
    id: number,
    foo: number,
    bar: number,
}
ולכתוב פונקציה שתקבל משהו מהטיפוס או שיש לו id ואז יכולים להיות לו ערכים ל foo ול bar; או שאין לו id ואז הוא חייב לקבל ערכים ב foo וב bar. כלומר קוד כזה:
function upsert<T>(thing: Upsert<MyStuff, T>) {}

// I wanted these to compile:
upsert({ id: 10, foo: 5 });
upsert({ foo: 10, bar: 20 });

// And this to not compile:
upsert({ foo: 5 });
וכמובן שזה לא עובד. מה שמעניין כאן הוא הסיבה שזה לא עבד והפיתרון, שלקח לי יותר מדי זמן לראות. ביטויי תנאי בטייפסקריפט הם כמו תנאים רגילים רק עם טוויסט: בגלל שהם יכולים לעבוד על משתנים גנריים, הדבר שהם מחזירים יכול להשפיע אחורה על התנאי עצמו. זה לא שקודם משערכים את התנאי ואז הולכים להחזיר את הערך כמו בביטויי תנאי רגילים, אלא שהכל קורה יחד. הקוד הזה כבר עובד:
type Upsert<FullType, T> = T extends { id: number }
    ? T & Partial<FullType>
    : T & Omit<FullType, "id">;

type MyStuff = {
    id: number,
    foo: number,
    bar: number,
}

function upsert<T>(thing: Upsert<MyStuff, T>) {}

// Compiles
upsert({ id: 10, foo: 5 });
upsert({ foo: 10, bar: 20 });

// Doesn't compile - missing "id" or "bar"
upsert({ foo: 5 });
בגירסה הזאת טייפסקריפט יודע עוד דבר על T, וזה מאפשר לו לבחור טיפוס יותר טוב למשתנה הגנרי בקריאה ל upsert - בעצם בקריאה הזאת T מכיל את כל השדות שחוזרים מהתנאי, ולכן הוא מכיל גם את id. זה בניגוד לגירסה הראשונה שם לא היה שום אילוץ על T ולכן הוא היה יכול להיות טיפוס ריק והתנאי תמיד נכנס לענף השלילי. אז כן טיפוסים מותנים הם קצת כמו ביטויי תנאי רק עם טוויסט, אבל הטוויסט יכול לגרום לאינטואיציה לעבוד נגדנו.

ToCode
1 419

ToCode
1 419
לוז וובינרים עד סוף השנה:

ToCode
1 419
# הקפיצה בלימוד חומר חדש, בין אם מספר, מהרצאה, מ Tutorial או כל דרך שנבחר, יש נקודה שהמילים מתחילות לאבד משמעות. זה מרגיש כמו- "כן אני מבין את זה" "ברור" "רעיון טוב" "אני יכול לכתוב את זה" ואז משום מקום מגיע איזה "רגע, מה הרגע היה פה?!". זאת הקפיצה מאפליקציית Todo MVC פשוטה למערכת אמיתית עם משתמשים ומאות אלפי שורות קוד. והיא שם כי אותו בן אדם שכתב את המדריך באמת רוצה לעזור לנו להתמודד גם עם הסיטואציות המסובכות שהולכות להגיע. ההרגשה של "מה הרגע קרה פה" לא מגיעה בגלל שהמילים לא מספיק טובות או ההסבר לא מדויק. היא מגיעה בגלל שאין לנו מספיק ניסיון בשביל להבין את הבעיה שהמדריך מדבר עליה. ובלי ניסיון כל הסבר יהיה תלוש. כשמגיעים לנקודה בה ההסבר מתחיל לרוץ מהר, זה הרבה פעמים לא הקצב של ההסברים שהשתנה אלא המוכנות והיכולת שלנו לקבל אותם. במקום לחפש הסבר נוסף במקום אחר, שווה לעצור ולצבור עוד קצת ניסיון בדברים שנראים מובנים: עוד תוכנית, עוד פרויקט, וקצת יותר גדול, יכולים להביא אתכם לשאלות שנפתרות בחלק הבא של ההסבר.

ToCode
1 419
# לאן מזיזים את המחט אני לא יודע עוד מה אני רוצה לבנות. אני צריך את הגמישות לשנות תוך כדי תנועה. אני מחפש לבנות פרויקט אמין. כל התרסקות תעלה לנו המון כסף. אני צריכה לבנות פרויקט שיתפקד טוב עבור מיליוני משתתפים ושתמיד אפשר יהיה להגדיל אותו. הפרויקט יחליף מערכת קיימת ואנחנו יודעים בדיוק כמה תנועה צפויה. יש לנו כמה מאות מתכנתים שיעבדו על הפרויקט. אנחנו מחפשים תשתית שבה הם יוכלו לעבוד טוב יחד ולהיות יעילים. אני מחפשת לבנות פרויקט צד בתור מתכנתת יחידה שעובדת שעה-שעתיים ביום. אולי בעתיד אכניס פרילאנסר שיעזור עם חלק מהפיצ'רים. אני חייב להגיע כמה שיותר מהר עם מוצר לשוק. לא משנה לי אם יהיו באגים או אם זה לא יעבוד הכי טוב בגירסה ראשונה - המשקיעים יושבים לי על הווריד ואנחנו כבר אחרי שני פיבוטים. הדבר הכי חשוב לי זה שיהיה קל לגייס אנשים. כרגע אנחנו עדיין צוות קטן אבל מהר מאוד אני הולכת לקבל השקעה רצינית וצריכים להיות מסוגלים להכניס מתכנתים חדשים לעניינים בקלות. אנחנו בונים מחדש את ה Front End כל שנתיים - כל השכל של הכלי הוא בצד השרת. --- אני מקווה ששמתם לב איך כל סיפור מגיע עם אילוצים משלו. כל התלבטות טכנולוגית צריכה להתחיל מרשימת אילוצים, והרבה פעמים כשיש לנו את האילוצים ברורים מול העיניים אנחנו מגלים שהבחירה הטכנולוגית היא Non Issue.

ToCode
1 419
# כשהכלי לא מספק את התמורה אם אתם עובדים על פרויקט טייפסקריפט וכותבים הגדרות טיפוסים רק איפה שקל, יש סיכוי טוב שאין לכם באמת Type Safety בפרויקט - פשוט כי הרבה פונקציות משתמשים בטיפוסים גנריים מדי. אם אתם כותבים בדיקות רק איפה שקל, יש סיכוי טוב שהבדיקות שלכם לא מציעות כיסוי קוד אמיתי. אם אתם כותבים תיעוד רק בכתיבת קוד חדש, ולא טורחים לעדכן אותו כשאתם מעדכנים קוד (כי אין זמן לדברים כאלה יש באג קריטי בפרודקשן), יש סיכוי טוב שהתיעוד שלכם לא מביא את הערך שתיעוד יכול להביא. יש לא מעט מצבים בהם 50% השקעה (ואפילו 80% השקעה) לא תיתן 50% תמורה. אם החלטתם לבחור כלי שדורש מסירות מוחלטת, תוודאו קודם שאתם מוכנים לשלם את המחיר גם כשקשה.

ToCode
1 419
# הבעיה עם טריקים זולים הבעיה עם טריקים זולים היא לא שהם לא עובדים - אם את מנסה לעודד את הצוות לעבוד קשה עם דדליין מומצא, ואת מצליחה לשמור על פרצוף רציני, יש סיכוי טוב שאנשים יישארו שעות נוספות ויעשו מאמץ כדי להגיע לדד ליין הזה עם מוצר שעובד; אם אתה צריך להגיע עם מוצר ללקוח ואתה נותן מוצר שנראה עובד אבל בעצם עומד על כרעי תרנגולת, הלקוח אולי לא ישים לב לבעיות במבט ראשון; אם את מלמדת קורס ומראה לאנשים רק דברים שקלים להם הם יצאו מהקורס בהרגשה שהם יודעים הכל והקורס הצליח. הבעיה עם טריקים זולים היא בדיוק שהם עובדים. אבל רק פעם או פעמיים. כי אחרי דד-ליין פיקטיבי או שניים אנשים כבר לא יאמינו לך, ויהיה קשה מאוד לעורר בצוות מוטיבציה גם בדברים חשובים. יותר מזה, כשאנשים מתרגלים לתרבות של שקר כולם נפגעים. כי אחרי מוצר שבור אחד הלקוח הולך להיות הרבה יותר חשדן לקראת המוצר הבא, אם בכלל תקבל הזדמנות שניה. וגם אם תמשיך ללקוח חדש לאורך זמן אנשים יתחילו לדבר ויהיה לך קשה לגדול. ואחרי שאנשים יבינו שפשוט דילגת על כל החלקים הקשים בקורס, הם עלולים לחשוב פעמיים לפני שיקחו קורס נוסף - כי ממילא לקורסים אין ערך ואחריהם צריך ללמוד הכל לבד. לפעמים שווה לא לספר את הכל מיד כדי ליצור מתח ולעזור לאנשים לעבור איזשהו מחסום פסיכולוגי. אבל רוב הזמן פתיחות משתלמת הרבה יותר כי היא יוצרת תרבות טובה יותר לטווח הרחוק.

ToCode
1 419
EventPayload extends Extract<Event, { type: EventType }>["payload"]
>(
  eventType: EventType,
  ...eventPayload: EventPayload extends undefined
      ? [undefined?] 
      : [EventPayload]
) {}

handle("login", { username: "ynon" });
handle("logout");
handle("sendMessage", { to: "ynon", text: "hi ;)"});
הקוד הזה כבר עובד ממש אחלה. אירועים שמוגדרים עם payload דורשים שנעביר את ה payload בפרמטר השני ובודקים שמה שהעברנו תואם להגדרה בטיפוס האירוע, אירועים שמוגדרים בלי payload אפשר להפעיל רק עם פרמטר אחד.