הבלוג של ינון פרק

טיפים קצרים וחדשות למתכנתים

דינו או באן

03/04/2024

כמו כל תוכנה שהיתה איתנו הרבה זמן, גם node.js מושך איתו הרבה קוד ישן ופיצ'רים שאף אחד כבר לא יודע שקיימים. הדברים האלה מאטים את הפיתוח וגם את הזמן הריצה וזה לא סוד שיישומי node היו יכולים להיות מהירים יותר.

נוסף על כך נוד מוגבל על ידי מבנה וארכיטקטורה שאנשים כבר התרגלו אליהם ונטועים עמוק בתוך האקוסיסטם של הפרויקט. דברים כמו ניהול תלויות דרך package.json, הגירסה שלהם ל Buffer-ים והגירסה שלהם ל Stream וכמובן CommonJS ועוד המון פיתוחים שעברו סטנדרטיזציה אחרי שנכנסו ל Node.

בכל ההשוואות שמצאתי בין שלושת הכלים באן היה המהיר ביותר, עם יעד להיות כמה שיותר תואם ל node. בגירסה 1.1 שיצאה עכשיו הם מספרים בהתלהבות על ה APIs הלא מתועדים של node שהם משכפלים.

דינו הגיע עם מודל הרשאות מאובטח By Default, תמיכה בלתי מסויגת בסטנדרטים של JavaScript ו TypeScript. הם באו כדי ליצור עולם טוב וסטנדרטי שיהיה קל להעביר בו קוד בין השרת לדפדפנים.

לאן זה הולך?

קשה לי לדמיין עתיד בו node.js ממשיך להוביל. אם באן יעבדו נכון מהר מאוד אנשים יתחילו להחליף את ה node שלהם ב bun בלי להרגיש בהבדל. ובדיוק כמו שאף אחד לא מתגעגע לוובפאק גם לא נראה געגועים ל node. הדבר החדש יעבוד בדיוק אותו דבר רק מהר יותר.

ועדיין אני מקווה לראות את דינו לוקח את ההובלה. יש להם קהילה מפרגנת ומיתוג טוב וכוונה אמיתית ליצור משהו טוב יותר. אם זה יקרה זה לא יהיה Drop In Replacement אלא בחירה חדשה שנצטרך לעשות. אני אשמח להתעורר יום אחד ולגלות שכמו שלא הייתי צריך יותר את jQuery אני גם לא אצטרך את כל ה require, ה Buffer וה APIs הלא מתועדים.

אבל אני לא משתמש ב xz

02/04/2024

כמה בעיות של העולם בעקבות הניסיון האחרון לשתול דלת אחורית בתוכנה שכולם משתמשים בה-

קוד פתוח זה רעיון מעולה. כולם משתמשים באותם מוצרים ובאותן תשתיות וזה כיף ונותן מקום לחדשנות טכנולוגית. קוד פתוח ממש חסר לי בעולם של ה Generative AI ואני לא רוצה לדמיין איך החיים היו נראים אם בכל מוצר היינו צריכים לשלם תמלוגים למאות או אלפי חברות. אבל קוד פתוח זה גם בעיה, כי פירצה קוראת לגנב וכי מתכנתי קוד פתוח מרוויחים מעט מדי ואחראים על יותר מדי. בסוף אנשים חכמים מצליחים לקבל את אמון הקהילה והפוטנציאל לנזק הוא עצום.

שרשרת אספקה זאת בעיה אמיתית. מוצר א משתמש במוצר ב שמשתמש במוצר ג שמשתמש במוצר ד וכך הלאה עשרות ומאות רמות. מספיק שמישהו מצליח לשכנע את אחד המתכנתים שאחראיים על מוצר שנמצא ממש למטה בשרשרת לתת בו אמון וכך לשתול קוד זדוני באותו מוצר, שרשרת האמון עובדת לרעתנו והקוד הזדוני מגיע למוצרים שאנחנו כן מכירים ואוהבים ואפילו למערכת ההפעלה.

מזל כך הגדיר אנדרס פרוינד, המתכנת שלגמרי לא קשור לסיפור אבל במקרה גילה את הקוד הזדוני, את הנסיבות לזיהוי הדלת האחורית. בהסתכלות קדימה יש סיבה לדאגה. גם אם המבצע הזה נכשל הוא לא היה הראשון ולא יהיה האחרון.

נראות אולי הסיכוי היחיד שלנו לצאת טוב וקצת לשפר את האבטחה של העולם. פרויקט xz הוא פרויקט בקוד פתוח אבל בניית הבינארים להפצה נעשתה על המכונה של המפתח הזדוני. אחת ההבטחות של קוד פתוח היא שיותר עיניים יכולות לראות יותר באגים. בסיפור של xz מישהו ידע את זה ועבד קשה בשביל להסתיר את הפריצה.

קבצים סטטיים מאקספרס על Deno Deploy

01/04/2024

אם תהיתם מאיפה הגיעו כל הפוסטים על node ו deno בימים האחרונים אז תשמחו לשמוע שאני עובד עכשיו על רענון קורס Node שבאתר ומקווה שעד פסח אתם תקבלו קורס מעודכן על Node שכבר כולל אינסוף דוגמאות קוד ב TypeScript ותואם גם ל Node וגם ל Deno. רוב הזמן זה לא נורא מסובך אבל בלי קצת בעיות תאימות החיים לא היו מעניינים. בפוסט היום אני רוצה לדבר על בעיית תאימות קטנה כזאת בשירות Deno Deploy.

למי שלא מכירים Deno Deploy זה שירות איחסון חינמי של Deno אליו אפשר להעלות כל יישום צד שרת והם פשוט מריצים אותו על השרתים שלהם. יש להם מסלולים בתשלום לאנשים שצריכים המון משאבים, אבל למשחקים המסלול החינמי ממש מעולה.

בהינתן תיקיית פרויקט Deno אפשר לכתוב משורת הפקודה:

$ deployctl deploy

והפרויקט עולה לאוויר.

וכמובן אם הפרויקט מאוחסן על גיטהאב אפשר דרך התפריטים במערכת לחבר את דינו דיפלוי לגיטהאב שלכם ואז כל Push מייצר גירסה חדשה של הפרויקט על השרת.

האתגר היחיד הוא בעיית תאימות, כי לדינו יש Web Frameworks שלהם ואקספרס כנראה לא מקבל שם עדיפות. פיצ'ר קטן אבל חשוב של קבצים סטטיים דרש קצת יותר עבודה ממה שהיה צריך. בואו נראה למה.

את קוד הפרויקט אתם יכולים למצוא בגיטהאב שלי בקישור: https://github.com/ynonp/deno-deploy-files

כאן אציג רק את עיקרי הדברים.

המשך קריאה

כוונות טובות, ביצועים גרועים

30/03/2024

בבוט אוצר המילים שאני כותב שכבת המידע יחסית פשוטה - הבוט שומר כרטיסיות מילים כשבכל כרטיסיה יש מילה מקדימה ומילה מאחורה ואז הוא יכול לשאול חידונים על המילים האלה. בשביל לא ליצור כפילויות כל פעם שמוסיפים תרגום חדש הבוט בודק בבסיס הנתונים אם יש כבר כרטיסיה עם המילים האלה, ויוצר רק אם מדובר בחיבור חדש.

זאת היתה השאילתה שלוקחת מילים ויוצרת כרטיסיה או מחזירה את הכרטיסיה הקיימת:

g
  .V()
  .has(VertexLabels.Card, Properties.IndexedLabel, VertexLabels.Card)
  .where(__.and(
    __.out(EdgeLabels.Front).hasId(front.entityId),
    __.out(EdgeLabels.Back).hasId(back.entityId)))
  .fold()
  .coalesce(
    __.unfold(),
    __.addV(VertexLabels.Card)
      .as("card")
      .addTimestampsProperties()
      .property(Properties.IndexedLabel, VertexLabels.Card)
      .asCard()
      .addE(EdgeLabels.Front).to(__.V(front.entityId))
      .select("card")
      .addE(EdgeLabels.Back).to(__.V(back.entityId))
      .select("card"))
  .id()
  .next()

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

בגלל שאין מזהה ייחודי לכרטיס, מהר מאוד יש יותר מדי כרטיסים בגרף וחיפוש כרטיס לפי החיבורים היוצאים ממנו מתחיל לקחת יותר מדי זמן. כמה זמן? כשאני תפסתי את השאילתה אתמול כבר לקח לה 3-4 שניות למזג כרטיס.

וזאת דוגמה טובה לדעתי לייתרון של גרמלין - קל לראות את הבעיות וקל לתקן אותן. בגרמלין אנחנו תמיד רוצים להתחיל שאילתה מצומת שמופיע באינדקס. במודל שלי כרטיס לא מכיר שום מידע אבל הוא מחובר למילים והצמתים של המילים כן מכילים את הטקסט של המילה, שזה כבר מידע שאפשר לשמור באינדקס. לכן התיקון הוא בסך הכל לשנות את נקודת הכניסה לשאילתה. במקום להתחיל עם כל הכרטיסים ולחפש את זה שמתחבר למילים שיש לי, אני מתחיל עם אחת המילים והולך לפי הקשתות כדי להבין אם היא מחוברת לכרטיס שמתאים למילה השניה. זאת השאילתה המתוקנת:

    val id = g
      .V(front.entityId)
      .coalesce(
        __.in(EdgeLabels.Front).where(__.out(EdgeLabels.Back).hasId(back.entityId)),
        __.addV(VertexLabels.Card)
          .as("card")
          .addTimestampsProperties()
          .property(Properties.IndexedLabel, VertexLabels.Card)
          .asCard()
          .addE(EdgeLabels.Front).to(__.V(front.entityId))
          .select("card")
          .addE(EdgeLabels.Back).to(__.V(back.entityId))
          .select("card")
      ).id()
      .next()

וזאת אגב הסיבה שאני מעדיף את גרמלין על פני Cypher ו Datalog. גרמלין נותן הכי הרבה שליטה באיך מבוצעת השאילתה, ומאפשר מאוד בקלות להתאים את אופן סריקת הגרף למודל הנתונים הספציפי של המערכת.

כשהתקן עובד לרעתך

29/03/2024

בגירסאות ישנות של הספריה הסטנדרטית של דינו היה מודול בשם read_lines שאפשר לכתוב קוד כזה:

import { readLines } from "https://deno.land/std@0.221.0/io/read_lines.ts";
import * as path from "https://deno.land/std@0.221.0/path/mod.ts";

const filename = path.join(Deno.cwd(), "std/io/README.md");
let fileReader = await Deno.open(filename);

for await (let line of readLines(fileReader)) {
  console.log(line);
}

מה שחשוב זה הלולאה בסוף שקוראת קובץ שורה אחר שורה ומאפשרת לטפל בכל שורה בנפרד.

המודול מגיע היום עם אזהרת Deprecation. בשביל תאימות לתקן של Web Streams API החברים בדינו החליטו לוותר עליו ולהמליץ לעבוד עם Web Streams, שכרגע לא כולל את הפונקציונאליות הזאת.

ועכשיו השאלה - האם לשתף פעולה עם הקידמה ולכתוב לבד מנגנון שובר שורות, להישאר עם המנגנון ה Deprecated או בכלל להשתמש במודול readline של node, שגם נטען בקלות מתוכנית דינו?

אני מודה שאין לי תשובה. הנטיה שלי היא להישאר עם readline של node ובאופן כללי להשתמש בספריה הסטנדרטית של node גם בעבודה עם דינו, בגלל שהספריה הסטנדרטית של דינו עדיין לא נראית מספיק יציבה.

נ.ב. ככה זה נראה כשקוראים קובץ שורה אחרי שורה עם ה Web Streams API:

const file = await Deno.open("a.js", { read: true });
const readableStream = file.readable.pipeThrough(new TextDecoderStream()).pipeThrough(new TransformStream({
  transform: (chunk, controller) => {
    const lines = chunk.split("\n");
    for (const line of lines) {
      if (line) {
        controller.enqueue(line);
      }
    }
  },
}));

for await (const line of readableStream) {
  console.log(`> ${line}`);
}

וזאת הגירסה עם readline של node:

import readline from 'node:readline';
import fs from 'node:fs';

const myName = "a.js";

const rl = readline.createInterface({
  input: fs.createReadStream(myName),
})

let index = 0;
rl.on('line', (line) => {
  index += 1;
  console.log(`${String(index).padStart(2, '0')} ${line}`);
});

מה דעתכם? איזה גירסה הייתם בוחרים? ולמה?

גרמלין - סיכום ניסוי

27/03/2024

בחודשים האחרונים בניתי בוט לטלגרם שהשתמש בבסיס נתונים גרפי. מאוד נהניתי מהמחקר על בסיסי נתונים גרפיים ולמדתי על המון סוגים שלהם אבל בשורה התחתונה לא עפתי עליהם וכנראה שבפרויקט הבא כבר אחזור לפוסטגרס. אני מסכם כאן את הנקודות המרכזיות מהניסוי.

המשך קריאה

שני תרגילי עוקץ לכבוד פורים

25/03/2024

אנחנו ב 2024, וכדאי לזכור שתרגילי עוקץ לא מתו - וזה כולל בתעשיית ההייטק כשאנשים כביכול מודעים לכל הסכנות. הנה שני סיפורים שנתקלתי בהם בתקופה האחרונה ורציתי לשתף כדי להעלות קצת את רמת העירנות של כולנו.

המשך קריאה