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

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

מה שאפשר לתקן

27/07/2024

לפי התפיסה הרווחת לגבי איכות של מתכנתים (נינג'ות, כפי שמכונים במודעות הדרושים) אלה הדברים שכדאי להסתכל עליהם כשאנחנו מגיעים להעריך מתכנת או מתכנתת:

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

  2. היכולת לפתור בעיה מהר בתוך הסטאק הטכנולוגי הרלוונטי.

  3. היכולת להיכנס לקוד, לתקן או להרחיב אותו בלי לשבור דברים קיימים.

  4. היכולת לזהות בעיות מהר.

  5. היכולת לבחור מבין מספר אלטרנטיבות את הפיתרון היעיל ביותר.

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

  1. המחויבות להשאיר קוד נקי אחרי שפותרים בעיה.

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

  3. המחויבות לחפש מקרי קצה ולטפל בהם, גם כשה Happy Path כבר כתוב.

  4. המחויבות לחפש כמה דרכי פעולה כדי שאפשר יהיה לבחור את הטובה ביותר.

  5. המחויבות לבנות תהליכים שיעזרו גם למתכנתים שיבואו אחרינו.

  6. המחויבות לחפש מקומות עבודה מאתגרים ושותפים שאפשר ללמוד מהם.

מחויבות למצוינות לאורך זמן היא הדרך היחידה והבטוחה להתקדם.

טיפ ריילס: איך לבדוק ש sprocket מותקן ועובד

26/07/2024

ספריית sprocket של ריילס אחראית על כל מה שקשור לעיבוד מראש של קבצי JavaScript ו CSS והכנתם לדפדפן. סוג של וובפאק שמשולב בקוד השרת. מאז ומעולם המנגנון הזה היה מסורבל ודרש המון קונפיגורציה, ובנוסף בגירסה 7 של ריילס הם הפכו את sprocket לאופציונאלי ושינו חלק מה APIs. ביישום ריילס חדש אין בעיה וכל ההגדרות נוצרות כמו שצריך מתוך ה Project Generator, אבל בשידרוג השרת עלול להחזיר נתיבים לא נכונים או לא לשים את הקבצים במקום שלהם, מה שגורם לתקלות מוזרות.

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

ynonp@Ynons-MacBook-Air ~/tmp/testrailsasaf/demo (main?) $ ./bin/rails c
Loading development environment (Rails 7.1.3.4)
3.1.1 :001 > ActionController::Base.helpers.asset_path('application.css')

 => "/assets/application-0cdd707d119a6e0b8c1eab9387d787586044421b4e95fbbb9e668e75c6d9fe6f.css"
3.1.1 :002 > ActionController::Base.helpers.asset_path('xyz')
(irb):2:in `<main>': The asset "xyz" is not present in the asset pipeline. (Sprockets::Rails::Helper::AssetNotFound)

הפונקציה מקבלת פרמטר אחד שהוא שם של קובץ Asset, שזה אפילו לא קובץ CSS אלא קובץ שמגדיר איזה קבצי css או scss צריך לטעון. זכרו ש sprocket מאחד את כל קבצי ה CSS לקובץ אחד (כי זה מה שהוא עושה), ואז הוא מוסיף Hash לשם הקובץ כדי שדפדפנים יוכלו לשמור את הקובץ ב cache. התוצאה של זה היא התוצאה של אותה asset_path. אם מפעילים את הפונקציה על שם שלא מתאים לקובץ הגדרות CSS מקבלים את השגיאה שה asset לא נמצא.

בעיות ב sprocket יגרמו או להחזרת אותו שם קובץ (בלי ה digest), להדפסת ערך אפילו בתגובה למחרוזות שלא מתאימות ל asset-ים או אפילו להתרסקות של ה ruby. רוב הזמן אפשר לסדר את הבעיות בעזרת שידרוג sprocket עצמו, שידרוג רובי או שידרוג Gem-ים אחרים בפרויקט.

חדש ב node - תמיכה מובנית ב SQLite

25/07/2024

זה לקח המון זמן אבל גירסה 22.5 של node.js הפכה את העבודה עם SQLite להרבה יותר קלה באמצעות שילוב הספריה כמודול מובנה. זה תקציר של ה API:

import { DatabaseSync } from 'node:sqlite';
const database = new DatabaseSync(':memory:');

// Execute SQL statements from strings.
database.exec(`
  CREATE TABLE data(
    key INTEGER PRIMARY KEY,
    value TEXT
  ) STRICT
`);

// Create a prepared statement to insert data into the database.
const insert = database.prepare('INSERT INTO data (key, value) VALUES (?, ?)');
// Execute the prepared statement with bound values.
insert.run(1, 'hello');
insert.run(2, 'world');

// Create a prepared statement to read data from the database.
const query = database.prepare('SELECT * FROM data ORDER BY key');

// Execute the prepared statement and log the result set.
console.log(query.all());

הפקודה exec פשוט מריצה קוד SQL, הפקודה prepare יוצרת פקודת SQL עם משתנים קשורים להרצה ואחרי שיצרתם פקודה מוכנה להרצה תוכלו להשתמש ב run כדי להריץ אותה או ב all כדי להריץ אותה ולקבל חזרה תוצאות.

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

כרגע כל ה API סינכרוני והכי חשוב בשביל להפעיל את הקוד יש להוסיף מתג ניסיוני לשורת הפקודה כלומר:

node --experimental-sqlite testdb.mjs

ממש ברגעים אלה הם מוסיפים את ההערה על המתג הניסיוני לעמוד התיעוד, תוכלו לבדוק אם זה כבר עלה לאוויר ולקרוא יותר פרטים על ה API בדף התיעוד כאן: https://nodejs.org/api/sqlite.html#sqlite

הספר שלא סיימתי

24/07/2024

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

הנה עוד כמה, עם הדגשים הרלוונטיים-

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

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

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

טיפ סקאלה: שרת REST API פשוט עם cask

23/07/2024

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

package restapi
import cask._
import cask.model.{Request, Response}
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
case class Demo(text: String)

object MyServer extends cask.MainRoutes {
  val JsonCorsHeaders: Seq[(String, String)] = Seq(
    "Content-Type" -> "application/json",
    "Access-Control-Allow-Origin" -> "*",
    "Access-Control-Allow-Methods" -> "GET, POST, PUT, DELETE, OPTIONS",
    "Access-Control-Allow-Headers" -> "Origin, X-Requested-With, Content-Type, Accept, Authorization"
  )

  @cask.options("/*")
  def options() = {
    cask.Response(
      "",
      headers = JsonCorsHeaders
    )
  }

  @cask.route("/", methods = Seq("get"))
  def hello() = {
    cask.Response(
      Demo("hello").asJson.toString,
      headers = JsonCorsHeaders
    )
  }


  initialize()
}

הקוד יצר אוביקט שאפשר להריץ אותו (לא צריך להוסיף לזה main או שום דבר), שמפעיל שרת ווב עם נתיב אחד - רק הנתיב הראשי, שמחזיר תמיד אוביקט JSON עם מפתח בשם text והערך hello. הוא מחזיר גם סט של כותרות שהגדרתי עבור CORS, ואם יהיו כמה נתיבים אפשר להשתמש באותו סט כותרות לכולם. בנוסף הקוד מגדיר טיפול גנרי למתודת OPTIONS, שוב בשביל ה CORS ששולחת רק את הכותרות כדי שדפדפנים יוכלו לקבל מידע משרת זה מכל דומיין.

בשביל להתקין את Cask יש להוסיף ל build.sbt את השורה:

libraryDependencies += "com.lihaoyi" %% "cask" % "0.9.1"

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

כזה ניסיתי: Webvm.io

22/07/2024

צריכים סביבת לינוקס לתרגולים? webvm.io הוא אחד הפרויקטים המעניינים שראיתי באזור הזה. הם לקחו את Debian ומריצים אותה בדפדפן בתור אפליקציית Client Side בלבד דרך ווב אסמבלי, וגם בנו מנגנון שמאפשר לבנות כל Dockerfile למכונת לינוקס שרצה בדפדפן. מספיק להיכנס ללינק כדי להתרשם והנה עוד כמה דברים שאהבתי בפרויקט:

  1. המכונה ממשיכה לרוץ כל הזמן (טוב, כל עוד הדפדפן דולק). אפילו כשעברתי למצב שינה והפעלתי את המחשב שוב אחרי שעות הכל חזר לאותו מצב. ככה זה כשאין צד שרת.

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

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

  4. יש תמיכה בעבודה בתור root (נו זה רץ בדפדפן אז אין שום בעיית אבטחה). פשוט מפעילים su וכותבים את הסיסמה password.

  5. יש על המכונה כבר כמה תוכנות כמו python, perl, gcc, vim, ruby. בגלל שלא הצלחתי להפעיל את החיבור לרשת גם לא הצלחתי להתקין תוכנות אחרות.

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

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

תרגילי תכנות בראיונות עבודה

21/07/2024

מייקל שקור מציג טיעון מעניין נגד תרגילי תכנות בראיונות עבודה. הטיעון בגדול הוא:

  1. תרגילי תכנות בראיונות עבודה מפלים לטובה אנשים שיש להם זמן (ולרעה אנשים עם ילדים)

  2. תרגילי תכנות לא משקפים את העבודה שלנו בעולם האמיתי

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

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

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

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

מסע דילוגים

20/07/2024

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

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

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

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

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

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

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

למה וויתרתי על סוליד ב 2024

19/07/2024

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

  1. הגודל - לקחתי פרויקט ריאקט קטן (פחות מ-10 קבצים, חצי מגה של JavaScript כולל הכל) ותרגמתי לסוליד. התוצאה לקחה 400K. נכון זה 20% פחות וזה מרשים והכל, אבל 20% מחצי מגה זה עדיין מעט. יכול להיות שאם היה לי פרויקט ריאקט של 10 מגה הייתי רואה הבדל משמעותי, אבל גם יכול להיות שבפרויקט של 10 מגה החיסכון היה קטן בהרבה מ 20%. בכל מקרה עבור פרויקט קטן החיסכון בגודל לא מורגש.

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

function MyComponent(props) {
  // Using mergeProps to set default values for props
  const finalProps = mergeProps({ defaultName: "Ryan Carniato" }, props);

  return <div>Hello {finalProps.defaultName}</div>;
}

וזו אותה קומפוננטה בריאקט:

function MyComponent({defaultName = "Ryan Carniato"}) {
    return <div>Hello {defaultName}</div>;
}
  1. שימוש ב class במקום className. כן אני יודע כולם כועסים על ריאקט וה className שלהם, אבל תכל'ס אני שמח לכתוב קומפוננטה שמקבלת className בתור פרופ:
function ReactComponent({className}) {
    return (<div className={className}>yay</div>)
}

הכתיב הזה לא עובד אם שם הפרמטר היה class, כי class זו מילה שמורה ב JavaScript.

  1. קבלת ערך מסיגנל דורשת קריאה לפונקציה. עוד משהו שלפני שנתיים לא הפריע לי אבל היום נראה מוזר זה השימוש בסיגנלים דרך קריאה לפונקציה:
const [count, setCount] = createSignal(0);
console.log(count()); 

גם בהשוואה ל vue וגם בהשוואה ל preact הכתיב הזה מסורבל. זו דוגמה מתוך התיעוד של preact לסיגנלים שלהם שעובדים בצורה הרבה יותר טבעית:

import { signal } from "@preact/signals";

const count = signal(0);

// Read a signal’s value by accessing .value:
console.log(count.value);   // 0
  1. אקוסיסטם ופופולריות - לפני שנתיים האקוסיסטם של Solid היתה קטנה אבל היתה איזה תחושה שאולי בעתיד זה יתפוס והיא תגדל. היום כבר ברור שסוליד לא יהיה הדבר הגדול הבא והאקוסיסטם לא יגדל.

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

יותר מדי מהונדס

18/07/2024

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

ספציפית בתוכנה כשאנחנו אומרים על מוצר שהוא מהונדס יתר על המידה אנחנו מתכוונים:

  1. קשה לעדכן את הקוד, כיוון שכל שינוי שובר הרבה מנגנונים אחרים.

  2. הקוד נשבר לבד עם הזמן (כי ספריות מתיישנות) ולכן דורש הרבה עבודת תחזוקה רק כדי להמשיך לעבוד.

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

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

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

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

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